core: lookup txs by block number instead of block hash (#19431)

This commit is contained in:
Daniel Liu 2025-01-23 09:32:14 +08:00
parent 2b8b7e68f7
commit 85fc56733f
3 changed files with 100 additions and 70 deletions

View file

@ -113,7 +113,10 @@ const (
// * the `TxHash`, `GasCost`, and `ContractAddress` fields are no longer stored for a receipt // * the `TxHash`, `GasCost`, and `ContractAddress` fields are no longer stored for a receipt
// * the `TxHash`, `GasCost`, and `ContractAddress` fields are computed by looking up the // * the `TxHash`, `GasCost`, and `ContractAddress` fields are computed by looking up the
// receipts' corresponding block // receipts' corresponding block
BlockChainVersion uint64 = 5 // - Version 6
// The following incompatible database changes were added:
// * Transaction lookup information stores the corresponding block number instead of block hash
BlockChainVersion uint64 = 6
// Maximum length of chain to cache by block's number // Maximum length of chain to cache by block's number
blocksHashCacheLimit = 900 blocksHashCacheLimit = 900

View file

@ -17,6 +17,8 @@
package rawdb package rawdb
import ( import (
"math/big"
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/ethdb" "github.com/XinFinOrg/XDPoSChain/ethdb"
@ -27,28 +29,34 @@ import (
// ReadTxLookupEntry retrieves the positional metadata associated with a transaction // ReadTxLookupEntry retrieves the positional metadata associated with a transaction
// hash to allow retrieving the transaction or receipt by hash. // hash to allow retrieving the transaction or receipt by hash.
func ReadTxLookupEntry(db ethdb.Reader, hash common.Hash) common.Hash { func ReadTxLookupEntry(db ethdb.Reader, hash common.Hash) *uint64 {
data, _ := db.Get(txLookupKey(hash)) data, _ := db.Get(txLookupKey(hash))
if len(data) == 0 { if len(data) == 0 {
return common.Hash{} return nil
} }
// Database v6 tx lookup just stores the block number
if len(data) < common.HashLength {
number := new(big.Int).SetBytes(data).Uint64()
return &number
}
// Database v4-v5 tx lookup format just stores the hash
if len(data) == common.HashLength { if len(data) == common.HashLength {
return common.BytesToHash(data) return ReadHeaderNumber(db, common.BytesToHash(data))
} }
// Probably it's legacy txlookup entry data, try to decode it. // Finally try database v3 tx lookup format
var entry LegacyTxLookupEntry var entry LegacyTxLookupEntry
if err := rlp.DecodeBytes(data, &entry); err != nil { if err := rlp.DecodeBytes(data, &entry); err != nil {
log.Error("Invalid transaction lookup entry RLP", "hash", hash, "blob", data, "err", err) log.Error("Invalid transaction lookup entry RLP", "hash", hash, "blob", data, "err", err)
return common.Hash{} return nil
} }
return entry.BlockHash return &entry.BlockIndex
} }
// WriteTxLookupEntriesByBlock stores a positional metadata for every transaction from // WriteTxLookupEntriesByBlock stores a positional metadata for every transaction from
// a block, enabling hash based transaction and receipt lookups. // a block, enabling hash based transaction and receipt lookups.
func WriteTxLookupEntriesByBlock(db ethdb.KeyValueWriter, block *types.Block) { func WriteTxLookupEntriesByBlock(db ethdb.KeyValueWriter, block *types.Block) {
for _, tx := range block.Transactions() { for _, tx := range block.Transactions() {
if err := db.Put(txLookupKey(tx.Hash()), block.Hash().Bytes()); err != nil { if err := db.Put(txLookupKey(tx.Hash()), block.Number().Bytes()); err != nil {
log.Crit("Failed to store transaction lookup entry", "err", err) log.Crit("Failed to store transaction lookup entry", "err", err)
} }
} }
@ -62,8 +70,8 @@ func DeleteTxLookupEntry(db ethdb.KeyValueWriter, hash common.Hash) {
// ReadTransaction retrieves a specific transaction from the database, along with // ReadTransaction retrieves a specific transaction from the database, along with
// its added positional metadata. // its added positional metadata.
func ReadTransaction(db ethdb.Reader, hash common.Hash) (*types.Transaction, common.Hash, uint64, uint64) { func ReadTransaction(db ethdb.Reader, hash common.Hash) (*types.Transaction, common.Hash, uint64, uint64) {
blockHash := ReadTxLookupEntry(db, hash) blockNumber := ReadTxLookupEntry(db, hash)
if blockHash == (common.Hash{}) { if blockNumber == nil {
// return nil, common.Hash{}, 0, 0 // return nil, common.Hash{}, 0, 0
// TODO(daniel): delete the following old codes // TODO(daniel): delete the following old codes
// Old transaction representation, load the transaction and it's metadata separately // Old transaction representation, load the transaction and it's metadata separately
@ -86,8 +94,8 @@ func ReadTransaction(db ethdb.Reader, hash common.Hash) (*types.Transaction, com
} }
return &tx, entry.BlockHash, entry.BlockIndex, entry.Index return &tx, entry.BlockHash, entry.BlockIndex, entry.Index
} }
blockNumber := ReadHeaderNumber(db, blockHash) blockHash := ReadCanonicalHash(db, *blockNumber)
if blockNumber == nil { if blockHash == (common.Hash{}) {
return nil, common.Hash{}, 0, 0 return nil, common.Hash{}, 0, 0
} }
body := ReadBody(db, blockHash, *blockNumber) body := ReadBody(db, blockHash, *blockNumber)
@ -107,8 +115,9 @@ func ReadTransaction(db ethdb.Reader, hash common.Hash) (*types.Transaction, com
// ReadReceipt retrieves a specific transaction receipt from the database, along with // ReadReceipt retrieves a specific transaction receipt from the database, along with
// its added positional metadata. // its added positional metadata.
func ReadReceipt(db ethdb.Reader, hash common.Hash, config *params.ChainConfig) (*types.Receipt, common.Hash, uint64, uint64) { func ReadReceipt(db ethdb.Reader, hash common.Hash, config *params.ChainConfig) (*types.Receipt, common.Hash, uint64, uint64) {
blockHash := ReadTxLookupEntry(db, hash) // Retrieve the context of the receipt based on the transaction hash
if blockHash == (common.Hash{}) { blockNumber := ReadTxLookupEntry(db, hash)
if blockNumber == nil {
// return nil, common.Hash{}, 0, 0 // return nil, common.Hash{}, 0, 0
// TODO(daniel): delete the following old codes // TODO(daniel): delete the following old codes
// Old receipt representation, load the receipt and set an unknown metadata // Old receipt representation, load the receipt and set an unknown metadata
@ -124,8 +133,8 @@ func ReadReceipt(db ethdb.Reader, hash common.Hash, config *params.ChainConfig)
} }
return (*types.Receipt)(&receipt), common.Hash{}, 0, 0 return (*types.Receipt)(&receipt), common.Hash{}, 0, 0
} }
blockNumber := ReadHeaderNumber(db, blockHash) blockHash := ReadCanonicalHash(db, *blockNumber)
if blockNumber == nil { if blockHash == (common.Hash{}) {
return nil, common.Hash{}, 0, 0 return nil, common.Hash{}, 0, 0
} }
// Read all the receipts from the block and return the one with the matching hash // Read all the receipts from the block and return the one with the matching hash

View file

@ -22,69 +22,87 @@ import (
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/ethdb"
"github.com/XinFinOrg/XDPoSChain/rlp" "github.com/XinFinOrg/XDPoSChain/rlp"
) )
// Tests that positional lookup metadata can be stored and retrieved. // Tests that positional lookup metadata can be stored and retrieved.
func TestLookupStorage(t *testing.T) { func TestLookupStorage(t *testing.T) {
db := NewMemoryDatabase() tests := []struct {
name string
tx1 := types.NewTransaction(1, common.BytesToAddress([]byte{0x11}), big.NewInt(111), 1111, big.NewInt(11111), []byte{0x11, 0x11, 0x11}) writeTxLookupEntries func(ethdb.Writer, *types.Block)
tx2 := types.NewTransaction(2, common.BytesToAddress([]byte{0x22}), big.NewInt(222), 2222, big.NewInt(22222), []byte{0x22, 0x22, 0x22}) }{
tx3 := types.NewTransaction(3, common.BytesToAddress([]byte{0x33}), big.NewInt(333), 3333, big.NewInt(33333), []byte{0x33, 0x33, 0x33}) {
txs := []*types.Transaction{tx1, tx2, tx3} "DatabaseV6",
func(db ethdb.Writer, block *types.Block) {
block := types.NewBlock(&types.Header{Number: big.NewInt(314)}, txs, nil, nil) WriteTxLookupEntriesByBlock(db, block)
},
// Check that no transactions entries are in a pristine database },
for i, tx := range txs { {
if txn, _, _, _ := ReadTransaction(db, tx.Hash()); txn != nil { "DatabaseV4-V5",
t.Fatalf("tx #%d [%x]: non existent transaction returned: %v", i, tx.Hash(), txn) func(db ethdb.Writer, block *types.Block) {
} for _, tx := range block.Transactions() {
db.Put(txLookupKey(tx.Hash()), block.Hash().Bytes())
}
},
},
{
"DatabaseV3",
func(db ethdb.Writer, block *types.Block) {
for index, tx := range block.Transactions() {
entry := LegacyTxLookupEntry{
BlockHash: block.Hash(),
BlockIndex: block.NumberU64(),
Index: uint64(index),
}
data, _ := rlp.EncodeToBytes(entry)
db.Put(txLookupKey(tx.Hash()), data)
}
},
},
} }
// Insert all the transactions into the database, and verify contents
WriteBlock(db, block)
WriteTxLookupEntriesByBlock(db, block)
for i, tx := range txs { for _, tc := range tests {
if txn, hash, number, index := ReadTransaction(db, tx.Hash()); txn == nil { t.Run(tc.name, func(t *testing.T) {
t.Fatalf("tx #%d [%x]: transaction not found", i, tx.Hash()) db := NewMemoryDatabase()
} else {
if hash != block.Hash() || number != block.NumberU64() || index != uint64(i) { tx1 := types.NewTransaction(1, common.BytesToAddress([]byte{0x11}), big.NewInt(111), 1111, big.NewInt(11111), []byte{0x11, 0x11, 0x11})
t.Fatalf("tx #%d [%x]: positional metadata mismatch: have %x/%d/%d, want %x/%v/%v", i, tx.Hash(), hash, number, index, block.Hash(), block.NumberU64(), i) tx2 := types.NewTransaction(2, common.BytesToAddress([]byte{0x22}), big.NewInt(222), 2222, big.NewInt(22222), []byte{0x22, 0x22, 0x22})
tx3 := types.NewTransaction(3, common.BytesToAddress([]byte{0x33}), big.NewInt(333), 3333, big.NewInt(33333), []byte{0x33, 0x33, 0x33})
txs := []*types.Transaction{tx1, tx2, tx3}
block := types.NewBlock(&types.Header{Number: big.NewInt(314)}, txs, nil, 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)
}
} }
if tx.Hash() != txn.Hash() { // Insert all the transactions into the database, and verify contents
t.Fatalf("tx #%d [%x]: transaction mismatch: have %v, want %v", i, tx.Hash(), txn, tx) WriteCanonicalHash(db, block.Hash(), block.NumberU64())
WriteBlock(db, block)
tc.writeTxLookupEntries(db, block)
for i, tx := range txs {
if txn, hash, number, index := ReadTransaction(db, tx.Hash()); txn == nil {
t.Fatalf("tx #%d [%x]: transaction not found", i, tx.Hash())
} else {
if hash != block.Hash() || number != block.NumberU64() || index != uint64(i) {
t.Fatalf("tx #%d [%x]: positional metadata mismatch: have %x/%d/%d, want %x/%v/%v", i, tx.Hash(), hash, number, index, block.Hash(), block.NumberU64(), 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 {
// Delete the transactions and check purge DeleteTxLookupEntry(db, tx.Hash())
for i, tx := range txs { if txn, _, _, _ := ReadTransaction(db, tx.Hash()); txn != nil {
DeleteTxLookupEntry(db, tx.Hash()) t.Fatalf("tx #%d [%x]: deleted transaction returned: %v", i, tx.Hash(), txn)
if txn, _, _, _ := ReadTransaction(db, tx.Hash()); txn != nil { }
t.Fatalf("tx #%d [%x]: deleted transaction returned: %v", i, tx.Hash(), txn)
}
}
// Insert legacy txlookup and verify the data retrieval
for index, tx := range block.Transactions() {
entry := LegacyTxLookupEntry{
BlockHash: block.Hash(),
BlockIndex: block.NumberU64(),
Index: uint64(index),
}
data, _ := rlp.EncodeToBytes(entry)
db.Put(txLookupKey(tx.Hash()), data)
}
for i, tx := range txs {
if txn, hash, number, index := ReadTransaction(db, tx.Hash()); txn == nil {
t.Fatalf("tx #%d [%x]: transaction not found", i, tx.Hash())
} else {
if hash != block.Hash() || number != block.NumberU64() || index != uint64(i) {
t.Fatalf("tx #%d [%x]: positional metadata mismatch: have %x/%d/%d, want %x/%v/%v", i, tx.Hash(), hash, number, index, block.Hash(), block.NumberU64(), i)
} }
if tx.Hash() != txn.Hash() { })
t.Fatalf("tx #%d [%x]: transaction mismatch: have %v, want %v", i, tx.Hash(), txn, tx)
}
}
} }
} }