Init wire Messages

This commit is contained in:
iteye 2026-07-06 15:49:06 +08:00
parent d24560ad65
commit 971409030b
5 changed files with 1926 additions and 0 deletions

View 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
}

View 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)
}

View 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)
}
}

View 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
View 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)