From 8219bfcaddb79ada704713d216ae54b1f7fb13f2 Mon Sep 17 00:00:00 2001 From: Delweng Date: Thu, 19 Jun 2025 09:44:39 +0800 Subject: [PATCH 1/2] eth,core: terminate the downloader immediately when shutdown signal is received (#32062) Closes https://github.com/ethereum/go-ethereum/issues/32058 --- core/blockchain.go | 7 ++----- eth/downloader/downloader.go | 6 ++++++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 553be35e53..2794513025 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -278,9 +278,8 @@ type BlockChain struct { txLookupLock sync.RWMutex txLookupCache *lru.Cache[common.Hash, txLookup] - quit chan struct{} // shutdown signal, closed in Stop. - stopping atomic.Bool // false if chain is running, true when stopped - procInterrupt atomic.Bool // interrupt signaler for block processing + stopping atomic.Bool // false if chain is running, true when stopped + procInterrupt atomic.Bool // interrupt signaler for block processing engine consensus.Engine validator Validator // Block and state validator interface @@ -328,7 +327,6 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis db: db, triedb: triedb, triegc: prque.New[int64, common.Hash](nil), - quit: make(chan struct{}), chainmu: syncx.NewClosableMutex(), bodyCache: lru.NewCache[common.Hash, *types.Body](bodyCacheLimit), bodyRLPCache: lru.NewCache[common.Hash, rlp.RawValue](bodyCacheLimit), @@ -1206,7 +1204,6 @@ func (bc *BlockChain) stopWithoutSaving() { bc.scope.Close() // Signal shutdown to all goroutines. - close(bc.quit) bc.StopInsert() // Now wait for all chain modifications to end and persistent goroutines to exit. diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index 762fb9283e..8f8f219597 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -199,6 +199,9 @@ type BlockChain interface { // InsertChain inserts a batch of blocks into the local chain. InsertChain(types.Blocks) (int, error) + // StopInsert interrupts the inserting process. + StopInsert() + // InsertReceiptChain inserts a batch of blocks along with their receipts // into the local chain. Blocks older than the specified `ancientLimit` // are stored directly in the ancient store, while newer blocks are stored @@ -634,6 +637,9 @@ func (d *Downloader) Cancel() { // Terminate interrupts the downloader, canceling all pending operations. // The downloader cannot be reused after calling Terminate. func (d *Downloader) Terminate() { + // Signal to stop inserting in-flight blocks + d.blockchain.StopInsert() + // Close the termination channel (make sure double close is allowed) d.quitLock.Lock() select { From 4997a248ab4acdb40383f1e1a5d3813a634370a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Faruk=20Irmak?= Date: Thu, 19 Jun 2025 05:05:32 +0300 Subject: [PATCH 2/2] core/rawdb: don't decode the full block body in ReadTransaction (#32027) Reading a single transaction out of a block shouldn't need decoding the entire body --------- Co-authored-by: Felix Lange Co-authored-by: Gary Rong --- core/rawdb/accessors_indexes.go | 56 ++++++++++++-- core/rawdb/accessors_indexes_test.go | 112 ++++++++++++++++++++++++++- 2 files changed, 159 insertions(+), 9 deletions(-) diff --git a/core/rawdb/accessors_indexes.go b/core/rawdb/accessors_indexes.go index fafde25606..b5491f76ef 100644 --- a/core/rawdb/accessors_indexes.go +++ b/core/rawdb/accessors_indexes.go @@ -24,6 +24,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" @@ -128,6 +129,46 @@ func DeleteAllTxLookupEntries(db ethdb.KeyValueStore, condition func(common.Hash } } +// findTxInBlockBody traverses the given RLP-encoded block body, searching for +// the transaction specified by its hash. +func findTxInBlockBody(blockbody rlp.RawValue, target common.Hash) (*types.Transaction, uint64, error) { + txnListRLP, _, err := rlp.SplitList(blockbody) + if err != nil { + return nil, 0, err + } + iter, err := rlp.NewListIterator(txnListRLP) + if err != nil { + return nil, 0, err + } + txIndex := uint64(0) + for iter.Next() { + if iter.Err() != nil { + return nil, 0, err + } + // The preimage for the hash calculation of legacy transactions + // is just their RLP encoding. For typed (EIP-2718) transactions, + // which are encoded as byte arrays, the preimage is the content of + // the byte array, so trim their prefix here. + txRLP := iter.Value() + kind, txHashPayload, _, err := rlp.Split(txRLP) + if err != nil { + return nil, 0, err + } + if kind == rlp.List { // Legacy transaction + txHashPayload = txRLP + } + if crypto.Keccak256Hash(txHashPayload) == target { + var tx types.Transaction + if err := rlp.DecodeBytes(txRLP, &tx); err != nil { + return nil, 0, err + } + return &tx, txIndex, nil + } + txIndex++ + } + return nil, 0, errors.New("transaction not found") +} + // ReadTransaction retrieves a specific transaction from the database, along with // its added positional metadata. func ReadTransaction(db ethdb.Reader, hash common.Hash) (*types.Transaction, common.Hash, uint64, uint64) { @@ -139,18 +180,17 @@ func ReadTransaction(db ethdb.Reader, hash common.Hash) (*types.Transaction, com if blockHash == (common.Hash{}) { return nil, common.Hash{}, 0, 0 } - body := ReadBody(db, blockHash, *blockNumber) - if body == nil { + bodyRLP := ReadBodyRLP(db, blockHash, *blockNumber) + if bodyRLP == nil { log.Error("Transaction referenced missing", "number", *blockNumber, "hash", blockHash) return nil, common.Hash{}, 0, 0 } - for txIndex, tx := range body.Transactions { - if tx.Hash() == hash { - return tx, blockHash, *blockNumber, uint64(txIndex) - } + tx, txIndex, err := findTxInBlockBody(bodyRLP, hash) + if err != nil { + log.Error("Transaction not found", "number", *blockNumber, "hash", blockHash, "txhash", hash, "err", err) + return nil, common.Hash{}, 0, 0 } - log.Error("Transaction not found", "number", *blockNumber, "hash", blockHash, "txhash", hash) - return nil, common.Hash{}, 0, 0 + return tx, blockHash, *blockNumber, txIndex } // ReadReceipt retrieves a specific transaction receipt from the database, along with diff --git a/core/rawdb/accessors_indexes_test.go b/core/rawdb/accessors_indexes_test.go index 29b468fb2a..b768deed9b 100644 --- a/core/rawdb/accessors_indexes_test.go +++ b/core/rawdb/accessors_indexes_test.go @@ -20,11 +20,13 @@ import ( "math/big" "testing" + "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/internal/blocktest" "github.com/ethereum/go-ethereum/rlp" + "github.com/holiman/uint256" ) var newTestHasher = blocktest.NewHasher @@ -72,7 +74,15 @@ func TestLookupStorage(t *testing.T) { tx1 := types.NewTransaction(1, common.BytesToAddress([]byte{0x11}), big.NewInt(111), 1111, big.NewInt(11111), []byte{0x11, 0x11, 0x11}) 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} + tx4 := types.NewTx(&types.DynamicFeeTx{ + To: new(common.Address), + Nonce: 5, + Value: big.NewInt(5), + Gas: 5, + GasTipCap: big.NewInt(55), + GasFeeCap: big.NewInt(1055), + }) + txs := []*types.Transaction{tx1, tx2, tx3, tx4} block := types.NewBlock(&types.Header{Number: big.NewInt(314)}, &types.Body{Transactions: txs}, nil, newTestHasher()) @@ -109,3 +119,103 @@ func TestLookupStorage(t *testing.T) { }) } } + +func TestFindTxInBlockBody(t *testing.T) { + tx1 := types.NewTx(&types.LegacyTx{ + Nonce: 1, + GasPrice: big.NewInt(1), + Gas: 1, + To: new(common.Address), + Value: big.NewInt(5), + Data: []byte{0x11, 0x11, 0x11}, + }) + tx2 := types.NewTx(&types.AccessListTx{ + Nonce: 1, + GasPrice: big.NewInt(1), + Gas: 1, + To: new(common.Address), + Value: big.NewInt(5), + Data: []byte{0x11, 0x11, 0x11}, + AccessList: []types.AccessTuple{ + { + Address: common.Address{0x1}, + StorageKeys: []common.Hash{{0x1}, {0x2}}, + }, + }, + }) + tx3 := types.NewTx(&types.DynamicFeeTx{ + Nonce: 1, + Gas: 1, + To: new(common.Address), + Value: big.NewInt(5), + Data: []byte{0x11, 0x11, 0x11}, + GasTipCap: big.NewInt(55), + GasFeeCap: big.NewInt(1055), + AccessList: []types.AccessTuple{ + { + Address: common.Address{0x1}, + StorageKeys: []common.Hash{{0x1}, {0x2}}, + }, + }, + }) + tx4 := types.NewTx(&types.BlobTx{ + Nonce: 1, + Gas: 1, + To: common.Address{0x1}, + Value: uint256.NewInt(5), + Data: []byte{0x11, 0x11, 0x11}, + GasTipCap: uint256.NewInt(55), + GasFeeCap: uint256.NewInt(1055), + AccessList: []types.AccessTuple{ + { + Address: common.Address{0x1}, + StorageKeys: []common.Hash{{0x1}, {0x2}}, + }, + }, + BlobFeeCap: uint256.NewInt(1), + BlobHashes: []common.Hash{{0x1}, {0x2}}, + }) + tx5 := types.NewTx(&types.SetCodeTx{ + Nonce: 1, + Gas: 1, + To: common.Address{0x1}, + Value: uint256.NewInt(5), + Data: []byte{0x11, 0x11, 0x11}, + GasTipCap: uint256.NewInt(55), + GasFeeCap: uint256.NewInt(1055), + AccessList: []types.AccessTuple{ + { + Address: common.Address{0x1}, + StorageKeys: []common.Hash{{0x1}, {0x2}}, + }, + }, + AuthList: []types.SetCodeAuthorization{ + { + ChainID: uint256.Int{1}, + Address: common.Address{0x1}, + }, + }, + }) + + txs := []*types.Transaction{tx1, tx2, tx3, tx4, tx5} + + block := types.NewBlock(&types.Header{Number: big.NewInt(314)}, &types.Body{Transactions: txs}, nil, newTestHasher()) + db := NewMemoryDatabase() + WriteBlock(db, block) + + rlp := ReadBodyRLP(db, block.Hash(), block.NumberU64()) + for i := 0; i < len(txs); i++ { + tx, txIndex, err := findTxInBlockBody(rlp, txs[i].Hash()) + if err != nil { + t.Fatalf("Failed to retrieve tx, err: %v", err) + } + if txIndex != uint64(i) { + t.Fatalf("Unexpected transaction index, want: %d, got: %d", i, txIndex) + } + if tx.Hash() != txs[i].Hash() { + want := spew.Sdump(txs[i]) + got := spew.Sdump(tx) + t.Fatalf("Unexpected transaction, want: %s, got: %s", want, got) + } + } +}