mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-19 19:30:44 +00:00
Init wire Messages
This commit is contained in:
parent
d24560ad65
commit
971409030b
5 changed files with 1926 additions and 0 deletions
897
qkc/cluster/wire/messages.go
Normal file
897
qkc/cluster/wire/messages.go
Normal file
|
|
@ -0,0 +1,897 @@
|
|||
// Copyright 2026-2027, QuarkChain.
|
||||
|
||||
// Package wire: serializable message structs for every cluster RPC opcode.
|
||||
//
|
||||
// Each struct mirrors a pyquarkchain Serializable from
|
||||
// quarkchain/cluster/rpc.py (ClusterOp messages) or
|
||||
// quarkchain/cluster/p2p_commands.py (CommandOp messages).
|
||||
//
|
||||
// Field layout MUST stay byte-compatible with the Python wire format. Every
|
||||
// field name matches the Python FIELDS order and the wire encoding is enforced
|
||||
// by qkc/serialize/ struct tags (see typecache.go):
|
||||
//
|
||||
// bytesizeofslicelen:"4" 4-byte big-endian length prefix for slices
|
||||
// (Python PrependedSizeBytesSerializer(4) and
|
||||
// PrependedSizeListSerializer(4, T))
|
||||
// ser:"nil" nullable pointer — 1-byte presence marker
|
||||
// (Python Optional(T))
|
||||
// ser:"-" ignored field (not serialised)
|
||||
//
|
||||
// Primitive type mapping (Python → Go):
|
||||
//
|
||||
// uint8 → uint8 1 byte
|
||||
// uint16 → uint16 2 bytes big-endian
|
||||
// uint32 → uint32 4 bytes big-endian
|
||||
// uint64 → uint64 8 bytes big-endian
|
||||
// uint128 → [16]byte 16 bytes big-endian
|
||||
// uint256 → *big.Int 1-byte length prefix + big-endian bytes
|
||||
// biguint → *big.Int same as uint256
|
||||
// hash256 → [32]byte 32 bytes
|
||||
// Branch → uint32 4 bytes
|
||||
// Address → [20]byte 20 bytes
|
||||
// signature65 → [65]byte 65 bytes
|
||||
// boolean → bool 1 byte (0x00 / 0x01)
|
||||
//
|
||||
// =============================================================================
|
||||
// Placeholder: RawBytes
|
||||
// =============================================================================
|
||||
//
|
||||
// pyquarkchain defines many complex Serializable types (RootBlock,
|
||||
// MinorBlockHeader, TypedTransaction, CrossShardTransactionList,
|
||||
// TokenBalanceMap, TransactionReceipt, Log, MinorBlock, RootBlockHeader)
|
||||
// that are NOT yet ported to Go. They all have a Python FIELDS list, so
|
||||
// the Go wire length for any given field is well-defined — it just depends
|
||||
// on the future Go type's encoding.
|
||||
//
|
||||
// To keep this PR self-contained and the wire layout pinned down NOW, each
|
||||
// not-yet-ported type is referenced as *RawBytes. RawBytes is a transparent
|
||||
// Serializable that round-trips arbitrary bytes, matching the wire length
|
||||
// of the future Go struct once the fields are filled in. When the real Go
|
||||
// type lands, only the field type needs to change — the wire encoding stays
|
||||
// byte-identical.
|
||||
//
|
||||
// SAFETY: RawBytes.Deserialize consumes ALL remaining bytes in the buffer.
|
||||
// It is only safe when the *RawBytes field is the LAST field of its parent
|
||||
// struct. Fields that are not last are annotated with a WARNING and cannot be
|
||||
// correctly deserialized until the real Go type is ported.
|
||||
//
|
||||
// =============================================================================
|
||||
// Layout
|
||||
// =============================================================================
|
||||
//
|
||||
// Grouped to match the wire opcode sections in opcode.go:
|
||||
//
|
||||
// §1 Cluster initialisation (PING, CONNECT_TO_SLAVES, MINE, GEN_TX)
|
||||
// §2 Virtual connection mgmt (CREATE/DESTROY_CLUSTER_PEER)
|
||||
// §3 Block updates (ADD_ROOT_BLOCK, ADD_MINOR_BLOCK,
|
||||
// SYNC_MINOR_BLOCK_LIST, CHECK_MINOR_BLOCK,
|
||||
// GET_UNCONFIRMED_HEADERS, ADD_MINOR_BLOCK_HEADER)
|
||||
// §4 Block queries (GET_ECO_INFO_LIST, GET_NEXT_BLOCK_TO_MINE,
|
||||
// GET_MINOR_BLOCK, GET_TRANSACTION, EXECUTE_TX,
|
||||
// GET_TX_RECEIPT, GET_TX_LIST_BY_ADDRESS,
|
||||
// GET_ALL_TX, GET_LOG, ESTIMATE_GAS, GET_STORAGE,
|
||||
// GET_CODE, GAS_PRICE, GET_WORK, SUBMIT_WORK)
|
||||
// §5 Account / staking (GET_ACCOUNT_DATA, GET_ROOT_CHAIN_STAKES,
|
||||
// GET_TOTAL_BALANCE)
|
||||
// §6 Cross-shard (Slave↔Slave) (ADD_XSHARD_TX_LIST, BATCH_ADD_XSHARD_TX_LIST)
|
||||
// §7 P2P commands (HELLO, NEW_MINOR_BLOCK_HEADER_LIST,
|
||||
// NEW_TRANSACTION_LIST, NEW_BLOCK_MINOR,
|
||||
// PING_PONG, NEW_ROOT_BLOCK)
|
||||
// §8 P2P queries (GET_ROOT_BLOCK_*, GET_MINOR_BLOCK_*)
|
||||
//
|
||||
// Every struct is defined in one place to keep the opcode-to-struct mapping
|
||||
// in protocol.go complete and avoid scattering definitions across PRs.
|
||||
package wire
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// =============================================================================
|
||||
// Wire-level address / branch / hash constants
|
||||
// =============================================================================
|
||||
//
|
||||
// These match quarkchain/core.py:Constant and Python Branch/Address types.
|
||||
|
||||
// AddressLength is the byte length of an Address (20 bytes).
|
||||
const AddressLength = 20
|
||||
|
||||
// HashLength is the byte length of a hash256.
|
||||
const HashLength = 32
|
||||
|
||||
// SignatureLength is the byte length of a signature65.
|
||||
const SignatureLength = 65
|
||||
|
||||
// UInt128Length is the byte length of a uint128.
|
||||
const UInt128Length = 16
|
||||
|
||||
// =============================================================================
|
||||
// §1 Cluster initialisation
|
||||
// =============================================================================
|
||||
|
||||
// PingRequest (ClusterOp.PING, 0x81) — sent by master to initialise a slave.
|
||||
//
|
||||
// FIELDS = [
|
||||
// ("id", PrependedSizeBytesSerializer(4)),
|
||||
// ("full_shard_id_list", PrependedSizeListSerializer(4, uint32)),
|
||||
// ("root_tip", Optional(RootBlock)),
|
||||
// ]
|
||||
type PingRequest struct {
|
||||
ID []byte `bytesizeofslicelen:"4"`
|
||||
FullShardIDList []uint32 `bytesizeofslicelen:"4"`
|
||||
RootTip *RawBytes `ser:"nil"` // TODO: Replace with *RootBlock once core.RootBlock is ported
|
||||
}
|
||||
|
||||
// PongResponse (ClusterOp.PONG, 0x82) — slave's reply to PING.
|
||||
//
|
||||
// FIELDS = [
|
||||
// ("id", PrependedSizeBytesSerializer(4)),
|
||||
// ("full_shard_id_list", PrependedSizeListSerializer(4, uint32)),
|
||||
// ]
|
||||
type PongResponse struct {
|
||||
ID []byte `bytesizeofslicelen:"4"`
|
||||
FullShardIDList []uint32 `bytesizeofslicelen:"4"`
|
||||
}
|
||||
|
||||
// SlaveInfo (used by ConnectToSlavesRequest) — describes a remote slave.
|
||||
//
|
||||
// FIELDS = [
|
||||
// ("id", PrependedSizeBytesSerializer(4)),
|
||||
// ("host", PrependedSizeBytesSerializer(4)),
|
||||
// ("port", uint16),
|
||||
// ("full_shard_id_list", PrependedSizeListSerializer(4, uint32)),
|
||||
// ]
|
||||
type SlaveInfo struct {
|
||||
ID []byte `bytesizeofslicelen:"4"`
|
||||
Host []byte `bytesizeofslicelen:"4"`
|
||||
Port uint16
|
||||
FullShardIDList []uint32 `bytesizeofslicelen:"4"`
|
||||
}
|
||||
|
||||
// ConnectToSlavesRequest (ClusterOp.CONNECT_TO_SLAVES_REQUEST, 0x83).
|
||||
//
|
||||
// FIELDS = [("slave_info_list", PrependedSizeListSerializer(4, SlaveInfo))]
|
||||
type ConnectToSlavesRequest struct {
|
||||
SlaveInfoList []SlaveInfo `bytesizeofslicelen:"4"`
|
||||
}
|
||||
|
||||
// ConnectToSlavesResponse (ClusterOp.CONNECT_TO_SLAVES_RESPONSE, 0x84).
|
||||
//
|
||||
// result_list has the same size as slave_info_list; empty result = success,
|
||||
// otherwise the bytes are a serialised error message.
|
||||
//
|
||||
// FIELDS = [
|
||||
// ("result_list", PrependedSizeListSerializer(4, PrependedSizeBytesSerializer(4)))
|
||||
// ]
|
||||
type ConnectToSlavesResponse struct {
|
||||
ResultList []PrependedSizeBytes4 `bytesizeofslicelen:"4"`
|
||||
}
|
||||
|
||||
// ArtificialTxConfig — used by MineRequest / GetNextBlockToMineRequest /
|
||||
// AddMinorBlockHeaderResponse.
|
||||
//
|
||||
// FIELDS = [("target_root_block_time", uint32), ("target_minor_block_time", uint32)]
|
||||
type ArtificialTxConfig struct {
|
||||
TargetRootBlockTime uint32
|
||||
TargetMinorBlockTime uint32
|
||||
}
|
||||
|
||||
// MineRequest (ClusterOp.MINE_REQUEST, 0xA7) — start/stop mining on slaves.
|
||||
//
|
||||
// FIELDS = [("artificial_tx_config", ArtificialTxConfig), ("mining", boolean)]
|
||||
type MineRequest struct {
|
||||
ArtificialTxConfig ArtificialTxConfig
|
||||
Mining bool
|
||||
}
|
||||
|
||||
// MineResponse (ClusterOp.MINE_RESPONSE, 0xA8).
|
||||
type MineResponse struct {
|
||||
ErrorCode uint32
|
||||
}
|
||||
|
||||
// GenTxRequest (ClusterOp.GEN_TX_REQUEST, 0xA9) — generate transactions.
|
||||
//
|
||||
// FIELDS = [
|
||||
// ("num_tx_per_shard", uint32),
|
||||
// ("x_shard_percent", uint32), # [0, 100]
|
||||
// ("tx", TypedTransaction),
|
||||
// ]
|
||||
type GenTxRequest struct {
|
||||
NumTxPerShard uint32
|
||||
XShardPercent uint32
|
||||
Tx *RawBytes // TODO: Replace with *TypedTransaction once core.TypedTransaction is ported
|
||||
}
|
||||
|
||||
// GenTxResponse (ClusterOp.GEN_TX_RESPONSE, 0xAA).
|
||||
type GenTxResponse struct {
|
||||
ErrorCode uint32
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// §2 Virtual connection management (mode 3 — Peer→Master→Slave)
|
||||
// =============================================================================
|
||||
|
||||
// CreateClusterPeerConnectionRequest (ClusterOp.CREATE_CLUSTER_PEER_CONNECTION_REQUEST, 0x99).
|
||||
type CreateClusterPeerConnectionRequest struct {
|
||||
ClusterPeerID uint64
|
||||
}
|
||||
|
||||
// CreateClusterPeerConnectionResponse (ClusterOp.CREATE_CLUSTER_PEER_CONNECTION_RESPONSE, 0x9A).
|
||||
type CreateClusterPeerConnectionResponse struct {
|
||||
ErrorCode uint32
|
||||
}
|
||||
|
||||
// DestroyClusterPeerConnectionCommand (ClusterOp.DESTROY_CLUSTER_PEER_CONNECTION_COMMAND, 0x9B)
|
||||
// — fire-and-forget, no response.
|
||||
type DestroyClusterPeerConnectionCommand struct {
|
||||
ClusterPeerID uint64
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// §3 Block updates
|
||||
// =============================================================================
|
||||
|
||||
// AddRootBlockRequest (ClusterOp.ADD_ROOT_BLOCK_REQUEST, 0x85).
|
||||
//
|
||||
// FIELDS = [("root_block", RootBlock), ("expect_switch", boolean)]
|
||||
type AddRootBlockRequest struct {
|
||||
// TODO: Replace with *RootBlock once core.RootBlock is ported.
|
||||
// WARNING: not the last field; RawBytes.Deserialize consumes remaining bytes.
|
||||
RootBlock *RawBytes
|
||||
ExpectSwitch bool
|
||||
}
|
||||
|
||||
// AddRootBlockResponse (ClusterOp.ADD_ROOT_BLOCK_RESPONSE, 0x86).
|
||||
type AddRootBlockResponse struct {
|
||||
ErrorCode uint32
|
||||
Switched bool
|
||||
}
|
||||
|
||||
// EcoInfo — used by GetEcoInfoListResponse.
|
||||
//
|
||||
// FIELDS = [
|
||||
// ("branch", Branch),
|
||||
// ("height", uint64),
|
||||
// ("coinbase_amount", uint256),
|
||||
// ("difficulty", biguint),
|
||||
// ("unconfirmed_headers_coinbase_amount", uint256),
|
||||
// ]
|
||||
type EcoInfo struct {
|
||||
Branch uint32
|
||||
Height uint64
|
||||
CoinbaseAmount *big.Int // uint256
|
||||
Difficulty *big.Int // biguint
|
||||
UnconfirmedHeadersCoinbaseAmount *big.Int // uint256
|
||||
}
|
||||
|
||||
// GetEcoInfoListRequest (ClusterOp.GET_ECO_INFO_LIST_REQUEST, 0x87) — empty body.
|
||||
type GetEcoInfoListRequest struct{}
|
||||
|
||||
// GetEcoInfoListResponse (ClusterOp.GET_ECO_INFO_LIST_RESPONSE, 0x88).
|
||||
type GetEcoInfoListResponse struct {
|
||||
ErrorCode uint32
|
||||
EcoInfoList []EcoInfo `bytesizeofslicelen:"4"`
|
||||
}
|
||||
|
||||
// GetNextBlockToMineRequest (ClusterOp.GET_NEXT_BLOCK_TO_MINE_REQUEST, 0x89).
|
||||
//
|
||||
// FIELDS = [
|
||||
// ("branch", Branch),
|
||||
// ("address", Address),
|
||||
// ("artificial_tx_config", ArtificialTxConfig),
|
||||
// ]
|
||||
type GetNextBlockToMineRequest struct {
|
||||
Branch uint32
|
||||
Address [AddressLength]byte
|
||||
ArtificialTxConfig ArtificialTxConfig
|
||||
}
|
||||
|
||||
// GetNextBlockToMineResponse (ClusterOp.GET_NEXT_BLOCK_TO_MINE_RESPONSE, 0x8A).
|
||||
type GetNextBlockToMineResponse struct {
|
||||
ErrorCode uint32
|
||||
Block *RawBytes // TODO: Replace with *MinorBlock once core.MinorBlock is ported
|
||||
}
|
||||
|
||||
// AddMinorBlockRequest (ClusterOp.ADD_MINOR_BLOCK_REQUEST, 0x97) — JRPC-mined blocks.
|
||||
//
|
||||
// FIELDS = [("minor_block_data", PrependedSizeBytesSerializer(4))]
|
||||
type AddMinorBlockRequest struct {
|
||||
MinorBlockData []byte `bytesizeofslicelen:"4"`
|
||||
}
|
||||
|
||||
// AddMinorBlockResponse (ClusterOp.ADD_MINOR_BLOCK_RESPONSE, 0x98).
|
||||
type AddMinorBlockResponse struct {
|
||||
ErrorCode uint32
|
||||
}
|
||||
|
||||
// CheckMinorBlockRequest (ClusterOp.CHECK_MINOR_BLOCK_REQUEST, 0xBD).
|
||||
type CheckMinorBlockRequest struct {
|
||||
MinorBlockHeader *RawBytes // TODO: Replace with *MinorBlockHeader once core.MinorBlockHeader is ported
|
||||
}
|
||||
|
||||
// CheckMinorBlockResponse (ClusterOp.CHECK_MINOR_BLOCK_RESPONSE, 0xBE).
|
||||
type CheckMinorBlockResponse struct {
|
||||
ErrorCode uint32
|
||||
}
|
||||
|
||||
// HeadersInfo — used by GetUnconfirmedHeadersResponse.
|
||||
type HeadersInfo struct {
|
||||
Branch uint32
|
||||
HeaderList []*RawBytes `bytesizeofslicelen:"4"` // TODO: Replace with []*MinorBlockHeader once core.MinorBlockHeader is ported
|
||||
}
|
||||
|
||||
// GetUnconfirmedHeadersRequest (ClusterOp.GET_UNCONFIRMED_HEADERS_REQUEST, 0x8B) — empty body.
|
||||
type GetUnconfirmedHeadersRequest struct{}
|
||||
|
||||
// GetUnconfirmedHeadersResponse (ClusterOp.GET_UNCONFIRMED_HEADERS_RESPONSE, 0x8C).
|
||||
type GetUnconfirmedHeadersResponse struct {
|
||||
ErrorCode uint32
|
||||
HeadersInfoList []HeadersInfo `bytesizeofslicelen:"4"`
|
||||
}
|
||||
|
||||
// AccountBranchData — used by GetAccountDataResponse.
|
||||
//
|
||||
// FIELDS = [
|
||||
// ("branch", Branch),
|
||||
// ("transaction_count", uint256),
|
||||
// ("token_balances", TokenBalanceMap),
|
||||
// ("is_contract", boolean),
|
||||
// ("posw_mineable_blocks", uint16),
|
||||
// ("mined_blocks", uint16),
|
||||
// ]
|
||||
type AccountBranchData struct {
|
||||
Branch uint32
|
||||
TransactionCount *big.Int // uint256
|
||||
// TODO: Replace with *TokenBalanceMap once core.TokenBalanceMap is ported.
|
||||
// WARNING: not the last field; RawBytes.Deserialize consumes remaining bytes.
|
||||
TokenBalances *RawBytes
|
||||
IsContract bool
|
||||
PoswMineableBlocks uint16
|
||||
MinedBlocks uint16
|
||||
}
|
||||
|
||||
// GetAccountDataRequest (ClusterOp.GET_ACCOUNT_DATA_REQUEST, 0x8D).
|
||||
type GetAccountDataRequest struct {
|
||||
Address [AddressLength]byte
|
||||
BlockHeight *uint64 `ser:"nil"` // Optional uint64
|
||||
}
|
||||
|
||||
// GetAccountDataResponse (ClusterOp.GET_ACCOUNT_DATA_RESPONSE, 0x8E).
|
||||
type GetAccountDataResponse struct {
|
||||
ErrorCode uint32
|
||||
AccountBranchDataList []AccountBranchData `bytesizeofslicelen:"4"`
|
||||
}
|
||||
|
||||
// AddTransactionRequest (ClusterOp.ADD_TRANSACTION_REQUEST, 0x8F).
|
||||
type AddTransactionRequest struct {
|
||||
Tx *RawBytes // TODO: Replace with *TypedTransaction once core.TypedTransaction is ported
|
||||
}
|
||||
|
||||
// AddTransactionResponse (ClusterOp.ADD_TRANSACTION_RESPONSE, 0x90).
|
||||
type AddTransactionResponse struct {
|
||||
ErrorCode uint32
|
||||
}
|
||||
|
||||
// ShardStats — used by AddMinorBlockHeaderRequest / SyncMinorBlockListResponse.
|
||||
//
|
||||
// FIELDS = [
|
||||
// ("branch", Branch),
|
||||
// ("height", uint64),
|
||||
// ("difficulty", biguint),
|
||||
// ("coinbase_address", Address),
|
||||
// ("timestamp", uint64),
|
||||
// ("tx_count60s", uint32),
|
||||
// ("pending_tx_count", uint32),
|
||||
// ("total_tx_count", uint32),
|
||||
// ("block_count60s", uint32),
|
||||
// ("stale_block_count60s", uint32),
|
||||
// ("last_block_time", uint32),
|
||||
// ]
|
||||
type ShardStats struct {
|
||||
Branch uint32
|
||||
Height uint64
|
||||
Difficulty *big.Int // biguint
|
||||
CoinbaseAddress [AddressLength]byte
|
||||
Timestamp uint64
|
||||
TxCount60s uint32
|
||||
PendingTxCount uint32
|
||||
TotalTxCount uint32
|
||||
BlockCount60s uint32
|
||||
StaleBlockCount60s uint32
|
||||
LastBlockTime uint32
|
||||
}
|
||||
|
||||
// AddMinorBlockHeaderRequest (ClusterOp.ADD_MINOR_BLOCK_HEADER_REQUEST, 0x91) — slave→master.
|
||||
//
|
||||
// FIELDS = [
|
||||
// ("minor_block_header", MinorBlockHeader),
|
||||
// ("tx_count", uint32),
|
||||
// ("x_shard_tx_count", uint32),
|
||||
// ("coinbase_amount_map", TokenBalanceMap),
|
||||
// ("shard_stats", ShardStats),
|
||||
// ]
|
||||
type AddMinorBlockHeaderRequest struct {
|
||||
// TODO: Replace with *MinorBlockHeader once core.MinorBlockHeader is ported.
|
||||
// WARNING: not the last field; RawBytes.Deserialize consumes remaining bytes.
|
||||
MinorBlockHeader *RawBytes
|
||||
TxCount uint32
|
||||
XShardTxCount uint32
|
||||
// TODO: Replace with *TokenBalanceMap once core.TokenBalanceMap is ported.
|
||||
// WARNING: not the last field; RawBytes.Deserialize consumes remaining bytes.
|
||||
CoinbaseAmountMap *RawBytes
|
||||
ShardStats ShardStats
|
||||
}
|
||||
|
||||
// AddMinorBlockHeaderResponse (ClusterOp.ADD_MINOR_BLOCK_HEADER_RESPONSE, 0x92).
|
||||
type AddMinorBlockHeaderResponse struct {
|
||||
ErrorCode uint32
|
||||
ArtificialTxConfig ArtificialTxConfig
|
||||
}
|
||||
|
||||
// AddMinorBlockHeaderListRequest (ClusterOp.ADD_MINOR_BLOCK_HEADER_LIST_REQUEST, 0xBB) — slave→master.
|
||||
type AddMinorBlockHeaderListRequest struct {
|
||||
MinorBlockHeaderList []*RawBytes `bytesizeofslicelen:"4"` // TODO: Replace with []*MinorBlockHeader once core.MinorBlockHeader is ported
|
||||
CoinbaseAmountMapList []*RawBytes `bytesizeofslicelen:"4"` // TODO: Replace with []*TokenBalanceMap once core.TokenBalanceMap is ported
|
||||
}
|
||||
|
||||
// AddMinorBlockHeaderListResponse (ClusterOp.ADD_MINOR_BLOCK_HEADER_LIST_RESPONSE, 0xBC).
|
||||
type AddMinorBlockHeaderListResponse struct {
|
||||
ErrorCode uint32
|
||||
}
|
||||
|
||||
// SyncMinorBlockListRequest (ClusterOp.SYNC_MINOR_BLOCK_LIST_REQUEST, 0x95).
|
||||
type SyncMinorBlockListRequest struct {
|
||||
MinorBlockHashList [][HashLength]byte `bytesizeofslicelen:"4"`
|
||||
Branch uint32
|
||||
ClusterPeerID uint64
|
||||
}
|
||||
|
||||
// SyncMinorBlockListResponse (ClusterOp.SYNC_MINOR_BLOCK_LIST_RESPONSE, 0x96).
|
||||
//
|
||||
// block_coinbase_map: PrependedSizeMapSerializer(4, hash256, TokenBalanceMap)
|
||||
//
|
||||
// FIELDS = [
|
||||
// ("error_code", uint32),
|
||||
// ("block_coinbase_map", PrependedSizeMapSerializer(4, hash256, TokenBalanceMap)),
|
||||
// ("shard_stats", Optional(ShardStats)),
|
||||
// ]
|
||||
type SyncMinorBlockListResponse struct {
|
||||
ErrorCode uint32
|
||||
// TODO: Replace with real block_coinbase_map once core.TokenBalanceMap is ported.
|
||||
// WARNING: not the last field; RawBytes.Deserialize consumes remaining bytes.
|
||||
BlockCoinbaseMap *RawBytes
|
||||
ShardStats *ShardStats `ser:"nil"`
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// §4 Block queries
|
||||
// =============================================================================
|
||||
|
||||
// MinorBlockExtraInfo — used by GetMinorBlockResponse.
|
||||
type MinorBlockExtraInfo struct {
|
||||
EffectiveDifficulty *big.Int // biguint
|
||||
PoswMineableBlocks uint16
|
||||
PoswMinedBlocks uint16
|
||||
}
|
||||
|
||||
// GetMinorBlockRequest (ClusterOp.GET_MINOR_BLOCK_REQUEST, 0x9D).
|
||||
type GetMinorBlockRequest struct {
|
||||
Branch uint32
|
||||
MinorBlockHash [HashLength]byte
|
||||
Height uint64
|
||||
NeedExtraInfo bool
|
||||
}
|
||||
|
||||
// GetMinorBlockResponse (ClusterOp.GET_MINOR_BLOCK_RESPONSE, 0x9E).
|
||||
type GetMinorBlockResponse struct {
|
||||
ErrorCode uint32
|
||||
// TODO: Replace with *MinorBlock once core.MinorBlock is ported.
|
||||
// WARNING: not the last field; RawBytes.Deserialize consumes remaining bytes.
|
||||
MinorBlock *RawBytes
|
||||
ExtraInfo *MinorBlockExtraInfo `ser:"nil"`
|
||||
}
|
||||
|
||||
// GetTransactionRequest (ClusterOp.GET_TRANSACTION_REQUEST, 0x9F).
|
||||
type GetTransactionRequest struct {
|
||||
TxHash [HashLength]byte
|
||||
Branch uint32
|
||||
}
|
||||
|
||||
// GetTransactionResponse (ClusterOp.GET_TRANSACTION_RESPONSE, 0xA0).
|
||||
type GetTransactionResponse struct {
|
||||
ErrorCode uint32
|
||||
// TODO: Replace with *MinorBlock once core.MinorBlock is ported.
|
||||
// WARNING: not the last field; RawBytes.Deserialize consumes remaining bytes.
|
||||
MinorBlock *RawBytes
|
||||
Index uint32
|
||||
}
|
||||
|
||||
// ExecuteTransactionRequest (ClusterOp.EXECUTE_TRANSACTION_REQUEST, 0xA3).
|
||||
type ExecuteTransactionRequest struct {
|
||||
// TODO: Replace with *TypedTransaction once core.TypedTransaction is ported.
|
||||
// WARNING: not the last field; RawBytes.Deserialize consumes remaining bytes.
|
||||
Tx *RawBytes
|
||||
FromAddress [AddressLength]byte
|
||||
BlockHeight *uint64 `ser:"nil"`
|
||||
}
|
||||
|
||||
// ExecuteTransactionResponse (ClusterOp.EXECUTE_TRANSACTION_RESPONSE, 0xA4).
|
||||
type ExecuteTransactionResponse struct {
|
||||
ErrorCode uint32
|
||||
Result []byte `bytesizeofslicelen:"4"`
|
||||
}
|
||||
|
||||
// GetTransactionReceiptRequest (ClusterOp.GET_TRANSACTION_RECEIPT_REQUEST, 0xA5).
|
||||
type GetTransactionReceiptRequest struct {
|
||||
TxHash [HashLength]byte
|
||||
Branch uint32
|
||||
}
|
||||
|
||||
// GetTransactionReceiptResponse (ClusterOp.GET_TRANSACTION_RECEIPT_RESPONSE, 0xA6).
|
||||
type GetTransactionReceiptResponse struct {
|
||||
ErrorCode uint32
|
||||
// TODO: Replace with *MinorBlock once core.MinorBlock is ported.
|
||||
// WARNING: not the last field; RawBytes.Deserialize consumes remaining bytes.
|
||||
MinorBlock *RawBytes
|
||||
Index uint32
|
||||
// TODO: Replace with *TransactionReceipt once core.TransactionReceipt is ported.
|
||||
Receipt *RawBytes
|
||||
}
|
||||
|
||||
// TransactionDetail — used by GetTransactionListByAddressResponse and
|
||||
// GetAllTransactionsResponse.
|
||||
type TransactionDetail struct {
|
||||
TxHash [HashLength]byte
|
||||
Nonce uint64
|
||||
FromAddress [AddressLength]byte
|
||||
ToAddress *[AddressLength]byte `ser:"nil"` // Optional Address
|
||||
Value *big.Int // uint256
|
||||
BlockHeight uint64
|
||||
Timestamp uint64
|
||||
Success bool
|
||||
GasTokenID uint64
|
||||
TransferTokenID uint64
|
||||
IsFromRootChain bool
|
||||
}
|
||||
|
||||
// GetTransactionListByAddressRequest (ClusterOp.GET_TRANSACTION_LIST_BY_ADDRESS_REQUEST, 0xAB).
|
||||
type GetTransactionListByAddressRequest struct {
|
||||
Address [AddressLength]byte
|
||||
TransferTokenID *uint64 `ser:"nil"`
|
||||
Start []byte `bytesizeofslicelen:"4"`
|
||||
Limit uint32
|
||||
}
|
||||
|
||||
// GetTransactionListByAddressResponse (ClusterOp.GET_TRANSACTION_LIST_BY_ADDRESS_RESPONSE, 0xAC).
|
||||
type GetTransactionListByAddressResponse struct {
|
||||
ErrorCode uint32
|
||||
TxList []TransactionDetail `bytesizeofslicelen:"4"`
|
||||
Next []byte `bytesizeofslicelen:"4"`
|
||||
}
|
||||
|
||||
// GetAllTransactionsRequest (ClusterOp.GET_ALL_TRANSACTIONS_REQUEST, 0xBF).
|
||||
type GetAllTransactionsRequest struct {
|
||||
Branch uint32
|
||||
Start []byte `bytesizeofslicelen:"4"`
|
||||
Limit uint32
|
||||
}
|
||||
|
||||
// GetAllTransactionsResponse (ClusterOp.GET_ALL_TRANSACTIONS_RESPONSE, 0xC0).
|
||||
type GetAllTransactionsResponse struct {
|
||||
ErrorCode uint32
|
||||
TxList []TransactionDetail `bytesizeofslicelen:"4"`
|
||||
Next []byte `bytesizeofslicelen:"4"`
|
||||
}
|
||||
|
||||
// GetLogRequest (ClusterOp.GET_LOG_REQUEST, 0xAD).
|
||||
//
|
||||
// FIELDS = [
|
||||
// ("branch", Branch),
|
||||
// ("addresses", PrependedSizeListSerializer(4, Address)),
|
||||
// ("topics", PrependedSizeListSerializer(4, PrependedSizeListSerializer(4, hash256))),
|
||||
// ("start_block", uint64),
|
||||
// ("end_block", uint64),
|
||||
// ]
|
||||
type GetLogRequest struct {
|
||||
Branch uint32
|
||||
Addresses [][AddressLength]byte `bytesizeofslicelen:"4"`
|
||||
Topics []PrependedSizeHashList4 `bytesizeofslicelen:"4"`
|
||||
StartBlock uint64
|
||||
EndBlock uint64
|
||||
}
|
||||
|
||||
// GetLogResponse (ClusterOp.GET_LOG_RESPONSE, 0xAE).
|
||||
type GetLogResponse struct {
|
||||
ErrorCode uint32
|
||||
Logs []*RawBytes `bytesizeofslicelen:"4"` // TODO: Replace with []*Log once core.Log is ported
|
||||
}
|
||||
|
||||
// EstimateGasRequest (ClusterOp.ESTIMATE_GAS_REQUEST, 0xAF).
|
||||
type EstimateGasRequest struct {
|
||||
// TODO: Replace with *TypedTransaction once core.TypedTransaction is ported.
|
||||
// WARNING: not the last field; RawBytes.Deserialize consumes remaining bytes.
|
||||
Tx *RawBytes
|
||||
FromAddress [AddressLength]byte
|
||||
}
|
||||
|
||||
// EstimateGasResponse (ClusterOp.ESTIMATE_GAS_RESPONSE, 0xB0).
|
||||
type EstimateGasResponse struct {
|
||||
ErrorCode uint32
|
||||
Result uint32
|
||||
}
|
||||
|
||||
// GetStorageRequest (ClusterOp.GET_STORAGE_REQUEST, 0xB1).
|
||||
type GetStorageRequest struct {
|
||||
Address [AddressLength]byte
|
||||
Key *big.Int // uint256
|
||||
BlockHeight *uint64 `ser:"nil"`
|
||||
}
|
||||
|
||||
// GetStorageResponse (ClusterOp.GET_STORAGE_RESPONSE, 0xB2).
|
||||
type GetStorageResponse struct {
|
||||
ErrorCode uint32
|
||||
Result [HashLength]byte
|
||||
}
|
||||
|
||||
// GetCodeRequest (ClusterOp.GET_CODE_REQUEST, 0xB3).
|
||||
type GetCodeRequest struct {
|
||||
Address [AddressLength]byte
|
||||
BlockHeight *uint64 `ser:"nil"`
|
||||
}
|
||||
|
||||
// GetCodeResponse (ClusterOp.GET_CODE_RESPONSE, 0xB4).
|
||||
type GetCodeResponse struct {
|
||||
ErrorCode uint32
|
||||
Result []byte `bytesizeofslicelen:"4"`
|
||||
}
|
||||
|
||||
// GasPriceRequest (ClusterOp.GAS_PRICE_REQUEST, 0xB5).
|
||||
type GasPriceRequest struct {
|
||||
Branch uint32
|
||||
TokenID uint64
|
||||
}
|
||||
|
||||
// GasPriceResponse (ClusterOp.GAS_PRICE_RESPONSE, 0xB6).
|
||||
type GasPriceResponse struct {
|
||||
ErrorCode uint32
|
||||
Result uint64
|
||||
}
|
||||
|
||||
// GetWorkRequest (ClusterOp.GET_WORK_REQUEST, 0xB7).
|
||||
type GetWorkRequest struct {
|
||||
Branch uint32
|
||||
CoinbaseAddr *[AddressLength]byte `ser:"nil"` // Optional Address
|
||||
}
|
||||
|
||||
// GetWorkResponse (ClusterOp.GET_WORK_RESPONSE, 0xB8).
|
||||
type GetWorkResponse struct {
|
||||
ErrorCode uint32
|
||||
HeaderHash [HashLength]byte
|
||||
Height uint64
|
||||
Difficulty *big.Int // biguint
|
||||
}
|
||||
|
||||
// SubmitWorkRequest (ClusterOp.SUBMIT_WORK_REQUEST, 0xB9).
|
||||
type SubmitWorkRequest struct {
|
||||
Branch uint32
|
||||
HeaderHash [HashLength]byte
|
||||
Nonce uint64
|
||||
Mixhash [HashLength]byte
|
||||
Signature *[SignatureLength]byte `ser:"nil"` // Optional signature65
|
||||
}
|
||||
|
||||
// SubmitWorkResponse (ClusterOp.SUBMIT_WORK_RESPONSE, 0xBA).
|
||||
type SubmitWorkResponse struct {
|
||||
ErrorCode uint32
|
||||
Success bool
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// §5 Account / staking
|
||||
// =============================================================================
|
||||
|
||||
// GetRootChainStakesRequest (ClusterOp.GET_ROOT_CHAIN_STAKES_REQUEST, 0xC1).
|
||||
type GetRootChainStakesRequest struct {
|
||||
Address [AddressLength]byte
|
||||
MinorBlockHash [HashLength]byte
|
||||
}
|
||||
|
||||
// GetRootChainStakesResponse (ClusterOp.GET_ROOT_CHAIN_STAKES_RESPONSE, 0xC2).
|
||||
type GetRootChainStakesResponse struct {
|
||||
ErrorCode uint32
|
||||
Stakes *big.Int // biguint
|
||||
Signer [AddressLength]byte
|
||||
}
|
||||
|
||||
// GetTotalBalanceRequest (ClusterOp.GET_TOTAL_BALANCE_REQUEST, 0xC3).
|
||||
type GetTotalBalanceRequest struct {
|
||||
Branch uint32
|
||||
Start *[HashLength]byte `ser:"nil"` // Optional hash256
|
||||
TokenID uint64
|
||||
Limit uint32
|
||||
MinorBlockHash [HashLength]byte
|
||||
RootBlockHash *[HashLength]byte `ser:"nil"` // Optional hash256
|
||||
}
|
||||
|
||||
// GetTotalBalanceResponse (ClusterOp.GET_TOTAL_BALANCE_RESPONSE, 0xC4).
|
||||
type GetTotalBalanceResponse struct {
|
||||
ErrorCode uint32
|
||||
TotalBalance *big.Int // biguint
|
||||
Next []byte `bytesizeofslicelen:"4"`
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// §6 Cross-shard (Slave↔Slave, direct TCP, no metadata)
|
||||
// =============================================================================
|
||||
|
||||
// AddXshardTxListRequest (ClusterOp.ADD_XSHARD_TX_LIST_REQUEST, 0x93).
|
||||
type AddXshardTxListRequest struct {
|
||||
Branch uint32
|
||||
MinorBlockHash [HashLength]byte
|
||||
TxList *RawBytes // TODO: Replace with *CrossShardTransactionList once core.CrossShardTransactionList is ported
|
||||
}
|
||||
|
||||
// AddXshardTxListResponse (ClusterOp.ADD_XSHARD_TX_LIST_RESPONSE, 0x94).
|
||||
type AddXshardTxListResponse struct {
|
||||
ErrorCode uint32
|
||||
}
|
||||
|
||||
// BatchAddXshardTxListRequest (ClusterOp.BATCH_ADD_XSHARD_TX_LIST_REQUEST, 0xA1).
|
||||
type BatchAddXshardTxListRequest struct {
|
||||
AddXshardTxListRequestList []AddXshardTxListRequest `bytesizeofslicelen:"4"`
|
||||
}
|
||||
|
||||
// BatchAddXshardTxListResponse (ClusterOp.BATCH_ADD_XSHARD_TX_LIST_RESPONSE, 0xA2).
|
||||
type BatchAddXshardTxListResponse struct {
|
||||
ErrorCode uint32
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// §7 P2P commands (CommandOp, cluster_peer_id != 0)
|
||||
// =============================================================================
|
||||
|
||||
// HelloCommand (CommandOp.HELLO, 0x00) — initial inter-cluster handshake.
|
||||
//
|
||||
// FIELDS = [
|
||||
// ("version", uint32),
|
||||
// ("network_id", uint32),
|
||||
// ("peer_id", hash256),
|
||||
// ("peer_ip", uint128),
|
||||
// ("peer_port", uint16),
|
||||
// ("chain_mask_list", PrependedSizeListSerializer(4, uint32)),
|
||||
// ("root_block_header", RootBlockHeader),
|
||||
// ("genesis_root_block_hash", hash256),
|
||||
// ]
|
||||
type HelloCommand struct {
|
||||
Version uint32
|
||||
NetworkID uint32
|
||||
PeerID [HashLength]byte
|
||||
PeerIP [UInt128Length]byte
|
||||
PeerPort uint16
|
||||
ChainMaskList []uint32 `bytesizeofslicelen:"4"`
|
||||
// TODO: Replace with *RootBlockHeader once core.RootBlockHeader is ported.
|
||||
// WARNING: not the last field; RawBytes.Deserialize consumes remaining bytes.
|
||||
RootBlockHeader *RawBytes
|
||||
GenesisRootBlockHash [HashLength]byte
|
||||
}
|
||||
|
||||
// NewMinorBlockHeaderListCommand (CommandOp.NEW_MINOR_BLOCK_HEADER_LIST, 0x01).
|
||||
type NewMinorBlockHeaderListCommand struct {
|
||||
// TODO: Replace with *RootBlockHeader once core.RootBlockHeader is ported.
|
||||
// WARNING: not the last field; RawBytes.Deserialize consumes remaining bytes.
|
||||
RootBlockHeader *RawBytes
|
||||
MinorBlockHeaderList []*RawBytes `bytesizeofslicelen:"4"` // TODO: Replace with []*MinorBlockHeader once core.MinorBlockHeader is ported
|
||||
}
|
||||
|
||||
// NewTransactionListCommand (CommandOp.NEW_TRANSACTION_LIST, 0x02).
|
||||
type NewTransactionListCommand struct {
|
||||
TransactionList []*RawBytes `bytesizeofslicelen:"4"` // TODO: Replace with []*TypedTransaction once core.TypedTransaction is ported
|
||||
}
|
||||
|
||||
// NewBlockMinorCommand (CommandOp.NEW_BLOCK_MINOR, 0x0D).
|
||||
type NewBlockMinorCommand struct {
|
||||
Block *RawBytes // TODO: Replace with *MinorBlock once core.MinorBlock is ported
|
||||
}
|
||||
|
||||
// PingPongCommand (CommandOp.PING 0x0E, PONG 0x0F).
|
||||
type PingPongCommand struct {
|
||||
Message [HashLength]byte
|
||||
}
|
||||
|
||||
// NewRootBlockCommand (CommandOp.NEW_ROOT_BLOCK, 0x12).
|
||||
type NewRootBlockCommand struct {
|
||||
Block *RawBytes // TODO: Replace with *RootBlock once core.RootBlock is ported
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// §8 P2P queries (CommandOp)
|
||||
// =============================================================================
|
||||
|
||||
// PeerInfo — used by GetPeerListResponse.
|
||||
type PeerInfo struct {
|
||||
IP [UInt128Length]byte
|
||||
Port uint16
|
||||
}
|
||||
|
||||
// GetPeerListRequest (CommandOp.GET_PEER_LIST_REQUEST, 0x03).
|
||||
type GetPeerListRequest struct {
|
||||
MaxPeers uint32
|
||||
}
|
||||
|
||||
// GetPeerListResponse (CommandOp.GET_PEER_LIST_RESPONSE, 0x04).
|
||||
type GetPeerListResponse struct {
|
||||
PeerInfoList []PeerInfo `bytesizeofslicelen:"4"`
|
||||
}
|
||||
|
||||
// GetRootBlockHeaderListRequest (CommandOp.GET_ROOT_BLOCK_HEADER_LIST_REQUEST, 0x05).
|
||||
type GetRootBlockHeaderListRequest struct {
|
||||
BlockHash [HashLength]byte
|
||||
Limit uint32
|
||||
Direction Direction
|
||||
}
|
||||
|
||||
// GetRootBlockHeaderListResponse (CommandOp.GET_ROOT_BLOCK_HEADER_LIST_RESPONSE, 0x06).
|
||||
type GetRootBlockHeaderListResponse struct {
|
||||
// TODO: Replace with *RootBlockHeader once core.RootBlockHeader is ported.
|
||||
// WARNING: not the last field; RawBytes.Deserialize consumes remaining bytes.
|
||||
RootTip *RawBytes
|
||||
BlockHeaderList []*RawBytes `bytesizeofslicelen:"4"` // TODO: Replace with []*RootBlockHeader once core.RootBlockHeader is ported
|
||||
}
|
||||
|
||||
// GetRootBlockHeaderListWithSkipRequest (CommandOp.GET_ROOT_BLOCK_HEADER_LIST_WITH_SKIP_REQUEST, 0x10).
|
||||
type GetRootBlockHeaderListWithSkipRequest struct {
|
||||
Type uint8
|
||||
Data [HashLength]byte
|
||||
Limit uint32
|
||||
Skip uint32
|
||||
Direction Direction
|
||||
}
|
||||
|
||||
// GetRootBlockListRequest (CommandOp.GET_ROOT_BLOCK_LIST_REQUEST, 0x07).
|
||||
type GetRootBlockListRequest struct {
|
||||
RootBlockHashList [][HashLength]byte `bytesizeofslicelen:"4"`
|
||||
}
|
||||
|
||||
// GetRootBlockListResponse (CommandOp.GET_ROOT_BLOCK_LIST_RESPONSE, 0x08).
|
||||
type GetRootBlockListResponse struct {
|
||||
RootBlockList []*RawBytes `bytesizeofslicelen:"4"` // TODO: Replace with []*RootBlock once core.RootBlock is ported
|
||||
}
|
||||
|
||||
// GetMinorBlockListRequest (CommandOp.GET_MINOR_BLOCK_LIST_REQUEST, 0x09).
|
||||
type GetMinorBlockListRequest struct {
|
||||
MinorBlockHashList [][HashLength]byte `bytesizeofslicelen:"4"`
|
||||
}
|
||||
|
||||
// GetMinorBlockListResponse (CommandOp.GET_MINOR_BLOCK_LIST_RESPONSE, 0x0A).
|
||||
type GetMinorBlockListResponse struct {
|
||||
MinorBlockList []*RawBytes `bytesizeofslicelen:"4"` // TODO: Replace with []*MinorBlock once core.MinorBlock is ported
|
||||
}
|
||||
|
||||
// GetMinorBlockHeaderListRequest (CommandOp.GET_MINOR_BLOCK_HEADER_LIST_REQUEST, 0x0B).
|
||||
type GetMinorBlockHeaderListRequest struct {
|
||||
BlockHash [HashLength]byte
|
||||
Branch uint32
|
||||
Limit uint32
|
||||
Direction Direction
|
||||
}
|
||||
|
||||
// GetMinorBlockHeaderListResponse (CommandOp.GET_MINOR_BLOCK_HEADER_LIST_RESPONSE, 0x0C).
|
||||
type GetMinorBlockHeaderListResponse struct {
|
||||
// TODO: Replace with *RootBlockHeader once core.RootBlockHeader is ported.
|
||||
// WARNING: not the last field; RawBytes.Deserialize consumes remaining bytes.
|
||||
RootTip *RawBytes
|
||||
// TODO: Replace with *MinorBlockHeader once core.MinorBlockHeader is ported.
|
||||
// WARNING: not the last field; RawBytes.Deserialize consumes remaining bytes.
|
||||
ShardTip *RawBytes
|
||||
BlockHeaderList []*RawBytes `bytesizeofslicelen:"4"` // TODO: Replace with []*MinorBlockHeader once core.MinorBlockHeader is ported
|
||||
}
|
||||
|
||||
// GetMinorBlockHeaderListWithSkipRequest (CommandOp.GET_MINOR_BLOCK_HEADER_LIST_WITH_SKIP_REQUEST, 0x13).
|
||||
type GetMinorBlockHeaderListWithSkipRequest struct {
|
||||
Type uint8
|
||||
Data [HashLength]byte
|
||||
Branch uint32
|
||||
Limit uint32
|
||||
Skip uint32
|
||||
Direction Direction
|
||||
}
|
||||
646
qkc/cluster/wire/messages_test.go
Normal file
646
qkc/cluster/wire/messages_test.go
Normal file
|
|
@ -0,0 +1,646 @@
|
|||
// Copyright 2026-2027, QuarkChain.
|
||||
|
||||
package wire
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/qkc/serialize"
|
||||
)
|
||||
|
||||
// =============================================================================
|
||||
// §1 Custom wire types
|
||||
// =============================================================================
|
||||
|
||||
func TestPrependedSizeBytes4_RoundTrip(t *testing.T) {
|
||||
cases := [][]byte{
|
||||
nil,
|
||||
{},
|
||||
{0x00},
|
||||
{0xAA, 0xBB},
|
||||
bytes.Repeat([]byte{0xFF}, 100),
|
||||
}
|
||||
for i, data := range cases {
|
||||
t.Run("", func(t *testing.T) {
|
||||
p := PrependedSizeBytes4(data)
|
||||
var buf []byte
|
||||
if err := p.Serialize(&buf); err != nil {
|
||||
t.Fatalf("case %d: Serialize: %v", i, err)
|
||||
}
|
||||
|
||||
bb := serialize.NewByteBuffer(buf)
|
||||
var got PrependedSizeBytes4
|
||||
if err := got.Deserialize(bb); err != nil {
|
||||
t.Fatalf("case %d: Deserialize: %v", i, err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(got, data) {
|
||||
t.Errorf("case %d: mismatch\n got %x\n want %x", i, got, data)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrependedSizeBytes4_WireFormat(t *testing.T) {
|
||||
wantHex := "00000002aabb"
|
||||
|
||||
p := PrependedSizeBytes4{0xAA, 0xBB}
|
||||
var buf []byte
|
||||
if err := p.Serialize(&buf); err != nil {
|
||||
t.Fatalf("Serialize: %v", err)
|
||||
}
|
||||
|
||||
gotHex := hexEncode(buf)
|
||||
if gotHex != wantHex {
|
||||
t.Errorf("wire format mismatch:\n got %s\n want %s", gotHex, wantHex)
|
||||
}
|
||||
|
||||
bb := serialize.NewByteBuffer(buf)
|
||||
var got PrependedSizeBytes4
|
||||
if err := got.Deserialize(bb); err != nil {
|
||||
t.Fatalf("Deserialize: %v", err)
|
||||
}
|
||||
if !bytes.Equal(got, p) {
|
||||
t.Errorf("round-trip mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrependedSizeBytes4_Deserialize_InvalidLength(t *testing.T) {
|
||||
buf := []byte{0xFF, 0xFF, 0xFF, 0xFF}
|
||||
bb := serialize.NewByteBuffer(buf)
|
||||
|
||||
var p PrependedSizeBytes4
|
||||
err := p.Deserialize(bb)
|
||||
if err == nil {
|
||||
t.Error("expected error for length exceeding remaining buffer")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrependedSizeHashList4_RoundTrip(t *testing.T) {
|
||||
cases := [][][HashLength]byte{
|
||||
nil,
|
||||
{},
|
||||
{makeHash(1)},
|
||||
{makeHash(1), makeHash(2)},
|
||||
}
|
||||
for i, hashes := range cases {
|
||||
t.Run("", func(t *testing.T) {
|
||||
p := PrependedSizeHashList4(hashes)
|
||||
var buf []byte
|
||||
if err := p.Serialize(&buf); err != nil {
|
||||
t.Fatalf("case %d: Serialize: %v", i, err)
|
||||
}
|
||||
|
||||
bb := serialize.NewByteBuffer(buf)
|
||||
var got PrependedSizeHashList4
|
||||
if err := got.Deserialize(bb); err != nil {
|
||||
t.Fatalf("case %d: Deserialize: %v", i, err)
|
||||
}
|
||||
|
||||
if len(got) != len(hashes) {
|
||||
t.Fatalf("case %d: length mismatch: got %d, want %d", i, len(got), len(hashes))
|
||||
}
|
||||
for j := range got {
|
||||
if got[j] != hashes[j] {
|
||||
t.Errorf("case %d: hash[%d] mismatch", i, j)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrependedSizeHashList4_WireFormat(t *testing.T) {
|
||||
p := PrependedSizeHashList4{makeHash(0x11), makeHash(0x22)}
|
||||
var buf []byte
|
||||
if err := p.Serialize(&buf); err != nil {
|
||||
t.Fatalf("Serialize: %v", err)
|
||||
}
|
||||
|
||||
if len(buf) != 4+2*HashLength {
|
||||
t.Errorf("wire length mismatch: got %d, want %d", len(buf), 4+2*HashLength)
|
||||
}
|
||||
|
||||
// count prefix is already checked by Deserialize; this is supplementary.
|
||||
bb := serialize.NewByteBuffer(buf)
|
||||
var got PrependedSizeHashList4
|
||||
if err := got.Deserialize(bb); err != nil {
|
||||
t.Fatalf("Deserialize: %v", err)
|
||||
}
|
||||
if len(got) != 2 {
|
||||
t.Errorf("round-trip length mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrependedSizeHashList4_Deserialize_InvalidLength(t *testing.T) {
|
||||
buf := []byte{0xFF, 0xFF, 0xFF, 0xFF}
|
||||
bb := serialize.NewByteBuffer(buf)
|
||||
|
||||
var p PrependedSizeHashList4
|
||||
err := p.Deserialize(bb)
|
||||
if err == nil {
|
||||
t.Error("expected error for count exceeding buffer capacity")
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// §2 Message struct round-trips
|
||||
// =============================================================================
|
||||
//
|
||||
// Only structs with *RawBytes as the LAST field (or no RawBytes at all) can
|
||||
// safely round-trip. Structs with non-last RawBytes are only verified via the
|
||||
// factory completeness test (§7) — they serialize correctly but cannot be
|
||||
// deserialized until the real Go type replaces RawBytes.
|
||||
|
||||
func TestMessageRoundTrip(t *testing.T) {
|
||||
toAddr := makeAddress(0x00010001, 2)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
msg any
|
||||
}{
|
||||
// --- no RawBytes ---
|
||||
{"PingRequest_without_root_tip", PingRequest{
|
||||
ID: []byte("slave1"),
|
||||
FullShardIDList: []uint32{0x00010001, 0x00020002},
|
||||
RootTip: nil,
|
||||
}},
|
||||
{"PongResponse", PongResponse{
|
||||
ID: []byte("master"),
|
||||
FullShardIDList: []uint32{0x00010001},
|
||||
}},
|
||||
{"SlaveInfo", SlaveInfo{
|
||||
ID: []byte("s1"),
|
||||
Host: []byte("127.0.0.1"),
|
||||
Port: 38391,
|
||||
FullShardIDList: []uint32{0x00010001, 0x00010002},
|
||||
}},
|
||||
{"ConnectToSlavesRequest", ConnectToSlavesRequest{
|
||||
SlaveInfoList: []SlaveInfo{
|
||||
{ID: []byte("s1"), Host: []byte("10.0.0.1"), Port: 38391, FullShardIDList: []uint32{0x00010001}},
|
||||
{ID: []byte("s2"), Host: []byte("10.0.0.2"), Port: 38392, FullShardIDList: []uint32{0x00020001}},
|
||||
},
|
||||
}},
|
||||
{"ConnectToSlavesResponse", ConnectToSlavesResponse{
|
||||
ResultList: []PrependedSizeBytes4{{0xAA, 0xBB}, {0xCC, 0xDD, 0xEE}},
|
||||
}},
|
||||
{"ArtificialTxConfig", ArtificialTxConfig{60, 10}},
|
||||
{"MineRequest", MineRequest{
|
||||
ArtificialTxConfig: ArtificialTxConfig{TargetRootBlockTime: 60, TargetMinorBlockTime: 10},
|
||||
Mining: true,
|
||||
}},
|
||||
{"EcoInfo", EcoInfo{
|
||||
Branch: 0x00010001,
|
||||
Height: 12345,
|
||||
CoinbaseAmount: big.NewInt(1000),
|
||||
Difficulty: big.NewInt(1000000),
|
||||
UnconfirmedHeadersCoinbaseAmount: big.NewInt(500),
|
||||
}},
|
||||
{"GetEcoInfoListRequest", GetEcoInfoListRequest{}},
|
||||
{"GetEcoInfoListResponse", GetEcoInfoListResponse{
|
||||
ErrorCode: 0,
|
||||
EcoInfoList: []EcoInfo{
|
||||
{Branch: 0x00010001, Height: 1, CoinbaseAmount: big.NewInt(1), Difficulty: big.NewInt(1), UnconfirmedHeadersCoinbaseAmount: big.NewInt(1)},
|
||||
},
|
||||
}},
|
||||
{"GetNextBlockToMineRequest", GetNextBlockToMineRequest{
|
||||
Branch: 0x00010001,
|
||||
Address: makeAddress(0x00010001, 1),
|
||||
ArtificialTxConfig: ArtificialTxConfig{TargetRootBlockTime: 60, TargetMinorBlockTime: 10},
|
||||
}},
|
||||
{"GetAccountDataRequest", GetAccountDataRequest{
|
||||
Address: makeAddress(0x00010001, 1),
|
||||
BlockHeight: nil,
|
||||
}},
|
||||
{"GetLogRequest", GetLogRequest{
|
||||
Branch: 0x00010001,
|
||||
Addresses: [][AddressLength]byte{makeAddress(0x00010001, 1)},
|
||||
Topics: []PrependedSizeHashList4{
|
||||
{makeHash(0xAA)},
|
||||
{makeHash(0xBB), makeHash(0xCC)},
|
||||
},
|
||||
StartBlock: 100,
|
||||
EndBlock: 200,
|
||||
}},
|
||||
{"PingPongCommand", PingPongCommand{makeHash(42)}},
|
||||
{"PeerInfo", PeerInfo{IP: makeUint128(0x0102030405060708), Port: 38391}},
|
||||
{"TransactionDetail", TransactionDetail{
|
||||
TxHash: makeHash(1),
|
||||
Nonce: 10,
|
||||
FromAddress: makeAddress(0x00010001, 1),
|
||||
ToAddress: &toAddr,
|
||||
Value: big.NewInt(100),
|
||||
BlockHeight: 50,
|
||||
Timestamp: 1600000000,
|
||||
Success: true,
|
||||
GasTokenID: 1,
|
||||
TransferTokenID: 1,
|
||||
IsFromRootChain: false,
|
||||
}},
|
||||
|
||||
// --- RawBytes as last field (safe to round-trip) ---
|
||||
{"GenTxRequest", GenTxRequest{
|
||||
NumTxPerShard: 10,
|
||||
XShardPercent: 30,
|
||||
Tx: &RawBytes{0x01, 0x02, 0x03},
|
||||
}},
|
||||
{"GetNextBlockToMineResponse", GetNextBlockToMineResponse{
|
||||
ErrorCode: 0,
|
||||
Block: &RawBytes{0xAA, 0xBB},
|
||||
}},
|
||||
{"AddTransactionRequest", AddTransactionRequest{
|
||||
Tx: &RawBytes{0x01, 0x02},
|
||||
}},
|
||||
{"CheckMinorBlockRequest", CheckMinorBlockRequest{
|
||||
MinorBlockHeader: &RawBytes{0x01, 0x02},
|
||||
}},
|
||||
{"GetLogResponse", GetLogResponse{
|
||||
ErrorCode: 0,
|
||||
Logs: []*RawBytes{{0x01, 0x02}}, // single element only — multi-element []*RawBytes cannot round-trip
|
||||
}},
|
||||
{"BatchAddXshardTxListRequest", BatchAddXshardTxListRequest{
|
||||
AddXshardTxListRequestList: []AddXshardTxListRequest{
|
||||
{Branch: 0x00010001, MinorBlockHash: makeHash(1), TxList: &RawBytes{0x01, 0x02}},
|
||||
},
|
||||
}},
|
||||
{"AddXshardTxListRequest", AddXshardTxListRequest{
|
||||
Branch: 0x00010001,
|
||||
MinorBlockHash: makeHash(1),
|
||||
TxList: &RawBytes{0x01, 0x02},
|
||||
}},
|
||||
{"NewTransactionListCommand", NewTransactionListCommand{
|
||||
TransactionList: []*RawBytes{{0x01, 0x02}}, // single element only
|
||||
}},
|
||||
{"NewBlockMinorCommand", NewBlockMinorCommand{
|
||||
Block: &RawBytes{0x01, 0x02},
|
||||
}},
|
||||
{"NewRootBlockCommand", NewRootBlockCommand{
|
||||
Block: &RawBytes{0x01, 0x02},
|
||||
}},
|
||||
{"GetRootBlockListResponse", GetRootBlockListResponse{
|
||||
RootBlockList: []*RawBytes{{0x01, 0x02}}, // single element only
|
||||
}},
|
||||
{"GetMinorBlockListResponse", GetMinorBlockListResponse{
|
||||
MinorBlockList: []*RawBytes{{0x01, 0x02}}, // single element only
|
||||
}},
|
||||
{"GetUnconfirmedHeadersResponse", GetUnconfirmedHeadersResponse{
|
||||
ErrorCode: 0,
|
||||
HeadersInfoList: []HeadersInfo{
|
||||
{Branch: 0x00010001, HeaderList: []*RawBytes{{0x01, 0x02}}}, // single element only
|
||||
},
|
||||
}},
|
||||
{"PingRequest_with_root_tip", PingRequest{
|
||||
ID: []byte("test"),
|
||||
FullShardIDList: []uint32{1, 2},
|
||||
RootTip: &RawBytes{0xAA, 0xBB, 0xCC},
|
||||
}},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var buf []byte
|
||||
if err := serialize.Serialize(&buf, tc.msg); err != nil {
|
||||
t.Fatalf("Serialize: %v", err)
|
||||
}
|
||||
|
||||
bb := serialize.NewByteBuffer(buf)
|
||||
got := reflect.New(reflect.TypeOf(tc.msg)).Interface()
|
||||
if err := serialize.Deserialize(bb, got); err != nil {
|
||||
t.Fatalf("Deserialize: %v", err)
|
||||
}
|
||||
|
||||
gotVal := reflect.ValueOf(got).Elem().Interface()
|
||||
|
||||
// Re-serialize and compare bytes. This sidesteps reflect.DeepEqual
|
||||
// pointer-identity problems with *RawBytes while still verifying
|
||||
// that the wire format is preserved through round-trip.
|
||||
var buf2 []byte
|
||||
if err := serialize.Serialize(&buf2, gotVal); err != nil {
|
||||
t.Fatalf("Re-serialize: %v", err)
|
||||
}
|
||||
if !bytes.Equal(buf, buf2) {
|
||||
t.Errorf("round-trip mismatch\n got %x\n want %x", buf2, buf)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// §3 ser:"nil" behavior
|
||||
// =============================================================================
|
||||
|
||||
func TestOptionalMarker_PresentAndAbsent(t *testing.T) {
|
||||
absent := PingRequest{ID: []byte("x"), RootTip: nil}
|
||||
var buf []byte
|
||||
if err := serialize.Serialize(&buf, &absent); err != nil {
|
||||
t.Fatalf("Serialize absent: %v", err)
|
||||
}
|
||||
if buf[len(buf)-1] != 0x00 {
|
||||
t.Errorf("absent optional should end with 0x00, got %x", buf[len(buf)-1])
|
||||
}
|
||||
|
||||
present := PingRequest{ID: []byte("x"), RootTip: &RawBytes{0xAA}}
|
||||
buf = nil
|
||||
if err := serialize.Serialize(&buf, &present); err != nil {
|
||||
t.Fatalf("Serialize present: %v", err)
|
||||
}
|
||||
if buf[len(buf)-2] != 0x01 || buf[len(buf)-1] != 0xAA {
|
||||
t.Errorf("present optional should write marker 0x01 then data, got %x", buf[len(buf)-2:])
|
||||
}
|
||||
}
|
||||
|
||||
func TestNonOptionalRawBytes_NoMarker(t *testing.T) {
|
||||
req := AddRootBlockRequest{RootBlock: &RawBytes{0xAA}, ExpectSwitch: true}
|
||||
var buf []byte
|
||||
if err := serialize.Serialize(&buf, &req); err != nil {
|
||||
t.Fatalf("Serialize: %v", err)
|
||||
}
|
||||
if buf[0] != 0xAA {
|
||||
t.Errorf("non-optional RawBytes should not have presence marker, got %x", buf[0])
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// §4 Wire compatibility (hand-computed vectors)
|
||||
// =============================================================================
|
||||
//
|
||||
// These test vectors are hand-computed from the Python FIELDS definitions to
|
||||
// verify that Go serialization produces identical bytes. They are NOT produced
|
||||
// by running pyquarkchain directly.
|
||||
|
||||
func TestWireCompat_PingRequest(t *testing.T) {
|
||||
wantHex := "0000000474657374000000020000000100000002" + "00"
|
||||
|
||||
ping := PingRequest{
|
||||
ID: []byte("test"),
|
||||
FullShardIDList: []uint32{1, 2},
|
||||
RootTip: nil,
|
||||
}
|
||||
var buf []byte
|
||||
if err := serialize.Serialize(&buf, &ping); err != nil {
|
||||
t.Fatalf("Serialize: %v", err)
|
||||
}
|
||||
gotHex := hexEncode(buf)
|
||||
if gotHex != wantHex {
|
||||
t.Errorf("wire mismatch:\n got %s\n want %s", gotHex, wantHex)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWireCompat_PongResponse(t *testing.T) {
|
||||
wantHex := "000000026f6b0000000100000003"
|
||||
|
||||
pong := PongResponse{
|
||||
ID: []byte("ok"),
|
||||
FullShardIDList: []uint32{3},
|
||||
}
|
||||
var buf []byte
|
||||
if err := serialize.Serialize(&buf, &pong); err != nil {
|
||||
t.Fatalf("Serialize: %v", err)
|
||||
}
|
||||
gotHex := hexEncode(buf)
|
||||
if gotHex != wantHex {
|
||||
t.Errorf("wire mismatch:\n got %s\n want %s", gotHex, wantHex)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWireCompat_SlaveInfo(t *testing.T) {
|
||||
wantHex := "000000027331" +
|
||||
"000000096c6f63616c686f7374" +
|
||||
"95f7" +
|
||||
"0000000100010001"
|
||||
|
||||
slave := SlaveInfo{
|
||||
ID: []byte("s1"),
|
||||
Host: []byte("localhost"),
|
||||
Port: 38391,
|
||||
FullShardIDList: []uint32{0x00010001},
|
||||
}
|
||||
var buf []byte
|
||||
if err := serialize.Serialize(&buf, &slave); err != nil {
|
||||
t.Fatalf("Serialize: %v", err)
|
||||
}
|
||||
gotHex := hexEncode(buf)
|
||||
if gotHex != wantHex {
|
||||
t.Errorf("wire mismatch:\n got %s\n want %s", gotHex, wantHex)
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// §5 Field order verification
|
||||
// =============================================================================
|
||||
|
||||
func TestFieldOrder(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
typ reflect.Type
|
||||
expected []string
|
||||
}{
|
||||
{"PingRequest", reflect.TypeFor[PingRequest](), []string{"ID", "FullShardIDList", "RootTip"}},
|
||||
{"PongResponse", reflect.TypeFor[PongResponse](), []string{"ID", "FullShardIDList"}},
|
||||
{"SlaveInfo", reflect.TypeFor[SlaveInfo](), []string{"ID", "Host", "Port", "FullShardIDList"}},
|
||||
{"EcoInfo", reflect.TypeFor[EcoInfo](), []string{"Branch", "Height", "CoinbaseAmount", "Difficulty", "UnconfirmedHeadersCoinbaseAmount"}},
|
||||
{"ShardStats", reflect.TypeFor[ShardStats](), []string{"Branch", "Height", "Difficulty", "CoinbaseAddress", "Timestamp",
|
||||
"TxCount60s", "PendingTxCount", "TotalTxCount", "BlockCount60s", "StaleBlockCount60s", "LastBlockTime"}},
|
||||
{"TransactionDetail", reflect.TypeFor[TransactionDetail](), []string{"TxHash", "Nonce", "FromAddress", "ToAddress", "Value",
|
||||
"BlockHeight", "Timestamp", "Success", "GasTokenID", "TransferTokenID", "IsFromRootChain"}},
|
||||
{"HelloCommand", reflect.TypeFor[HelloCommand](), []string{"Version", "NetworkID", "PeerID", "PeerIP", "PeerPort",
|
||||
"ChainMaskList", "RootBlockHeader", "GenesisRootBlockHash"}},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if tc.typ.NumField() != len(tc.expected) {
|
||||
t.Fatalf("field count mismatch: got %d, want %d", tc.typ.NumField(), len(tc.expected))
|
||||
}
|
||||
for i, want := range tc.expected {
|
||||
if tc.typ.Field(i).Name != want {
|
||||
t.Errorf("field %d: got %s, want %s", i, tc.typ.Field(i).Name, want)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// §6 Factory completeness
|
||||
// =============================================================================
|
||||
|
||||
func TestNewClusterMessage_Completeness(t *testing.T) {
|
||||
ops := []ClusterOp{
|
||||
ClusterOpPing,
|
||||
ClusterOpPong,
|
||||
ClusterOpConnectToSlavesRequest,
|
||||
ClusterOpConnectToSlavesResponse,
|
||||
ClusterOpAddRootBlockRequest,
|
||||
ClusterOpAddRootBlockResponse,
|
||||
ClusterOpGetEcoInfoListRequest,
|
||||
ClusterOpGetEcoInfoListResponse,
|
||||
ClusterOpGetNextBlockToMineRequest,
|
||||
ClusterOpGetNextBlockToMineResponse,
|
||||
ClusterOpGetUnconfirmedHeadersRequest,
|
||||
ClusterOpGetUnconfirmedHeadersResponse,
|
||||
ClusterOpGetAccountDataRequest,
|
||||
ClusterOpGetAccountDataResponse,
|
||||
ClusterOpAddTransactionRequest,
|
||||
ClusterOpAddTransactionResponse,
|
||||
ClusterOpAddMinorBlockHeaderRequest,
|
||||
ClusterOpAddMinorBlockHeaderResponse,
|
||||
ClusterOpAddXshardTxListRequest,
|
||||
ClusterOpAddXshardTxListResponse,
|
||||
ClusterOpSyncMinorBlockListRequest,
|
||||
ClusterOpSyncMinorBlockListResponse,
|
||||
ClusterOpAddMinorBlockRequest,
|
||||
ClusterOpAddMinorBlockResponse,
|
||||
ClusterOpCreateClusterPeerConnectionRequest,
|
||||
ClusterOpCreateClusterPeerConnectionResponse,
|
||||
ClusterOpDestroyClusterPeerConnectionCommand,
|
||||
ClusterOpGetMinorBlockRequest,
|
||||
ClusterOpGetMinorBlockResponse,
|
||||
ClusterOpGetTransactionRequest,
|
||||
ClusterOpGetTransactionResponse,
|
||||
ClusterOpBatchAddXshardTxListRequest,
|
||||
ClusterOpBatchAddXshardTxListResponse,
|
||||
ClusterOpExecuteTransactionRequest,
|
||||
ClusterOpExecuteTransactionResponse,
|
||||
ClusterOpGetTransactionReceiptRequest,
|
||||
ClusterOpGetTransactionReceiptResponse,
|
||||
ClusterOpMineRequest,
|
||||
ClusterOpMineResponse,
|
||||
ClusterOpGenTxRequest,
|
||||
ClusterOpGenTxResponse,
|
||||
ClusterOpGetTransactionListByAddressRequest,
|
||||
ClusterOpGetTransactionListByAddressResponse,
|
||||
ClusterOpGetLogRequest,
|
||||
ClusterOpGetLogResponse,
|
||||
ClusterOpEstimateGasRequest,
|
||||
ClusterOpEstimateGasResponse,
|
||||
ClusterOpGetStorageRequest,
|
||||
ClusterOpGetStorageResponse,
|
||||
ClusterOpGetCodeRequest,
|
||||
ClusterOpGetCodeResponse,
|
||||
ClusterOpGasPriceRequest,
|
||||
ClusterOpGasPriceResponse,
|
||||
ClusterOpGetWorkRequest,
|
||||
ClusterOpGetWorkResponse,
|
||||
ClusterOpSubmitWorkRequest,
|
||||
ClusterOpSubmitWorkResponse,
|
||||
ClusterOpAddMinorBlockHeaderListRequest,
|
||||
ClusterOpAddMinorBlockHeaderListResponse,
|
||||
ClusterOpCheckMinorBlockRequest,
|
||||
ClusterOpCheckMinorBlockResponse,
|
||||
ClusterOpGetAllTransactionsRequest,
|
||||
ClusterOpGetAllTransactionsResponse,
|
||||
ClusterOpGetRootChainStakesRequest,
|
||||
ClusterOpGetRootChainStakesResponse,
|
||||
ClusterOpGetTotalBalanceRequest,
|
||||
ClusterOpGetTotalBalanceResponse,
|
||||
}
|
||||
for _, op := range ops {
|
||||
t.Run("ClusterOp(0x"+string("0123456789ABCDEF"[byte(op)>>4])+string("0123456789ABCDEF"[byte(op)&0x0F])+")", func(t *testing.T) {
|
||||
msg, err := NewClusterMessage(op)
|
||||
if err != nil {
|
||||
t.Fatalf("NewClusterMessage(0x%x): %v", op, err)
|
||||
}
|
||||
if msg == nil {
|
||||
t.Fatalf("NewClusterMessage(0x%x) returned nil", op)
|
||||
}
|
||||
typ := reflect.TypeOf(msg)
|
||||
if typ.Kind() != reflect.Pointer || typ.Elem().Kind() != reflect.Struct {
|
||||
t.Errorf("expected *struct, got %T", msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewCommandMessage_Completeness(t *testing.T) {
|
||||
ops := []CommandOp{
|
||||
CommandOpHello,
|
||||
CommandOpNewMinorBlockHeaderList,
|
||||
CommandOpNewTransactionList,
|
||||
CommandOpGetPeerListRequest,
|
||||
CommandOpGetPeerListResponse,
|
||||
CommandOpGetRootBlockHeaderListRequest,
|
||||
CommandOpGetRootBlockHeaderListResponse,
|
||||
CommandOpGetRootBlockListRequest,
|
||||
CommandOpGetRootBlockListResponse,
|
||||
CommandOpGetMinorBlockListRequest,
|
||||
CommandOpGetMinorBlockListResponse,
|
||||
CommandOpGetMinorBlockHeaderListRequest,
|
||||
CommandOpGetMinorBlockHeaderListResponse,
|
||||
CommandOpNewBlockMinor,
|
||||
CommandOpPing,
|
||||
CommandOpPong,
|
||||
CommandOpGetRootBlockHeaderListWithSkipRequest,
|
||||
CommandOpGetRootBlockHeaderListWithSkipResponse,
|
||||
CommandOpNewRootBlock,
|
||||
CommandOpGetMinorBlockHeaderListWithSkipRequest,
|
||||
CommandOpGetMinorBlockHeaderListWithSkipResponse,
|
||||
}
|
||||
for _, op := range ops {
|
||||
t.Run("", func(t *testing.T) {
|
||||
msg, err := NewCommandMessage(op)
|
||||
if err != nil {
|
||||
t.Fatalf("NewCommandMessage(0x%x): %v", op, err)
|
||||
}
|
||||
if msg == nil {
|
||||
t.Fatalf("NewCommandMessage(0x%x) returned nil", op)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewClusterMessage_UnknownOpcode(t *testing.T) {
|
||||
_, err := NewClusterMessage(ClusterOp(0xFF))
|
||||
if err == nil {
|
||||
t.Error("expected error for unknown ClusterOp")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewCommandMessage_UnknownOpcode(t *testing.T) {
|
||||
_, err := NewCommandMessage(CommandOp(0xEE))
|
||||
if err == nil {
|
||||
t.Error("expected error for unknown CommandOp")
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Helpers
|
||||
// =============================================================================
|
||||
|
||||
func makeAddress(fullShardID uint32, recipient byte) [AddressLength]byte {
|
||||
var addr [AddressLength]byte
|
||||
addr[16] = byte(fullShardID >> 24)
|
||||
addr[17] = byte(fullShardID >> 16)
|
||||
addr[18] = byte(fullShardID >> 8)
|
||||
addr[19] = byte(fullShardID)
|
||||
addr[0] = recipient
|
||||
return addr
|
||||
}
|
||||
|
||||
func makeHash(seed byte) [HashLength]byte {
|
||||
var h [HashLength]byte
|
||||
for i := range h {
|
||||
h[i] = seed + byte(i)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func makeUint128(seed uint64) [UInt128Length]byte {
|
||||
var u [UInt128Length]byte
|
||||
for i := range 8 {
|
||||
u[i] = byte(seed >> (56 - 8*i))
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
func hexEncode(b []byte) string {
|
||||
const hexChars = "0123456789abcdef"
|
||||
s := make([]byte, len(b)*2)
|
||||
for i, v := range b {
|
||||
s[i*2] = hexChars[v>>4]
|
||||
s[i*2+1] = hexChars[v&0x0f]
|
||||
}
|
||||
return string(s)
|
||||
}
|
||||
236
qkc/cluster/wire/protocol.go
Normal file
236
qkc/cluster/wire/protocol.go
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
// Copyright 2026-2027, QuarkChain.
|
||||
|
||||
// Package wire: opcode → message factory and protocol-level enumeration
|
||||
// types. This file maps each ClusterOp / CommandOp to the concrete message
|
||||
// struct that decodes a frame payload of that opcode.
|
||||
package wire
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Direction matches the P2P sync Direction enum in
|
||||
// quarkchain/cluster/p2p_commands.py.
|
||||
//
|
||||
// DIRECTIONS = [GENESIS, TIP]
|
||||
// class Direction(IntEnum):
|
||||
// GENESIS = 0
|
||||
// TIP = 1
|
||||
type Direction uint8
|
||||
|
||||
const (
|
||||
DirectionGenesis Direction = 0
|
||||
DirectionTip Direction = 1
|
||||
)
|
||||
|
||||
func (d Direction) String() string {
|
||||
switch d {
|
||||
case DirectionGenesis:
|
||||
return "GENESIS"
|
||||
case DirectionTip:
|
||||
return "TIP"
|
||||
default:
|
||||
return fmt.Sprintf("Direction(%d)", uint8(d))
|
||||
}
|
||||
}
|
||||
|
||||
// NewClusterMessage returns a new empty message struct pointer corresponding
|
||||
// to the given ClusterOp. Used by slave/master_conn.go to allocate the
|
||||
// concrete type before deserialising a frame payload.
|
||||
func NewClusterMessage(op ClusterOp) (interface{}, error) {
|
||||
switch op {
|
||||
case ClusterOpPing:
|
||||
return &PingRequest{}, nil
|
||||
case ClusterOpPong:
|
||||
return &PongResponse{}, nil
|
||||
case ClusterOpConnectToSlavesRequest:
|
||||
return &ConnectToSlavesRequest{}, nil
|
||||
case ClusterOpConnectToSlavesResponse:
|
||||
return &ConnectToSlavesResponse{}, nil
|
||||
case ClusterOpAddRootBlockRequest:
|
||||
return &AddRootBlockRequest{}, nil
|
||||
case ClusterOpAddRootBlockResponse:
|
||||
return &AddRootBlockResponse{}, nil
|
||||
case ClusterOpGetEcoInfoListRequest:
|
||||
return &GetEcoInfoListRequest{}, nil
|
||||
case ClusterOpGetEcoInfoListResponse:
|
||||
return &GetEcoInfoListResponse{}, nil
|
||||
case ClusterOpGetNextBlockToMineRequest:
|
||||
return &GetNextBlockToMineRequest{}, nil
|
||||
case ClusterOpGetNextBlockToMineResponse:
|
||||
return &GetNextBlockToMineResponse{}, nil
|
||||
case ClusterOpGetUnconfirmedHeadersRequest:
|
||||
return &GetUnconfirmedHeadersRequest{}, nil
|
||||
case ClusterOpGetUnconfirmedHeadersResponse:
|
||||
return &GetUnconfirmedHeadersResponse{}, nil
|
||||
case ClusterOpGetAccountDataRequest:
|
||||
return &GetAccountDataRequest{}, nil
|
||||
case ClusterOpGetAccountDataResponse:
|
||||
return &GetAccountDataResponse{}, nil
|
||||
case ClusterOpAddTransactionRequest:
|
||||
return &AddTransactionRequest{}, nil
|
||||
case ClusterOpAddTransactionResponse:
|
||||
return &AddTransactionResponse{}, nil
|
||||
case ClusterOpAddMinorBlockHeaderRequest:
|
||||
return &AddMinorBlockHeaderRequest{}, nil
|
||||
case ClusterOpAddMinorBlockHeaderResponse:
|
||||
return &AddMinorBlockHeaderResponse{}, nil
|
||||
case ClusterOpAddXshardTxListRequest:
|
||||
return &AddXshardTxListRequest{}, nil
|
||||
case ClusterOpAddXshardTxListResponse:
|
||||
return &AddXshardTxListResponse{}, nil
|
||||
case ClusterOpSyncMinorBlockListRequest:
|
||||
return &SyncMinorBlockListRequest{}, nil
|
||||
case ClusterOpSyncMinorBlockListResponse:
|
||||
return &SyncMinorBlockListResponse{}, nil
|
||||
case ClusterOpAddMinorBlockRequest:
|
||||
return &AddMinorBlockRequest{}, nil
|
||||
case ClusterOpAddMinorBlockResponse:
|
||||
return &AddMinorBlockResponse{}, nil
|
||||
case ClusterOpCreateClusterPeerConnectionRequest:
|
||||
return &CreateClusterPeerConnectionRequest{}, nil
|
||||
case ClusterOpCreateClusterPeerConnectionResponse:
|
||||
return &CreateClusterPeerConnectionResponse{}, nil
|
||||
case ClusterOpDestroyClusterPeerConnectionCommand:
|
||||
return &DestroyClusterPeerConnectionCommand{}, nil
|
||||
case ClusterOpGetMinorBlockRequest:
|
||||
return &GetMinorBlockRequest{}, nil
|
||||
case ClusterOpGetMinorBlockResponse:
|
||||
return &GetMinorBlockResponse{}, nil
|
||||
case ClusterOpGetTransactionRequest:
|
||||
return &GetTransactionRequest{}, nil
|
||||
case ClusterOpGetTransactionResponse:
|
||||
return &GetTransactionResponse{}, nil
|
||||
case ClusterOpBatchAddXshardTxListRequest:
|
||||
return &BatchAddXshardTxListRequest{}, nil
|
||||
case ClusterOpBatchAddXshardTxListResponse:
|
||||
return &BatchAddXshardTxListResponse{}, nil
|
||||
case ClusterOpExecuteTransactionRequest:
|
||||
return &ExecuteTransactionRequest{}, nil
|
||||
case ClusterOpExecuteTransactionResponse:
|
||||
return &ExecuteTransactionResponse{}, nil
|
||||
case ClusterOpGetTransactionReceiptRequest:
|
||||
return &GetTransactionReceiptRequest{}, nil
|
||||
case ClusterOpGetTransactionReceiptResponse:
|
||||
return &GetTransactionReceiptResponse{}, nil
|
||||
case ClusterOpMineRequest:
|
||||
return &MineRequest{}, nil
|
||||
case ClusterOpMineResponse:
|
||||
return &MineResponse{}, nil
|
||||
case ClusterOpGenTxRequest:
|
||||
return &GenTxRequest{}, nil
|
||||
case ClusterOpGenTxResponse:
|
||||
return &GenTxResponse{}, nil
|
||||
case ClusterOpGetTransactionListByAddressRequest:
|
||||
return &GetTransactionListByAddressRequest{}, nil
|
||||
case ClusterOpGetTransactionListByAddressResponse:
|
||||
return &GetTransactionListByAddressResponse{}, nil
|
||||
case ClusterOpGetLogRequest:
|
||||
return &GetLogRequest{}, nil
|
||||
case ClusterOpGetLogResponse:
|
||||
return &GetLogResponse{}, nil
|
||||
case ClusterOpEstimateGasRequest:
|
||||
return &EstimateGasRequest{}, nil
|
||||
case ClusterOpEstimateGasResponse:
|
||||
return &EstimateGasResponse{}, nil
|
||||
case ClusterOpGetStorageRequest:
|
||||
return &GetStorageRequest{}, nil
|
||||
case ClusterOpGetStorageResponse:
|
||||
return &GetStorageResponse{}, nil
|
||||
case ClusterOpGetCodeRequest:
|
||||
return &GetCodeRequest{}, nil
|
||||
case ClusterOpGetCodeResponse:
|
||||
return &GetCodeResponse{}, nil
|
||||
case ClusterOpGasPriceRequest:
|
||||
return &GasPriceRequest{}, nil
|
||||
case ClusterOpGasPriceResponse:
|
||||
return &GasPriceResponse{}, nil
|
||||
case ClusterOpGetWorkRequest:
|
||||
return &GetWorkRequest{}, nil
|
||||
case ClusterOpGetWorkResponse:
|
||||
return &GetWorkResponse{}, nil
|
||||
case ClusterOpSubmitWorkRequest:
|
||||
return &SubmitWorkRequest{}, nil
|
||||
case ClusterOpSubmitWorkResponse:
|
||||
return &SubmitWorkResponse{}, nil
|
||||
case ClusterOpAddMinorBlockHeaderListRequest:
|
||||
return &AddMinorBlockHeaderListRequest{}, nil
|
||||
case ClusterOpAddMinorBlockHeaderListResponse:
|
||||
return &AddMinorBlockHeaderListResponse{}, nil
|
||||
case ClusterOpCheckMinorBlockRequest:
|
||||
return &CheckMinorBlockRequest{}, nil
|
||||
case ClusterOpCheckMinorBlockResponse:
|
||||
return &CheckMinorBlockResponse{}, nil
|
||||
case ClusterOpGetAllTransactionsRequest:
|
||||
return &GetAllTransactionsRequest{}, nil
|
||||
case ClusterOpGetAllTransactionsResponse:
|
||||
return &GetAllTransactionsResponse{}, nil
|
||||
case ClusterOpGetRootChainStakesRequest:
|
||||
return &GetRootChainStakesRequest{}, nil
|
||||
case ClusterOpGetRootChainStakesResponse:
|
||||
return &GetRootChainStakesResponse{}, nil
|
||||
case ClusterOpGetTotalBalanceRequest:
|
||||
return &GetTotalBalanceRequest{}, nil
|
||||
case ClusterOpGetTotalBalanceResponse:
|
||||
return &GetTotalBalanceResponse{}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown ClusterOp: 0x%x", op)
|
||||
}
|
||||
}
|
||||
|
||||
// NewCommandMessage returns a new empty message struct pointer corresponding
|
||||
// to the given CommandOp. Note: both PING and PONG share PingPongCommand —
|
||||
// this matches the Python implementation where the same class is registered
|
||||
// for both opcodes (see p2p_commands.py: REGISTER_OP_TO_SERIALIZER).
|
||||
func NewCommandMessage(op CommandOp) (interface{}, error) {
|
||||
switch op {
|
||||
case CommandOpHello:
|
||||
return &HelloCommand{}, nil
|
||||
case CommandOpNewMinorBlockHeaderList:
|
||||
return &NewMinorBlockHeaderListCommand{}, nil
|
||||
case CommandOpNewTransactionList:
|
||||
return &NewTransactionListCommand{}, nil
|
||||
case CommandOpGetPeerListRequest:
|
||||
return &GetPeerListRequest{}, nil
|
||||
case CommandOpGetPeerListResponse:
|
||||
return &GetPeerListResponse{}, nil
|
||||
case CommandOpGetRootBlockHeaderListRequest:
|
||||
return &GetRootBlockHeaderListRequest{}, nil
|
||||
case CommandOpGetRootBlockHeaderListResponse:
|
||||
return &GetRootBlockHeaderListResponse{}, nil
|
||||
case CommandOpGetRootBlockListRequest:
|
||||
return &GetRootBlockListRequest{}, nil
|
||||
case CommandOpGetRootBlockListResponse:
|
||||
return &GetRootBlockListResponse{}, nil
|
||||
case CommandOpGetMinorBlockListRequest:
|
||||
return &GetMinorBlockListRequest{}, nil
|
||||
case CommandOpGetMinorBlockListResponse:
|
||||
return &GetMinorBlockListResponse{}, nil
|
||||
case CommandOpGetMinorBlockHeaderListRequest:
|
||||
return &GetMinorBlockHeaderListRequest{}, nil
|
||||
case CommandOpGetMinorBlockHeaderListResponse:
|
||||
return &GetMinorBlockHeaderListResponse{}, nil
|
||||
case CommandOpNewBlockMinor:
|
||||
return &NewBlockMinorCommand{}, nil
|
||||
case CommandOpPing, CommandOpPong:
|
||||
return &PingPongCommand{}, nil
|
||||
case CommandOpGetRootBlockHeaderListWithSkipRequest:
|
||||
return &GetRootBlockHeaderListWithSkipRequest{}, nil
|
||||
case CommandOpGetRootBlockHeaderListWithSkipResponse:
|
||||
// Shares GetRootBlockHeaderListResponse with CommandOpGetRootBlockHeaderListResponse (0x06).
|
||||
// Python's REGISTER_OP_TO_SERIALIZER also maps both opcodes (0x06, 0x11) to the same class.
|
||||
// Verified: p2p_commands.py line 357.
|
||||
return &GetRootBlockHeaderListResponse{}, nil
|
||||
case CommandOpNewRootBlock:
|
||||
return &NewRootBlockCommand{}, nil
|
||||
case CommandOpGetMinorBlockHeaderListWithSkipRequest:
|
||||
return &GetMinorBlockHeaderListWithSkipRequest{}, nil
|
||||
case CommandOpGetMinorBlockHeaderListWithSkipResponse:
|
||||
// Shares GetMinorBlockHeaderListResponse with CommandOpGetMinorBlockHeaderListResponse (0x0C).
|
||||
// Python's REGISTER_OP_TO_SERIALIZER also maps both opcodes (0x0C, 0x14) to the same class.
|
||||
// Verified: p2p_commands.py line 360.
|
||||
return &GetMinorBlockHeaderListResponse{}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown CommandOp: 0x%x", op)
|
||||
}
|
||||
}
|
||||
57
qkc/cluster/wire/rawbytes_placeholder.go
Normal file
57
qkc/cluster/wire/rawbytes_placeholder.go
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
// Copyright 2026-2027, QuarkChain.
|
||||
|
||||
// =============================================================================
|
||||
// TEMPORARY PLACEHOLDER FILE — DELETE after real types merge
|
||||
// =============================================================================
|
||||
//
|
||||
// RawBytes is a placeholder used during pyquarkchain → Go migration.
|
||||
// Delete this file once real types (RootBlock, MinorBlockHeader, etc.) are ported.
|
||||
// Replace `*RawBytes` fields in messages.go with real typed pointers.
|
||||
//
|
||||
// DO NOT REVIEW AS PRODUCTION CODE.
|
||||
package wire
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/qkc/serialize"
|
||||
)
|
||||
|
||||
// RawBytes is a transparent byte passthrough placeholder for unported complex types.
|
||||
type RawBytes []byte
|
||||
|
||||
func (r RawBytes) Serialize(w *[]byte) error {
|
||||
const maxRawBytesSize = 100 * 1024 * 1024 // 100 MB
|
||||
if len(r) > maxRawBytesSize {
|
||||
return fmt.Errorf("RawBytes.Serialize: size %d exceeds max %d", len(r), maxRawBytesSize)
|
||||
}
|
||||
|
||||
*w = append(*w, r...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deserialize consumes all remaining bytes from the buffer.
|
||||
//
|
||||
// SAFETY: This is only safe when RawBytes is the LAST field in its parent
|
||||
// struct. If RawBytes appears before other fields, consuming the remaining
|
||||
// bytes will corrupt subsequent fields. Structs with non-last RawBytes fields
|
||||
// are marked with a WARNING in messages.go and cannot be correctly deserialized
|
||||
// until the real Go type is ported.
|
||||
//
|
||||
// TEMPORARY: Delete this placeholder once real types (RootBlock, MinorBlockHeader) are ported.
|
||||
func (r *RawBytes) Deserialize(bb *serialize.ByteBuffer) error {
|
||||
const maxRawBytesSize = 100 * 1024 * 1024 // 100 MB, matches Serialize
|
||||
if bb.Remaining() > maxRawBytesSize {
|
||||
return fmt.Errorf("RawBytes.Deserialize: size %d exceeds max %d", bb.Remaining(), maxRawBytesSize)
|
||||
}
|
||||
bytes, err := bb.ReadRemaining()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*r = RawBytes(bytes)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Compile-time check: RawBytes implements Serializable (required by serialize package).
|
||||
// This ensures serialize.Serialize(&buf, &struct{Field *RawBytes}) works.
|
||||
var _ serialize.Serializable = (*RawBytes)(nil)
|
||||
90
qkc/cluster/wire/types.go
Normal file
90
qkc/cluster/wire/types.go
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
// Copyright 2026-2027, QuarkChain.
|
||||
|
||||
package wire
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/ethereum/go-ethereum/qkc/serialize"
|
||||
)
|
||||
|
||||
// =============================================================================
|
||||
// Custom wire types for Python format compatibility
|
||||
// =============================================================================
|
||||
//
|
||||
// Python uses 4-byte length prefix for nested slices in some wire messages.
|
||||
// Go's serialize framework defaults to 1-byte prefix for nested elements.
|
||||
// These custom types enforce 4-byte prefix to match Python wire format.
|
||||
|
||||
// PrependedSizeBytes4 is []byte with 4-byte length prefix (matches Python PrependedSizeBytesSerializer(4)).
|
||||
type PrependedSizeBytes4 []byte
|
||||
|
||||
func (p PrependedSizeBytes4) Serialize(w *[]byte) error {
|
||||
lenBuf := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(lenBuf, uint32(len(p)))
|
||||
*w = append(*w, lenBuf...)
|
||||
*w = append(*w, p...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PrependedSizeBytes4) Deserialize(bb *serialize.ByteBuffer) error {
|
||||
length, err := bb.GetUInt32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if length > math.MaxInt32 || int(length) > bb.Remaining() {
|
||||
return fmt.Errorf("PrependedSizeBytes4.Deserialize: length %d exceeds remaining %d", length, bb.Remaining())
|
||||
}
|
||||
|
||||
bytes, err := bb.ReadBytes(int(length))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*p = PrependedSizeBytes4(bytes)
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ serialize.Serializable = (*PrependedSizeBytes4)(nil)
|
||||
|
||||
// PrependedSizeHashList4 is [][HashLength]byte with 4-byte length prefix (matches Python PrependedSizeListSerializer(4, hash256)).
|
||||
type PrependedSizeHashList4 [][HashLength]byte
|
||||
|
||||
func (p PrependedSizeHashList4) Serialize(w *[]byte) error {
|
||||
lenBuf := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(lenBuf, uint32(len(p)))
|
||||
*w = append(*w, lenBuf...)
|
||||
|
||||
for _, hash := range p {
|
||||
*w = append(*w, hash[:]...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PrependedSizeHashList4) Deserialize(bb *serialize.ByteBuffer) error {
|
||||
length, err := bb.GetUInt32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if length > math.MaxInt32/HashLength || int(length)*HashLength > bb.Remaining() {
|
||||
return fmt.Errorf("PrependedSizeHashList4.Deserialize: length %d exceeds capacity", length)
|
||||
}
|
||||
|
||||
list := make([][HashLength]byte, length)
|
||||
for i := 0; i < int(length); i++ {
|
||||
hashBytes, err := bb.ReadBytes(HashLength)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
list[i] = [HashLength]byte(hashBytes)
|
||||
}
|
||||
|
||||
*p = PrependedSizeHashList4(list)
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ serialize.Serializable = (*PrependedSizeHashList4)(nil)
|
||||
Loading…
Reference in a new issue