diff --git a/core/rawdb/accessors_chain_qkc.go b/core/rawdb/accessors_chain_qkc.go new file mode 100644 index 000000000000..9e7dab74a0c0 --- /dev/null +++ b/core/rawdb/accessors_chain_qkc.go @@ -0,0 +1,333 @@ +// Copyright 2026-2027, QuarkChain. +package rawdb + +import ( + "encoding/binary" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/qkc/serialize" + "github.com/ethereum/go-ethereum/qkc/types" + "github.com/ethereum/go-ethereum/rlp" +) + +const qkcDBLog = "db-operation" + +// QKCHashList is the serialized list of cross-shard deposit hashes. +type HashList struct { + HList []common.Hash `bytesizeofslicelen:"4"` +} + +// ReadRootCanonicalHash retrieves the root block hash assigned to a canonical block number. +func ReadRootCanonicalHash(db ethdb.KeyValueReader, number uint64) common.Hash { + data, _ := db.Get(rootCanonicalHashKey(number)) + if len(data) == 0 { + return common.Hash{} + } + return common.BytesToHash(data) +} + +// WriteRootCanonicalHash stores the root block hash assigned to a canonical block number. +func WriteRootCanonicalHash(db ethdb.KeyValueWriter, hash common.Hash, number uint64) { + if err := db.Put(rootCanonicalHashKey(number), hash.Bytes()); err != nil { + log.Crit("Failed to store root number to hash mapping", "err", err) + } +} + +// DeleteRootCanonicalHash removes the root number to hash canonical mapping. +func DeleteRootCanonicalHash(db ethdb.KeyValueWriter, number uint64) { + if err := db.Delete(rootCanonicalHashKey(number)); err != nil { + log.Crit("Failed to delete root number to hash mapping", "err", err) + } +} + +// ReadMinorCanonicalHash retrieves the minor block hash assigned to a canonical block number. +func ReadMinorCanonicalHash(db ethdb.KeyValueReader, number uint64) common.Hash { + data, _ := db.Get(minorCanonicalHashKey(number)) + if len(data) == 0 { + return common.Hash{} + } + return common.BytesToHash(data) +} + +// WriteMinorCanonicalHash stores the minor block hash assigned to a canonical block number. +func WriteMinorCanonicalHash(db ethdb.KeyValueWriter, hash common.Hash, number uint64) { + if err := db.Put(minorCanonicalHashKey(number), hash.Bytes()); err != nil { + log.Crit("Failed to store minor number to hash mapping", "err", err) + } +} + +// DeleteMinorCanonicalHash removes the minor number to hash canonical mapping. +func DeleteMinorCanonicalHash(db ethdb.KeyValueWriter, number uint64) { + if err := db.Delete(minorCanonicalHashKey(number)); err != nil { + log.Crit("Failed to delete minor number to hash mapping", "err", err) + } +} + +// ReadRootHeadHash retrieves the current canonical root block hash for a shard. +func ReadRootHeadHash(db ethdb.KeyValueReader) common.Hash { + data, _ := db.Get(qkcRootHeadKey()) + if len(data) == 0 { + return common.Hash{} + } + return common.BytesToHash(data) +} + +// WriteRootHeadHash stores the current canonical root block hash for a shard. +func WriteRootHeadHash(db ethdb.KeyValueWriter, hash common.Hash) { + if err := db.Put(qkcRootHeadKey(), hash.Bytes()); err != nil { + log.Crit("Failed to store root head block hash", "err", err) + } +} + +// HasReceipts verifies the existence of all the transaction receipts belonging +// to a block. +func HasQKCReceipts(db ethdb.KeyValueReader, hash common.Hash) bool { + if has, err := db.Has(qkcBlockReceiptsKey(hash)); !has || err != nil { + return false + } + return true +} + +// ReadReceipts retrieves the consensus-encoded receipts belonging to a block. +func ReadQKCReceipts(db ethdb.KeyValueReader, hash common.Hash) types.Receipts { + data, _ := db.Get(qkcBlockReceiptsKey(hash)) + if len(data) == 0 { + return nil + } + var receipts types.Receipts + if err := rlp.DecodeBytes(data, &receipts); err != nil { + log.Error("Invalid receipt array RLP", "hash", hash, "err", err) + return nil + } + return receipts +} + +// WriteReceipts stores the consensus encoding of all receipts belonging to a block. +func WriteQKCReceipts(db ethdb.KeyValueWriter, hash common.Hash, receipts types.Receipts) { + bytes, err := rlp.EncodeToBytes(receipts) + if err != nil { + log.Crit("Failed to encode block receipts", "err", err) + } + // Store the flattened receipt slice + if err := db.Put(qkcBlockReceiptsKey(hash), bytes); err != nil { + log.Crit("Failed to store block receipts", "err", err) + } +} + +// DeleteReceipts removes all receipt data associated with a block hash. +func DeleteQKCReceipts(db ethdb.KeyValueWriter, hash common.Hash) { + if err := db.Delete(qkcBlockReceiptsKey(hash)); err != nil { + log.Crit("Failed to delete block receipts", "err", err) + } +} + +// HasMinorBlock verifies the existence of a minor block corresponding to the hash. +func HasMinorBlock(db ethdb.KeyValueReader, hash common.Hash) bool { + if has, err := db.Has(qkcMinorBlockKey(hash)); !has || err != nil { + return false + } + return true +} + +// ReadMinorBlock retrieves the block body corresponding to the hash. +func ReadMinorBlock(db ethdb.KeyValueReader, hash common.Hash) *types.MinorBlock { + data, _ := db.Get(qkcMinorBlockKey(hash)) + if len(data) == 0 { + return nil + } + block := new(types.MinorBlock) + if err := serialize.Deserialize(serialize.NewByteBuffer(data), block); err != nil { + log.Error("Invalid block body Deserialize", "hash", hash, "err", err) + return nil + } + return block +} + +// WriteMinorBlock storea a block body into the database. +func WriteMinorBlock(db ethdb.KeyValueWriter, block *types.MinorBlock) { + data, err := serialize.SerializeToBytes(block) + if err != nil { + log.Crit("Failed to serialize body", "err", err) + } + log.Info(qkcDBLog+" Write MinorBlock", "branch", fmt.Sprintf("%x", block.Branch().Value), "height", block.NumberU64(), "hash", block.Hash().TerminalString(), "len(tx)", len(block.Transactions())) + if err := db.Put(qkcMinorBlockKey(block.Hash()), data); err != nil { + log.Crit("Failed to store minor block body", "err", err) + } +} + +// HasRootBlock verifies the existence of a root block corresponding to the hash. +func HasRootBlock(db ethdb.KeyValueReader, hash common.Hash) bool { + if has, err := db.Has(qkcRootBlockKey(hash)); !has || err != nil { + return false + } + return true +} + +// ReadRootBlock retrieves the block rootBlockBody corresponding to the hash. +func ReadRootBlock(db ethdb.KeyValueReader, hash common.Hash) *types.RootBlock { + data, _ := db.Get(qkcRootBlockKey(hash)) + if len(data) == 0 { + return nil + } + block := new(types.RootBlock) + if err := serialize.Deserialize(serialize.NewByteBuffer(data), block); err != nil { + log.Error("Invalid block rootBlockBody Deserialize", "hash", hash, "err", err) + return nil + } + return block +} + +// WriteRootBlock storea a block rootBlockBody into the database. +func WriteRootBlock(db ethdb.KeyValueWriter, block *types.RootBlock) { + data, err := serialize.SerializeToBytes(block) + if err != nil { + log.Crit("Failed to serialize RootBlock", "err", err) + } + log.Info(qkcDBLog+" Write RootBlock", "height", block.NumberU64(), "hash", block.Hash()) + if err := db.Put(qkcRootBlockKey(block.Hash()), data); err != nil { + log.Crit("Failed to store RootBlock", "err", err) + } +} + +// DeleteRootBlock removes all block data associated with a hash. +func DeleteRootBlock(db ethdb.KeyValueWriter, hash common.Hash) { + if err := db.Delete(qkcRootBlockKey(hash)); err != nil { + log.Crit("Failed to delete root block", "err", err) + } +} + +// DeleteBlock removes all block data associated with a hash. +func DeleteMinorBlock(db ethdb.KeyValueWriter, hash common.Hash) { + DeleteQKCReceipts(db, hash) + if err := db.Delete(qkcMinorBlockKey(hash)); err != nil { + log.Crit("Failed to delete minor block", "err", err) + } +} + +func WriteTotalTx(db ethdb.KeyValueWriter, hash common.Hash, txCount uint32) { + data := qkcEncodeUint32(txCount) + if err := db.Put(qkcTotalTxCountKey(hash), data); err != nil { + log.Crit("Failed to store total Tx", "err", err) + } +} + +func ReadTotalTx(db ethdb.KeyValueReader, hash common.Hash) *uint32 { + data, _ := db.Get(qkcTotalTxCountKey(hash)) + if len(data) != 4 { + return nil + } + number := binary.BigEndian.Uint32(data) + return &number + +} + +func WriteGenesisBlock(db ethdb.KeyValueWriter, rHash common.Hash, block *types.MinorBlock) { + data, err := serialize.SerializeToBytes(block) + if err != nil { + log.Crit("can not serilalize Minor block") + } + key := qkcGenesisKey(rHash) + if err := db.Put(key, data); err != nil { + log.Crit("Failed to store genesis", "err", err) + } +} + +func ReadGenesis(db ethdb.KeyValueReader, rHash common.Hash) *types.MinorBlock { + data, _ := db.Get(qkcGenesisKey(rHash)) + if len(data) == 0 { + return nil + } + res := new(types.MinorBlock) + if err := serialize.DeserializeFromBytes(data, res); err != nil { + return nil + } + return res +} + +func WriteConfirmedCrossShardTxList(db ethdb.KeyValueWriter, rHash common.Hash, list *types.CrossShardTransactionList) { + data, err := serialize.SerializeToBytes(list) + if err != nil { + log.Crit("can not serialize CrossShardTransactionList") + } + key := qkcConfirmedXShardKey(rHash) + if err := db.Put(key, data); err != nil { + log.Crit("Failed to store header", "err", err) + } +} + +func ReadConfirmedCrossShardTxList(db ethdb.KeyValueReader, rHash common.Hash) *types.CrossShardTransactionList { + data, _ := db.Get(qkcConfirmedXShardKey(rHash)) + if len(data) == 0 { + return nil + } + list := new(types.CrossShardTransactionList) + if err := serialize.Deserialize(serialize.NewByteBuffer(data), list); err != nil { + log.Error("Invalid block header Deserialize", "hash", rHash, "err", err) + return nil + } + return list +} + +func WriteCrossShardTxList(db ethdb.KeyValueWriter, rHash common.Hash, list *types.CrossShardTransactionList) { + data, err := serialize.SerializeToBytes(list) + if err != nil { + log.Crit("can not serialize CrossShardTransactionList") + } + key := qkcXShardTxListKey(rHash) + if err := db.Put(key, data); err != nil { + log.Crit("Failed to store header", "err", err) + } +} + +func ReadCrossShardTxList(db ethdb.KeyValueReader, rHash common.Hash) *types.CrossShardTransactionList { + data, _ := db.Get(qkcXShardTxListKey(rHash)) + if len(data) == 0 { + return nil + } + list := new(types.CrossShardTransactionList) + if err := serialize.Deserialize(serialize.NewByteBuffer(data), list); err != nil { + log.Error("Invalid block header Deserialize", "hash", rHash, "err", err) + return nil + } + return list +} + +func WriteLastConfirmedMinorBlockHeaderAtRootBlock(db ethdb.KeyValueWriter, rHash common.Hash, mHash common.Hash) { + if err := db.Put(qkcLastMinorAtRootKey(rHash), mHash.Bytes()); err != nil { + log.Crit("failed to store last confirmed minot block at root block") + } +} + +func ReadLastConfirmedMinorBlockHeaderAtRootBlock(db ethdb.KeyValueReader, rHash common.Hash) common.Hash { + data, _ := db.Get(qkcLastMinorAtRootKey(rHash)) + if len(data) == 0 { + return common.Hash{} + } + return common.BytesToHash(data) +} + +func PutXShardDepositHashList(db ethdb.KeyValueWriter, h common.Hash, hList *HashList) { + bytes, err := serialize.SerializeToBytes(hList) + if err != nil { + log.Crit("can not serialize HashList") + } + if err := db.Put(qkcXShardDepositHashListKey(h), bytes); err != nil { + log.Crit("failed to put xshard deposit hash list err", err) + } +} + +func GetXShardDepositHashList(db ethdb.KeyValueReader, h common.Hash) *HashList { + data, _ := db.Get(qkcXShardDepositHashListKey(h)) + if len(data) == 0 { + return nil + } + hList := new(HashList) + if err := serialize.DeserializeFromBytes(data, hList); err != nil { + log.Error("GetXShardDepositHashList", "DeserializeFromBytes err", err) + return nil + } + return hList +} diff --git a/core/rawdb/accessors_chain_qkc_test.go b/core/rawdb/accessors_chain_qkc_test.go new file mode 100644 index 000000000000..96c604eaeaea --- /dev/null +++ b/core/rawdb/accessors_chain_qkc_test.go @@ -0,0 +1,278 @@ +// Copyright 2026-2027, QuarkChain. + +package rawdb + +import ( + "bytes" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + coretypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethdb/memorydb" + "github.com/ethereum/go-ethereum/qkc/account" + "github.com/ethereum/go-ethereum/qkc/types" + "github.com/ethereum/go-ethereum/rlp" +) + +var ( + qkcLimitedSizeBytes = []byte{'\x01', '\x02', '\x03'} + qkcTx1 = types.NewEvmTransaction(1, account.BytesToIdentityRecipient([]byte{0x11}), big.NewInt(111), 1111, big.NewInt(11111), 0, 1, 1, 0, []byte{0x11, 0x11, 0x11}, 0, 0) + qkcTx2 = types.NewEvmTransaction(2, account.BytesToIdentityRecipient([]byte{0x22}), big.NewInt(222), 2222, big.NewInt(22222), 0, 1, 1, 0, []byte{0x22, 0x22, 0x22}, 0, 0) + qkcTx3 = types.NewEvmTransaction(3, account.BytesToIdentityRecipient([]byte{0x33}), big.NewInt(333), 3333, big.NewInt(33333), 0, 1, 1, 0, []byte{0x33, 0x33, 0x33}, 0, 0) + qkcTxs = types.Transactions{qkcTx1, qkcTx2, qkcTx3} + + qkcHeader1 = &types.MinorBlockHeader{Number: uint64(41)} + qkcHeader2 = &types.MinorBlockHeader{Number: uint64(42)} + qkcHeader3 = &types.MinorBlockHeader{Number: uint64(43)} + qkcHeaders = types.MinorBlockHeaders{qkcHeader1, qkcHeader2, qkcHeader3} +) + +func TestQKCBlockKeys(t *testing.T) { + hash := common.HexToHash("0x1234") + if got, want := qkcRootBlockKey(hash), append([]byte("qkc_rb"), hash.Bytes()...); !bytes.Equal(got, want) { + t.Fatalf("root block key mismatch: have %x, want %x", got, want) + } + if got, want := qkcMinorBlockKey(hash), append([]byte("qkc_mb"), hash.Bytes()...); !bytes.Equal(got, want) { + t.Fatalf("minor block key mismatch: have %x, want %x", got, want) + } +} + +// Tests block storage and retrieval operations. +func TestQKCRootBlockStorage(t *testing.T) { + db := memorydb.New() + + // Create a test block to move around the database and make sure it's really new + block := types.NewRootBlock(&types.RootBlockHeader{ + Extra: qkcLimitedSizeBytes, + ParentHash: types.EmptyHash, + MinorHeaderHash: types.EmptyHash, + }, qkcHeaders, qkcLimitedSizeBytes) + + if entry := ReadRootBlock(db, block.Hash()); entry != nil { + t.Fatalf("Non existent block returned: %v", entry) + } + // Write and verify the block in the database + WriteRootBlock(db, block) + if !HasRootBlock(db, block.Hash()) { + t.Fatal("Stored root block key not found") + } + if HasMinorBlock(db, block.Hash()) { + t.Fatal("Root block was written with the minor block key") + } + if entry := ReadRootBlock(db, block.Hash()); entry == nil { + t.Fatalf("Stored block not found") + } else if entry.Hash() != block.Hash() { + t.Fatalf("Retrieved block mismatch: have %v, want %v", entry, block) + } + // Delete the block and verify the execution + DeleteRootBlock(db, block.Hash()) + if entry := ReadRootBlock(db, block.Hash()); entry != nil { + t.Fatalf("Deleted block returned: %v", entry) + } +} + +// Tests block storage and retrieval operations. +func TestQKCMinorBlockStorage(t *testing.T) { + db := memorydb.New() + + // Create a test block to move around the database and make sure it's really new + block := types.NewMinorBlockWithHeader(&types.MinorBlockHeader{ + Extra: qkcLimitedSizeBytes, + ParentHash: types.EmptyHash, + }, &types.MinorBlockMeta{}).WithBody(qkcTxs, qkcLimitedSizeBytes) + + if entry := ReadMinorBlock(db, block.Hash()); entry != nil { + t.Fatalf("Non existent block returned: %v", entry) + } + // Write and verify the block in the database + WriteMinorBlock(db, block) + if !HasMinorBlock(db, block.Hash()) { + t.Fatal("Stored minor block key not found") + } + if HasRootBlock(db, block.Hash()) { + t.Fatal("Minor block was written with the root block key") + } + if entry := ReadMinorBlock(db, block.Hash()); entry == nil { + t.Fatalf("Stored block not found") + } else if entry.Hash() != block.Hash() { + t.Fatalf("Retrieved block mismatch: have %v, want %v", entry, block) + } + + // Delete the block and verify the execution + DeleteMinorBlock(db, block.Hash()) + if entry := ReadMinorBlock(db, block.Hash()); entry != nil { + t.Fatalf("Deleted block returned: %v", entry) + } +} + +// Tests that canonical numbers can be mapped to hashes and retrieved. +func TestQKCCanonicalMappingStorage(t *testing.T) { + db := memorydb.New() + + rootHash := common.Hash{0: 0xff} + minorHash := common.Hash{0: 0xee} + number := uint64(314) + if entry := ReadRootCanonicalHash(db, number); entry != (common.Hash{}) { + t.Fatalf("Non existent root canonical mapping returned: %v", entry) + } + if entry := ReadMinorCanonicalHash(db, number); entry != (common.Hash{}) { + t.Fatalf("Non existent minor canonical mapping returned: %v", entry) + } + + WriteRootCanonicalHash(db, rootHash, number) + WriteMinorCanonicalHash(db, minorHash, number) + if entry := ReadRootCanonicalHash(db, number); entry != rootHash { + t.Fatalf("Root canonical mapping mismatch: have %v, want %v", entry, rootHash) + } + if entry := ReadMinorCanonicalHash(db, number); entry != minorHash { + t.Fatalf("Minor canonical mapping mismatch: have %v, want %v", entry, minorHash) + } + if has, _ := db.Has(append([]byte("rn"), encodeBlockNumber(number)...)); has { + t.Fatal("Root canonical mapping was written without the qkc_ prefix") + } + if has, _ := db.Has(append([]byte("mn"), encodeBlockNumber(number)...)); has { + t.Fatal("Minor canonical mapping was written without the qkc_ prefix") + } + if has, _ := db.Has(append([]byte("qkc_rn"), encodeBlockNumber(number)...)); !has { + t.Fatal("Root canonical mapping was not written with the qkc_ prefix") + } + if has, _ := db.Has(append([]byte("qkc_mn"), encodeBlockNumber(number)...)); !has { + t.Fatal("Minor canonical mapping was not written with the qkc_ prefix") + } + + DeleteRootCanonicalHash(db, number) + DeleteMinorCanonicalHash(db, number) + if entry := ReadRootCanonicalHash(db, number); entry != (common.Hash{}) { + t.Fatalf("Deleted root canonical mapping returned: %v", entry) + } + if entry := ReadMinorCanonicalHash(db, number); entry != (common.Hash{}) { + t.Fatalf("Deleted minor canonical mapping returned: %v", entry) + } +} + +// Tests that head headers and head blocks can be assigned, individually. +func TestQKCHeadStorage(t *testing.T) { + db := memorydb.New() + + blockHeadHash := common.BytesToHash([]byte{0x44}) + blockFullHash := common.BytesToHash([]byte{0x55}) + rootHeadHash := common.BytesToHash([]byte{0x66}) + + // Check that no head entries are in a pristine database + if entry := ReadHeadHeaderHash(db); entry != (common.Hash{}) { + t.Fatalf("Non head header entry returned: %v", entry) + } + if entry := ReadHeadBlockHash(db); entry != (common.Hash{}) { + t.Fatalf("Non head block entry returned: %v", entry) + } + if entry := ReadHeadFastBlockHash(db); entry != (common.Hash{}) { + t.Fatalf("Non fast head block entry returned: %v", entry) + } + // Assign separate entries for the head header and block + WriteHeadHeaderHash(db, blockHeadHash) + WriteHeadBlockHash(db, blockFullHash) + WriteRootHeadHash(db, rootHeadHash) + + // Check that both heads are present, and different (i.e. two heads maintained) + if entry := ReadHeadHeaderHash(db); entry != blockHeadHash { + t.Fatalf("Head header hash mismatch: have %v, want %v", entry, blockHeadHash) + } + if entry := ReadHeadBlockHash(db); entry != blockFullHash { + t.Fatalf("Head block hash mismatch: have %v, want %v", entry, blockFullHash) + } + if entry := ReadRootHeadHash(db); entry != rootHeadHash { + t.Fatalf("Root head block hash mismatch: have %v, want %v", entry, rootHeadHash) + } + if key := qkcRootHeadKey(); !bytes.Equal(key, []byte("qkc_LastRoot")) { + t.Fatalf("Root head block key mismatch: have %x, want %x", key, []byte("qkc_LastRoot")) + } +} + +// Tests that receipts associated with a single block can be stored and retrieved. +func TestQKCBlockReceiptStorage(t *testing.T) { + db := memorydb.New() + + receipt1 := &types.Receipt{ + Status: types.ReceiptStatusFailed, + CumulativeGasUsed: 1, + Logs: []*coretypes.Log{ + { + Address: common.BytesToAddress([]byte{0x11}), + Topics: []common.Hash{common.HexToHash("0x21")}, + Data: []byte{0x31}, + BlockNumber: 11, + TxHash: common.BytesToHash([]byte{0x12}), + TxIndex: 1, + BlockHash: common.BytesToHash([]byte{0x13}), + Index: 2, + }, + {Address: common.BytesToAddress([]byte{0x01, 0x11}), Topics: []common.Hash{}, Data: []byte{0x32}}, + }, + TxHash: common.BytesToHash([]byte{0x11, 0x11}), + ContractAddress: account.BytesToIdentityRecipient([]byte{0x01, 0x11, 0x11}), + GasUsed: 111111, + } + receipt2 := &types.Receipt{ + Status: types.ReceiptStatusSuccessful, + CumulativeGasUsed: 2, + Logs: []*coretypes.Log{ + {Address: common.BytesToAddress([]byte{0x22}), Topics: []common.Hash{common.HexToHash("0x23")}, Data: []byte{0x33}, BlockNumber: 22, TxIndex: 3, Index: 4}, + {Address: common.BytesToAddress([]byte{0x02, 0x22}), Topics: []common.Hash{}, Data: []byte{0x34}}, + }, + TxHash: common.BytesToHash([]byte{0x22, 0x22}), + ContractAddress: account.BytesToIdentityRecipient([]byte{0x02, 0x22, 0x22}), + GasUsed: 222222, + } + receipts := types.Receipts{receipt1, receipt2} + + // Check that no receipt entries are in a pristine database + hash := common.BytesToHash([]byte{0x03, 0x14}) + if rs := ReadQKCReceipts(db, hash); len(rs) != 0 { + t.Fatalf("non existent receipts returned: %v", rs) + } + // Insert the receipt slice into the database and check presence + WriteQKCReceipts(db, hash, receipts) + wantEncoding, err := rlp.EncodeToBytes(receipts) + if err != nil { + t.Fatal("encode receipts:", err) + } + stored, err := db.Get(qkcBlockReceiptsKey(hash)) + if err != nil { + t.Fatal("read stored receipts:", err) + } + if !bytes.Equal(stored, wantEncoding) { + t.Fatalf("stored receipt encoding mismatch: have %x, want %x", stored, wantEncoding) + } + if rs := ReadQKCReceipts(db, hash); len(rs) == 0 { + t.Fatalf("no receipts returned") + } else { + for i := range receipts { + got, err := rlp.EncodeToBytes(rs[i]) + if err != nil { + t.Fatalf("encode receipt %d: %v", i, err) + } + want, err := rlp.EncodeToBytes(receipts[i]) + if err != nil { + t.Fatalf("encode expected receipt %d: %v", i, err) + } + if !bytes.Equal(got, want) { + t.Fatalf("receipt %d consensus fields mismatch: have %x, want %x", i, got, want) + } + if rs[i].TxHash != (common.Hash{}) || rs[i].GasUsed != 0 { + t.Fatalf("receipt %d retained derived fields: %+v", i, rs[i]) + } + for j, log := range rs[i].Logs { + if log.BlockNumber != 0 || log.TxHash != (common.Hash{}) || log.TxIndex != 0 || + log.BlockHash != (common.Hash{}) || log.BlockTimestamp != 0 || log.Index != 0 || log.Removed { + t.Fatalf("receipt %d log %d retained derived fields: %+v", i, j, log) + } + } + } + } + // Delete the receipt slice and check purge + DeleteQKCReceipts(db, hash) + if rs := ReadQKCReceipts(db, hash); len(rs) != 0 { + t.Fatalf("deleted receipts returned: %v", rs) + } +} diff --git a/core/rawdb/accessors_indexes_qkc.go b/core/rawdb/accessors_indexes_qkc.go new file mode 100644 index 000000000000..2df7f95dee43 --- /dev/null +++ b/core/rawdb/accessors_indexes_qkc.go @@ -0,0 +1,94 @@ +// Copyright 2026-2027, QuarkChain. + +package rawdb + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/qkc/serialize" + "github.com/ethereum/go-ethereum/qkc/types" +) + +// ReadBlockContentLookupEntry retrieves the positional metadata associated with a transaction +// hash to allow retrieving the transaction or receipt by hash. +func ReadBlockContentLookupEntry(db ethdb.KeyValueReader, hash common.Hash) (common.Hash, uint32) { + data, _ := db.Get(txLookupKey(hash)) + if len(data) == 0 { + return common.Hash{}, 0 + } + var entry qkcLookupEntry + if err := serialize.Deserialize(serialize.NewByteBuffer(data), &entry); err != nil { + log.Error("Invalid transaction lookup entry RLP", "hash", hash, "err", err) + return common.Hash{}, 0 + } + return entry.BlockHash, entry.Index +} + +// WriteBlockContentLookupEntriesWithCrossShardHashList stores a positional metadata for every transaction from +// a block, enabling hash based transaction and receipt lookups. +func WriteBlockContentLookupEntriesWithCrossShardHashList(db ethdb.KeyValueWriter, block *types.MinorBlock, hList *HashList) { + blockHash := block.Hash() + for i, item := range block.Content() { + entry := qkcLookupEntry{ + BlockHash: blockHash, + Index: uint32(i), + } + data, err := serialize.SerializeToBytes(entry) + if err != nil { + log.Crit("Failed to encode content lookup entry", "err", err) + } + if err := db.Put(txLookupKey(item.Hash()), data); err != nil { + log.Crit("Failed to store content lookup entry", "err", err) + } + } + if hList == nil || len(hList.HList) == 0 { + return + } + for i, h := range hList.HList { + entry := qkcLookupEntry{ + BlockHash: blockHash, + Index: uint32(i) + uint32(len(block.Content())), + } + data, err := serialize.SerializeToBytes(entry) + if err != nil { + log.Crit("Failed to encode xshard tx lookup entry", "err", err) + } + if err := db.Put(txLookupKey(h), data); err != nil { + log.Crit("Failed to store xshard tx lookup entry") + } + } +} + +// ReadTransaction retrieves a specific transaction from the database, along with +// its added positional metadata. +func ReadTransaction(db ethdb.KeyValueReader, hash common.Hash) (*types.Transaction, common.Hash, uint32) { + blockHash, txIndex := ReadBlockContentLookupEntry(db, hash) + if blockHash == (common.Hash{}) { + return nil, common.Hash{}, 0 + } + block := ReadMinorBlock(db, blockHash) + if block == nil { + log.Error("Transaction referenced missing", "hash", blockHash, "index", txIndex) + return nil, common.Hash{}, 0 + } + if int(txIndex) < len(block.Transactions()) { + return block.Transactions()[txIndex], blockHash, txIndex + } + return nil, blockHash, txIndex //xShardTx +} + +// ReadReceipt retrieves a specific transaction receipt from the database, along with +// its added positional metadata. +func ReadReceipt(db ethdb.KeyValueReader, hash common.Hash) (*types.Receipt, common.Hash, uint32) { + blockHash, receiptIndex := ReadBlockContentLookupEntry(db, hash) + if blockHash == (common.Hash{}) { + return nil, common.Hash{}, 0 + } + receipts := ReadQKCReceipts(db, blockHash) + if len(receipts) <= int(receiptIndex) { + log.Error("Receipt refereced missing", "hash", blockHash, "index", receiptIndex) + return nil, common.Hash{}, 0 + } + return receipts[receiptIndex], blockHash, receiptIndex +} diff --git a/core/rawdb/accessors_indexes_qkc_test.go b/core/rawdb/accessors_indexes_qkc_test.go new file mode 100644 index 000000000000..e81be22d493d --- /dev/null +++ b/core/rawdb/accessors_indexes_qkc_test.go @@ -0,0 +1,61 @@ +// Copyright 2026-2027, QuarkChain. + +package rawdb + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/ethdb/memorydb" + "github.com/ethereum/go-ethereum/qkc/account" + "github.com/ethereum/go-ethereum/qkc/types" +) + +// Tests that positional lookup metadata can be stored and retrieved. +func TestQKCLookupStorage(t *testing.T) { + db := memorydb.New() + + //nonce uint64, to account.Recipient, amount *big.Int, gasLimit uint64, gasPrice *big.Int, fromFullShardId uint32, toFullShardId uint32, networkId uint32, version uint32, data []byte) *EvmTransaction { + tx1 := types.NewEvmTransaction(1, account.BytesToIdentityRecipient([]byte{0x11}), big.NewInt(111), 1111, big.NewInt(11111), 0, 1, 1, 0, []byte{0x11, 0x11, 0x11}, 0, 0) + tx2 := types.NewEvmTransaction(2, account.BytesToIdentityRecipient([]byte{0x22}), big.NewInt(222), 2222, big.NewInt(22222), 0, 1, 1, 0, []byte{0x22, 0x22, 0x22}, 0, 0) + tx3 := types.NewEvmTransaction(3, account.BytesToIdentityRecipient([]byte{0x33}), big.NewInt(333), 3333, big.NewInt(33333), 0, 1, 1, 0, []byte{0x33, 0x33, 0x33}, 0, 0) + txs := []*types.Transaction{tx1, tx2, tx3} + + block := types.NewMinorBlockWithHeader(&types.MinorBlockHeader{Number: uint64(314)}, &types.MinorBlockMeta{}).WithBody(txs, nil) + + // Check that no transactions entries are in a pristine database + for i, tx := range txs { + if txn, _, _ := ReadTransaction(db, tx.Hash()); txn != nil { + t.Fatalf("tx #%d [%x]: non existent transaction returned: %v", i, tx.Hash(), txn) + } + } + // Insert all the transactions into the database, and verify contents + WriteMinorBlock(db, block) + WriteBlockContentLookupEntriesWithCrossShardHashList(db, block, nil) + + for i, tx := range txs { + if has, _ := db.Has(txLookupKey(tx.Hash())); !has { + t.Fatalf("tx #%d [%x]: lookup was not written with the existing geth key", i, tx.Hash()) + } + if has, _ := db.Has(qkcKey(txLookupPrefix, tx.Hash().Bytes())); has { + t.Fatalf("tx #%d [%x]: lookup was written with a redundant QKC key", i, tx.Hash()) + } + if txn, hash, index := ReadTransaction(db, tx.Hash()); txn == nil { + t.Fatalf("tx #%d [%x]: transaction not found", i, tx.Hash()) + } else { + if hash != block.Hash() || index != uint32(i) { + t.Fatalf("tx #%d [%x]: positional metadata mismatch: have %x/%d, want %x/%v", i, tx.Hash(), hash, index, block.Hash(), i) + } + if tx.Hash() != txn.Hash() { + t.Fatalf("tx #%d [%x]: transaction mismatch: have %v, want %v", i, tx.Hash(), txn, tx) + } + } + } + // Delete the transactions and check purge + for i, tx := range txs { + DeleteTxLookupEntry(db, tx.Hash()) + if txn, _, _ := ReadTransaction(db, tx.Hash()); txn != nil { + t.Fatalf("tx #%d [%x]: deleted transaction returned: %v", i, tx.Hash(), txn) + } + } +} diff --git a/core/rawdb/accessors_metadata_qkc.go b/core/rawdb/accessors_metadata_qkc.go new file mode 100644 index 000000000000..0f2c5a3c5f36 --- /dev/null +++ b/core/rawdb/accessors_metadata_qkc.go @@ -0,0 +1,40 @@ +// Copyright 2026-2027, QuarkChain. + +package rawdb + +import ( + "encoding/json" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + qkcconfig "github.com/ethereum/go-ethereum/qkc/config" +) + +// QKCReadChainConfig retrieves the QuarkChain consensus settings based on the given genesis hash. +func QKCReadChainConfig(db ethdb.KeyValueReader, hash common.Hash) *qkcconfig.QuarkChainConfig { + data, _ := db.Get(qkcChainConfigKey(hash)) + if len(data) == 0 { + return nil + } + var config qkcconfig.QuarkChainConfig + if err := json.Unmarshal(data, &config); err != nil { + log.Error("Invalid QKC chain config JSON", "hash", hash, "err", err) + return nil + } + return &config +} + +// QKCWriteChainConfig writes the QuarkChain consensus settings to the database. +func QKCWriteChainConfig(db ethdb.KeyValueWriter, hash common.Hash, cfg *qkcconfig.QuarkChainConfig) { + if cfg == nil { + return + } + data, err := json.Marshal(cfg) + if err != nil { + log.Crit("Failed to JSON encode QKC chain config", "err", err) + } + if err := db.Put(qkcChainConfigKey(hash), data); err != nil { + log.Crit("Failed to store QKC chain config", "err", err) + } +} diff --git a/core/rawdb/accessors_metadata_qkc_test.go b/core/rawdb/accessors_metadata_qkc_test.go new file mode 100644 index 000000000000..66cb1d319493 --- /dev/null +++ b/core/rawdb/accessors_metadata_qkc_test.go @@ -0,0 +1,48 @@ +// Copyright 2026-2027, QuarkChain. + +package rawdb + +import ( + "bytes" + "encoding/json" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb/memorydb" + qkcconfig "github.com/ethereum/go-ethereum/qkc/config" +) + +func TestQKCChainConfigStorage(t *testing.T) { + db := memorydb.New() + hash := common.HexToHash("0x1234") + if cfg := QKCReadChainConfig(db, hash); cfg != nil { + t.Fatalf("non-existent QKC chain config returned: %v", cfg) + } + + cfg := qkcconfig.NewQuarkChainConfig() + cfg.NetworkID = 123 + QKCWriteChainConfig(db, hash, cfg) + + wantKey := append([]byte("qkc_config-"), hash.Bytes()...) + if key := qkcChainConfigKey(hash); !bytes.Equal(key, wantKey) { + t.Fatalf("QKC chain config key mismatch: have %x, want %x", key, wantKey) + } + stored, err := db.Get(qkcChainConfigKey(hash)) + if err != nil { + t.Fatal("read stored QKC chain config:", err) + } + if !json.Valid(stored) { + t.Fatalf("stored QKC chain config is invalid JSON: %x", stored) + } + if has, _ := db.Has(configKey(hash)); has { + t.Fatal("QKC chain config was written with the Ethereum config key") + } + + got := QKCReadChainConfig(db, hash) + if got == nil { + t.Fatal("stored QKC chain config not found") + } + if got.NetworkID != cfg.NetworkID || got.ChainSize != cfg.ChainSize || got.GenesisToken != cfg.GenesisToken { + t.Fatalf("QKC chain config mismatch: have %+v, want %+v", got, cfg) + } +} diff --git a/core/rawdb/schema_qkc.go b/core/rawdb/schema_qkc.go new file mode 100644 index 000000000000..29bee877c590 --- /dev/null +++ b/core/rawdb/schema_qkc.go @@ -0,0 +1,109 @@ +// Copyright 2026-2027, QuarkChain. + +package rawdb + +import ( + "encoding/binary" + + "github.com/ethereum/go-ethereum/common" +) + +// The fields below define the QKC-specific low-level database schema. +var ( + qkcPrefix = []byte("qkc_") // qkcPrefix + QKC-specific prefix + key parts -> QKC namespaced key + + rootHashPrefixQKC = []byte("rn") // qkcPrefix + rootHashPrefixQKC + num (uint64 big endian) -> root canonical hash + minorHashPrefixQKC = []byte("mn") // qkcPrefix + minorHashPrefixQKC + num (uint64 big endian) -> minor canonical hash + rootBlockPrefixQKC = []byte("rb") // qkcPrefix + rootBlockPrefixQKC + hash -> root block + minorBlockPrefixQKC = []byte("mb") // qkcPrefix + minorBlockPrefixQKC + hash -> minor block + totalTxCountPrefixQKC = []byte("txC") // qkcPrefix + totalTxCountPrefixQKC + hash -> total tx count (uint32 big endian) + confirmedXShardPrefixQKC = []byte("xr") // qkcPrefix + confirmedXShardPrefixQKC + hash -> confirmed cross-shard tx list + xShardListPrefixQKC = []byte("xSL") // qkcPrefix + xShardListPrefixQKC + hash -> cross-shard tx list + xShardHashListPrefixQKC = []byte("xSHL") // qkcPrefix + xShardHashListPrefixQKC + hash -> cross-shard deposit hash list + lastMinorAtRootPrefixQKC = []byte("rLM") // qkcPrefix + lastMinorAtRootPrefixQKC + root hash -> last confirmed minor block hash + genesisPrefixQKC = []byte("genesis") // qkcPrefix + genesisPrefixQKC + root hash -> genesis minor block + chainConfigPrefixQKC = []byte("config-") // qkcPrefix + chainConfigPrefixQKC + genesis hash -> QKC chain config + rootHeadKey = []byte("LastRoot") // qkcPrefix + rootHeadPrefixQKC -> canonical root head hash +) + +// qkcLookupEntry is positional metadata for looking up block content by hash. +type qkcLookupEntry struct { + BlockHash common.Hash + Index uint32 +} + +func qkcEncodeUint32(number uint32) []byte { + enc := make([]byte, 4) + binary.BigEndian.PutUint32(enc, number) + return enc +} + +// qkcKey builds a QKC-specific database key by prepending qkcPrefix to the +// record prefix and key parts, keeping QKC additions isolated from geth keys. +func qkcKey(prefix []byte, parts ...[]byte) []byte { + size := len(qkcPrefix) + len(prefix) + for _, part := range parts { + size += len(part) + } + key := make([]byte, 0, size) + key = append(key, qkcPrefix...) + key = append(key, prefix...) + for _, part := range parts { + key = append(key, part...) + } + return key +} + +func rootCanonicalHashKey(number uint64) []byte { + return qkcKey(rootHashPrefixQKC, encodeBlockNumber(number)) +} + +func minorCanonicalHashKey(number uint64) []byte { + return qkcKey(minorHashPrefixQKC, encodeBlockNumber(number)) +} + +func qkcRootBlockKey(hash common.Hash) []byte { + return qkcKey(rootBlockPrefixQKC, hash.Bytes()) +} + +func qkcMinorBlockKey(hash common.Hash) []byte { + return qkcKey(minorBlockPrefixQKC, hash.Bytes()) +} + +func qkcBlockReceiptsKey(hash common.Hash) []byte { + return qkcKey(blockReceiptsPrefix, hash.Bytes()) +} + +func qkcTotalTxCountKey(hash common.Hash) []byte { + return qkcKey(totalTxCountPrefixQKC, hash.Bytes()) +} + +// qkcConfirmedXShardKey returns the key for deposits executed by a receiving minor block. +func qkcConfirmedXShardKey(hash common.Hash) []byte { + return qkcKey(confirmedXShardPrefixQKC, hash.Bytes()) +} + +// qkcXShardTxListKey returns the key for deposits broadcast by a source minor block. +func qkcXShardTxListKey(hash common.Hash) []byte { + return qkcKey(xShardListPrefixQKC, hash.Bytes()) +} + +func qkcGenesisKey(hash common.Hash) []byte { + return qkcKey(genesisPrefixQKC, hash.Bytes()) +} + +func qkcLastMinorAtRootKey(hash common.Hash) []byte { + return qkcKey(lastMinorAtRootPrefixQKC, hash.Bytes()) +} + +func qkcXShardDepositHashListKey(hash common.Hash) []byte { + return qkcKey(xShardHashListPrefixQKC, hash.Bytes()) +} + +func qkcChainConfigKey(hash common.Hash) []byte { + return qkcKey(chainConfigPrefixQKC, hash.Bytes()) +} + +func qkcRootHeadKey() []byte { + return qkcKey(rootHeadKey) +} diff --git a/qkc/types/derive_sha.go b/qkc/types/derive_sha.go index 549b221cd2fe..f77718bfe3f9 100644 --- a/qkc/types/derive_sha.go +++ b/qkc/types/derive_sha.go @@ -2,10 +2,6 @@ // QKC trie/hash helpers follow pyquarkchain-compatible wire hashing. // Modified from go-ethereum under GNU Lesser General Public License -// -// Adaptations (hash output identical): -// - new(trie.Trie)/trie.Update -> trie.NewEmpty(nil)/MustUpdate. -// - sha3.NewKeccak256() -> crypto.NewKeccakState(). package types @@ -14,11 +10,11 @@ import ( "reflect" "github.com/ethereum/go-ethereum/common" + coretypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" qkcCommon "github.com/ethereum/go-ethereum/qkc/common" "github.com/ethereum/go-ethereum/qkc/serialize" "github.com/ethereum/go-ethereum/rlp" - "github.com/ethereum/go-ethereum/trie" ) type DerivableList interface { @@ -26,18 +22,20 @@ type DerivableList interface { Bytes(i int) []byte } -func DeriveSha(list DerivableList) common.Hash { +func DeriveSha(list DerivableList, hasher coretypes.ListHasher) common.Hash { + hasher.Reset() keybuf := new(bytes.Buffer) - trie := trie.NewEmpty(nil) for i := 0; i < list.Len(); i++ { keybuf.Reset() rlp.Encode(keybuf, uint(i)) - trie.MustUpdate(keybuf.Bytes(), list.Bytes(i)) + if err := hasher.Update(keybuf.Bytes(), list.Bytes(i)); err != nil { + panic(err) + } } - return trie.Hash() + return hasher.Hash() } -var EmptyTrieHash = trie.NewEmpty(nil).Hash() +var EmptyTrieHash = coretypes.EmptyRootHash var EmptyHash = common.Hash{} diff --git a/qkc/types/derive_sha_test.go b/qkc/types/derive_sha_test.go index dafa9c762ed2..c3084ff417b1 100644 --- a/qkc/types/derive_sha_test.go +++ b/qkc/types/derive_sha_test.go @@ -15,12 +15,6 @@ type merkleGoldenItem struct { Payload []byte } -type derivablePayloads [][]byte - -func (p derivablePayloads) Len() int { return len(p) } - -func (p derivablePayloads) Bytes(i int) []byte { return p[i] } - type unserializableMerkleItem struct { Value chan int } @@ -68,39 +62,6 @@ func TestCalculateMerkleRootGoldenVectors(t *testing.T) { } } -func TestDeriveShaGoldenVectors(t *testing.T) { - // TODO: Add receipt-trie golden vectors in the receipt/types PR once Receipt is introduced. - tests := []struct { - name string - list derivablePayloads - want common.Hash - }{ - { - name: "empty", - list: nil, - want: common.HexToHash("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"), - }, - { - name: "single", - list: derivablePayloads{[]byte("cat")}, - want: common.HexToHash("0xb423fb4e634b237f9e4fe311a0b72e299540b2407f2fe06f262cac177dd755bd"), - }, - { - name: "multi", - list: derivablePayloads{[]byte("cat"), []byte("dog"), []byte("fish")}, - want: common.HexToHash("0x47fdad14c87a0b6acdce6fc6c4d65e315d3e0db6276ae0ae510b1681a28974d3"), - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - if got := DeriveSha(test.list); got != test.want { - t.Fatalf("DeriveSha mismatch: got %s, want %s", got.Hex(), test.want.Hex()) - } - }) - } -} - func TestEmptyTrieHashGoldenVector(t *testing.T) { want := common.HexToHash("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") if EmptyTrieHash != want { diff --git a/qkc/types/derive_sha_trie_test.go b/qkc/types/derive_sha_trie_test.go new file mode 100644 index 000000000000..6f8e98c38438 --- /dev/null +++ b/qkc/types/derive_sha_trie_test.go @@ -0,0 +1,50 @@ +// Copyright 2026-2027, QuarkChain. + +package types_test + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + qkctypes "github.com/ethereum/go-ethereum/qkc/types" + "github.com/ethereum/go-ethereum/trie" +) + +type derivablePayloads [][]byte + +func (p derivablePayloads) Len() int { return len(p) } + +func (p derivablePayloads) Bytes(i int) []byte { return p[i] } + +func TestDeriveShaGoldenVectors(t *testing.T) { + tests := []struct { + name string + list derivablePayloads + want common.Hash + }{ + { + name: "empty", + list: nil, + want: common.HexToHash("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"), + }, + { + name: "single", + list: derivablePayloads{[]byte("cat")}, + want: common.HexToHash("0xb423fb4e634b237f9e4fe311a0b72e299540b2407f2fe06f262cac177dd755bd"), + }, + { + name: "multi", + list: derivablePayloads{[]byte("cat"), []byte("dog"), []byte("fish")}, + want: common.HexToHash("0x47fdad14c87a0b6acdce6fc6c4d65e315d3e0db6276ae0ae510b1681a28974d3"), + }, + } + + hasher := trie.NewListHasher() + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if got := qkctypes.DeriveSha(test.list, hasher); got != test.want { + t.Fatalf("DeriveSha mismatch: got %s, want %s", got.Hex(), test.want.Hex()) + } + }) + } +} diff --git a/qkc/types/minorblock.go b/qkc/types/minorblock.go index 76403f9f769d..9a7b4064cd0c 100644 --- a/qkc/types/minorblock.go +++ b/qkc/types/minorblock.go @@ -9,6 +9,7 @@ import ( "sync/atomic" "github.com/ethereum/go-ethereum/common" + coretypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/qkc/account" qkcCommon "github.com/ethereum/go-ethereum/qkc/common" "github.com/ethereum/go-ethereum/qkc/params" @@ -155,7 +156,7 @@ type extminorblock struct { // // TxHash and ReceiptHash in meta, and Bloom and MetaHash in header, // are replaced with values derived from the transactions and receipts. -func NewMinorBlock(header *MinorBlockHeader, meta *MinorBlockMeta, txs []*Transaction, receipts []*Receipt, trackingdata []byte) *MinorBlock { +func NewMinorBlock(header *MinorBlockHeader, meta *MinorBlockMeta, txs []*Transaction, receipts []*Receipt, trackingdata []byte, hasher coretypes.ListHasher) *MinorBlock { // Every local transaction produces a receipt, while incoming cross-shard // deposits may add receipts that have no corresponding local transaction. if len(receipts) < len(txs) { @@ -168,7 +169,7 @@ func NewMinorBlock(header *MinorBlockHeader, meta *MinorBlockMeta, txs []*Transa } b.meta.TxHash = CalculateMerkleRoot(b.transactions) - b.meta.ReceiptHash = DeriveSha(Receipts(receipts)) + b.meta.ReceiptHash = DeriveSha(Receipts(receipts), hasher) b.header.Bloom = CreateBloom(receipts) b.header.MetaHash = b.meta.Hash() @@ -394,7 +395,7 @@ func (b *MinorBlock) GetSize() common.StorageSize { return b.Size() } -func (m *MinorBlock) Finalize(receipts Receipts, rootHash common.Hash, gasUsed *big.Int, xShardReceiveGasUsed *big.Int, coinbaseAmount *qkcCommon.TokenBalances, xShardTxCursorInfo *XShardTxCursorInfo) { +func (m *MinorBlock) Finalize(receipts Receipts, rootHash common.Hash, gasUsed *big.Int, xShardReceiveGasUsed *big.Int, coinbaseAmount *qkcCommon.TokenBalances, xShardTxCursorInfo *XShardTxCursorInfo, hasher coretypes.ListHasher) { if len(receipts) < len(m.transactions) { panic("receipts count is less than txs count") } @@ -420,7 +421,7 @@ func (m *MinorBlock) Finalize(receipts Receipts, rootHash common.Hash, gasUsed * m.header.CoinbaseAmount = coinbaseAmount.Copy() } m.meta.TxHash = CalculateMerkleRoot(m.transactions) - m.meta.ReceiptHash = DeriveSha(receipts) + m.meta.ReceiptHash = DeriveSha(receipts, hasher) m.header.MetaHash = m.meta.Hash() m.header.Bloom = CreateBloom(receipts) hash := m.header.Hash() diff --git a/qkc/types/minorblock_test.go b/qkc/types/minorblock_test.go index fa4be37b9306..f503f6cf5b03 100644 --- a/qkc/types/minorblock_test.go +++ b/qkc/types/minorblock_test.go @@ -25,6 +25,16 @@ func testU256(v uint64) *uint256.Int { return uint256.NewInt(v) } +type testListHasher struct{} + +func (testListHasher) Reset() {} + +func (testListHasher) Update([]byte, []byte) error { return nil } + +func (testListHasher) Hash() common.Hash { return EmptyTrieHash } + +func newTestListHasher() *testListHasher { return new(testListHasher) } + var ( // reciept, _ = account.BytesToIdentityRecipient(common.Hex2Bytes("b94f5374fce5edbc8e2a8697c15331677e6ebf0b")) tx1 = NewEvmTransaction( @@ -218,9 +228,10 @@ func TestMinorBlockReceiptCount(t *testing.T) { header, meta := testMinorBlockHeader() tx := goldenTxs()[0] receipt := NewReceipt(false, 0) + hasher := newTestListHasher() // Incoming cross-shard deposits may add receipts beyond the local tx count. - NewMinorBlock(header, meta, []*Transaction{tx}, []*Receipt{receipt, receipt}, nil) + NewMinorBlock(header, meta, []*Transaction{tx}, []*Receipt{receipt, receipt}, nil, hasher) tests := []struct { name string @@ -228,13 +239,13 @@ func TestMinorBlockReceiptCount(t *testing.T) { }{ { name: "NewMinorBlock", - call: func() { NewMinorBlock(header, meta, []*Transaction{tx}, nil, nil) }, + call: func() { NewMinorBlock(header, meta, []*Transaction{tx}, nil, nil, newTestListHasher()) }, }, { name: "Finalize", call: func() { block := NewMinorBlockWithHeader(header, meta).WithBody([]*Transaction{tx}, nil) - block.Finalize(nil, common.Hash{}, nil, nil, nil, nil) + block.Finalize(nil, common.Hash{}, nil, nil, nil, nil, newTestListHasher()) }, }, } @@ -294,7 +305,7 @@ func TestCalculateMerkleRoot(t *testing.T) { func TestNewMinorBlockEmptyDerivedFields(t *testing.T) { header, meta := testMinorBlockHeader() header.Bloom[0] = 1 - block := NewMinorBlock(header, meta, nil, nil, nil) + block := NewMinorBlock(header, meta, nil, nil, nil, newTestListHasher()) wantTxRoot := common.HexToHash("0xdaa77426c30c02a43d9fba4e841a6556c524d47030762eb14dc4af897e605d9b") if got := block.TxHash(); got != wantTxRoot { t.Fatalf("empty transaction root mismatch: got %s, want %s", got, wantTxRoot) @@ -504,7 +515,7 @@ func TestMinorBlockMutationInvalidatesCaches(t *testing.T) { t.Fatal("AddTx did not clear the size cache") } block.Size() - block.Finalize(receipts, common.Hash{}, nil, nil, nil, nil) + block.Finalize(receipts, common.Hash{}, nil, nil, nil, nil, newTestListHasher()) if block.size.Load() != nil { t.Fatal("Finalize did not clear the size cache") } @@ -513,7 +524,7 @@ func TestMinorBlockMutationInvalidatesCaches(t *testing.T) { } cursor := &XShardTxCursorInfo{RootBlockHeight: 1, MinorBlockIndex: 2, XShardDepositIndex: 3} - block.Finalize(receipts, common.Hash{}, nil, nil, nil, cursor) + block.Finalize(receipts, common.Hash{}, nil, nil, nil, cursor, newTestListHasher()) wantMetaHash := block.MetaHash() wantHash := block.Hash() cursor.RootBlockHeight = 9 @@ -528,7 +539,7 @@ func TestMinorBlockMutationInvalidatesCaches(t *testing.T) { xShardGasUsed := big.NewInt(12) coinbaseAmount := qkcCommon.NewEmptyTokenBalances() coinbaseAmount.SetValue(uint256.NewInt(13), 1) - block.Finalize(receipts, common.Hash{}, gasUsed, xShardGasUsed, coinbaseAmount, nil) + block.Finalize(receipts, common.Hash{}, gasUsed, xShardGasUsed, coinbaseAmount, nil, newTestListHasher()) wantMetaHash = block.MetaHash() wantHash = block.Hash() gasUsed.SetInt64(21)