diff --git a/qkc/cluster/slave/master_conn.go b/qkc/cluster/slave/master_conn.go index 5b1d1953108d..f155670ffc20 100644 --- a/qkc/cluster/slave/master_conn.go +++ b/qkc/cluster/slave/master_conn.go @@ -13,6 +13,7 @@ import ( "github.com/ethereum/go-ethereum/qkc/cluster/conn" "github.com/ethereum/go-ethereum/qkc/cluster/wire" "github.com/ethereum/go-ethereum/qkc/serialize" + "github.com/ethereum/go-ethereum/qkc/types" ) // MasterHandler handles master requests delegated by MasterConn. @@ -23,7 +24,8 @@ type MasterHandler interface { // CreateShards creates the shards made eligible by the given root tip. // // It is invoked when a PING carries a root tip. - CreateShards(rootTip *wire.RawBytes) error + // (py: SlaveServer.create_shards(root_block: RootBlock), slave.py:933) + CreateShards(rootTip *types.RootBlock) error // ConnectToSlaves connects to the slaves advertised by the master. ConnectToSlaves(req *wire.ConnectToSlavesRequest) (*wire.ConnectToSlavesResponse, error) diff --git a/qkc/cluster/slave/master_conn_test.go b/qkc/cluster/slave/master_conn_test.go index 263c685ef7bc..f045c0c77c48 100644 --- a/qkc/cluster/slave/master_conn_test.go +++ b/qkc/cluster/slave/master_conn_test.go @@ -6,16 +6,30 @@ import ( "bufio" "context" "errors" + "math/big" "net" "sync/atomic" "testing" "time" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/qkc/account" "github.com/ethereum/go-ethereum/qkc/cluster/wire" + qkcCommon "github.com/ethereum/go-ethereum/qkc/common" "github.com/ethereum/go-ethereum/qkc/serialize" + "github.com/ethereum/go-ethereum/qkc/types" ) +// ── test helpers ───────────────────────────────────────────────────────────── + +// newTestTx builds a minimal valid EVM transaction for wire tests. +// *types.Transaction is not zero-value serializable (Serialize needs a +// concrete inner tx), so every request carrying a Tx field must embed one. +func newTestTx() *types.Transaction { + return types.NewEvmTransaction(1, account.Recipient{}, big.NewInt(100), 21000, big.NewInt(10), + 0x00010001, 0x00010002, 1, 1, []byte{0xAA}, 1, 1) +} + // ── test handler ───────────────────────────────────────────────────────────── // fakeMasterHandler is the only handler the tests inject into MasterConn: it @@ -31,9 +45,10 @@ type fakeMasterHandler struct { // createShardsCalls counts CreateShards invocations (the PING RootTip // callback). createShardsCalls atomic.Int32 - // lastRootTip stores a copy of the most recent CreateShards - // RootTip argument. - lastRootTip atomic.Pointer[wire.RawBytes] + // lastRootTip stores the most recent CreateShards RootTip argument. + // Each inbound frame is deserialized into a fresh RootBlock, so storing + // the reference is safe: the wire reader never mutates it afterwards. + lastRootTip atomic.Pointer[types.RootBlock] // errCreateShards, if set, is returned by CreateShards to simulate a // shard-activation failure. errCreateShards error @@ -50,12 +65,10 @@ type fakeMasterHandler struct { // CreateShards is the shard-creation callback invoked by MasterConn.handlePing // when the PING carries a RootTip. -func (h *fakeMasterHandler) CreateShards(rootTip *wire.RawBytes) error { +func (h *fakeMasterHandler) CreateShards(rootTip *types.RootBlock) error { h.createShardsCalls.Add(1) if rootTip != nil { - cp := make(wire.RawBytes, len(*rootTip)) - copy(cp, *rootTip) - h.lastRootTip.Store(&cp) + h.lastRootTip.Store(rootTip) } return h.errCreateShards } @@ -89,13 +102,10 @@ func (h *fakeMasterHandler) GenTx(*wire.GenTxRequest) (*wire.GenTxResponse, erro func (h *fakeMasterHandler) AddRootBlock(req *wire.AddRootBlockRequest) (*wire.AddRootBlockResponse, error) { h.addRootBlockCalls.Add(1) if req != nil { - cp := *req - if req.RootBlock != nil { - rb := make(wire.RawBytes, len(*req.RootBlock)) - copy(rb, *req.RootBlock) - cp.RootBlock = &rb - } - h.lastAddRootBlockReq.Store(&cp) + // The request is a fresh object per inbound frame and is never + // mutated afterwards, so storing the reference preserves the values + // the handler observed. + h.lastAddRootBlockReq.Store(req) } if h.respAddRootBlock != nil { return h.respAddRootBlock, nil @@ -346,7 +356,7 @@ func TestMasterConn_Ping(t *testing.T) { server, peer, cleanup := newMasterConnWithPeer(t, handler) defer cleanup() - for i, rootTip := range []*wire.RawBytes{nil, {0x01, 0x02}} { + for i, rootTip := range []*types.RootBlock{nil, types.NewRootBlockWithHeader(&types.RootBlockHeader{Number: 1})} { payload, err := serialize.SerializeToBytes(&wire.PingRequest{ ID: []byte("master"), FullShardIDList: []uint32{0x000f0001}, // deliberately differs from the slave's own, to prove PONG never adopts it @@ -393,8 +403,8 @@ func TestMasterConn_Ping(t *testing.T) { if got := handler.createShardsCalls.Load(); got != 1 { t.Fatalf("CreateShards calls: got %d, want 1 (only the non-nil RootTip)", got) } - if got := handler.lastRootTip.Load(); got == nil || len(*got) != 2 || (*got)[0] != 0x01 || (*got)[1] != 0x02 { - t.Fatalf("CreateShards RootTip mismatch: got %v, want [2]byte{0x01, 0x02}", got) + if got := handler.lastRootTip.Load(); got == nil || got.Number() != 1 { + t.Fatalf("CreateShards RootTip mismatch: got %v, want RootBlock(Number=1)", got) } } @@ -411,7 +421,7 @@ func TestMasterConn_CreateShardsErrorClosesConnection(t *testing.T) { payload, err := serialize.SerializeToBytes(&wire.PingRequest{ ID: []byte("master"), FullShardIDList: []uint32{0x00010001}, - RootTip: &wire.RawBytes{0x01}, + RootTip: types.NewRootBlockWithHeader(&types.RootBlockHeader{Number: 1}), }) if err != nil { t.Fatalf("serialize ping: %v", err) @@ -542,7 +552,7 @@ func TestMasterConn_AddRootBlockDelegated(t *testing.T) { defer cleanup() req := &wire.AddRootBlockRequest{ - RootBlock: &wire.RawBytes{0xde, 0xad, 0xbe, 0xef}, + RootBlock: types.NewRootBlockWithHeader(&types.RootBlockHeader{Number: 0xbeef}), ExpectSwitch: true, } payload, err := serialize.SerializeToBytes(req) @@ -579,9 +589,8 @@ func TestMasterConn_AddRootBlockDelegated(t *testing.T) { t.Fatalf("AddRootBlock called %d times, want 1", got) } gotReq := handler.lastAddRootBlockReq.Load() - if gotReq == nil || gotReq.RootBlock == nil || len(*gotReq.RootBlock) != 4 || - (*gotReq.RootBlock)[0] != 0xde || (*gotReq.RootBlock)[3] != 0xef || !gotReq.ExpectSwitch { - t.Fatalf("AddRootBlock request mismatch: got %+v, want RootBlock=[de ad be ef] ExpectSwitch=true", gotReq) + if gotReq == nil || gotReq.RootBlock == nil || gotReq.RootBlock.Number() != 0xbeef || !gotReq.ExpectSwitch { + t.Fatalf("AddRootBlock request mismatch: got %+v, want RootBlock(Number=0xbeef) ExpectSwitch=true", gotReq) } select { @@ -599,7 +608,7 @@ func TestMasterConn_BusinessHandlerErrorClosesConnection(t *testing.T) { }) defer cleanup() - payload, _ := serialize.SerializeToBytes(&wire.GenTxRequest{}) + payload, _ := serialize.SerializeToBytes(&wire.GenTxRequest{Tx: newTestTx()}) if err := peer.send(&wire.Frame{ Meta: wire.ClusterMetadata{}, Opcode: byte(wire.ClusterOpGenTxRequest), @@ -632,22 +641,22 @@ func TestMasterConn_MasterToSlaveOpcodeMatrix(t *testing.T) { }{ {"ConnectToSlaves", wire.ClusterOpConnectToSlavesRequest, wire.ClusterOpConnectToSlavesResponse, &wire.ConnectToSlavesRequest{}}, {"Mine", wire.ClusterOpMineRequest, wire.ClusterOpMineResponse, &wire.MineRequest{}}, - {"GenTx", wire.ClusterOpGenTxRequest, wire.ClusterOpGenTxResponse, &wire.GenTxRequest{}}, + {"GenTx", wire.ClusterOpGenTxRequest, wire.ClusterOpGenTxResponse, &wire.GenTxRequest{Tx: newTestTx()}}, {"AddRootBlock", wire.ClusterOpAddRootBlockRequest, wire.ClusterOpAddRootBlockResponse, &wire.AddRootBlockRequest{}}, {"GetEcoInfoList", wire.ClusterOpGetEcoInfoListRequest, wire.ClusterOpGetEcoInfoListResponse, &wire.GetEcoInfoListRequest{}}, {"GetNextBlockToMine", wire.ClusterOpGetNextBlockToMineRequest, wire.ClusterOpGetNextBlockToMineResponse, &wire.GetNextBlockToMineRequest{}}, {"AddMinorBlock", wire.ClusterOpAddMinorBlockRequest, wire.ClusterOpAddMinorBlockResponse, &wire.AddMinorBlockRequest{}}, {"GetUnconfirmedHeaders", wire.ClusterOpGetUnconfirmedHeadersRequest, wire.ClusterOpGetUnconfirmedHeadersResponse, &wire.GetUnconfirmedHeadersRequest{}}, {"GetAccountData", wire.ClusterOpGetAccountDataRequest, wire.ClusterOpGetAccountDataResponse, &wire.GetAccountDataRequest{}}, - {"AddTransaction", wire.ClusterOpAddTransactionRequest, wire.ClusterOpAddTransactionResponse, &wire.AddTransactionRequest{}}, + {"AddTransaction", wire.ClusterOpAddTransactionRequest, wire.ClusterOpAddTransactionResponse, &wire.AddTransactionRequest{Tx: newTestTx()}}, {"GetMinorBlock", wire.ClusterOpGetMinorBlockRequest, wire.ClusterOpGetMinorBlockResponse, &wire.GetMinorBlockRequest{}}, {"GetTransaction", wire.ClusterOpGetTransactionRequest, wire.ClusterOpGetTransactionResponse, &wire.GetTransactionRequest{}}, {"SyncMinorBlockList", wire.ClusterOpSyncMinorBlockListRequest, wire.ClusterOpSyncMinorBlockListResponse, &wire.SyncMinorBlockListRequest{}}, - {"ExecuteTransaction", wire.ClusterOpExecuteTransactionRequest, wire.ClusterOpExecuteTransactionResponse, &wire.ExecuteTransactionRequest{}}, + {"ExecuteTransaction", wire.ClusterOpExecuteTransactionRequest, wire.ClusterOpExecuteTransactionResponse, &wire.ExecuteTransactionRequest{Tx: newTestTx()}}, {"GetTransactionReceipt", wire.ClusterOpGetTransactionReceiptRequest, wire.ClusterOpGetTransactionReceiptResponse, &wire.GetTransactionReceiptRequest{}}, {"GetTransactionListByAddress", wire.ClusterOpGetTransactionListByAddressRequest, wire.ClusterOpGetTransactionListByAddressResponse, &wire.GetTransactionListByAddressRequest{}}, {"GetLogs", wire.ClusterOpGetLogRequest, wire.ClusterOpGetLogResponse, &wire.GetLogRequest{}}, - {"EstimateGas", wire.ClusterOpEstimateGasRequest, wire.ClusterOpEstimateGasResponse, &wire.EstimateGasRequest{}}, + {"EstimateGas", wire.ClusterOpEstimateGasRequest, wire.ClusterOpEstimateGasResponse, &wire.EstimateGasRequest{Tx: newTestTx()}}, {"GetStorageAt", wire.ClusterOpGetStorageRequest, wire.ClusterOpGetStorageResponse, &wire.GetStorageRequest{}}, {"GetCode", wire.ClusterOpGetCodeRequest, wire.ClusterOpGetCodeResponse, &wire.GetCodeRequest{}}, {"GasPrice", wire.ClusterOpGasPriceRequest, wire.ClusterOpGasPriceResponse, &wire.GasPriceRequest{}}, @@ -772,10 +781,10 @@ func TestMasterConn_SendAddMinorBlockHeader(t *testing.T) { }() req := &wire.AddMinorBlockHeaderRequest{ - MinorBlockHeader: &wire.RawBytes{}, + MinorBlockHeader: &types.MinorBlockHeader{}, TxCount: 5, XShardTxCount: 0, - CoinbaseAmountMap: &wire.RawBytes{}, + CoinbaseAmountMap: qkcCommon.NewEmptyTokenBalances(), ShardStats: wire.ShardStats{Branch: 0x00010001}, } type sendResult struct { @@ -859,8 +868,8 @@ func TestMasterConn_SendAddMinorBlockHeaderList(t *testing.T) { }() req := &wire.AddMinorBlockHeaderListRequest{ - MinorBlockHeaderList: []*wire.RawBytes{{0x01}}, - CoinbaseAmountMapList: []*wire.RawBytes{{0x02}}, + MinorBlockHeaderList: []*types.MinorBlockHeader{{}}, + CoinbaseAmountMapList: []*qkcCommon.TokenBalances{qkcCommon.NewEmptyTokenBalances()}, } type sendResult struct { resp *wire.AddMinorBlockHeaderListResponse diff --git a/qkc/cluster/slave/xshard_conn.go b/qkc/cluster/slave/xshard_conn.go index 450008de5745..f387d88534e9 100644 --- a/qkc/cluster/slave/xshard_conn.go +++ b/qkc/cluster/slave/xshard_conn.go @@ -15,6 +15,7 @@ import ( "github.com/ethereum/go-ethereum/qkc/cluster/conn" "github.com/ethereum/go-ethereum/qkc/cluster/wire" "github.com/ethereum/go-ethereum/qkc/serialize" + "github.com/ethereum/go-ethereum/qkc/types" ) // XshardHandler serves inbound xshard requests, implemented by the business @@ -234,7 +235,7 @@ func (x *XshardConn) sendPing(ctx context.Context) ([]byte, []uint32, error) { req := &wire.PingRequest{ ID: x.localID, FullShardIDList: x.localFullShardIDList, - RootTip: nil, // TODO: RootTip stays nil until the RootBlock wire type is ported. + RootTip: types.NewRootBlockWithHeader(&types.RootBlockHeader{}), } resp, err := x.sendRPC(ctx, byte(wire.ClusterOpPing), req) if err != nil { diff --git a/qkc/cluster/slave/xshard_test.go b/qkc/cluster/slave/xshard_test.go index f4fe7f02c02b..22fe16f6b97d 100644 --- a/qkc/cluster/slave/xshard_test.go +++ b/qkc/cluster/slave/xshard_test.go @@ -16,6 +16,7 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/qkc/cluster/wire" "github.com/ethereum/go-ethereum/qkc/serialize" + "github.com/ethereum/go-ethereum/qkc/types" ) // ── pool test helpers (white-box, same package) ────────────────────────────── @@ -236,7 +237,7 @@ func TestXshardConn_XshardTxListServedByHandler(t *testing.T) { server.Start() client.Start() - txList := wire.RawBytes{} + txList := types.CrossShardTransactionList{} ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() if err := client.SendAddXshardTxList(ctx, &wire.AddXshardTxListRequest{ @@ -259,7 +260,7 @@ func TestXshardConn_BatchAddXshardTxListServedByHandler(t *testing.T) { server.Start() client.Start() - txList := wire.RawBytes{} + txList := types.CrossShardTransactionList{} ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() if err := client.SendBatchAddXshardTxList(ctx, &wire.BatchAddXshardTxListRequest{ @@ -721,7 +722,7 @@ func TestXshardPool_SequentialDialLeavesOneLiveRoute(t *testing.T) { // retained (inbound) end back to S0 across the kept connection. ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() - if err := pool1.Lookup(s0Shards[0])[0].SendAddXshardTxList(ctx, &wire.AddXshardTxListRequest{Branch: s0Shards[0], TxList: &wire.RawBytes{}}); err != nil { + if err := pool1.Lookup(s0Shards[0])[0].SendAddXshardTxList(ctx, &wire.AddXshardTxListRequest{Branch: s0Shards[0], TxList: &types.CrossShardTransactionList{}}); err != nil { t.Fatalf("round-trip over retained route failed: %v", err) } } @@ -874,7 +875,7 @@ func TestXshardConn_SendXshardTxListErrorCode(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() - err = client.SendAddXshardTxList(ctx, &wire.AddXshardTxListRequest{Branch: 1, TxList: &wire.RawBytes{}}) + err = client.SendAddXshardTxList(ctx, &wire.AddXshardTxListRequest{Branch: 1, TxList: &types.CrossShardTransactionList{}}) if tc.wantErr { if err == nil { t.Fatal("expected error for non-zero error_code, got nil") diff --git a/qkc/cluster/wire/messages.go b/qkc/cluster/wire/messages.go index 684b72bd8a78..5a50aef61068 100644 --- a/qkc/cluster/wire/messages.go +++ b/qkc/cluster/wire/messages.go @@ -88,7 +88,9 @@ package wire import ( "github.com/ethereum/go-ethereum/qkc/account" + qkcCommon "github.com/ethereum/go-ethereum/qkc/common" "github.com/ethereum/go-ethereum/qkc/serialize" + "github.com/ethereum/go-ethereum/qkc/types" ) // ============================================================================= @@ -118,9 +120,9 @@ const UInt128Length = 16 // ("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 + ID []byte `bytesizeofslicelen:"4"` + FullShardIDList []uint32 `bytesizeofslicelen:"4"` + RootTip *types.RootBlock `ser:"nil"` } // PongResponse (ClusterOp.PONG, 0x82) — slave's reply to PING. @@ -200,7 +202,7 @@ type MineResponse struct { type GenTxRequest struct { NumTxPerShard uint32 XShardPercent uint32 - Tx *RawBytes // TODO: Replace with *TypedTransaction once core.TypedTransaction is ported + Tx *types.Transaction } // GenTxResponse (ClusterOp.GEN_TX_RESPONSE, 0xAA). @@ -236,8 +238,7 @@ type DestroyClusterPeerConnectionCommand struct { // // FIELDS = [("root_block", RootBlock), ("expect_switch", boolean)] type AddRootBlockRequest struct { - // TODO: Replace with *RootBlock once core.RootBlock is ported. - RootBlock *RawBytes + RootBlock *types.RootBlock ExpectSwitch bool } @@ -289,7 +290,7 @@ type GetNextBlockToMineRequest struct { // 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 + Block *types.MinorBlock } // AddMinorBlockRequest (ClusterOp.ADD_MINOR_BLOCK_REQUEST, 0x97) — JRPC-mined blocks. @@ -306,7 +307,7 @@ type AddMinorBlockResponse struct { // CheckMinorBlockRequest (ClusterOp.CHECK_MINOR_BLOCK_REQUEST, 0xBD). type CheckMinorBlockRequest struct { - MinorBlockHeader *RawBytes // TODO: Replace with *MinorBlockHeader once core.MinorBlockHeader is ported + MinorBlockHeader *types.MinorBlockHeader } // CheckMinorBlockResponse (ClusterOp.CHECK_MINOR_BLOCK_RESPONSE, 0xBE). @@ -317,7 +318,7 @@ type CheckMinorBlockResponse struct { // HeadersInfo — used by GetUnconfirmedHeadersResponse. type HeadersInfo struct { Branch uint32 - HeaderList []*RawBytes `bytesizeofslicelen:"4"` // TODO: Replace with []*MinorBlockHeader once core.MinorBlockHeader is ported + HeaderList []*types.MinorBlockHeader `bytesizeofslicelen:"4"` } // GetUnconfirmedHeadersRequest (ClusterOp.GET_UNCONFIRMED_HEADERS_REQUEST, 0x8B) — empty body. @@ -340,10 +341,9 @@ type GetUnconfirmedHeadersResponse struct { // ("mined_blocks", uint16), // ] type AccountBranchData struct { - Branch uint32 - TransactionCount serialize.Uint256 - // TODO: Replace with *TokenBalanceMap once core.TokenBalanceMap is ported. - TokenBalances *RawBytes + Branch uint32 + TransactionCount serialize.Uint256 + TokenBalances *qkcCommon.TokenBalances IsContract bool PoswMineableBlocks uint16 MinedBlocks uint16 @@ -363,7 +363,7 @@ type GetAccountDataResponse struct { // AddTransactionRequest (ClusterOp.ADD_TRANSACTION_REQUEST, 0x8F). type AddTransactionRequest struct { - Tx *RawBytes // TODO: Replace with *TypedTransaction once core.TypedTransaction is ported + Tx *types.Transaction } // AddTransactionResponse (ClusterOp.ADD_TRANSACTION_RESPONSE, 0x90). @@ -410,12 +410,10 @@ type ShardStats struct { // ("shard_stats", ShardStats), // ] type AddMinorBlockHeaderRequest struct { - // TODO: Replace with *MinorBlockHeader once core.MinorBlockHeader is ported. - MinorBlockHeader *RawBytes - TxCount uint32 - XShardTxCount uint32 - // TODO: Replace with *TokenBalanceMap once core.TokenBalanceMap is ported. - CoinbaseAmountMap *RawBytes + MinorBlockHeader *types.MinorBlockHeader + TxCount uint32 + XShardTxCount uint32 + CoinbaseAmountMap *qkcCommon.TokenBalances ShardStats ShardStats } @@ -427,8 +425,8 @@ type AddMinorBlockHeaderResponse struct { // 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 + MinorBlockHeaderList []*types.MinorBlockHeader `bytesizeofslicelen:"4"` + CoinbaseAmountMapList []*qkcCommon.TokenBalances `bytesizeofslicelen:"4"` } // AddMinorBlockHeaderListResponse (ClusterOp.ADD_MINOR_BLOCK_HEADER_LIST_RESPONSE, 0xBC). @@ -453,9 +451,8 @@ type SyncMinorBlockListRequest struct { // ("shard_stats", Optional(ShardStats)), // ] type SyncMinorBlockListResponse struct { - ErrorCode uint32 - // TODO: Replace with real block_coinbase_map once core.TokenBalanceMap is ported. - BlockCoinbaseMap *RawBytes + ErrorCode uint32 + BlockCoinbaseMap PrependedSizeCoinbaseMap4 ShardStats *ShardStats `ser:"nil"` } @@ -480,9 +477,8 @@ type GetMinorBlockRequest struct { // GetMinorBlockResponse (ClusterOp.GET_MINOR_BLOCK_RESPONSE, 0x9E). type GetMinorBlockResponse struct { - ErrorCode uint32 - // TODO: Replace with *MinorBlock once core.MinorBlock is ported. - MinorBlock *RawBytes + ErrorCode uint32 + MinorBlock *types.MinorBlock ExtraInfo *MinorBlockExtraInfo `ser:"nil"` } @@ -494,16 +490,14 @@ type GetTransactionRequest struct { // GetTransactionResponse (ClusterOp.GET_TRANSACTION_RESPONSE, 0xA0). type GetTransactionResponse struct { - ErrorCode uint32 - // TODO: Replace with *MinorBlock once core.MinorBlock is ported. - MinorBlock *RawBytes + ErrorCode uint32 + MinorBlock *types.MinorBlock Index uint32 } // ExecuteTransactionRequest (ClusterOp.EXECUTE_TRANSACTION_REQUEST, 0xA3). type ExecuteTransactionRequest struct { - // TODO: Replace with *TypedTransaction once core.TypedTransaction is ported. - Tx *RawBytes + Tx *types.Transaction FromAddress account.Address BlockHeight *uint64 `ser:"nil"` } @@ -522,12 +516,10 @@ type GetTransactionReceiptRequest struct { // GetTransactionReceiptResponse (ClusterOp.GET_TRANSACTION_RECEIPT_RESPONSE, 0xA6). type GetTransactionReceiptResponse struct { - ErrorCode uint32 - // TODO: Replace with *MinorBlock once core.MinorBlock is ported. - MinorBlock *RawBytes + ErrorCode uint32 + MinorBlock *types.MinorBlock Index uint32 - // TODO: Replace with *TransactionReceipt once core.TransactionReceipt is ported. - Receipt *RawBytes + Receipt *types.ClusterTransactionReceipt } // TransactionDetail — used by GetTransactionListByAddressResponse and @@ -595,13 +587,12 @@ type GetLogRequest struct { // GetLogResponse (ClusterOp.GET_LOG_RESPONSE, 0xAE). type GetLogResponse struct { ErrorCode uint32 - Logs []*RawBytes `bytesizeofslicelen:"4"` // TODO: Replace with []*Log once core.Log is ported + Logs []*types.ClusterLog `bytesizeofslicelen:"4"` } // EstimateGasRequest (ClusterOp.ESTIMATE_GAS_REQUEST, 0xAF). type EstimateGasRequest struct { - // TODO: Replace with *TypedTransaction once core.TypedTransaction is ported. - Tx *RawBytes + Tx *types.Transaction FromAddress account.Address } @@ -719,7 +710,7 @@ type GetTotalBalanceResponse struct { type AddXshardTxListRequest struct { Branch uint32 MinorBlockHash [HashLength]byte - TxList *RawBytes // TODO: Replace with *CrossShardTransactionList once core.CrossShardTransactionList is ported + TxList *types.CrossShardTransactionList } // AddXshardTxListResponse (ClusterOp.ADD_XSHARD_TX_LIST_RESPONSE, 0x94). @@ -754,32 +745,30 @@ type BatchAddXshardTxListResponse struct { // ("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. - RootBlockHeader *RawBytes + Version uint32 + NetworkID uint32 + PeerID [HashLength]byte + PeerIP [UInt128Length]byte + PeerPort uint16 + ChainMaskList []uint32 `bytesizeofslicelen:"4"` + RootBlockHeader *types.RootBlockHeader GenesisRootBlockHash [HashLength]byte } // NewMinorBlockHeaderListCommand (CommandOp.NEW_MINOR_BLOCK_HEADER_LIST, 0x01). type NewMinorBlockHeaderListCommand struct { - // TODO: Replace with *RootBlockHeader once core.RootBlockHeader is ported. - RootBlockHeader *RawBytes - MinorBlockHeaderList []*RawBytes `bytesizeofslicelen:"4"` // TODO: Replace with []*MinorBlockHeader once core.MinorBlockHeader is ported + RootBlockHeader *types.RootBlockHeader + MinorBlockHeaderList []*types.MinorBlockHeader `bytesizeofslicelen:"4"` } // NewTransactionListCommand (CommandOp.NEW_TRANSACTION_LIST, 0x02). type NewTransactionListCommand struct { - TransactionList []*RawBytes `bytesizeofslicelen:"4"` // TODO: Replace with []*TypedTransaction once core.TypedTransaction is ported + TransactionList []*types.Transaction `bytesizeofslicelen:"4"` } // NewBlockMinorCommand (CommandOp.NEW_BLOCK_MINOR, 0x0D). type NewBlockMinorCommand struct { - Block *RawBytes // TODO: Replace with *MinorBlock once core.MinorBlock is ported + Block *types.MinorBlock } // PingPongCommand (CommandOp.PING 0x0E, PONG 0x0F). @@ -789,7 +778,7 @@ type PingPongCommand struct { // NewRootBlockCommand (CommandOp.NEW_ROOT_BLOCK, 0x12). type NewRootBlockCommand struct { - Block *RawBytes // TODO: Replace with *RootBlock once core.RootBlock is ported + Block *types.RootBlock } // ============================================================================= @@ -821,9 +810,8 @@ type GetRootBlockHeaderListRequest struct { // GetRootBlockHeaderListResponse (CommandOp.GET_ROOT_BLOCK_HEADER_LIST_RESPONSE, 0x06). type GetRootBlockHeaderListResponse struct { - // TODO: Replace with *RootBlockHeader once core.RootBlockHeader is ported. - RootTip *RawBytes - BlockHeaderList []*RawBytes `bytesizeofslicelen:"4"` // TODO: Replace with []*RootBlockHeader once core.RootBlockHeader is ported + RootTip *types.RootBlockHeader + BlockHeaderList []*types.RootBlockHeader `bytesizeofslicelen:"4"` } // GetRootBlockHeaderListWithSkipRequest (CommandOp.GET_ROOT_BLOCK_HEADER_LIST_WITH_SKIP_REQUEST, 0x10). @@ -842,7 +830,7 @@ type GetRootBlockListRequest struct { // GetRootBlockListResponse (CommandOp.GET_ROOT_BLOCK_LIST_RESPONSE, 0x08). type GetRootBlockListResponse struct { - RootBlockList []*RawBytes `bytesizeofslicelen:"4"` // TODO: Replace with []*RootBlock once core.RootBlock is ported + RootBlockList []*types.RootBlock `bytesizeofslicelen:"4"` } // GetMinorBlockListRequest (CommandOp.GET_MINOR_BLOCK_LIST_REQUEST, 0x09). @@ -852,7 +840,7 @@ type GetMinorBlockListRequest struct { // GetMinorBlockListResponse (CommandOp.GET_MINOR_BLOCK_LIST_RESPONSE, 0x0A). type GetMinorBlockListResponse struct { - MinorBlockList []*RawBytes `bytesizeofslicelen:"4"` // TODO: Replace with []*MinorBlock once core.MinorBlock is ported + MinorBlockList []*types.MinorBlock `bytesizeofslicelen:"4"` } // GetMinorBlockHeaderListRequest (CommandOp.GET_MINOR_BLOCK_HEADER_LIST_REQUEST, 0x0B). @@ -865,11 +853,9 @@ type GetMinorBlockHeaderListRequest struct { // GetMinorBlockHeaderListResponse (CommandOp.GET_MINOR_BLOCK_HEADER_LIST_RESPONSE, 0x0C). type GetMinorBlockHeaderListResponse struct { - // TODO: Replace with *RootBlockHeader once core.RootBlockHeader is ported. - RootTip *RawBytes - // TODO: Replace with *MinorBlockHeader once core.MinorBlockHeader is ported. - ShardTip *RawBytes - BlockHeaderList []*RawBytes `bytesizeofslicelen:"4"` // TODO: Replace with []*MinorBlockHeader once core.MinorBlockHeader is ported + RootTip *types.RootBlockHeader + ShardTip *types.MinorBlockHeader + BlockHeaderList []*types.MinorBlockHeader `bytesizeofslicelen:"4"` } // GetMinorBlockHeaderListWithSkipRequest (CommandOp.GET_MINOR_BLOCK_HEADER_LIST_WITH_SKIP_REQUEST, 0x13). diff --git a/qkc/cluster/wire/messages_test.go b/qkc/cluster/wire/messages_test.go index d4f69bbff5ff..11eef89fe076 100644 --- a/qkc/cluster/wire/messages_test.go +++ b/qkc/cluster/wire/messages_test.go @@ -11,8 +11,6 @@ // // - This is not an exhaustive byte-level test of all 60+ messages. // - Python/Go golden vectors are added for selected fully concrete messages. -// - Messages containing RawBytes placeholders are excluded from byte-level -// compatibility checks until their underlying types are migrated. // - Some concrete messages may not have golden vectors yet and will be added // as additional protocol types are validated. // @@ -29,7 +27,10 @@ import ( "testing" "github.com/ethereum/go-ethereum/qkc/account" + qkcCommon "github.com/ethereum/go-ethereum/qkc/common" "github.com/ethereum/go-ethereum/qkc/serialize" + "github.com/ethereum/go-ethereum/qkc/types" + "github.com/holiman/uint256" ) // ============================================================================= @@ -94,6 +95,133 @@ func TestPrependedSizeHashList4_RoundTrip(t *testing.T) { } } +// tokenBalancesEqual reports whether two TokenBalances hold identical +// (tokenID, balance) entries. Test-only: TokenBalances itself has no Equal. +func tokenBalancesEqual(a, b *qkcCommon.TokenBalances) bool { + if (a == nil) != (b == nil) { + return false + } + if a == nil { + return true + } + ma, mb := a.GetBalanceMap(), b.GetBalanceMap() + if len(ma) != len(mb) { + return false + } + for k, va := range ma { + vb, ok := mb[k] + if !ok || va == nil || vb == nil || va.Cmp(vb) != 0 { + return false + } + } + return true +} + +func TestPrependedSizeCoinbaseMap4_RoundTrip(t *testing.T) { + cases := []PrependedSizeCoinbaseMap4{ + nil, + {}, + {makeHash(1): qkcCommon.NewEmptyTokenBalances()}, + {makeHash(1): qkcCommon.NewTokenBalancesWithMap(map[uint64]*uint256.Int{1: uint256.NewInt(100)})}, + { + makeHash(1): qkcCommon.NewTokenBalancesWithMap(map[uint64]*uint256.Int{1: uint256.NewInt(1), 2: uint256.NewInt(2)}), + makeHash(2): qkcCommon.NewTokenBalancesWithMap(map[uint64]*uint256.Int{735800: uint256.NewInt(42)}), + }, + } + for _, want := range cases { + var buf []byte + if err := want.Serialize(&buf); err != nil { + t.Fatalf("Serialize: %v", err) + } + bb := serialize.NewByteBuffer(buf) + var got PrependedSizeCoinbaseMap4 + if err := got.Deserialize(bb); err != nil { + t.Fatalf("Deserialize: %v", err) + } + if len(got) != len(want) { + t.Fatalf("length mismatch: got %d, want %d", len(got), len(want)) + } + for k, wantV := range want { + if !tokenBalancesEqual(got[k], wantV) { + t.Errorf("value mismatch for key %x", k[:]) + } + } + // Re-serialization must reproduce identical bytes (skip-zero symmetry). + var buf2 []byte + if err := got.Serialize(&buf2); err != nil { + t.Fatalf("Re-serialize: %v", err) + } + if !bytes.Equal(buf, buf2) { + t.Errorf("round-trip bytes mismatch") + } + } +} + +func TestPrependedSizeCoinbaseMap4_EmptyGolden(t *testing.T) { + // Python PrependedSizeMapSerializer(4, hash256, TokenBalanceMap).serialize({}) + // emits a bare 4-byte zero count: block_coinbase_map is never Optional in + // pyquarkchain (SyncMinorBlockListResponse.__init__ normalizes None to {}). + cases := map[string]PrependedSizeCoinbaseMap4{ + "nil_map": nil, + "empty_map": {}, + } + for name, m := range cases { + t.Run(name, func(t *testing.T) { + var buf []byte + if err := m.Serialize(&buf); err != nil { + t.Fatalf("Serialize: %v", err) + } + if got := hex.EncodeToString(buf); got != "00000000" { + t.Errorf("wire: got %s, want 00000000", got) + } + }) + } +} + +func TestPrependedSizeCoinbaseMap4_SortedKeys(t *testing.T) { + // Keys are inserted out of order; the wire encoding must emit them in + // ascending byte order regardless (Python sorted(item_map) determinism). + m := PrependedSizeCoinbaseMap4{ + makeHash(3): qkcCommon.NewTokenBalancesWithMap(map[uint64]*uint256.Int{3: uint256.NewInt(3)}), + makeHash(1): qkcCommon.NewTokenBalancesWithMap(map[uint64]*uint256.Int{1: uint256.NewInt(1)}), + makeHash(2): qkcCommon.NewTokenBalancesWithMap(map[uint64]*uint256.Int{2: uint256.NewInt(2)}), + } + wantHex := "00000003" + // entry count (4B) + "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20" + // key makeHash(1) (32B) + "0000000101010101" + // value TokenBalanceMap{1: 1} + "02030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021" + // key makeHash(2) (32B) + "0000000101020102" + // value TokenBalanceMap{2: 2} + "030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122" + // key makeHash(3) (32B) + "0000000101030103" // value TokenBalanceMap{3: 3} + + var buf []byte + if err := m.Serialize(&buf); err != nil { + t.Fatalf("Serialize: %v", err) + } + if got := hex.EncodeToString(buf); got != wantHex { + t.Errorf("wire: got %s, want %s", got, wantHex) + } + + // Determinism: Go map iteration randomness must not leak into the encoding. + var buf2 []byte + if err := m.Serialize(&buf2); err != nil { + t.Fatalf("Serialize (2nd): %v", err) + } + if !bytes.Equal(buf, buf2) { + t.Errorf("encoding not deterministic across serializations") + } +} + +func TestPrependedSizeCoinbaseMap4_NilValueError(t *testing.T) { + // A nil *TokenBalances value indicates a caller bug: Serialize must return + // an error instead of panicking (TokenBalances.Len dereferences nil). + m := PrependedSizeCoinbaseMap4{makeHash(1): nil} + var buf []byte + if err := m.Serialize(&buf); err == nil { + t.Fatalf("Serialize: expected error for nil value, got nil") + } +} + // ============================================================================= // §2 Message round-trips (representative samples) // ============================================================================= @@ -103,14 +231,20 @@ func TestMessageRoundTrip(t *testing.T) { name string msg any }{ - {"PingRequest_no_RawBytes", PingRequest{ + {"PingRequest_no_RootTip", PingRequest{ ID: []byte("slave1"), FullShardIDList: []uint32{0x00010001, 0x00020002}, }}, - {"GenTxRequest_RawBytes_last", GenTxRequest{ + {"PingRequest_with_RootTip", PingRequest{ + ID: []byte("slave1"), + FullShardIDList: []uint32{0x00010001, 0x00020002}, + RootTip: types.NewRootBlockWithHeader(&types.RootBlockHeader{}), + }}, + {"GenTxRequest_Tx_last", GenTxRequest{ NumTxPerShard: 10, XShardPercent: 30, - Tx: &RawBytes{0x01, 0x02, 0x03}, + Tx: types.NewEvmTransaction(1, account.Recipient{}, big.NewInt(100), 21000, big.NewInt(10), + 0x00010001, 0x00010002, 1, 1, []byte{0xAA}, 1, 1), }}, } @@ -281,6 +415,26 @@ func TestPythonCompat_PingRequest(t *testing.T) { assertPythonMatch(t, wantHex, buf) } +func TestPythonCompat_PingRequest_WithRootTip(t *testing.T) { + // ID="test", FullShardIDList=[1,2], RootTip empty RootBlock. + // The leading "01" after full_shard_id_list is Optional's presence marker; + // it is emitted when RootTip is non-nil and omitted when nil (the previous + // test). Golden generated by pyquarkchain: Ping(b'test', [1,2], + // RootBlock(RootBlockHeader())).serialize().hex() + wantHex := "000000047465737400000002000000010000000201000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + + ping := PingRequest{ + ID: []byte("test"), + FullShardIDList: []uint32{1, 2}, + RootTip: types.NewRootBlockWithHeader(&types.RootBlockHeader{}), + } + var buf []byte + if err := serialize.Serialize(&buf, &ping); err != nil { + t.Fatalf("Serialize: %v", err) + } + assertPythonMatch(t, wantHex, buf) +} + func TestPythonCompat_SlaveInfo(t *testing.T) { // ID="s1", Host="localhost", Port=38391, FullShardIDList=[0x00010001] wantHex := "000000027331" + @@ -397,17 +551,16 @@ func TestPythonCompat_TransactionDetail(t *testing.T) { func TestMessageEncoding_SyncMinorBlockListResponse_NonNilShardStats(t *testing.T) { // Tests wire encoding of SyncMinorBlockListResponse. // - // NOTE: - // BlockCoinbaseMap is currently represented as *RawBytes placeholder. - // Its encoding follows Go byte slice serialization and is NOT compatible - // with Python's PrependedSizeMapSerializer(4, hash256, TokenBalanceMap). + // block_coinbase_map uses PrependedSizeCoinbaseMap4 — a Python-compatible + // PrependedSizeMapSerializer(4, hash256, TokenBalanceMap) — with a non-empty + // map asserted byte-for-byte against the Python golden. // // Covers: // - BigUint in message context (via ShardStats.Difficulty) // - Optional(struct) non-nil (ShardStats) // - nested struct composition // - // Python schema reference (not a golden byte compatibility assertion): + // Python schema reference: // ("error_code", uint32), // ("block_coinbase_map", PrependedSizeMapSerializer(4, hash256, TokenBalanceMap)), // ("shard_stats", Optional(ShardStats)), @@ -424,8 +577,19 @@ func TestMessageEncoding_SyncMinorBlockListResponse_NonNilShardStats(t *testing. // ("block_count60s", uint32), # uint32 // ("stale_block_count60s", uint32),# uint32 // ("last_block_time", uint32), # uint32 + // + // block_coinbase_map = { makeHash(1): TokenBalanceMap({1: 100}) }: + // "00000001" count=1 + // "0102...20" hash key makeHash(1) (32B) + // "00000001" TokenBalanceMap count=1 + // "0101" biguint key 1 + // "0164" biguint value 100 wantHex := "00000000" + // error_code (4B) - "02aabb" + // block_coinbase_map: RawBytes placeholder encoding + "00000001" + // block_coinbase_map: map count (4B) + "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20" + // hash key (32B) + "00000001" + // TokenBalanceMap count (4B) + "0101" + // token id 1 (biguint) + "0164" + // balance 100 (biguint) "01" + // shard_stats present marker (1B) "00000001" + // branch (4B) "0000000000000064" + // height (8B) @@ -440,8 +604,10 @@ func TestMessageEncoding_SyncMinorBlockListResponse_NonNilShardStats(t *testing. "77359400" // last_block_time (4B) resp := SyncMinorBlockListResponse{ - ErrorCode: 0, - BlockCoinbaseMap: &RawBytes{0xAA, 0xBB}, + ErrorCode: 0, + BlockCoinbaseMap: PrependedSizeCoinbaseMap4{ + makeHash(1): qkcCommon.NewTokenBalancesWithMap(map[uint64]*uint256.Int{1: uint256.NewInt(100)}), + }, ShardStats: &ShardStats{ Branch: 1, Height: 100, diff --git a/qkc/cluster/wire/rawbytes_placeholder.go b/qkc/cluster/wire/rawbytes_placeholder.go deleted file mode 100644 index ee72db617827..000000000000 --- a/qkc/cluster/wire/rawbytes_placeholder.go +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright 2026-2027, QuarkChain. - -// ============================================================================= -// WIRE MIGRATION SHIM (NOT PART OF PROTOCOL SPEC) -// ============================================================================= -// -// This file provides temporary placeholder types used during the migration -// from Python QuarkChain Serializable types to native Go structs. -// -// This is an IMPLEMENTATION-ONLY migration aid. -// It is NOT part of the wire protocol specification. -// -// ----------------------------------------------------------------------------- -// IMPORTANT DISTINCTION -// ----------------------------------------------------------------------------- -// -// The wire protocol is defined by the concrete message structs in package wire. -// -// RawBytes does NOT implement the original serialization logic of the Python -// Serializable type it replaces. It only exists to allow incremental migration -// by temporarily representing unported complex types. -// -// ----------------------------------------------------------------------------- -// Migration Strategy -// ----------------------------------------------------------------------------- -// -// Some Python Serializable types (for example: -// -// - RootBlock -// - MinorBlockHeader -// - TypedTransaction -// - CrossShardTransactionList -// - TokenBalanceMap -// -// ) may not yet have corresponding Go implementations. -// -// During migration, these types may temporarily be represented as: -// -// *RawBytes -// -// This allows: -// - message structs to be migrated incrementally -// - Go code to compile before all dependent types are ported -// - each complex type to be replaced independently -// -// RawBytes is expected to be removed once the corresponding native Go type -// has been implemented. -// -// ----------------------------------------------------------------------------- -// RawBytes Semantics -// ----------------------------------------------------------------------------- -// -// RawBytes is an opaque placeholder containing serialized bytes of an -// unported Python Serializable object. -// -// It does NOT: -// - decode the contained data -// - inspect the contained data -// - reproduce the original Python serialization format -// - define any protocol-level wire encoding rules -// -// Serialization behavior is inherited from the generic serialization framework -// for byte slices. The resulting wire format may differ from the original -// Python Serializable encoding. -// -// Any required wire compatibility must be achieved by replacing RawBytes with -// the correct concrete Go type. -// -// ----------------------------------------------------------------------------- -// SAFETY CONSTRAINTS -// ----------------------------------------------------------------------------- -// -// RawBytes MUST: -// -// 1. remain opaque and must not be partially decoded -// 2. not be used as a permanent protocol type -// 3. be replaced by the corresponding concrete Go implementation -// -// Using RawBytes as a final protocol representation may result in wire format -// incompatibility. -// -// ----------------------------------------------------------------------------- -// Lifecycle -// ----------------------------------------------------------------------------- -// -// Migration completion: -// -// 1. Implement the corresponding native Go struct -// 2. Replace all *RawBytes fields with concrete types -// 3. Verify compatibility through Python/Go wire compatibility tests -// 4. Remove this migration shim -// -// ----------------------------------------------------------------------------- -// WARNING -// ----------------------------------------------------------------------------- -// -// This file contains temporary migration helpers only. -// It must not become part of the production protocol implementation. -// - -package wire - -// RawBytes is an opaque placeholder for Python Serializable types that have -// not yet been migrated to native Go structs. -// -// RawBytes does not define custom wire behavior. It follows the default -// serialization behavior of the underlying byte slice type. -// -// It must be replaced by the corresponding concrete Go type once migration -// is complete. -type RawBytes []byte diff --git a/qkc/cluster/wire/types.go b/qkc/cluster/wire/types.go index 5790d4e572b7..7783ab7e4676 100644 --- a/qkc/cluster/wire/types.go +++ b/qkc/cluster/wire/types.go @@ -3,10 +3,13 @@ package wire import ( + "bytes" "encoding/binary" "fmt" "math" + "sort" + qkcCommon "github.com/ethereum/go-ethereum/qkc/common" "github.com/ethereum/go-ethereum/qkc/serialize" ) @@ -90,3 +93,67 @@ func (p *PrependedSizeHashList4) Deserialize(bb *serialize.ByteBuffer) error { } var _ serialize.Serializable = (*PrependedSizeHashList4)(nil) + +// PrependedSizeCoinbaseMap4 is a block-hash → TokenBalances map with a 4-byte +// length prefix (matches Python +// PrependedSizeMapSerializer(4, hash256, TokenBalanceMap)). Keys are emitted in +// ascending byte order, mirroring the Python serializer's sorted() iteration. +// +// Values must be non-nil: Python map values are never None, and a nil +// *TokenBalances entry indicates a caller bug. Serialize returns an error for +// it (encoding it as an empty map would silently lose the distinction). +type PrependedSizeCoinbaseMap4 map[[HashLength]byte]*qkcCommon.TokenBalances + +func (p PrependedSizeCoinbaseMap4) Serialize(w *[]byte) error { + lenBuf := make([]byte, 4) + binary.BigEndian.PutUint32(lenBuf, uint32(len(p))) + *w = append(*w, lenBuf...) + + keys := make([][HashLength]byte, 0, len(p)) + for k := range p { + keys = append(keys, k) + } + sort.Slice(keys, func(i, j int) bool { return bytes.Compare(keys[i][:], keys[j][:]) < 0 }) + for _, key := range keys { + value := p[key] + if value == nil { + return fmt.Errorf("PrependedSizeCoinbaseMap4.Serialize: nil TokenBalances value for key %x", key[:]) + } + *w = append(*w, key[:]...) + if err := value.Serialize(w); err != nil { + return err + } + } + return nil +} + +func (p *PrependedSizeCoinbaseMap4) Deserialize(bb *serialize.ByteBuffer) error { + length, err := bb.GetUInt32() + if err != nil { + return err + } + + if uint64(length) > math.MaxInt32 || int64(length)*int64(HashLength) > int64(bb.Remaining()) { + return fmt.Errorf("PrependedSizeCoinbaseMap4.Deserialize: length %d exceeds remaining %d", length, bb.Remaining()) + } + + m := make(map[[HashLength]byte]*qkcCommon.TokenBalances, int(length)) + for i := 0; i < int(length); i++ { + hashBytes, err := bb.ReadBytes(HashLength) + if err != nil { + return err + } + var hash [HashLength]byte + copy(hash[:], hashBytes) + + value := qkcCommon.NewEmptyTokenBalances() + if err := value.Deserialize(bb); err != nil { + return err + } + m[hash] = value + } + *p = m + return nil +} + +var _ serialize.Serializable = (*PrependedSizeCoinbaseMap4)(nil)