From 58c792a1f914fa4f3c137e05502859ae4abef213 Mon Sep 17 00:00:00 2001 From: lupin012 <58134934+lupin012@users.noreply.github.com.> Date: Fri, 14 Aug 2026 11:32:10 +0200 Subject: [PATCH 1/2] resolve eth_getLogs and trace_filter tags on the committed view --- rpc/jsonrpc/debug_api.go | 3 + rpc/jsonrpc/eth_receipts.go | 6 +- rpc/jsonrpc/overlay_race_test.go | 114 ++++++++++++++++++++++++++++++- rpc/jsonrpc/trace_filtering.go | 14 +++- 4 files changed, 131 insertions(+), 6 deletions(-) diff --git a/rpc/jsonrpc/debug_api.go b/rpc/jsonrpc/debug_api.go index 783c53e2cd1..54e068f7676 100644 --- a/rpc/jsonrpc/debug_api.go +++ b/rpc/jsonrpc/debug_api.go @@ -530,6 +530,9 @@ func (api *DebugAPIImpl) GetModifiedAccountsByHash(ctx context.Context, startHas if err != nil { return nil, fmt.Errorf("start block %x not found", startHash) } + if startNum > latestBlock { + return nil, fmt.Errorf("start block (%d) is later than the latest block (%d)", startNum, latestBlock) + } if endHash == nil { // Single param: cover exactly block startNum. diff --git a/rpc/jsonrpc/eth_receipts.go b/rpc/jsonrpc/eth_receipts.go index 91e995147e6..00bb579ddaf 100644 --- a/rpc/jsonrpc/eth_receipts.go +++ b/rpc/jsonrpc/eth_receipts.go @@ -167,7 +167,9 @@ func (api *BaseAPI) resolveLogsRange(ctx context.Context, tx kv.Tx, crit filters begin = uint64(fromBlock) } else { blockNum := rpc.BlockNumber(fromBlock) - begin, _, _, err = rpchelper.GetBlockNumber(ctx, rpc.BlockNumberOrHashWithNumber(blockNum), tx, api._blockReader, api.filters) + // nil filters: tags must resolve on the same committed view as the + // baseline above and the log scan, not on the block overlay. + begin, _, _, err = rpchelper.GetBlockNumber(ctx, rpc.BlockNumberOrHashWithNumber(blockNum), tx, api._blockReader, nil) if err != nil { return 0, 0, err } @@ -184,7 +186,7 @@ func (api *BaseAPI) resolveLogsRange(ctx context.Context, tx kv.Tx, crit filters end = uint64(toBlock) } else { blockNum := rpc.BlockNumber(toBlock) - end, _, _, err = rpchelper.GetBlockNumber(ctx, rpc.BlockNumberOrHashWithNumber(blockNum), tx, api._blockReader, api.filters) + end, _, _, err = rpchelper.GetBlockNumber(ctx, rpc.BlockNumberOrHashWithNumber(blockNum), tx, api._blockReader, nil) if err != nil { return 0, 0, err } diff --git a/rpc/jsonrpc/overlay_race_test.go b/rpc/jsonrpc/overlay_race_test.go index 1ec6fd9de58..e2f6ab4e867 100644 --- a/rpc/jsonrpc/overlay_race_test.go +++ b/rpc/jsonrpc/overlay_race_test.go @@ -19,16 +19,19 @@ package jsonrpc import ( "bytes" "context" + "math/big" "strconv" "testing" "github.com/holiman/uint256" "github.com/jinzhu/copier" + jsoniter "github.com/json-iterator/go" "github.com/stretchr/testify/require" "google.golang.org/grpc" "github.com/erigontech/erigon/common" "github.com/erigontech/erigon/common/hexutil" + "github.com/erigontech/erigon/db/kv" "github.com/erigontech/erigon/db/kv/kvcache" "github.com/erigontech/erigon/db/rawdb" "github.com/erigontech/erigon/db/state/execctx" @@ -41,6 +44,8 @@ import ( "github.com/erigontech/erigon/node/gointerfaces/txpoolproto" "github.com/erigontech/erigon/node/shards" "github.com/erigontech/erigon/rpc" + "github.com/erigontech/erigon/rpc/filters" + "github.com/erigontech/erigon/rpc/jsonstream" "github.com/erigontech/erigon/rpc/rpccfg" "github.com/erigontech/erigon/rpc/rpchelper" ) @@ -105,9 +110,9 @@ func newOverlayAheadTestAPI(t *testing.T) (base *BaseAPI, m *execmoduletester.Ex events := shards.NewEvents() events.PublishOverlay(doms) - filters := rpchelper.New(ctx, rpchelper.DefaultFiltersConfig, nil, nil, nil, func() {}, m.Log, events) + ff := rpchelper.New(ctx, rpchelper.DefaultFiltersConfig, nil, nil, nil, func() {}, m.Log, events) stateCache := kvcache.New(kvcache.DefaultCoherentConfig) - base = newBaseApiWithFiltersForTest(filters, stateCache, m) + base = newBaseApiWithFiltersForTest(ff, stateCache, m) return base, m, overlayHeader } @@ -234,6 +239,111 @@ func TestTxPoolContent_UsesOverlayHead(t *testing.T) { "pending tx gas price must be derived from the overlay head's base fee, not the stale MDBX head") } +// TestGetLogs_UsesCommittedFromTag pins that eth_getLogs resolves a "latest" +// fromBlock on the committed view: with the overlay head published ahead of +// MDBX, the tag must not resolve past the executed head and fail the request. +func TestGetLogs_UsesCommittedFromTag(t *testing.T) { + t.Parallel() + base, m, _ := newOverlayAheadTestAPI(t) + api := newEthApiForTest(base, m.DB, nil, nil) + + _, err := api.GetLogs(m.Ctx, filters.FilterCriteria{FromBlock: big.NewInt(int64(rpc.LatestBlockNumber))}) + require.NoError(t, err) +} + +// TestGetLogs_UsesCommittedToTag is the toBlock counterpart of +// TestGetLogs_UsesCommittedFromTag. +func TestGetLogs_UsesCommittedToTag(t *testing.T) { + t.Parallel() + base, m, _ := newOverlayAheadTestAPI(t) + api := newEthApiForTest(base, m.DB, nil, nil) + + _, err := api.GetLogs(m.Ctx, filters.FilterCriteria{ + FromBlock: big.NewInt(1), + ToBlock: big.NewInt(int64(rpc.LatestBlockNumber)), + }) + require.NoError(t, err) +} + +// TestTraceFilter_UsesCommittedFromTag pins that trace_filter resolves a +// "latest" fromBlock on the committed view: with the overlay head published +// ahead of MDBX, the tag must not resolve past a numeric toBlock at the +// executed head. +func TestTraceFilter_UsesCommittedFromTag(t *testing.T) { + t.Parallel() + base, m, _ := newOverlayAheadTestAPI(t) + api := NewTraceAPI(base, m.DB, &rpccfg.TraceApiConfig{}) + + s := jsoniter.ConfigDefault.BorrowStream(nil) + defer jsoniter.ConfigDefault.ReturnStream(s) + stream := jsonstream.Wrap(s) + + from := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) + to := rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(overlayRaceChainSize)) + err := api.Filter(m.Ctx, TraceFilterRequest{FromBlock: &from, ToBlock: &to}, new(bool), nil, stream) + require.NoError(t, err) +} + +// newHeaderAheadTester commits overlayRaceChainSize executed blocks, then +// writes a canonical header one past execution progress directly to the DB. +// This reproduces the window where the headers stage is ahead of execution, +// so a block number resolves while its data is not yet available. +func newHeaderAheadTester(t *testing.T) (m *execmoduletester.ExecModuleTester, aheadHash common.Hash) { + t.Helper() + m = execmoduletester.New(t) + c, err := m.GenerateChain(overlayRaceChainSize, func(i int, gen *blockgen.BlockGen) { + gen.SetCoinbase(common.Address{1}) + }) + require.NoError(t, err) + require.NoError(t, m.InsertChain(c)) + + aheadNumber := uint64(overlayRaceChainSize) + 1 + header := &types.Header{ + ParentHash: c.TopBlock.Hash(), + Number: *uint256.NewInt(aheadNumber), + Difficulty: *uint256.NewInt(0), + Time: c.TopBlock.Time() + 10, + GasLimit: 30_000_000, + } + aheadHash = header.Hash() + require.NoError(t, m.DB.Update(m.Ctx, func(tx kv.RwTx) error { + if err := rawdb.WriteHeader(tx, header); err != nil { + return err + } + return rawdb.WriteCanonicalHash(tx, aheadHash, aheadNumber) + })) + return m, aheadHash +} + +// TestTraceFilter_FutureToBlockErrors pins that an explicit toBlock past the +// executed head errors instead of silently clamping the scan to the last +// available txnum, which would make an omitted head block look empty. +func TestTraceFilter_FutureToBlockErrors(t *testing.T) { + t.Parallel() + m, _ := newHeaderAheadTester(t) + api := newTraceApiForTest(m) + + s := jsoniter.ConfigDefault.BorrowStream(nil) + defer jsoniter.ConfigDefault.ReturnStream(s) + stream := jsonstream.Wrap(s) + + to := rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(overlayRaceChainSize + 1)) + err := api.Filter(m.Ctx, TraceFilterRequest{ToBlock: &to}, new(bool), nil, stream) + require.ErrorContains(t, err, "not executed") +} + +// TestGetModifiedAccountsByHash_FutureStartBlockErrors pins that ByHash rejects +// a not-yet-executed start block like its ByNumber twin, instead of returning +// a silent result from a clamped txnum range. +func TestGetModifiedAccountsByHash_FutureStartBlockErrors(t *testing.T) { + t.Parallel() + m, aheadHash := newHeaderAheadTester(t) + api := newDebugApiForTest(m) + + _, err := api.GetModifiedAccountsByHash(m.Ctx, aheadHash, nil) + require.ErrorContains(t, err, "later than the latest block") +} + // TestTxPoolContentFrom_UsesOverlayHead pins that txpool_contentFrom reads the // current header through the block overlay, matching TestTxPoolContent_UsesOverlayHead. func TestTxPoolContentFrom_UsesOverlayHead(t *testing.T) { diff --git a/rpc/jsonrpc/trace_filtering.go b/rpc/jsonrpc/trace_filtering.go index 49e9cd286db..696ed41668c 100644 --- a/rpc/jsonrpc/trace_filtering.go +++ b/rpc/jsonrpc/trace_filtering.go @@ -325,7 +325,9 @@ func (api *TraceAPIImpl) Filter(ctx context.Context, req TraceFilterRequest, gas if req.FromBlock == nil { fromBlock = 0 } else { - fromBlock, _, _, err = rpchelper.GetBlockNumber(ctx, *req.FromBlock, dbtx, api._blockReader, api.filters) + // nil filters: tags must resolve on the same committed view as the + // txnum-index scan below, not on the block overlay. + fromBlock, _, _, err = rpchelper.GetBlockNumber(ctx, *req.FromBlock, dbtx, api._blockReader, nil) if err != nil { if errors.As(err, &rpc.BlockNotFoundErr{}) { stream.WriteEmptyArray() @@ -342,7 +344,7 @@ func (api *TraceAPIImpl) Filter(ctx context.Context, req TraceFilterRequest, gas } toBlock = *headNumber } else { - toBlock, _, _, err = rpchelper.GetBlockNumber(ctx, *req.ToBlock, dbtx, api._blockReader, api.filters) + toBlock, _, _, err = rpchelper.GetBlockNumber(ctx, *req.ToBlock, dbtx, api._blockReader, nil) if err != nil { if errors.As(err, &rpc.BlockNotFoundErr{}) { stream.WriteEmptyArray() @@ -355,6 +357,14 @@ func (api *TraceAPIImpl) Filter(ctx context.Context, req TraceFilterRequest, gas return errors.New("invalid parameters: fromBlock cannot be greater than toBlock") } + // The txnum index silently clamps a missing block to the last available + // txnum, so a not-yet-executed toBlock would be omitted without a trace. + if req.ToBlock != nil { + if err := rpchelper.CheckBlockExecuted(dbtx, toBlock); err != nil { + return err + } + } + // if we've pruned this history away for this block then just return early // to save any red herring errors From 49cc284de6dea73a60bfd894ef1ffc4e681ca4b2 Mon Sep 17 00:00:00 2001 From: lupin012 <58134934+lupin012@users.noreply.github.com.> Date: Sat, 15 Aug 2026 18:51:33 +0200 Subject: [PATCH 2/2] rpc/jsonrpc: reduce getLogsV3 cognitive complexity Sonar go:S3776 flagged getLogsV3 at 64, above the 60 limit. Extract the maxResults-capped log append, which had three near-identical copies in the loop, into appendErigonLogs, reusing the existing types.Logs.ToErigonLogs conversion. Extract the bor state sync event lookup into borStateSyncLogs, mirroring the sibling borReceiptForBlock. Flatten the bor branch with an early continue and drop the unreachable header re-fetch: the first Next() of MapTxNum2BlockNumIter always reports blockNumChanged, so header is non-nil past that block. In the test package, share the overlay-race chain setup between the two testers and replace the unused mockBridgeReader with a configurable one. Cognitive complexity of getLogsV3 goes from 78 to 44 by gocognit. --- rpc/jsonrpc/eth_api_test.go | 13 ---- rpc/jsonrpc/eth_receipts.go | 95 ++++++++++--------------- rpc/jsonrpc/eth_receipts_test.go | 118 +++++++++++++++++++++++++++++++ rpc/jsonrpc/overlay_race_test.go | 22 +++--- rpc/jsonrpc/trace_filtering.go | 3 +- 5 files changed, 168 insertions(+), 83 deletions(-) create mode 100644 rpc/jsonrpc/eth_receipts_test.go diff --git a/rpc/jsonrpc/eth_api_test.go b/rpc/jsonrpc/eth_api_test.go index bc692f0b6a1..46a699c7dd0 100644 --- a/rpc/jsonrpc/eth_api_test.go +++ b/rpc/jsonrpc/eth_api_test.go @@ -34,7 +34,6 @@ import ( "github.com/erigontech/erigon/db/kv/kvcache" "github.com/erigontech/erigon/execution/execmodule/execmoduletester" "github.com/erigontech/erigon/execution/tests/blockgen" - "github.com/erigontech/erigon/execution/types" "github.com/erigontech/erigon/node/ethconfig" "github.com/erigontech/erigon/node/gointerfaces/txpoolproto" "github.com/erigontech/erigon/rpc" @@ -310,18 +309,6 @@ func TestCall_ByBlockHash_WithRequireCanonicalTrue_NonCanonicalBlock(t *testing. } } -var _ bridgeReader = mockBridgeReader{} - -type mockBridgeReader struct{} - -func (m mockBridgeReader) Events(context.Context, common.Hash, uint64) ([]*types.Message, error) { - panic("mock") -} - -func (m mockBridgeReader) EventTxnLookup(context.Context, common.Hash) (uint64, bool, error) { - panic("mock") -} - func TestGetStorageValues_HappyPath(t *testing.T) { m, _, _ := rpcdaemontest.CreateTestExecModule(t) api := newEthApiForTest(newBaseApiForTest(m), m.DB, nil, nil) diff --git a/rpc/jsonrpc/eth_receipts.go b/rpc/jsonrpc/eth_receipts.go index 00bb579ddaf..44615ca4593 100644 --- a/rpc/jsonrpc/eth_receipts.go +++ b/rpc/jsonrpc/eth_receipts.go @@ -27,7 +27,6 @@ import ( "github.com/erigontech/erigon/rpc/jsonrpc/receipts" "github.com/erigontech/erigon/common" - "github.com/erigontech/erigon/common/hexutil" "github.com/erigontech/erigon/common/log/v3" "github.com/erigontech/erigon/db/kv" "github.com/erigontech/erigon/db/kv/order" @@ -167,8 +166,7 @@ func (api *BaseAPI) resolveLogsRange(ctx context.Context, tx kv.Tx, crit filters begin = uint64(fromBlock) } else { blockNum := rpc.BlockNumber(fromBlock) - // nil filters: tags must resolve on the same committed view as the - // baseline above and the log scan, not on the block overlay. + // nil filters: resolve on the committed view, like the baseline above. begin, _, _, err = rpchelper.GetBlockNumber(ctx, rpc.BlockNumberOrHashWithNumber(blockNum), tx, api._blockReader, nil) if err != nil { return 0, 0, err @@ -370,7 +368,6 @@ func (api *BaseAPI) getLogsV3(ctx context.Context, tx kv.TemporalTx, begin, end return nil, err } - //var blockHash common.Hash var header *types.Header txNumbers, err := applyFiltersV3(api._txNumReader, tx, begin, end, crit, order.Asc) @@ -402,54 +399,24 @@ func (api *BaseAPI) getLogsV3(ctx context.Context, tx kv.TemporalTx, begin, end } if isFinalTxn { - if chainConfig.Bor != nil { - if header == nil { - header, err = api._blockReader.HeaderByNumber(ctx, tx, blockNum) - if err != nil { - return nil, err - } - } - // check for state sync event logs - events, err := api.bridgeReader.Events(ctx, header.Hash(), blockNum) - if err != nil { - return logs, err - } - - if len(events) == 0 { - continue - } - - borLogs, err := api.borReceiptGenerator.GenerateBorLogs(ctx, events, api._txNumReader, tx, header, chainConfig, txIndex, txNum) - if err != nil { - return logs, err - } - - borLogs = borLogs.FilterWithTopicMap(addrMap, topicMap, 0) - - for _, filteredLog := range borLogs { - if maxResults != 0 && len(logs) >= maxResults { - return nil, &rpc.InvalidParamsError{ - Message: fmt.Sprintf("%s: %d", errExceedLogResults, maxResults), - } - } - logs = append(logs, &types.ErigonLog{ - Log: *filteredLog, - Timestamp: hexutil.Uint64(header.Time), - }) - } + if chainConfig.Bor == nil { + continue + } + borLogs, err := api.borStateSyncLogs(ctx, tx, chainConfig, header, txIndex, txNum) + if err != nil { + return logs, err + } + logs, err = appendErigonLogs(logs, borLogs.FilterWithTopicMap(addrMap, topicMap, 0), header.Time, maxResults) + if err != nil { + return nil, err } - continue } - //fmt.Printf("txNum=%d, blockNum=%d, txIndex=%d, maxTxNumInBlock=%d,mixTxNumInBlock=%d\n", txNum, blockNum, txIndex, maxTxNumInBlock, minTxNumInBlock) - if r, ok := api.receiptsGenerator.TryGetCachedReceipt(header.Hash(), txNum, txIndex); ok { - for _, filteredLog := range r.Logs.FilterWithTopicMap(addrMap, topicMap, 0) { - if maxResults != 0 && len(logs) >= maxResults { - return nil, &rpc.InvalidParamsError{Message: fmt.Sprintf("%s: %d", errExceedLogResults, maxResults)} - } - logs = append(logs, &types.ErigonLog{Log: *filteredLog, Timestamp: hexutil.Uint64(header.Time)}) + logs, err = appendErigonLogs(logs, r.Logs.FilterWithTopicMap(addrMap, topicMap, 0), header.Time, maxResults) + if err != nil { + return nil, err } continue } @@ -469,24 +436,36 @@ func (api *BaseAPI) getLogsV3(ctx context.Context, tx kv.TemporalTx, begin, end if r == nil { return nil, err } - filtered := r.Logs.FilterWithTopicMap(addrMap, topicMap, 0) - for _, filteredLog := range filtered { - if maxResults != 0 && len(logs) >= maxResults { - return nil, &rpc.InvalidParamsError{ - Message: fmt.Sprintf("%s: %d", errExceedLogResults, maxResults), - } - } - logs = append(logs, &types.ErigonLog{ - Log: *filteredLog, - Timestamp: hexutil.Uint64(header.Time), - }) + logs, err = appendErigonLogs(logs, r.Logs.FilterWithTopicMap(addrMap, topicMap, 0), header.Time, maxResults) + if err != nil { + return nil, err } } return logs, nil } +func appendErigonLogs(logs []*types.ErigonLog, filtered types.Logs, blockTime uint64, maxResults int) ([]*types.ErigonLog, error) { + if maxResults != 0 && len(logs)+len(filtered) > maxResults { + return nil, &rpc.InvalidParamsError{ + Message: fmt.Sprintf("%s: %d", errExceedLogResults, maxResults), + } + } + return append(logs, filtered.ToErigonLogs(blockTime)...), nil +} + +func (api *BaseAPI) borStateSyncLogs(ctx context.Context, tx kv.TemporalTx, chainConfig *chain.Config, header *types.Header, txIndex int, txNum uint64) (types.Logs, error) { + events, err := api.bridgeReader.Events(ctx, header.Hash(), header.Number.Uint64()) + if err != nil { + return nil, err + } + if len(events) == 0 { + return nil, nil + } + return api.borReceiptGenerator.GenerateBorLogs(ctx, events, api._txNumReader, tx, header, chainConfig, txIndex, txNum) +} + // The Topic list restricts matches to particular event topics. Each event has a list // of topics. Topics matches a prefix of that list. An empty element slice matches any // topic. Non-empty elements represent an alternative that matches any of the diff --git a/rpc/jsonrpc/eth_receipts_test.go b/rpc/jsonrpc/eth_receipts_test.go new file mode 100644 index 00000000000..91d2a2fe457 --- /dev/null +++ b/rpc/jsonrpc/eth_receipts_test.go @@ -0,0 +1,118 @@ +// Copyright 2026 The Erigon Authors +// This file is part of Erigon. +// +// Erigon is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Erigon is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Erigon. If not, see . + +package jsonrpc + +import ( + "context" + "errors" + "fmt" + "testing" + + "github.com/holiman/uint256" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/erigontech/erigon/common" + "github.com/erigontech/erigon/common/hexutil" + "github.com/erigontech/erigon/execution/types" + "github.com/erigontech/erigon/rpc" +) + +func logsWithIndexes(n int) types.Logs { + logs := make(types.Logs, n) + for i := range logs { + logs[i] = &types.Log{Index: hexutil.Uint(i)} + } + return logs +} + +func erigonLogsWithIndexes(n int) []*types.ErigonLog { + logs := make([]*types.ErigonLog, n) + for i, l := range logsWithIndexes(n) { + logs[i] = &types.ErigonLog{Log: *l} + } + return logs +} + +func TestAppendErigonLogs(t *testing.T) { + const blockTime = 42 + + cases := []struct { + name string + logs []*types.ErigonLog + filtered types.Logs + maxResults int + wantLen int + wantErr bool + }{ + {name: "unlimited", filtered: logsWithIndexes(3), maxResults: 0, wantLen: 3}, + {name: "below limit", filtered: logsWithIndexes(3), maxResults: 5, wantLen: 3}, + {name: "at limit", filtered: logsWithIndexes(3), maxResults: 3, wantLen: 3}, + {name: "above limit", filtered: logsWithIndexes(4), maxResults: 3, wantErr: true}, + {name: "limit counts logs appended earlier", logs: erigonLogsWithIndexes(2), filtered: logsWithIndexes(2), maxResults: 3, wantErr: true}, + {name: "nothing to append at limit", logs: erigonLogsWithIndexes(2), maxResults: 2, wantLen: 2}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + got, err := appendErigonLogs(tc.logs, tc.filtered, blockTime, tc.maxResults) + if tc.wantErr { + require.Nil(t, got) + var rpcErr rpc.Error + require.ErrorAs(t, err, &rpcErr) + assert.Equal(t, rpc.ErrCodeInvalidParams, rpcErr.ErrorCode()) + assert.Equal(t, fmt.Sprintf("%s: %d", errExceedLogResults, tc.maxResults), rpcErr.Error()) + return + } + require.NoError(t, err) + require.Len(t, got, tc.wantLen) + for i, l := range got[len(tc.logs):] { + assert.Equal(t, tc.filtered[i].Index, l.Log.Index) + assert.Equal(t, hexutil.Uint64(blockTime), l.Timestamp) + } + }) + } +} + +var _ bridgeReader = mockBridgeReader{} + +type mockBridgeReader struct { + events []*types.Message + err error +} + +func (b mockBridgeReader) Events(context.Context, common.Hash, uint64) ([]*types.Message, error) { + return b.events, b.err +} + +func (b mockBridgeReader) EventTxnLookup(context.Context, common.Hash) (uint64, bool, error) { + panic("not called") +} + +func TestBorStateSyncLogs_NoEvents(t *testing.T) { + api := &BaseAPI{bridgeReader: mockBridgeReader{}} + logs, err := api.borStateSyncLogs(context.Background(), nil, nil, &types.Header{Number: *uint256.NewInt(1)}, 0, 0) + require.NoError(t, err) + assert.Empty(t, logs) +} + +func TestBorStateSyncLogs_EventsError(t *testing.T) { + wantErr := errors.New("bridge down") + api := &BaseAPI{bridgeReader: mockBridgeReader{err: wantErr}} + _, err := api.borStateSyncLogs(context.Background(), nil, nil, &types.Header{Number: *uint256.NewInt(1)}, 0, 0) + require.ErrorIs(t, err, wantErr) +} diff --git a/rpc/jsonrpc/overlay_race_test.go b/rpc/jsonrpc/overlay_race_test.go index e2f6ab4e867..316afd071a7 100644 --- a/rpc/jsonrpc/overlay_race_test.go +++ b/rpc/jsonrpc/overlay_race_test.go @@ -55,6 +55,16 @@ const ( overlayRaceBaseFee = 424242 ) +func insertOverlayRaceChain(t *testing.T, m *execmoduletester.ExecModuleTester) *blockgen.ChainPack { + t.Helper() + c, err := m.GenerateChain(overlayRaceChainSize, func(i int, gen *blockgen.BlockGen) { + gen.SetCoinbase(common.Address{1}) + }) + require.NoError(t, err) + require.NoError(t, m.InsertChain(c)) + return c +} + // newOverlayAheadTestAPI builds overlayRaceChainSize committed MDBX blocks, // then publishes a fabricated block one past them (overlayRaceChainSize+1) // into the block overlay only, never committed to MDBX. This reproduces the @@ -72,11 +82,7 @@ func newOverlayAheadTestAPI(t *testing.T) (base *BaseAPI, m *execmoduletester.Ex cfg.LondonBlock = common.NewUint64(0) m = execmoduletester.New(t, execmoduletester.WithChainConfig(&cfg)) - c, err := m.GenerateChain(overlayRaceChainSize, func(i int, gen *blockgen.BlockGen) { - gen.SetCoinbase(common.Address{1}) - }) - require.NoError(t, err) - require.NoError(t, m.InsertChain(c)) + c := insertOverlayRaceChain(t, m) ctx := m.Ctx overlayRoTx, err := m.DB.BeginTemporalRo(ctx) @@ -291,11 +297,7 @@ func TestTraceFilter_UsesCommittedFromTag(t *testing.T) { func newHeaderAheadTester(t *testing.T) (m *execmoduletester.ExecModuleTester, aheadHash common.Hash) { t.Helper() m = execmoduletester.New(t) - c, err := m.GenerateChain(overlayRaceChainSize, func(i int, gen *blockgen.BlockGen) { - gen.SetCoinbase(common.Address{1}) - }) - require.NoError(t, err) - require.NoError(t, m.InsertChain(c)) + c := insertOverlayRaceChain(t, m) aheadNumber := uint64(overlayRaceChainSize) + 1 header := &types.Header{ diff --git a/rpc/jsonrpc/trace_filtering.go b/rpc/jsonrpc/trace_filtering.go index 696ed41668c..2fbc0738260 100644 --- a/rpc/jsonrpc/trace_filtering.go +++ b/rpc/jsonrpc/trace_filtering.go @@ -325,8 +325,7 @@ func (api *TraceAPIImpl) Filter(ctx context.Context, req TraceFilterRequest, gas if req.FromBlock == nil { fromBlock = 0 } else { - // nil filters: tags must resolve on the same committed view as the - // txnum-index scan below, not on the block overlay. + // nil filters: resolve on the committed view, like the scan below. fromBlock, _, _, err = rpchelper.GetBlockNumber(ctx, *req.FromBlock, dbtx, api._blockReader, nil) if err != nil { if errors.As(err, &rpc.BlockNotFoundErr{}) {