From 87d7c2aa125cba93e1d5b7e8eb3b3eb1327b51ba Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 30 Jun 2025 17:24:52 +0200 Subject: [PATCH 1/8] .gitea: disable cron schedule --- .gitea/workflows/release-azure-cleanup.yml | 9 +++++++-- .gitea/workflows/release-ppa.yml | 10 ++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/.gitea/workflows/release-azure-cleanup.yml b/.gitea/workflows/release-azure-cleanup.yml index 4a20828d2e..0b04581e9b 100644 --- a/.gitea/workflows/release-azure-cleanup.yml +++ b/.gitea/workflows/release-azure-cleanup.yml @@ -1,8 +1,13 @@ on: - schedule: - - cron: '0 15 * * *' workflow_dispatch: + ### Note we cannot use cron-triggered builds right now, Gitea seems to have + ### a few bugs in that area. So this workflow is scheduled using an external + ### triggering mechanism and workflow_dispatch. + # + # schedule: + # - cron: '0 15 * * *' + jobs: azure-cleanup: runs-on: ubuntu-latest diff --git a/.gitea/workflows/release-ppa.yml b/.gitea/workflows/release-ppa.yml index 34b6be4b83..129badac16 100644 --- a/.gitea/workflows/release-ppa.yml +++ b/.gitea/workflows/release-ppa.yml @@ -1,11 +1,17 @@ on: - schedule: - - cron: '0 16 * * *' push: tags: - "v*" workflow_dispatch: + ### Note we cannot use cron-triggered builds right now, Gitea seems to have + ### a few bugs in that area. So this workflow is scheduled using an external + ### triggering mechanism and workflow_dispatch. + # + # schedule: + # - cron: '0 16 * * *' + + jobs: ppa: name: PPA Upload From c59c647ed7db46ece42444731d8136c7f31a8692 Mon Sep 17 00:00:00 2001 From: Delweng Date: Tue, 1 Jul 2025 11:35:22 +0800 Subject: [PATCH 2/8] triedb: reset state indexer after snap synced (#32104) Fix the issue after initial snap sync with `gcmode=archive` enabled. ``` NewPayload: inserting block failed error="history indexing is out of order, last: null, requested: 1" ``` --------- Signed-off-by: Delweng Co-authored-by: Gary Rong --- triedb/pathdb/database.go | 10 ++++++++++ triedb/pathdb/history_indexer.go | 13 ++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/triedb/pathdb/database.go b/triedb/pathdb/database.go index 7c8c327484..23a7d383b5 100644 --- a/triedb/pathdb/database.go +++ b/triedb/pathdb/database.go @@ -520,6 +520,16 @@ func (db *Database) Enable(root common.Hash) error { // Re-construct a new disk layer backed by persistent state // and schedule the state snapshot generation if it's permitted. db.tree.init(generateSnapshot(db, root, db.isVerkle || db.config.SnapshotNoBuild)) + + // After snap sync, the state of the database may have changed completely. + // To ensure the history indexer always matches the current state, we must: + // 1. Close any existing indexer + // 2. Re-initialize the indexer so it starts indexing from the new state root. + if db.indexer != nil && db.freezer != nil && db.config.EnableStateIndexing { + db.indexer.close() + db.indexer = newHistoryIndexer(db.diskdb, db.freezer, db.tree.bottom().stateID()) + log.Info("Re-enabled state history indexing") + } log.Info("Rebuilt trie database", "root", root) return nil } diff --git a/triedb/pathdb/history_indexer.go b/triedb/pathdb/history_indexer.go index 6df74de61c..90816e6ef6 100644 --- a/triedb/pathdb/history_indexer.go +++ b/triedb/pathdb/history_indexer.go @@ -494,7 +494,18 @@ func (i *indexIniter) index(done chan struct{}, interrupt *atomic.Int32, lastID // when the state is reverted manually (chain.SetHead) or the deep reorg is // encountered. In such cases, no indexing should be scheduled. if beginID > lastID { - log.Debug("State history is fully indexed", "last", lastID) + if lastID == 0 && beginID == 1 { + // Initialize the indexing flag if the state history is empty by + // using zero as the disk layer ID. This is a common case that + // can occur after snap sync. + // + // This step is essential to avoid spinning up indexing thread + // endlessly until a history object is produced. + storeIndexMetadata(i.disk, 0) + log.Info("Initialized history indexing flag") + } else { + log.Debug("State history is fully indexed", "last", lastID) + } return } log.Info("Start history indexing", "beginID", beginID, "lastID", lastID) From 1f4ea4d162d4189b24af0de057b398773fd9fb4f Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 1 Jul 2025 09:13:19 +0300 Subject: [PATCH 3/8] eth/filters: add address limit to filters (#31876) The address filter was never checked against a maximum limit, which can be somewhat abusive for API nodes. This PR adds a limit similar to topics ## Description (AI generated) This pull request introduces a new validation to enforce a maximum limit on the number of addresses allowed in filter criteria for Ethereum logs. It includes updates to the `FilterAPI` and `EventSystem` logic, as well as corresponding test cases to ensure the new constraint is properly enforced. ### Core functionality changes: * **Validation for maximum addresses in filter criteria**: - Added a new constant, `maxAddresses`, set to 100, to define the maximum allowable addresses in a filter. - Introduced a new error, `errExceedMaxAddresses`, to handle cases where the number of addresses exceeds the limit. - Updated the `GetLogs` method in `FilterAPI` to validate the number of addresses against `maxAddresses`. - Modified the `UnmarshalJSON` method to return an error if the number of addresses in the input JSON exceeds `maxAddresses`. - Added similar validation to the `SubscribeLogs` method in `EventSystem`. ### Test updates: * **New test cases for address limit validation**: - Added a test in `TestUnmarshalJSONNewFilterArgs` to verify that exceeding the maximum number of addresses triggers the `errExceedMaxAddresses` error. - Updated `TestInvalidLogFilterCreation` to include a test case for an invalid filter with more than `maxAddresses` addresses. - Updated `TestInvalidGetLogsRequest` to test for invalid log requests with excessive addresses. These changes ensure that the system enforces a reasonable limit on the number of addresses in filter criteria, improving robustness and preventing potential performance issues. --------- Co-authored-by: zsfelfoldi --- eth/filters/api.go | 20 +++++++++++++++----- eth/filters/api_test.go | 12 ++++++++++++ eth/filters/filter_system.go | 3 +++ eth/filters/filter_system_test.go | 2 ++ 4 files changed, 32 insertions(+), 5 deletions(-) diff --git a/eth/filters/api.go b/eth/filters/api.go index f549dc4dec..232af23957 100644 --- a/eth/filters/api.go +++ b/eth/filters/api.go @@ -40,13 +40,17 @@ var ( errInvalidBlockRange = errors.New("invalid block range params") errPendingLogsUnsupported = errors.New("pending logs are not supported") errExceedMaxTopics = errors.New("exceed max topics") + errExceedMaxAddresses = errors.New("exceed max addresses") ) -// The maximum number of topic criteria allowed, vm.LOG4 - vm.LOG0 -const maxTopics = 4 - -// The maximum number of allowed topics within a topic criteria -const maxSubTopics = 1000 +const ( + // The maximum number of addresses allowed in a filter criteria + maxAddresses = 1000 + // The maximum number of topic criteria allowed, vm.LOG4 - vm.LOG0 + maxTopics = 4 + // The maximum number of allowed topics within a topic criteria + maxSubTopics = 1000 +) // filter is a helper struct that holds meta information over the filter type // and associated subscription in the event system. @@ -341,6 +345,9 @@ func (api *FilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*type if len(crit.Topics) > maxTopics { return nil, errExceedMaxTopics } + if len(crit.Addresses) > maxAddresses { + return nil, errExceedMaxAddresses + } var filter *Filter if crit.BlockHash != nil { // Block filter requested, construct a single-shot filter @@ -530,6 +537,9 @@ func (args *FilterCriteria) UnmarshalJSON(data []byte) error { // raw.Address can contain a single address or an array of addresses switch rawAddr := raw.Addresses.(type) { case []interface{}: + if len(rawAddr) > maxAddresses { + return errExceedMaxAddresses + } for i, addr := range rawAddr { if strAddr, ok := addr.(string); ok { addr, err := decodeAddress(strAddr) diff --git a/eth/filters/api_test.go b/eth/filters/api_test.go index 822bc826f6..2eb3ee97b3 100644 --- a/eth/filters/api_test.go +++ b/eth/filters/api_test.go @@ -19,6 +19,7 @@ package filters import ( "encoding/json" "fmt" + "strings" "testing" "github.com/ethereum/go-ethereum/common" @@ -182,4 +183,15 @@ func TestUnmarshalJSONNewFilterArgs(t *testing.T) { if len(test7.Topics[2]) != 0 { t.Fatalf("expected 0 topics, got %d topics", len(test7.Topics[2])) } + + // multiple address exceeding max + var test8 FilterCriteria + addresses := make([]string, maxAddresses+1) + for i := 0; i < maxAddresses+1; i++ { + addresses[i] = fmt.Sprintf(`"%s"`, common.HexToAddress(fmt.Sprintf("0x%x", i)).Hex()) + } + vector = fmt.Sprintf(`{"address": [%s]}`, strings.Join(addresses, ", ")) + if err := json.Unmarshal([]byte(vector), &test8); err != errExceedMaxAddresses { + t.Fatal("expected errExceedMaxAddresses, got", err) + } } diff --git a/eth/filters/filter_system.go b/eth/filters/filter_system.go index ee9dfcce01..82c91266c0 100644 --- a/eth/filters/filter_system.go +++ b/eth/filters/filter_system.go @@ -291,6 +291,9 @@ func (es *EventSystem) SubscribeLogs(crit ethereum.FilterQuery, logs chan []*typ if len(crit.Topics) > maxTopics { return nil, errExceedMaxTopics } + if len(crit.Addresses) > maxAddresses { + return nil, errExceedMaxAddresses + } var from, to rpc.BlockNumber if crit.FromBlock == nil { from = rpc.LatestBlockNumber diff --git a/eth/filters/filter_system_test.go b/eth/filters/filter_system_test.go index 85d4a33913..0839312b70 100644 --- a/eth/filters/filter_system_test.go +++ b/eth/filters/filter_system_test.go @@ -435,6 +435,7 @@ func TestInvalidLogFilterCreation(t *testing.T) { 1: {FromBlock: big.NewInt(rpc.PendingBlockNumber.Int64()), ToBlock: big.NewInt(100)}, 2: {FromBlock: big.NewInt(rpc.LatestBlockNumber.Int64()), ToBlock: big.NewInt(100)}, 3: {Topics: [][]common.Hash{{}, {}, {}, {}, {}}}, + 4: {Addresses: make([]common.Address, maxAddresses+1)}, } for i, test := range testCases { @@ -461,6 +462,7 @@ func TestInvalidGetLogsRequest(t *testing.T) { 1: {BlockHash: &blockHash, ToBlock: big.NewInt(500)}, 2: {BlockHash: &blockHash, FromBlock: big.NewInt(rpc.LatestBlockNumber.Int64())}, 3: {BlockHash: &blockHash, Topics: [][]common.Hash{{}, {}, {}, {}, {}}}, + 4: {BlockHash: &blockHash, Addresses: make([]common.Address, maxAddresses+1)}, } for i, test := range testCases { From 8a9f4bbb6d79653f7f8d812a81136777dc7870f1 Mon Sep 17 00:00:00 2001 From: Forrest Kim <38067691+ForrestKim42@users.noreply.github.com> Date: Tue, 1 Jul 2025 15:50:02 +0900 Subject: [PATCH 4/8] Fix log indexer noise after debug_setHead operations (#31934) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary This PR resolves Issue #31929 by reducing log noise generated by the log indexer after `debug_setHead` operations. ## Problem Description When `debug_setHead` is called to rewind the blockchain, blocks are removed from the database. However, the log indexer's `ChainView` objects may still hold references to these deleted blocks. When `extendNonCanonical()` attempts to access these missing headers, it results in: 1. **Repeated ERROR logs**: `Header not found number=X hash=0x...` 2. **Log noise** that can mask other important errors 3. **User confusion** about whether this indicates a real problem ## Root Cause Analysis The issue occurs because: - `debug_setHead` removes blocks from the blockchain database - Log indexer's `ChainView` may still reference deleted block hashes - `extendNonCanonical()` in `core/filtermaps/chain_view.go` tries to fetch these missing headers - The existing `return false` logic properly handles the error, but logs at ERROR level ## Solution This is a **logging improvement only** - no functional logic changes: ### Changes Made 1. **Log level**: Changed from `ERROR` to `DEBUG` 2. **Log message**: Enhanced with descriptive context about chain view extension 3. **Comments**: Added explanation for when this situation occurs 4. **Behavior**: Maintains existing error handling (`return false` was already present) ### Code Changes ```go // Before log.Error("Header not found", "number", number, "hash", hash) return false // After // Header not found - this can happen after debug_setHead operations // where blocks have been deleted. Return false to indicate the chain view // is no longer valid rather than logging repeated errors. log.Debug("Header not found during chain view extension", "number", number, "hash", hash) return false ``` ## Testing ### Automated Tests - ✅ All existing filtermaps tests pass: `go test ./core/filtermaps -v` - ✅ No regressions in related functionality ### Manual Verification 1. **Before fix**: Started geth in dev mode, generated blocks, called `debug_setHead(3)` → **5 repeated ERROR logs** 2. **After fix**: Same scenario → **4 DEBUG logs, no ERROR noise** ### Test Environment ```bash # Setup test environment rm -rf ./dev-test-data ./build/bin/geth --dev --datadir ./dev-test-data --http --http.api debug,eth,net,web3 --verbosity 4 # Generate test blocks and trigger issue curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"debug_setHead","params":["0x3"],"id":1}' http://localhost:8545 ``` ## Related Issues - Fixes #31929 ## Additional Context This issue was reported as spurious error messages appearing after `debug_setHead` operations. The investigation revealed that while the error handling was functionally correct, the ERROR log level was inappropriate for this expected scenario in development/debugging workflows. The fix maintains full compatibility while significantly improving the debugging experience for developers using `debug_setHead`. --------- Co-authored-by: Sun Tae, Kim <38067691+humblefirm@users.noreply.github.com> Co-authored-by: zsfelfoldi --- core/filtermaps/chain_view.go | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/core/filtermaps/chain_view.go b/core/filtermaps/chain_view.go index 433ca07cd0..7c48048ad9 100644 --- a/core/filtermaps/chain_view.go +++ b/core/filtermaps/chain_view.go @@ -55,7 +55,9 @@ func NewChainView(chain blockchain, number uint64, hash common.Hash) *ChainView headNumber: number, hashes: []common.Hash{hash}, } - cv.extendNonCanonical() + if !cv.extendNonCanonical() { + return nil + } return cv } @@ -129,7 +131,11 @@ func (cv *ChainView) SharedRange(cv2 *ChainView) common.Range[uint64] { return common.Range[uint64]{} } var sharedLen uint64 - for n := min(cv.headNumber+1-uint64(len(cv.hashes)), cv2.headNumber+1-uint64(len(cv2.hashes))); n <= cv.headNumber && n <= cv2.headNumber && cv.blockHash(n) == cv2.blockHash(n); n++ { + for n := min(cv.headNumber+1-uint64(len(cv.hashes)), cv2.headNumber+1-uint64(len(cv2.hashes))); n <= cv.headNumber && n <= cv2.headNumber; n++ { + h1, h2 := cv.blockHash(n), cv2.blockHash(n) + if h1 != h2 || h1 == (common.Hash{}) { + break + } sharedLen = n + 1 } return common.NewRange(0, sharedLen) @@ -153,10 +159,13 @@ func matchViews(cv1, cv2 *ChainView, number uint64) bool { if cv1.headNumber < number || cv2.headNumber < number { return false } + var h1, h2 common.Hash if number == cv1.headNumber || number == cv2.headNumber { - return cv1.BlockId(number) == cv2.BlockId(number) + h1, h2 = cv1.BlockId(number), cv2.BlockId(number) + } else { + h1, h2 = cv1.BlockHash(number), cv2.BlockHash(number) } - return cv1.BlockHash(number) == cv2.BlockHash(number) + return h1 == h2 && h1 != common.Hash{} } // extendNonCanonical checks whether the previously known reverse list of head @@ -175,7 +184,10 @@ func (cv *ChainView) extendNonCanonical() bool { } header := cv.chain.GetHeader(hash, number) if header == nil { - log.Error("Header not found", "number", number, "hash", hash) + // Header not found - this can happen after debug_setHead operations + // where blocks have been deleted. Return false to indicate the chain view + // is no longer valid rather than logging repeated errors. + log.Debug("Header not found during chain view extension", "number", number, "hash", hash) return false } cv.hashes = append(cv.hashes, header.ParentHash) From e92619221fdec79991db8ced43c1d4fd232b9af8 Mon Sep 17 00:00:00 2001 From: nthumann Date: Tue, 1 Jul 2025 08:00:33 +0100 Subject: [PATCH 5/8] core/filtermaps: clean up log format of unindexing message (#32123) Sorry for not fully fixed in https://github.com/ethereum/go-ethereum/pull/31761, now the log format of unindexing message is cleaned up, to make it consistent with the indexing message. --- core/filtermaps/filtermaps.go | 6 +++--- core/filtermaps/indexer.go | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/core/filtermaps/filtermaps.go b/core/filtermaps/filtermaps.go index cb155e9a8b..752a211786 100644 --- a/core/filtermaps/filtermaps.go +++ b/core/filtermaps/filtermaps.go @@ -268,9 +268,9 @@ func NewFilterMaps(db ethdb.KeyValueStore, initView *ChainView, historyCutoff, f if f.indexedRange.hasIndexedBlocks() { log.Info("Initialized log indexer", - "first block", f.indexedRange.blocks.First(), "last block", f.indexedRange.blocks.Last(), - "first map", f.indexedRange.maps.First(), "last map", f.indexedRange.maps.Last(), - "head indexed", f.indexedRange.headIndexed) + "firstblock", f.indexedRange.blocks.First(), "lastblock", f.indexedRange.blocks.Last(), + "firstmap", f.indexedRange.maps.First(), "lastmap", f.indexedRange.maps.Last(), + "headindexed", f.indexedRange.headIndexed) } return f } diff --git a/core/filtermaps/indexer.go b/core/filtermaps/indexer.go index 02ae2b920d..9e949fe299 100644 --- a/core/filtermaps/indexer.go +++ b/core/filtermaps/indexer.go @@ -380,9 +380,9 @@ func (f *FilterMaps) tryUnindexTail() (bool, error) { } if f.startedTailUnindex && f.indexedRange.hasIndexedBlocks() { log.Info("Log index tail unindexing finished", - "first block", f.indexedRange.blocks.First(), "last block", f.indexedRange.blocks.Last(), - "removed maps", f.indexedRange.maps.First()-f.ptrTailUnindexMap, - "removed blocks", f.indexedRange.blocks.First()-f.tailPartialBlocks()-f.ptrTailUnindexBlock, + "firstblock", f.indexedRange.blocks.First(), "lastblock", f.indexedRange.blocks.Last(), + "removedmaps", f.indexedRange.maps.First()-f.ptrTailUnindexMap, + "removedblocks", f.indexedRange.blocks.First()-f.tailPartialBlocks()-f.ptrTailUnindexBlock, "elapsed", common.PrettyDuration(time.Since(f.startedTailUnindexAt))) f.startedTailUnindex = false } From 2055196cea9aeba65ef3d14ec78c8d415f826a93 Mon Sep 17 00:00:00 2001 From: nthumann Date: Tue, 1 Jul 2025 08:04:05 +0100 Subject: [PATCH 6/8] eth/catalyst: fix the log message in newPayloadV4 (#32125) It should be `newPayloadV4 must only be called for prague payloads` for the V4 payload error --- eth/catalyst/api.go | 2 +- eth/catalyst/witness.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index e835344ca1..409899d582 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -648,7 +648,7 @@ func (api *ConsensusAPI) NewPayloadV4(params engine.ExecutableData, versionedHas case executionRequests == nil: return invalidStatus, paramsErr("nil executionRequests post-prague") case !api.checkFork(params.Timestamp, forks.Prague, forks.Osaka): - return invalidStatus, unsupportedForkErr("newPayloadV3 must only be called for cancun payloads") + return invalidStatus, unsupportedForkErr("newPayloadV4 must only be called for prague payloads") } requests := convertRequests(executionRequests) if err := validateRequests(requests); err != nil { diff --git a/eth/catalyst/witness.go b/eth/catalyst/witness.go index 570031d98b..712539c5e3 100644 --- a/eth/catalyst/witness.go +++ b/eth/catalyst/witness.go @@ -152,7 +152,7 @@ func (api *ConsensusAPI) NewPayloadWithWitnessV4(params engine.ExecutableData, v case executionRequests == nil: return invalidStatus, paramsErr("nil executionRequests post-prague") case !api.checkFork(params.Timestamp, forks.Prague): - return invalidStatus, unsupportedForkErr("newPayloadV3 must only be called for cancun payloads") + return invalidStatus, unsupportedForkErr("newPayloadV4 must only be called for prague payloads") } requests := convertRequests(executionRequests) if err := validateRequests(requests); err != nil { From 7c180f851c6110b4f46a12f3d895bd0dd520bdc2 Mon Sep 17 00:00:00 2001 From: nthumann Date: Tue, 1 Jul 2025 08:07:33 +0100 Subject: [PATCH 7/8] internal/ethapi: prealloc map for the txpool api (#32110) use `make(map, len(txpool))` to prealloc the map for the txpool content, to avoid the map growing in the loop. --- internal/ethapi/api.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 8f9442acee..9151249308 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -186,15 +186,15 @@ func NewTxPoolAPI(b Backend) *TxPoolAPI { // Content returns the transactions contained within the transaction pool. func (api *TxPoolAPI) Content() map[string]map[string]map[string]*RPCTransaction { - content := map[string]map[string]map[string]*RPCTransaction{ - "pending": make(map[string]map[string]*RPCTransaction), - "queued": make(map[string]map[string]*RPCTransaction), - } pending, queue := api.b.TxPoolContent() + content := map[string]map[string]map[string]*RPCTransaction{ + "pending": make(map[string]map[string]*RPCTransaction, len(pending)), + "queued": make(map[string]map[string]*RPCTransaction, len(queue)), + } curHeader := api.b.CurrentHeader() // Flatten the pending transactions for account, txs := range pending { - dump := make(map[string]*RPCTransaction) + dump := make(map[string]*RPCTransaction, len(txs)) for _, tx := range txs { dump[fmt.Sprintf("%d", tx.Nonce())] = NewRPCPendingTransaction(tx, curHeader, api.b.ChainConfig()) } @@ -202,7 +202,7 @@ func (api *TxPoolAPI) Content() map[string]map[string]map[string]*RPCTransaction } // Flatten the queued transactions for account, txs := range queue { - dump := make(map[string]*RPCTransaction) + dump := make(map[string]*RPCTransaction, len(txs)) for _, tx := range txs { dump[fmt.Sprintf("%d", tx.Nonce())] = NewRPCPendingTransaction(tx, curHeader, api.b.ChainConfig()) } @@ -246,11 +246,11 @@ func (api *TxPoolAPI) Status() map[string]hexutil.Uint { // Inspect retrieves the content of the transaction pool and flattens it into an // easily inspectable list. func (api *TxPoolAPI) Inspect() map[string]map[string]map[string]string { - content := map[string]map[string]map[string]string{ - "pending": make(map[string]map[string]string), - "queued": make(map[string]map[string]string), - } pending, queue := api.b.TxPoolContent() + content := map[string]map[string]map[string]string{ + "pending": make(map[string]map[string]string, len(pending)), + "queued": make(map[string]map[string]string, len(queue)), + } // Define a formatter to flatten a transaction into a string format := func(tx *types.Transaction) string { @@ -261,7 +261,7 @@ func (api *TxPoolAPI) Inspect() map[string]map[string]map[string]string { } // Flatten the pending transactions for account, txs := range pending { - dump := make(map[string]string) + dump := make(map[string]string, len(txs)) for _, tx := range txs { dump[fmt.Sprintf("%d", tx.Nonce())] = format(tx) } @@ -269,7 +269,7 @@ func (api *TxPoolAPI) Inspect() map[string]map[string]map[string]string { } // Flatten the queued transactions for account, txs := range queue { - dump := make(map[string]string) + dump := make(map[string]string, len(txs)) for _, tx := range txs { dump[fmt.Sprintf("%d", tx.Nonce())] = format(tx) } From f70aaa8399ccee429804eecf3fc4c6fd8d9e6cab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Faruk=20Irmak?= Date: Tue, 1 Jul 2025 10:18:49 +0300 Subject: [PATCH 8/8] ethapi: reduce some of the wasted effort in GetTransactionReceipt (#32021) Towards https://github.com/ethereum/go-ethereum/issues/26974 --------- Co-authored-by: Gary Rong --- core/blockchain.go | 2 +- core/blockchain_reader.go | 49 ++++++++- core/blockchain_test.go | 120 +++++++++++++++++++++-- core/rawdb/accessors_chain.go | 42 ++++++-- core/rawdb/accessors_chain_test.go | 6 ++ core/rawdb/accessors_indexes.go | 107 ++++++++++++++++++-- core/rawdb/accessors_indexes_test.go | 84 +++++++++++++++- core/rawdb/chain_iterator.go | 2 +- core/types/receipt.go | 118 +++++++++++++--------- eth/api_backend.go | 14 ++- eth/tracers/api.go | 4 +- eth/tracers/api_test.go | 4 +- graphql/graphql.go | 2 +- internal/ethapi/api.go | 22 ++--- internal/ethapi/api_test.go | 9 +- internal/ethapi/backend.go | 3 +- internal/ethapi/transaction_args_test.go | 5 +- 17 files changed, 487 insertions(+), 106 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index d8b101f616..2290b6d3cd 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -314,7 +314,7 @@ type BlockChain struct { bodyCache *lru.Cache[common.Hash, *types.Body] bodyRLPCache *lru.Cache[common.Hash, rlp.RawValue] - receiptsCache *lru.Cache[common.Hash, []*types.Receipt] + receiptsCache *lru.Cache[common.Hash, []*types.Receipt] // Receipts cache with all fields derived blockCache *lru.Cache[common.Hash, *types.Block] txLookupLock sync.RWMutex diff --git a/core/blockchain_reader.go b/core/blockchain_reader.go index 0734baab35..7626e9e30d 100644 --- a/core/blockchain_reader.go +++ b/core/blockchain_reader.go @@ -18,9 +18,12 @@ package core import ( "errors" + "fmt" + "math/big" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state/snapshot" @@ -213,6 +216,44 @@ func (bc *BlockChain) GetBlocksFromHash(hash common.Hash, n int) (blocks []*type return } +// GetCanonicalReceipt allows fetching a receipt for a transaction that was +// already looked up on the index. Notably, only receipt in canonical chain +// is visible. +func (bc *BlockChain) GetCanonicalReceipt(tx *types.Transaction, blockHash common.Hash, blockNumber, txIndex uint64) (*types.Receipt, error) { + // The receipt retrieved from the cache contains all previously derived fields + if receipts, ok := bc.receiptsCache.Get(blockHash); ok { + if int(txIndex) >= len(receipts) { + return nil, fmt.Errorf("receipt out of index, length: %d, index: %d", len(receipts), txIndex) + } + return receipts[int(txIndex)], nil + } + header := bc.GetHeader(blockHash, blockNumber) + if header == nil { + return nil, fmt.Errorf("block header is not found, %d, %x", blockNumber, blockHash) + } + var blobGasPrice *big.Int + if header.ExcessBlobGas != nil { + blobGasPrice = eip4844.CalcBlobFee(bc.chainConfig, header) + } + receipt, ctx, err := rawdb.ReadCanonicalRawReceipt(bc.db, blockHash, blockNumber, txIndex) + if err != nil { + return nil, err + } + signer := types.MakeSigner(bc.chainConfig, new(big.Int).SetUint64(blockNumber), header.Time) + receipt.DeriveFields(signer, types.DeriveReceiptContext{ + BlockHash: blockHash, + BlockNumber: blockNumber, + BlockTime: header.Time, + BaseFee: header.BaseFee, + BlobGasPrice: blobGasPrice, + GasUsed: ctx.GasUsed, + LogIndex: ctx.LogIndex, + Tx: tx, + TxIndex: uint(txIndex), + }) + return receipt, nil +} + // GetReceiptsByHash retrieves the receipts for all transactions in a given block. func (bc *BlockChain) GetReceiptsByHash(hash common.Hash) types.Receipts { if receipts, ok := bc.receiptsCache.Get(hash); ok { @@ -277,13 +318,15 @@ func (bc *BlockChain) GetAncestor(hash common.Hash, number, ancestor uint64, max return bc.hc.GetAncestor(hash, number, ancestor, maxNonCanonical) } -// GetTransactionLookup retrieves the lookup along with the transaction +// GetCanonicalTransaction retrieves the lookup along with the transaction // itself associate with the given transaction hash. // // A null will be returned if the transaction is not found. This can be due to // the transaction indexer not being finished. The caller must explicitly check // the indexer progress. -func (bc *BlockChain) GetTransactionLookup(hash common.Hash) (*rawdb.LegacyTxLookupEntry, *types.Transaction) { +// +// Notably, only the transaction in the canonical chain is visible. +func (bc *BlockChain) GetCanonicalTransaction(hash common.Hash) (*rawdb.LegacyTxLookupEntry, *types.Transaction) { bc.txLookupLock.RLock() defer bc.txLookupLock.RUnlock() @@ -291,7 +334,7 @@ func (bc *BlockChain) GetTransactionLookup(hash common.Hash) (*rawdb.LegacyTxLoo if item, exist := bc.txLookupCache.Get(hash); exist { return item.lookup, item.transaction } - tx, blockHash, blockNumber, txIndex := rawdb.ReadTransaction(bc.db, hash) + tx, blockHash, blockNumber, txIndex := rawdb.ReadCanonicalTransaction(bc.db, hash) if tx == nil { return nil, nil } diff --git a/core/blockchain_test.go b/core/blockchain_test.go index f47e922e18..5e768fccdf 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -25,10 +25,12 @@ import ( "math/rand" "os" "path" + "reflect" "sync" "testing" "time" + "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/beacon" @@ -46,6 +48,7 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/trie" "github.com/holiman/uint256" + "github.com/stretchr/testify/assert" ) // So we can deterministically seed different blockchains @@ -1004,29 +1007,47 @@ func testChainTxReorgs(t *testing.T, scheme string) { // removed tx for i, tx := range (types.Transactions{pastDrop, freshDrop}) { - if txn, _, _, _ := rawdb.ReadTransaction(db, tx.Hash()); txn != nil { + if txn, _, _, _ := rawdb.ReadCanonicalTransaction(db, tx.Hash()); txn != nil { t.Errorf("drop %d: tx %v found while shouldn't have been", i, txn) } - if rcpt, _, _, _ := rawdb.ReadReceipt(db, tx.Hash(), blockchain.Config()); rcpt != nil { + if rcpt, _, _, _ := rawdb.ReadCanonicalReceipt(db, tx.Hash(), blockchain.Config()); rcpt != nil { t.Errorf("drop %d: receipt %v found while shouldn't have been", i, rcpt) } } // added tx for i, tx := range (types.Transactions{pastAdd, freshAdd, futureAdd}) { - if txn, _, _, _ := rawdb.ReadTransaction(db, tx.Hash()); txn == nil { + if txn, _, _, _ := rawdb.ReadCanonicalTransaction(db, tx.Hash()); txn == nil { t.Errorf("add %d: expected tx to be found", i) } - if rcpt, _, _, _ := rawdb.ReadReceipt(db, tx.Hash(), blockchain.Config()); rcpt == nil { + if rcpt, _, _, index := rawdb.ReadCanonicalReceipt(db, tx.Hash(), blockchain.Config()); rcpt == nil { t.Errorf("add %d: expected receipt to be found", i) + } else if rawRcpt, ctx, _ := rawdb.ReadCanonicalRawReceipt(db, rcpt.BlockHash, rcpt.BlockNumber.Uint64(), index); rawRcpt == nil { + t.Errorf("add %d: expected raw receipt to be found", i) + } else { + if rcpt.GasUsed != ctx.GasUsed { + t.Errorf("add %d, raw gasUsedSoFar doesn't make sense", i) + } + if len(rcpt.Logs) > 0 && rcpt.Logs[0].Index != ctx.LogIndex { + t.Errorf("add %d, raw startingLogIndex doesn't make sense", i) + } } } // shared tx for i, tx := range (types.Transactions{postponed, swapped}) { - if txn, _, _, _ := rawdb.ReadTransaction(db, tx.Hash()); txn == nil { + if txn, _, _, _ := rawdb.ReadCanonicalTransaction(db, tx.Hash()); txn == nil { t.Errorf("share %d: expected tx to be found", i) } - if rcpt, _, _, _ := rawdb.ReadReceipt(db, tx.Hash(), blockchain.Config()); rcpt == nil { + if rcpt, _, _, index := rawdb.ReadCanonicalReceipt(db, tx.Hash(), blockchain.Config()); rcpt == nil { t.Errorf("share %d: expected receipt to be found", i) + } else if rawRcpt, ctx, _ := rawdb.ReadCanonicalRawReceipt(db, rcpt.BlockHash, rcpt.BlockNumber.Uint64(), index); rawRcpt == nil { + t.Errorf("add %d: expected raw receipt to be found", i) + } else { + if rcpt.GasUsed != ctx.GasUsed { + t.Errorf("add %d, raw gasUsedSoFar doesn't make sense", i) + } + if len(rcpt.Logs) > 0 && rcpt.Logs[0].Index != ctx.LogIndex { + t.Errorf("add %d, raw startingLogIndex doesn't make sense", i) + } } } } @@ -4404,6 +4425,93 @@ func testInsertChainWithCutoff(t *testing.T, cutoff uint64, ancientLimit uint64, if receipts == nil || len(receipts) != 1 { t.Fatalf("Missed block receipts: %d, cutoff: %d", num, cutoffBlock.NumberU64()) } + for indx, receipt := range receipts { + receiptByLookup, err := chain.GetCanonicalReceipt(body.Transactions[indx], receipt.BlockHash, + receipt.BlockNumber.Uint64(), uint64(indx)) + assert.NoError(t, err) + assert.Equal(t, receipt, receiptByLookup) + } + } + } +} + +func TestGetCanonicalReceipt(t *testing.T) { + const chainLength = 64 + + // Configure and generate a sample block chain + var ( + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + address = crypto.PubkeyToAddress(key.PublicKey) + funds = big.NewInt(1000000000000000000) + gspec = &Genesis{ + Config: params.MergedTestChainConfig, + Alloc: types.GenesisAlloc{address: {Balance: funds}}, + BaseFee: big.NewInt(params.InitialBaseFee), + } + signer = types.LatestSigner(gspec.Config) + engine = beacon.New(ethash.NewFaker()) + codeBin = common.FromHex("0x608060405234801561000f575f5ffd5b507f8ae1c8c6e5f91159d0bc1c4b9a47ce45301753843012cbe641e4456bfc73538b33426040516100419291906100ff565b60405180910390a1610139565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6100778261004e565b9050919050565b6100878161006d565b82525050565b5f819050919050565b61009f8161008d565b82525050565b5f82825260208201905092915050565b7f436f6e7374727563746f72207761732063616c6c6564000000000000000000005f82015250565b5f6100e96016836100a5565b91506100f4826100b5565b602082019050919050565b5f6060820190506101125f83018561007e565b61011f6020830184610096565b8181036040830152610130816100dd565b90509392505050565b603e806101455f395ff3fe60806040525f5ffdfea2646970667358221220e8bc3c31e3ac337eab702e8fdfc1c71894f4df1af4221bcde4a2823360f403fb64736f6c634300081e0033") + ) + _, blocks, receipts := GenerateChainWithGenesis(gspec, engine, chainLength, func(i int, block *BlockGen) { + // SPDX-License-Identifier: MIT + // pragma solidity ^0.8.0; + // + // contract ConstructorLogger { + // event ConstructorLog(address sender, uint256 timestamp, string message); + // + // constructor() { + // emit ConstructorLog(msg.sender, block.timestamp, "Constructor was called"); + // } + // } + // + // 608060405234801561000f575f5ffd5b507f8ae1c8c6e5f91159d0bc1c4b9a47ce45301753843012cbe641e4456bfc73538b33426040516100419291906100ff565b60405180910390a1610139565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6100778261004e565b9050919050565b6100878161006d565b82525050565b5f819050919050565b61009f8161008d565b82525050565b5f82825260208201905092915050565b7f436f6e7374727563746f72207761732063616c6c6564000000000000000000005f82015250565b5f6100e96016836100a5565b91506100f4826100b5565b602082019050919050565b5f6060820190506101125f83018561007e565b61011f6020830184610096565b8181036040830152610130816100dd565b90509392505050565b603e806101455f395ff3fe60806040525f5ffdfea2646970667358221220e8bc3c31e3ac337eab702e8fdfc1c71894f4df1af4221bcde4a2823360f403fb64736f6c634300081e0033 + nonce := block.TxNonce(address) + tx, err := types.SignTx(types.NewContractCreation(nonce, big.NewInt(0), 100_000, block.header.BaseFee, codeBin), signer, key) + if err != nil { + panic(err) + } + block.AddTx(tx) + + tx2, err := types.SignTx(types.NewContractCreation(nonce+1, big.NewInt(0), 100_000, block.header.BaseFee, codeBin), signer, key) + if err != nil { + panic(err) + } + block.AddTx(tx2) + + tx3, err := types.SignTx(types.NewContractCreation(nonce+2, big.NewInt(0), 100_000, block.header.BaseFee, codeBin), signer, key) + if err != nil { + panic(err) + } + block.AddTx(tx3) + }) + + db, _ := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{}) + defer db.Close() + options := DefaultConfig().WithStateScheme(rawdb.PathScheme) + chain, _ := NewBlockChain(db, gspec, beacon.New(ethash.NewFaker()), options) + defer chain.Stop() + + chain.InsertReceiptChain(blocks, types.EncodeBlockReceiptLists(receipts), 0) + + for i := 0; i < chainLength; i++ { + block := blocks[i] + blockReceipts := chain.GetReceiptsByHash(block.Hash()) + chain.receiptsCache.Purge() // ugly hack + for txIndex, tx := range block.Body().Transactions { + receipt, err := chain.GetCanonicalReceipt(tx, block.Hash(), block.NumberU64(), uint64(txIndex)) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + if !reflect.DeepEqual(receipts[i][txIndex], receipt) { + want := spew.Sdump(receipts[i][txIndex]) + got := spew.Sdump(receipt) + t.Fatalf("Receipt is not matched, want %s, got: %s", want, got) + } + if !reflect.DeepEqual(blockReceipts[txIndex], receipt) { + want := spew.Sdump(blockReceipts[txIndex]) + got := spew.Sdump(receipt) + t.Fatalf("Receipt is not matched, want %s, got: %s", want, got) + } } } } diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index 2386246caf..4426c6a9e7 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -449,9 +449,10 @@ func ReadBodyRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawValue return data } -// ReadCanonicalBodyRLP retrieves the block body (transactions and uncles) for the canonical -// block at number, in RLP encoding. -func ReadCanonicalBodyRLP(db ethdb.Reader, number uint64) rlp.RawValue { +// ReadCanonicalBodyRLP retrieves the block body (transactions and uncles) for the +// canonical block at number, in RLP encoding. Optionally it takes the block hash +// to avoid looking it up +func ReadCanonicalBodyRLP(db ethdb.Reader, number uint64, hash *common.Hash) rlp.RawValue { var data []byte db.ReadAncients(func(reader ethdb.AncientReaderOp) error { data, _ = reader.Ancient(ChainFreezerBodiesTable, number) @@ -459,10 +460,14 @@ func ReadCanonicalBodyRLP(db ethdb.Reader, number uint64) rlp.RawValue { return nil } // Block is not in ancients, read from leveldb by hash and number. - // Note: ReadCanonicalHash cannot be used here because it also - // calls ReadAncients internally. - hash, _ := db.Get(headerHashKey(number)) - data, _ = db.Get(blockBodyKey(number, common.BytesToHash(hash))) + if hash != nil { + data, _ = db.Get(blockBodyKey(number, *hash)) + } else { + // Note: ReadCanonicalHash cannot be used here because it also + // calls ReadAncients internally. + hashBytes, _ := db.Get(headerHashKey(number)) + data, _ = db.Get(blockBodyKey(number, common.BytesToHash(hashBytes))) + } return nil }) return data @@ -544,6 +549,29 @@ func ReadReceiptsRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawVa return data } +// ReadCanonicalReceiptsRLP retrieves the receipts RLP for the canonical block at +// number, in RLP encoding. Optionally it takes the block hash to avoid looking it up. +func ReadCanonicalReceiptsRLP(db ethdb.Reader, number uint64, hash *common.Hash) rlp.RawValue { + var data []byte + db.ReadAncients(func(reader ethdb.AncientReaderOp) error { + data, _ = reader.Ancient(ChainFreezerReceiptTable, number) + if len(data) > 0 { + return nil + } + // Block is not in ancients, read from leveldb by hash and number. + if hash != nil { + data, _ = db.Get(blockReceiptsKey(number, *hash)) + } else { + // Note: ReadCanonicalHash cannot be used here because it also + // calls ReadAncients internally. + hashBytes, _ := db.Get(headerHashKey(number)) + data, _ = db.Get(blockReceiptsKey(number, common.BytesToHash(hashBytes))) + } + return nil + }) + return data +} + // ReadRawReceipts retrieves all the transaction receipts belonging to a block. // The receipt metadata fields and the Bloom are not guaranteed to be populated, // so they should not be used. Use ReadReceipts instead if the metadata is needed. diff --git a/core/rawdb/accessors_chain_test.go b/core/rawdb/accessors_chain_test.go index 6d46239e27..196f3dac8f 100644 --- a/core/rawdb/accessors_chain_test.go +++ b/core/rawdb/accessors_chain_test.go @@ -441,6 +441,9 @@ func TestAncientStorage(t *testing.T) { if blob := ReadReceiptsRLP(db, hash, number); len(blob) > 0 { t.Fatalf("non existent receipts returned") } + if blob := ReadCanonicalReceiptsRLP(db, number, &hash); len(blob) > 0 { + t.Fatalf("non existent receipts returned") + } // Write and verify the header in the database WriteAncientBlocks(db, []*types.Block{block}, types.EncodeBlockReceiptLists([]types.Receipts{nil})) @@ -454,6 +457,9 @@ func TestAncientStorage(t *testing.T) { if blob := ReadReceiptsRLP(db, hash, number); len(blob) == 0 { t.Fatalf("no receipts returned") } + if blob := ReadCanonicalReceiptsRLP(db, number, &hash); len(blob) == 0 { + t.Fatalf("no receipts returned") + } // Use a fake hash for data retrieval, nothing should be returned. fakeHash := common.BytesToHash([]byte{0x01, 0x02, 0x03}) diff --git a/core/rawdb/accessors_indexes.go b/core/rawdb/accessors_indexes.go index b5491f76ef..f56ff066e9 100644 --- a/core/rawdb/accessors_indexes.go +++ b/core/rawdb/accessors_indexes.go @@ -20,6 +20,7 @@ import ( "bytes" "encoding/binary" "errors" + "fmt" "math/big" "github.com/ethereum/go-ethereum/common" @@ -169,9 +170,10 @@ func findTxInBlockBody(blockbody rlp.RawValue, target common.Hash) (*types.Trans 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) { +// ReadCanonicalTransaction retrieves a specific transaction from the database, along +// with its added positional metadata. Notably, only the transaction in the canonical +// chain is visible. +func ReadCanonicalTransaction(db ethdb.Reader, hash common.Hash) (*types.Transaction, common.Hash, uint64, uint64) { blockNumber := ReadTxLookupEntry(db, hash) if blockNumber == nil { return nil, common.Hash{}, 0, 0 @@ -180,7 +182,7 @@ func ReadTransaction(db ethdb.Reader, hash common.Hash) (*types.Transaction, com if blockHash == (common.Hash{}) { return nil, common.Hash{}, 0, 0 } - bodyRLP := ReadBodyRLP(db, blockHash, *blockNumber) + bodyRLP := ReadCanonicalBodyRLP(db, *blockNumber, &blockHash) if bodyRLP == nil { log.Error("Transaction referenced missing", "number", *blockNumber, "hash", blockHash) return nil, common.Hash{}, 0, 0 @@ -193,9 +195,10 @@ func ReadTransaction(db ethdb.Reader, hash common.Hash) (*types.Transaction, com return tx, blockHash, *blockNumber, txIndex } -// ReadReceipt retrieves a specific transaction receipt from the database, along with -// its added positional metadata. -func ReadReceipt(db ethdb.Reader, hash common.Hash, config *params.ChainConfig) (*types.Receipt, common.Hash, uint64, uint64) { +// ReadCanonicalReceipt retrieves a specific transaction receipt from the database, +// along with its added positional metadata. Notably, only the receipt in the canonical +// chain is visible. +func ReadCanonicalReceipt(db ethdb.Reader, hash common.Hash, config *params.ChainConfig) (*types.Receipt, common.Hash, uint64, uint64) { // Retrieve the context of the receipt based on the transaction hash blockNumber := ReadTxLookupEntry(db, hash) if blockNumber == nil { @@ -220,7 +223,91 @@ func ReadReceipt(db ethdb.Reader, hash common.Hash, config *params.ChainConfig) return nil, common.Hash{}, 0, 0 } -// ReadFilterMapRow retrieves a filter map row at the given mapRowIndex +// extractReceiptFields takes a raw RLP-encoded receipt blob and extracts +// specific fields from it. +func extractReceiptFields(receiptRLP rlp.RawValue) (uint64, uint, error) { + receiptList, _, err := rlp.SplitList(receiptRLP) + if err != nil { + return 0, 0, err + } + // Decode the field: receipt status + // for receipt before the byzantium fork: + // - bytes: post state root + // for receipt after the byzantium fork: + // - bytes: receipt status flag + _, _, rest, err := rlp.Split(receiptList) + if err != nil { + return 0, 0, err + } + // Decode the field: cumulative gas used (type: uint64) + gasUsed, rest, err := rlp.SplitUint64(rest) + if err != nil { + return 0, 0, err + } + // Decode the field: logs (type: rlp list) + logList, _, err := rlp.SplitList(rest) + if err != nil { + return 0, 0, err + } + logCount, err := rlp.CountValues(logList) + if err != nil { + return 0, 0, err + } + return gasUsed, uint(logCount), nil +} + +// RawReceiptContext carries the contextual information that is needed to derive +// a complete receipt from a raw one. +type RawReceiptContext struct { + GasUsed uint64 // Amount of gas used by the associated transaction + LogIndex uint // Starting index of the logs within the block +} + +// ReadCanonicalRawReceipt reads a raw receipt at the specified position. It also +// returns the gas used by the associated transaction and the starting index of +// the logs within the block. The main difference with ReadCanonicalReceipt is +// that the additional positional fields are not directly included in the receipt. +// Notably, only receipts from the canonical chain are visible. +func ReadCanonicalRawReceipt(db ethdb.Reader, blockHash common.Hash, blockNumber, txIndex uint64) (*types.Receipt, RawReceiptContext, error) { + receiptIt, err := rlp.NewListIterator(ReadCanonicalReceiptsRLP(db, blockNumber, &blockHash)) + if err != nil { + return nil, RawReceiptContext{}, err + } + var ( + cumulativeGasUsed uint64 + logIndex uint + ) + for i := uint64(0); i <= txIndex; i++ { + // Unexpected iteration error + if receiptIt.Err() != nil { + return nil, RawReceiptContext{}, receiptIt.Err() + } + // Unexpected end of iteration + if !receiptIt.Next() { + return nil, RawReceiptContext{}, fmt.Errorf("receipt not found, %d, %x, %d", blockNumber, blockHash, txIndex) + } + if i == txIndex { + var stored types.ReceiptForStorage + if err := rlp.DecodeBytes(receiptIt.Value(), &stored); err != nil { + return nil, RawReceiptContext{}, err + } + return (*types.Receipt)(&stored), RawReceiptContext{ + GasUsed: stored.CumulativeGasUsed - cumulativeGasUsed, + LogIndex: logIndex, + }, nil + } else { + gas, logs, err := extractReceiptFields(receiptIt.Value()) + if err != nil { + return nil, RawReceiptContext{}, err + } + cumulativeGasUsed = gas + logIndex += logs + } + } + return nil, RawReceiptContext{}, fmt.Errorf("receipt not found, %d, %x, %d", blockNumber, blockHash, txIndex) +} + +// ReadFilterMapExtRow retrieves a filter map row at the given mapRowIndex // (see filtermaps.mapRowIndex for the storage index encoding). // Note that zero length rows are not stored in the database and therefore all // non-existent entries are interpreted as empty rows and return no error. @@ -247,7 +334,7 @@ func ReadFilterMapExtRow(db ethdb.KeyValueReader, mapRowIndex uint64, bitLength return nil, err } if len(encRow)%byteLength != 0 { - return nil, errors.New("Invalid encoded extended filter row length") + return nil, errors.New("invalid encoded extended filter row length") } row := make([]uint32, len(encRow)/byteLength) var b [4]byte @@ -318,7 +405,7 @@ func ReadFilterMapBaseRows(db ethdb.KeyValueReader, mapRowIndex uint64, rowCount return rows, nil } -// WriteFilterMapRow stores a filter map row at the given mapRowIndex or deletes +// WriteFilterMapExtRow stores a filter map row at the given mapRowIndex or deletes // any existing entry if the row is empty. func WriteFilterMapExtRow(db ethdb.KeyValueWriter, mapRowIndex uint64, row []uint32, bitLength uint) { byteLength := int(bitLength) / 8 diff --git a/core/rawdb/accessors_indexes_test.go b/core/rawdb/accessors_indexes_test.go index b768deed9b..a812fefeaa 100644 --- a/core/rawdb/accessors_indexes_test.go +++ b/core/rawdb/accessors_indexes_test.go @@ -17,6 +17,7 @@ package rawdb import ( + "errors" "math/big" "testing" @@ -88,7 +89,7 @@ func TestLookupStorage(t *testing.T) { // Check that no transactions entries are in a pristine database for i, tx := range txs { - if txn, _, _, _ := ReadTransaction(db, tx.Hash()); txn != nil { + if txn, _, _, _ := ReadCanonicalTransaction(db, tx.Hash()); txn != nil { t.Fatalf("tx #%d [%x]: non existent transaction returned: %v", i, tx.Hash(), txn) } } @@ -98,7 +99,7 @@ func TestLookupStorage(t *testing.T) { tc.writeTxLookupEntriesByBlock(db, block) for i, tx := range txs { - if txn, hash, number, index := ReadTransaction(db, tx.Hash()); txn == nil { + if txn, hash, number, index := ReadCanonicalTransaction(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) { @@ -112,7 +113,7 @@ func TestLookupStorage(t *testing.T) { // Delete the transactions and check purge for i, tx := range txs { DeleteTxLookupEntry(db, tx.Hash()) - if txn, _, _, _ := ReadTransaction(db, tx.Hash()); txn != nil { + if txn, _, _, _ := ReadCanonicalTransaction(db, tx.Hash()); txn != nil { t.Fatalf("tx #%d [%x]: deleted transaction returned: %v", i, tx.Hash(), txn) } } @@ -219,3 +220,80 @@ func TestFindTxInBlockBody(t *testing.T) { } } } + +func TestExtractReceiptFields(t *testing.T) { + receiptWithPostState := types.ReceiptForStorage(types.Receipt{ + Type: types.LegacyTxType, + PostState: []byte{0x1, 0x2, 0x3}, + CumulativeGasUsed: 100, + }) + receiptWithPostStateBlob, _ := rlp.EncodeToBytes(&receiptWithPostState) + + receiptNoLogs := types.ReceiptForStorage(types.Receipt{ + Type: types.LegacyTxType, + Status: types.ReceiptStatusSuccessful, + CumulativeGasUsed: 100, + }) + receiptNoLogBlob, _ := rlp.EncodeToBytes(&receiptNoLogs) + + receiptWithLogs := types.ReceiptForStorage(types.Receipt{ + Type: types.LegacyTxType, + Status: types.ReceiptStatusSuccessful, + CumulativeGasUsed: 100, + Logs: []*types.Log{ + { + Address: common.BytesToAddress([]byte{0x1}), + Topics: []common.Hash{ + common.BytesToHash([]byte{0x1}), + }, + Data: []byte{0x1}, + }, + { + Address: common.BytesToAddress([]byte{0x2}), + Topics: []common.Hash{ + common.BytesToHash([]byte{0x2}), + }, + Data: []byte{0x2}, + }, + }, + }) + receiptWithLogBlob, _ := rlp.EncodeToBytes(&receiptWithLogs) + + invalidReceipt := types.ReceiptForStorage(types.Receipt{ + Type: types.LegacyTxType, + Status: types.ReceiptStatusSuccessful, + CumulativeGasUsed: 100, + }) + invalidReceiptBlob, _ := rlp.EncodeToBytes(&invalidReceipt) + invalidReceiptBlob[len(invalidReceiptBlob)-1] = 0xf + + var cases = []struct { + logs rlp.RawValue + expErr error + expGasUsed uint64 + expLogs uint + }{ + {receiptWithPostStateBlob, nil, 100, 0}, + {receiptNoLogBlob, nil, 100, 0}, + {receiptWithLogBlob, nil, 100, 2}, + {invalidReceiptBlob, rlp.ErrExpectedList, 100, 0}, + } + for _, c := range cases { + gasUsed, logs, err := extractReceiptFields(c.logs) + if c.expErr != nil { + if !errors.Is(err, c.expErr) { + t.Fatalf("Unexpected error, want: %v, got: %v", c.expErr, err) + } + } else { + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + if gasUsed != c.expGasUsed { + t.Fatalf("Unexpected gas used, want %d, got %d", c.expGasUsed, gasUsed) + } + if logs != c.expLogs { + t.Fatalf("Unexpected logs, want %d, got %d", c.expLogs, logs) + } + } + } +} diff --git a/core/rawdb/chain_iterator.go b/core/rawdb/chain_iterator.go index ecbc44e1f1..e7c89ca8d9 100644 --- a/core/rawdb/chain_iterator.go +++ b/core/rawdb/chain_iterator.go @@ -118,7 +118,7 @@ func iterateTransactions(db ethdb.Database, from uint64, to uint64, reverse bool } defer close(rlpCh) for n != end { - data := ReadCanonicalBodyRLP(db, n) + data := ReadCanonicalBodyRLP(db, n, nil) // Feed the block to the aggregator, or abort on interrupt select { case rlpCh <- &numberRlp{n, data}: diff --git a/core/types/receipt.go b/core/types/receipt.go index 3227d77986..5b6669f274 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -258,6 +258,62 @@ func (r *Receipt) Size() common.StorageSize { return size } +// DeriveReceiptContext holds the contextual information needed to derive a receipt +type DeriveReceiptContext struct { + BlockHash common.Hash + BlockNumber uint64 + BlockTime uint64 + BaseFee *big.Int + BlobGasPrice *big.Int + GasUsed uint64 + LogIndex uint // Number of logs in the block until this receipt + Tx *Transaction + TxIndex uint +} + +// DeriveFields fills the receipt with computed fields based on consensus +// data and contextual infos like containing block and transactions. +func (r *Receipt) DeriveFields(signer Signer, context DeriveReceiptContext) { + // The transaction type and hash can be retrieved from the transaction itself + r.Type = context.Tx.Type() + r.TxHash = context.Tx.Hash() + r.GasUsed = context.GasUsed + r.EffectiveGasPrice = context.Tx.inner.effectiveGasPrice(new(big.Int), context.BaseFee) + + // EIP-4844 blob transaction fields + if context.Tx.Type() == BlobTxType { + r.BlobGasUsed = context.Tx.BlobGas() + r.BlobGasPrice = context.BlobGasPrice + } + + // Block location fields + r.BlockHash = context.BlockHash + r.BlockNumber = new(big.Int).SetUint64(context.BlockNumber) + r.TransactionIndex = context.TxIndex + + // The contract address can be derived from the transaction itself + if context.Tx.To() == nil { + // Deriving the signer is expensive, only do if it's actually needed + from, _ := Sender(signer, context.Tx) + r.ContractAddress = crypto.CreateAddress(from, context.Tx.Nonce()) + } else { + r.ContractAddress = common.Address{} + } + // The derived log fields can simply be set from the block and transaction + logIndex := context.LogIndex + for j := 0; j < len(r.Logs); j++ { + r.Logs[j].BlockNumber = context.BlockNumber + r.Logs[j].BlockHash = context.BlockHash + r.Logs[j].BlockTimestamp = context.BlockTime + r.Logs[j].TxHash = r.TxHash + r.Logs[j].TxIndex = context.TxIndex + r.Logs[j].Index = logIndex + logIndex++ + } + // Also derive the Bloom if not derived yet + r.Bloom = CreateBloom(r) +} + // ReceiptForStorage is a wrapper around a Receipt with RLP serialization // that omits the Bloom field. The Bloom field is recomputed by DeriveFields. type ReceiptForStorage Receipt @@ -323,58 +379,30 @@ func (rs Receipts) EncodeIndex(i int, w *bytes.Buffer) { // DeriveFields fills the receipts with their computed fields based on consensus // data and contextual infos like containing block and transactions. -func (rs Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, number uint64, time uint64, baseFee *big.Int, blobGasPrice *big.Int, txs []*Transaction) error { - signer := MakeSigner(config, new(big.Int).SetUint64(number), time) +func (rs Receipts) DeriveFields(config *params.ChainConfig, blockHash common.Hash, blockNumber uint64, blockTime uint64, baseFee *big.Int, blobGasPrice *big.Int, txs []*Transaction) error { + signer := MakeSigner(config, new(big.Int).SetUint64(blockNumber), blockTime) logIndex := uint(0) if len(txs) != len(rs) { return errors.New("transaction and receipt count mismatch") } for i := 0; i < len(rs); i++ { - // The transaction type and hash can be retrieved from the transaction itself - rs[i].Type = txs[i].Type() - rs[i].TxHash = txs[i].Hash() - rs[i].EffectiveGasPrice = txs[i].inner.effectiveGasPrice(new(big.Int), baseFee) - - // EIP-4844 blob transaction fields - if txs[i].Type() == BlobTxType { - rs[i].BlobGasUsed = txs[i].BlobGas() - rs[i].BlobGasPrice = blobGasPrice + var cumulativeGasUsed uint64 + if i > 0 { + cumulativeGasUsed = rs[i-1].CumulativeGasUsed } - - // block location fields - rs[i].BlockHash = hash - rs[i].BlockNumber = new(big.Int).SetUint64(number) - rs[i].TransactionIndex = uint(i) - - // The contract address can be derived from the transaction itself - if txs[i].To() == nil { - // Deriving the signer is expensive, only do if it's actually needed - from, _ := Sender(signer, txs[i]) - rs[i].ContractAddress = crypto.CreateAddress(from, txs[i].Nonce()) - } else { - rs[i].ContractAddress = common.Address{} - } - - // The used gas can be calculated based on previous r - if i == 0 { - rs[i].GasUsed = rs[i].CumulativeGasUsed - } else { - rs[i].GasUsed = rs[i].CumulativeGasUsed - rs[i-1].CumulativeGasUsed - } - - // The derived log fields can simply be set from the block and transaction - for j := 0; j < len(rs[i].Logs); j++ { - rs[i].Logs[j].BlockNumber = number - rs[i].Logs[j].BlockHash = hash - rs[i].Logs[j].BlockTimestamp = time - rs[i].Logs[j].TxHash = rs[i].TxHash - rs[i].Logs[j].TxIndex = uint(i) - rs[i].Logs[j].Index = logIndex - logIndex++ - } - // also derive the Bloom if not derived yet - rs[i].Bloom = CreateBloom(rs[i]) + rs[i].DeriveFields(signer, DeriveReceiptContext{ + BlockHash: blockHash, + BlockNumber: blockNumber, + BlockTime: blockTime, + BaseFee: baseFee, + BlobGasPrice: blobGasPrice, + GasUsed: rs[i].CumulativeGasUsed - cumulativeGasUsed, + LogIndex: logIndex, + Tx: txs[i], + TxIndex: uint(i), + }) + logIndex += uint(len(rs[i].Logs)) } return nil } diff --git a/eth/api_backend.go b/eth/api_backend.go index bd90f611f1..3ae73e78af 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -282,6 +282,10 @@ func (b *EthAPIBackend) GetReceipts(ctx context.Context, hash common.Hash) (type return b.eth.blockchain.GetReceiptsByHash(hash), nil } +func (b *EthAPIBackend) GetCanonicalReceipt(tx *types.Transaction, blockHash common.Hash, blockNumber, blockIndex uint64) (*types.Receipt, error) { + return b.eth.blockchain.GetCanonicalReceipt(tx, blockHash, blockNumber, blockIndex) +} + func (b *EthAPIBackend) GetLogs(ctx context.Context, hash common.Hash, number uint64) ([][]*types.Log, error) { return rawdb.ReadLogs(b.eth.chainDb, hash, number), nil } @@ -354,14 +358,16 @@ func (b *EthAPIBackend) GetPoolTransaction(hash common.Hash) *types.Transaction return b.eth.txPool.Get(hash) } -// GetTransaction retrieves the lookup along with the transaction itself associate -// with the given transaction hash. +// GetCanonicalTransaction retrieves the lookup along with the transaction itself +// associate with the given transaction hash. // // A null will be returned if the transaction is not found. The transaction is not // existent from the node's perspective. This can be due to the transaction indexer // not being finished. The caller must explicitly check the indexer progress. -func (b *EthAPIBackend) GetTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) { - lookup, tx := b.eth.blockchain.GetTransactionLookup(txHash) +// +// Notably, only the transaction in the canonical chain is visible. +func (b *EthAPIBackend) GetCanonicalTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) { + lookup, tx := b.eth.blockchain.GetCanonicalTransaction(txHash) if lookup == nil || tx == nil { return false, nil, common.Hash{}, 0, 0 } diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 6b01976084..6962023343 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -82,7 +82,7 @@ type Backend interface { HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) - GetTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) + GetCanonicalTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) TxIndexDone() bool RPCGasCap() uint64 ChainConfig() *params.ChainConfig @@ -863,7 +863,7 @@ func containsTx(block *types.Block, hash common.Hash) bool { // TraceTransaction returns the structured logs created during the execution of EVM // and returns them as a JSON object. func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *TraceConfig) (interface{}, error) { - found, _, blockHash, blockNumber, index := api.backend.GetTransaction(hash) + found, _, blockHash, blockNumber, index := api.backend.GetCanonicalTransaction(hash) if !found { // Warn in case tx indexer is not done. if !api.backend.TxIndexDone() { diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index 1826c78582..076e6fd8d4 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -117,8 +117,8 @@ func (b *testBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) return b.chain.GetBlockByNumber(uint64(number)), nil } -func (b *testBackend) GetTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) { - tx, hash, blockNumber, index := rawdb.ReadTransaction(b.chaindb, txHash) +func (b *testBackend) GetCanonicalTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) { + tx, hash, blockNumber, index := rawdb.ReadCanonicalTransaction(b.chaindb, txHash) return tx != nil, tx, hash, blockNumber, index } diff --git a/graphql/graphql.go b/graphql/graphql.go index 6738cd913b..0b2a77a3c4 100644 --- a/graphql/graphql.go +++ b/graphql/graphql.go @@ -229,7 +229,7 @@ func (t *Transaction) resolve(ctx context.Context) (*types.Transaction, *Block) return t.tx, t.block } // Try to return an already finalized transaction - found, tx, blockHash, _, index := t.r.backend.GetTransaction(t.hash) + found, tx, blockHash, _, index := t.r.backend.GetCanonicalTransaction(t.hash) if found { t.tx = tx blockNrOrHash := rpc.BlockNumberOrHashWithHash(blockHash, false) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 9151249308..21c35b7665 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1334,7 +1334,7 @@ func (api *TransactionAPI) GetTransactionCount(ctx context.Context, address comm // GetTransactionByHash returns the transaction for the given hash func (api *TransactionAPI) GetTransactionByHash(ctx context.Context, hash common.Hash) (*RPCTransaction, error) { // Try to return an already finalized transaction - found, tx, blockHash, blockNumber, index := api.b.GetTransaction(hash) + found, tx, blockHash, blockNumber, index := api.b.GetCanonicalTransaction(hash) if !found { // No finalized transaction, try to retrieve it from the pool if tx := api.b.GetPoolTransaction(hash); tx != nil { @@ -1357,7 +1357,7 @@ func (api *TransactionAPI) GetTransactionByHash(ctx context.Context, hash common // GetRawTransactionByHash returns the bytes of the transaction for the given hash. func (api *TransactionAPI) GetRawTransactionByHash(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) { // Retrieve a finalized transaction, or a pooled otherwise - found, tx, _, _, _ := api.b.GetTransaction(hash) + found, tx, _, _, _ := api.b.GetCanonicalTransaction(hash) if !found { if tx = api.b.GetPoolTransaction(hash); tx != nil { return tx.MarshalBinary() @@ -1374,7 +1374,7 @@ func (api *TransactionAPI) GetRawTransactionByHash(ctx context.Context, hash com // GetTransactionReceipt returns the transaction receipt for the given transaction hash. func (api *TransactionAPI) GetTransactionReceipt(ctx context.Context, hash common.Hash) (map[string]interface{}, error) { - found, tx, blockHash, blockNumber, index := api.b.GetTransaction(hash) + found, tx, blockHash, blockNumber, index := api.b.GetCanonicalTransaction(hash) if !found { // Make sure indexer is done. if !api.b.TxIndexDone() { @@ -1383,22 +1383,12 @@ func (api *TransactionAPI) GetTransactionReceipt(ctx context.Context, hash commo // No such tx. return nil, nil } - header, err := api.b.HeaderByHash(ctx, blockHash) + receipt, err := api.b.GetCanonicalReceipt(tx, blockHash, blockNumber, index) if err != nil { return nil, err } - receipts, err := api.b.GetReceipts(ctx, blockHash) - if err != nil { - return nil, err - } - if uint64(len(receipts)) <= index { - return nil, nil - } - receipt := receipts[index] - // Derive the sender. - signer := types.MakeSigner(api.b.ChainConfig(), header.Number, header.Time) - return marshalReceipt(receipt, blockHash, blockNumber, signer, tx, int(index)), nil + return marshalReceipt(receipt, blockHash, blockNumber, api.signer, tx, int(index)), nil } // marshalReceipt marshals a transaction receipt into a JSON object. @@ -1781,7 +1771,7 @@ func (api *DebugAPI) GetRawReceipts(ctx context.Context, blockNrOrHash rpc.Block // GetRawTransaction returns the bytes of the transaction for the given hash. func (api *DebugAPI) GetRawTransaction(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) { // Retrieve a finalized transaction, or a pooled otherwise - found, tx, _, _, _ := api.b.GetTransaction(hash) + found, tx, _, _, _ := api.b.GetCanonicalTransaction(hash) if !found { if tx = api.b.GetPoolTransaction(hash); tx != nil { return tx.MarshalBinary() diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 4d1834d757..1c9be836fc 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -586,9 +586,12 @@ func (b testBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) even func (b testBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error { panic("implement me") } -func (b testBackend) GetTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) { - tx, blockHash, blockNumber, index := rawdb.ReadTransaction(b.db, txHash) - return true, tx, blockHash, blockNumber, index +func (b testBackend) GetCanonicalTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) { + tx, blockHash, blockNumber, index := rawdb.ReadCanonicalTransaction(b.db, txHash) + return tx != nil, tx, blockHash, blockNumber, index +} +func (b testBackend) GetCanonicalReceipt(tx *types.Transaction, blockHash common.Hash, blockNumber, blockIndex uint64) (*types.Receipt, error) { + return b.chain.GetCanonicalReceipt(tx, blockHash, blockNumber, blockIndex) } func (b testBackend) TxIndexDone() bool { return true diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index 49c3a37560..f709a1fcdc 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -68,13 +68,14 @@ type Backend interface { StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) Pending() (*types.Block, types.Receipts, *state.StateDB) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) + GetCanonicalReceipt(tx *types.Transaction, blockHash common.Hash, blockNumber, blockIndex uint64) (*types.Receipt, error) GetEVM(ctx context.Context, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) *vm.EVM SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription // Transaction pool API SendTx(ctx context.Context, signedTx *types.Transaction) error - GetTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) + GetCanonicalTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) TxIndexDone() bool GetPoolTransactions() (types.Transactions, error) GetPoolTransaction(txHash common.Hash) *types.Transaction diff --git a/internal/ethapi/transaction_args_test.go b/internal/ethapi/transaction_args_test.go index 9b86e452a5..30791f32b5 100644 --- a/internal/ethapi/transaction_args_test.go +++ b/internal/ethapi/transaction_args_test.go @@ -369,6 +369,9 @@ func (b *backendMock) Pending() (*types.Block, types.Receipts, *state.StateDB) { func (b *backendMock) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) { return nil, nil } +func (b *backendMock) GetCanonicalReceipt(tx *types.Transaction, blockHash common.Hash, blockNumber, blockIndex uint64) (*types.Receipt, error) { + return nil, nil +} func (b *backendMock) GetLogs(ctx context.Context, blockHash common.Hash, number uint64) ([][]*types.Log, error) { return nil, nil } @@ -380,7 +383,7 @@ func (b *backendMock) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) eve return nil } func (b *backendMock) SendTx(ctx context.Context, signedTx *types.Transaction) error { return nil } -func (b *backendMock) GetTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) { +func (b *backendMock) GetCanonicalTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) { return false, nil, [32]byte{}, 0, 0 } func (b *backendMock) TxIndexDone() bool { return true }