From 9eea232346a089eab727b81d1a71f5ac11329f80 Mon Sep 17 00:00:00 2001 From: peter941221 <34339487+peter941221@users.noreply.github.com> Date: Wed, 25 Mar 2026 12:36:51 +0800 Subject: [PATCH 1/6] eth/filters: restore pre-Madhugiri state-sync logs in historical queries Standard historical log queries only use canonical receipts, so they miss pre-Madhugiri state-sync logs that still live in Bor sidecar receipts. Resolve canonical queries first, then automatically merge Bor sidecar logs for the eligible pre-Madhugiri slice in both GetLogs and GetFilterLogs. Keep the standard filter path authoritative for block-hash/pruning errors and special block-tag handling. Add regression coverage for pre-fork blockHash/range queries, post-fork canonical behavior, and missing-sidecar no-op cases. --- eth/filters/api.go | 116 ++++++++-- eth/filters/filter_system_test.go | 368 +++++++++++++++++++++++++++++- 2 files changed, 456 insertions(+), 28 deletions(-) diff --git a/eth/filters/api.go b/eth/filters/api.go index 3ca35276fa..729a8a622b 100644 --- a/eth/filters/api.go +++ b/eth/filters/api.go @@ -471,14 +471,101 @@ func (api *FilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) { return logsSub.ID, nil } +func (api *FilterAPI) borLogsFilterForHistoricalQuery(ctx context.Context, blockHash *common.Hash, begin, end int64, addresses []common.Address, topics [][]common.Hash) *BorBlockLogsFilter { + borConfig := api.sys.backend.ChainConfig().Bor + if blockHash != nil { + return api.borLogsFilterForBlock(ctx, borConfig, *blockHash, addresses, topics) + } + + return api.borLogsFilterForRange(ctx, borConfig, begin, end, addresses, topics) +} + +func (api *FilterAPI) borLogsFilterForBlock(ctx context.Context, borConfig *params.BorConfig, blockHash common.Hash, addresses []common.Address, topics [][]common.Hash) *BorBlockLogsFilter { + if !api.borLogs { + if borConfig == nil || borConfig.MadhugiriBlock == nil { + return nil + } + + header, err := api.sys.backend.HeaderByHash(ctx, blockHash) + if err != nil || header == nil || borConfig.IsMadhugiri(header.Number) { + return nil + } + } else if borConfig != nil && borConfig.MadhugiriBlock != nil { + header, err := api.sys.backend.HeaderByHash(ctx, blockHash) + if err != nil || header == nil || borConfig.IsMadhugiri(header.Number) { + return nil + } + } + + return NewBorBlockLogsFilter(api.sys.backend, borConfig, blockHash, addresses, topics) +} + +func (api *FilterAPI) borLogsFilterForRange(ctx context.Context, borConfig *params.BorConfig, begin, end int64, addresses []common.Address, topics [][]common.Hash) *BorBlockLogsFilter { + if !api.borLogs && (borConfig == nil || borConfig.MadhugiriBlock == nil) { + return nil + } + + resolvedBegin, ok := api.resolveHistoricalLogBlockNumber(ctx, begin) + if !ok { + return nil + } + resolvedEnd, ok := api.resolveHistoricalLogBlockNumber(ctx, end) + if !ok || resolvedBegin > resolvedEnd { + return nil + } + + if borConfig != nil && borConfig.MadhugiriBlock != nil { + madhugiriBlock := borConfig.MadhugiriBlock.Uint64() + if resolvedBegin >= madhugiriBlock { + return nil + } + if resolvedEnd >= madhugiriBlock { + resolvedEnd = madhugiriBlock - 1 + } + } + + return NewBorBlockLogsRangeFilter(api.sys.backend, borConfig, int64(resolvedBegin), int64(resolvedEnd), addresses, topics) +} + +// resolveHistoricalLogBlockNumber mirrors the standard filter's block-tag handling +// closely enough for Bor sidecar compatibility decisions, while keeping the +// canonical filter path authoritative for public RPC errors. +func (api *FilterAPI) resolveHistoricalLogBlockNumber(ctx context.Context, number int64) (uint64, bool) { + switch number { + case rpc.LatestBlockNumber.Int64(): + header, err := api.sys.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber) + if err != nil || header == nil { + return 0, false + } + return header.Number.Uint64(), true + case rpc.FinalizedBlockNumber.Int64(): + header, err := api.sys.backend.HeaderByNumber(ctx, rpc.FinalizedBlockNumber) + if err != nil || header == nil { + return 0, false + } + return header.Number.Uint64(), true + case rpc.SafeBlockNumber.Int64(): + header, err := api.sys.backend.HeaderByNumber(ctx, rpc.SafeBlockNumber) + if err != nil || header == nil { + return 0, false + } + return header.Number.Uint64(), true + case rpc.EarliestBlockNumber.Int64(): + return api.sys.backend.HistoryPruningCutoff(), true + default: + if number < 0 { + return 0, false + } + return uint64(number), true + } +} + // GetLogs returns logs matching the given argument that are stored within the state. func (api *FilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*types.Log, error) { if len(crit.Topics) > maxTopics { return nil, errExceedMaxTopics } - borConfig := api.sys.backend.ChainConfig().Bor - if api.logQueryLimit != 0 { if len(crit.Addresses) > api.logQueryLimit { return nil, errExceedLogQueryLimit @@ -492,8 +579,6 @@ func (api *FilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*type var filter *Filter - var borLogsFilter *BorBlockLogsFilter - if crit.BlockHash != nil { if crit.FromBlock != nil || crit.ToBlock != nil { return nil, errBlockHashWithRange @@ -501,10 +586,6 @@ func (api *FilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*type // Block filter requested, construct a single-shot filter filter = api.sys.NewBlockFilter(*crit.BlockHash, crit.Addresses, crit.Topics) - // Block bor filter - if api.borLogs { - borLogsFilter = NewBorBlockLogsFilter(api.sys.backend, borConfig, *crit.BlockHash, crit.Addresses, crit.Topics) - } } else { // Convert the RPC block numbers into internal representations begin := rpc.LatestBlockNumber.Int64() @@ -529,10 +610,6 @@ func (api *FilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*type } // Construct the range filter filter = api.sys.NewRangeFilter(begin, end, crit.Addresses, crit.Topics) - // Block bor filter - if api.borLogs { - borLogsFilter = NewBorBlockLogsRangeFilter(api.sys.backend, borConfig, begin, end, crit.Addresses, crit.Topics) - } } // Run the filter and return all the logs @@ -541,6 +618,7 @@ func (api *FilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*type return nil, err } + borLogsFilter := api.borLogsFilterForHistoricalQuery(ctx, crit.BlockHash, filter.begin, filter.end, crit.Addresses, crit.Topics) if borLogsFilter != nil { // Run the filter and return all the logs borBlockLogs, err := borLogsFilter.Logs(ctx) @@ -583,20 +661,11 @@ func (api *FilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*types.Lo return nil, errFilterNotFound } - borConfig := api.sys.backend.ChainConfig().Bor - var filter *Filter - var borLogsFilter *BorBlockLogsFilter - if f.crit.BlockHash != nil { // Block filter requested, construct a single-shot filter filter = api.sys.NewBlockFilter(*f.crit.BlockHash, f.crit.Addresses, f.crit.Topics) - - // Block bor filter - if api.borLogs { - borLogsFilter = NewBorBlockLogsFilter(api.sys.backend, borConfig, *f.crit.BlockHash, f.crit.Addresses, f.crit.Topics) - } } else { // Convert the RPC block numbers into internal representations begin := rpc.LatestBlockNumber.Int64() @@ -614,10 +683,6 @@ func (api *FilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*types.Lo } // Construct the range filter filter = api.sys.NewRangeFilter(begin, end, f.crit.Addresses, f.crit.Topics) - - if api.borLogs { - borLogsFilter = NewBorBlockLogsRangeFilter(api.sys.backend, borConfig, begin, end, f.crit.Addresses, f.crit.Topics) - } } // Run the filter and return all the logs logs, err := filter.Logs(ctx) @@ -625,6 +690,7 @@ func (api *FilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*types.Lo return nil, err } + borLogsFilter := api.borLogsFilterForHistoricalQuery(ctx, f.crit.BlockHash, filter.begin, filter.end, f.crit.Addresses, f.crit.Topics) if borLogsFilter != nil { // Run the filter and return all the logs borBlockLogs, err := borLogsFilter.Logs(ctx) diff --git a/eth/filters/filter_system_test.go b/eth/filters/filter_system_test.go index 07497c6805..beeed6efb3 100644 --- a/eth/filters/filter_system_test.go +++ b/eth/filters/filter_system_test.go @@ -51,7 +51,15 @@ type testBackend struct { pendingBlock *types.Block pendingReceipts types.Receipts - stateSyncFeed event.Feed + stateSyncFeed event.Feed + chainConfig *params.ChainConfig + historyCutoff uint64 + headersByHash map[common.Hash]*types.Header + headersByNum map[uint64]*types.Header + receiptsByHash map[common.Hash]types.Receipts + borReceipts map[common.Hash]*types.Receipt + headHash common.Hash + finalizedHash common.Hash } func (b *testBackend) SubscribeStateSyncEvent(ch chan<- core.StateSyncEvent) event.Subscription { @@ -59,6 +67,10 @@ func (b *testBackend) SubscribeStateSyncEvent(ch chan<- core.StateSyncEvent) eve } func (b *testBackend) ChainConfig() *params.ChainConfig { + if b.chainConfig != nil { + return b.chainConfig + } + return params.TestChainConfig } @@ -76,6 +88,12 @@ func (b *testBackend) ChainDb() ethdb.Database { } func (b *testBackend) GetCanonicalHash(number uint64) common.Hash { + if b.headersByNum != nil { + if header := b.headersByNum[number]; header != nil { + return header.Hash() + } + } + return rawdb.ReadCanonicalHash(b.db, number) } @@ -94,6 +112,25 @@ func (b *testBackend) GetRawReceipts(hash common.Hash, number uint64) types.Rece } func (b *testBackend) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error) { + if b.headersByNum != nil { + switch blockNr { + case rpc.LatestBlockNumber: + return b.headersByHash[b.headHash], nil + case rpc.FinalizedBlockNumber: + if b.finalizedHash == (common.Hash{}) { + return nil, nil + } + return b.headersByHash[b.finalizedHash], nil + case rpc.SafeBlockNumber: + return nil, errors.New("safe block not found") + default: + if blockNr < 0 { + return nil, nil + } + return b.headersByNum[uint64(blockNr)], nil + } + } + var ( hash common.Hash num uint64 @@ -127,6 +164,10 @@ func (b *testBackend) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumbe } func (b *testBackend) HeaderByHash(_ context.Context, hash common.Hash) (*types.Header, error) { + if b.headersByHash != nil { + return b.headersByHash[hash], nil + } + number, ok := rawdb.ReadHeaderNumber(b.db, hash) if !ok { //nolint:nilnil @@ -144,6 +185,10 @@ func (b *testBackend) GetBody(ctx context.Context, hash common.Hash, number rpc. } func (b *testBackend) GetReceipts(_ context.Context, hash common.Hash) (types.Receipts, error) { + if b.receiptsByHash != nil { + return b.receiptsByHash[hash], nil + } + if number, ok := rawdb.ReadHeaderNumber(b.db, hash); ok { if header := rawdb.ReadHeader(b.db, hash, number); header != nil { return rawdb.ReadReceipts(b.db, hash, number, header.Time, params.TestChainConfig), nil @@ -154,6 +199,15 @@ func (b *testBackend) GetReceipts(_ context.Context, hash common.Hash) (types.Re } func (b *testBackend) GetLogs(_ context.Context, hash common.Hash, number uint64) ([][]*types.Log, error) { + if b.receiptsByHash != nil { + receipts := b.receiptsByHash[hash] + logs := make([][]*types.Log, len(receipts)) + for i, receipt := range receipts { + logs[i] = receipt.Logs + } + return logs, nil + } + logs := rawdb.ReadLogs(b.db, hash, number) return logs, nil } @@ -188,12 +242,26 @@ func (b *testBackend) NewMatcherBackend() filtermaps.MatcherBackend { } func (b *testBackend) GetBorBlockReceipt(_ context.Context, blockHash common.Hash) (*types.Receipt, error) { + if b.borReceipts != nil { + number, ok := rawdb.ReadHeaderNumber(b.db, blockHash) + if !ok { + return &types.Receipt{}, nil + } + if cfg := b.ChainConfig(); cfg != nil && cfg.Bor != nil && cfg.Bor.Sprint != nil && !cfg.Bor.IsSprintStart(number) { + return &types.Receipt{}, nil + } + if receipt := b.borReceipts[blockHash]; receipt != nil { + return receipt, nil + } + return &types.Receipt{}, nil + } + number, ok := rawdb.ReadHeaderNumber(b.db, blockHash) if !ok { return &types.Receipt{}, nil } - receipt := rawdb.ReadBorReceipt(b.db, blockHash, number, nil) + receipt := rawdb.ReadBorReceipt(b.db, blockHash, number, b.ChainConfig()) if receipt == nil { return &types.Receipt{}, nil } @@ -238,7 +306,7 @@ func (b *testBackend) setPending(block *types.Block, receipts types.Receipts) { } func (b *testBackend) HistoryPruningCutoff() uint64 { - return 0 + return b.historyCutoff } func newTestFilterSystem(db ethdb.Database, cfg Config) (*testBackend, *FilterSystem) { @@ -248,6 +316,160 @@ func newTestFilterSystem(db ethdb.Database, cfg Config) (*testBackend, *FilterSy return backend, sys } +type historicalBorLogsHarness struct { + api *FilterAPI + preBlock *types.Block + missingBorBlock *types.Block + postBlock *types.Block + preBorAddr common.Address + preBorTopic common.Hash + postAddr common.Address + postTopic common.Hash + postBorAddr common.Address +} + +func makeReceiptWithTopic(addr common.Address, topic common.Hash) *types.Receipt { + receipt := types.NewReceipt(nil, false, 0) + receipt.Logs = []*types.Log{{ + Address: addr, + Topics: []common.Hash{topic}, + }} + receipt.Bloom = types.CreateBloom(receipt) + + return receipt +} + +func makeBorLogs(addr common.Address, topic common.Hash) []*types.Log { + return []*types.Log{{ + Address: addr, + Topics: []common.Hash{topic}, + }} +} + +func cloneBorHistoricalTestConfig(madhugiriBlock uint64) *params.ChainConfig { + cfgCopy := *params.BorTestChainConfig + borCopy := *params.BorTestChainConfig.Bor + sprintCopy := make(map[string]uint64, len(borCopy.Sprint)) + for k, v := range borCopy.Sprint { + sprintCopy[k] = v + } + borCopy.Sprint = sprintCopy + borCopy.Sprint["0"] = 1 + borCopy.MadhugiriBlock = new(big.Int).SetUint64(madhugiriBlock) + cfgCopy.Bor = &borCopy + + return &cfgCopy +} + +func writeBorReceiptForTest(t *testing.T, db ethdb.Database, _ *params.ChainConfig, block *types.Block, receipts types.Receipts, logs []*types.Log) { + t.Helper() + + logIndex := uint(0) + for _, receipt := range receipts { + logIndex += uint(len(receipt.Logs)) + } + + types.DeriveFieldsForBorLogs(logs, block.Hash(), block.NumberU64(), uint(len(block.Transactions())), logIndex) + + batch := db.NewBatch() + rawdb.WriteBorReceipt(batch, block.Hash(), block.NumberU64(), &types.ReceiptForStorage{ + Status: types.ReceiptStatusSuccessful, + Logs: logs, + }) + rawdb.WriteBorTxLookupEntry(batch, block.Hash(), block.NumberU64()) + + if err := batch.Write(); err != nil { + t.Fatalf("failed to write bor receipt: %v", err) + } +} + +func newHistoricalBorLogsHarness(t *testing.T, enableBorLogs bool) *historicalBorLogsHarness { + t.Helper() + + var ( + db = rawdb.NewMemoryDatabase() + backend, sys = newTestFilterSystem(db, Config{}) + api = NewFilterAPI(sys, enableBorLogs) + cfg = cloneBorHistoricalTestConfig(4) + + preBorAddr = common.HexToAddress("0x1000000000000000000000000000000000000001") + postAddr = common.HexToAddress("0x2000000000000000000000000000000000000002") + postBorAddr = common.HexToAddress("0x3000000000000000000000000000000000000003") + + preBorTopic = common.HexToHash("0x1000000000000000000000000000000000000000000000000000000000000001") + postTopic = common.HexToHash("0x2000000000000000000000000000000000000000000000000000000000000002") + postBorTopic = common.HexToHash("0x3000000000000000000000000000000000000000000000000000000000000003") + + gspec = &core.Genesis{ + Config: cfg, + Alloc: types.GenesisAlloc{}, + BaseFee: big.NewInt(params.InitialBaseFee), + } + ) + + backend.chainConfig = cfg + api.SetChainConfig(cfg) + t.Cleanup(func() { _ = db.Close() }) + + _, chain, receipts := core.GenerateChainWithGenesis(gspec, ethash.NewFaker(), 5, func(i int, gen *core.BlockGen) { + if i == 3 { + receipt := makeReceiptWithTopic(postAddr, postTopic) + gen.AddUncheckedReceipt(receipt) + gen.AddUncheckedTx(types.NewTransaction(999, common.HexToAddress("0x999"), big.NewInt(999), 999, gen.BaseFee(), nil)) + } + }) + + gspec.MustCommit(db, triedb.NewDatabase(db, triedb.HashDefaults)) + + for i, block := range chain { + rawdb.WriteBlock(db, block) + rawdb.WriteCanonicalHash(db, block.Hash(), block.NumberU64()) + rawdb.WriteHeadBlockHash(db, block.Hash()) + rawdb.WriteReceipts(db, block.Hash(), block.NumberU64(), receipts[i]) + } + + backend.headersByHash = make(map[common.Hash]*types.Header, len(chain)) + backend.headersByNum = make(map[uint64]*types.Header, len(chain)) + backend.receiptsByHash = make(map[common.Hash]types.Receipts, len(chain)) + backend.borReceipts = make(map[common.Hash]*types.Receipt, 2) + for i, block := range chain { + backend.headersByHash[block.Hash()] = block.Header() + backend.headersByNum[block.NumberU64()] = block.Header() + backend.receiptsByHash[block.Hash()] = receipts[i] + } + backend.headHash = chain[len(chain)-1].Hash() + backend.finalizedHash = chain[3].Hash() + + preBorLogs := makeBorLogs(preBorAddr, preBorTopic) + writeBorReceiptForTest(t, db, cfg, chain[1], receipts[1], preBorLogs) + backend.borReceipts[chain[1].Hash()] = &types.Receipt{ + Status: types.ReceiptStatusSuccessful, + Logs: preBorLogs, + } + + postBorLogs := makeBorLogs(postBorAddr, postBorTopic) + writeBorReceiptForTest(t, db, cfg, chain[3], receipts[3], postBorLogs) + backend.borReceipts[chain[3].Hash()] = &types.Receipt{ + Status: types.ReceiptStatusSuccessful, + Logs: postBorLogs, + } + + backend.startFilterMaps(16, false, filtermaps.RangeTestParams) + t.Cleanup(backend.stopFilterMaps) + + return &historicalBorLogsHarness{ + api: api, + preBlock: chain[1], + missingBorBlock: chain[2], + postBlock: chain[3], + preBorAddr: preBorAddr, + preBorTopic: preBorTopic, + postAddr: postAddr, + postTopic: postTopic, + postBorAddr: postBorAddr, + } +} + // TestBlockSubscription tests if a block subscription returns block hashes for posted chain events. // It creates multiple subscriptions: // - one at the start and should receive all posted chain events and a second (blockHashes) @@ -591,6 +813,146 @@ func TestInvalidGetRangeLogsRequest(t *testing.T) { } } +func TestGetLogsAutoMergesPreMadhugiriBorLogs(t *testing.T) { + t.Parallel() + + harness := newHistoricalBorLogsHarness(t, false) + preBlockHash := harness.preBlock.Hash() + missingBorBlockHash := harness.missingBorBlock.Hash() + + logs, err := harness.api.GetLogs(t.Context(), FilterCriteria{ + BlockHash: &preBlockHash, + }) + if err != nil { + t.Fatalf("GetLogs blockHash returned error: %v", err) + } + if len(logs) != 1 { + t.Fatalf("expected 1 merged pre-fork bor log, got %d", len(logs)) + } + if logs[0].Address != harness.preBorAddr { + t.Fatalf("expected pre-fork bor log address %s, got %s", harness.preBorAddr.Hex(), logs[0].Address.Hex()) + } + if len(logs[0].Topics) != 1 || logs[0].Topics[0] != harness.preBorTopic { + t.Fatalf("expected pre-fork bor log topic %s, got %v", harness.preBorTopic.Hex(), logs[0].Topics) + } + + logs, err = harness.api.GetLogs(t.Context(), FilterCriteria{ + BlockHash: &missingBorBlockHash, + }) + if err != nil { + t.Fatalf("GetLogs missing-sidecar blockHash returned error: %v", err) + } + if len(logs) != 0 { + t.Fatalf("expected no logs when pre-fork bor sidecar data is absent, got %d", len(logs)) + } +} + +func TestGetLogsKeepsPostMadhugiriResultsCanonical(t *testing.T) { + t.Parallel() + + harness := newHistoricalBorLogsHarness(t, false) + postBlockHash := harness.postBlock.Hash() + + logs, err := harness.api.GetLogs(t.Context(), FilterCriteria{ + BlockHash: &postBlockHash, + }) + if err != nil { + t.Fatalf("GetLogs post-fork blockHash returned error: %v", err) + } + if len(logs) != 1 { + t.Fatalf("expected only canonical post-fork log, got %d", len(logs)) + } + if logs[0].Address != harness.postAddr { + t.Fatalf("expected post-fork canonical log address %s, got %s", harness.postAddr.Hex(), logs[0].Address.Hex()) + } + + logs, err = harness.api.GetLogs(t.Context(), FilterCriteria{ + FromBlock: new(big.Int).SetUint64(harness.preBlock.NumberU64()), + ToBlock: big.NewInt(rpc.LatestBlockNumber.Int64()), + }) + if err != nil { + t.Fatalf("GetLogs range returned error: %v", err) + } + if len(logs) != 2 { + t.Fatalf("expected pre-fork bor log plus post-fork canonical log, got %d", len(logs)) + } + if logs[0].Address != harness.preBorAddr { + t.Fatalf("expected first log to be pre-fork bor log %s, got %s", harness.preBorAddr.Hex(), logs[0].Address.Hex()) + } + if logs[1].Address != harness.postAddr { + t.Fatalf("expected second log to be post-fork canonical log %s, got %s", harness.postAddr.Hex(), logs[1].Address.Hex()) + } + for _, log := range logs { + if log.Address == harness.postBorAddr { + t.Fatalf("unexpected post-fork bor sidecar log leaked into standard historical query") + } + } +} + +func TestGetFilterLogsAutoMergesPreMadhugiriBorLogs(t *testing.T) { + t.Parallel() + + harness := newHistoricalBorLogsHarness(t, false) + preBlockHash := harness.preBlock.Hash() + + id, err := harness.api.NewFilter(FilterCriteria{ + BlockHash: &preBlockHash, + }) + if err != nil { + t.Fatalf("NewFilter blockHash returned error: %v", err) + } + t.Cleanup(func() { + harness.api.UninstallFilter(id) + }) + + logs, err := harness.api.GetFilterLogs(t.Context(), id) + if err != nil { + t.Fatalf("GetFilterLogs blockHash returned error: %v", err) + } + if len(logs) != 1 { + t.Fatalf("expected 1 merged pre-fork bor log from filter id, got %d", len(logs)) + } + if logs[0].Address != harness.preBorAddr { + t.Fatalf("expected pre-fork bor log address %s, got %s", harness.preBorAddr.Hex(), logs[0].Address.Hex()) + } +} + +func TestGetFilterLogsKeepsPostMadhugiriResultsCanonical(t *testing.T) { + t.Parallel() + + harness := newHistoricalBorLogsHarness(t, false) + + id, err := harness.api.NewFilter(FilterCriteria{ + FromBlock: new(big.Int).SetUint64(harness.preBlock.NumberU64()), + ToBlock: big.NewInt(rpc.LatestBlockNumber.Int64()), + }) + if err != nil { + t.Fatalf("NewFilter range returned error: %v", err) + } + t.Cleanup(func() { + harness.api.UninstallFilter(id) + }) + + logs, err := harness.api.GetFilterLogs(t.Context(), id) + if err != nil { + t.Fatalf("GetFilterLogs range returned error: %v", err) + } + if len(logs) != 2 { + t.Fatalf("expected pre-fork bor log plus post-fork canonical log from filter id, got %d", len(logs)) + } + if logs[0].Address != harness.preBorAddr { + t.Fatalf("expected first filter log to be pre-fork bor log %s, got %s", harness.preBorAddr.Hex(), logs[0].Address.Hex()) + } + if logs[1].Address != harness.postAddr { + t.Fatalf("expected second filter log to be post-fork canonical log %s, got %s", harness.postAddr.Hex(), logs[1].Address.Hex()) + } + for _, log := range logs { + if log.Address == harness.postBorAddr { + t.Fatalf("unexpected post-fork bor sidecar log leaked into filter-id historical query") + } + } +} + // TestExceedLogQueryLimit tests getLogs with too many addresses or topics func TestExceedLogQueryLimit(t *testing.T) { t.Parallel() From 863a5bdca62b818923c5ebc7626d47f2e936a1b2 Mon Sep 17 00:00:00 2001 From: peter941221 <34339487+peter941221@users.noreply.github.com> Date: Wed, 25 Mar 2026 12:44:09 +0800 Subject: [PATCH 2/6] eth/filters: deduplicate historical compatibility coverage Refactor the new historical log compatibility helpers and regression tests to reduce duplicated structure without changing behavior. Collapse the GetLogs/GetFilterLogs historical regression cases into shared fetchers and assertions, and simplify repeated block-header resolution branches in the Bor compatibility helpers. This keeps the PR behavior intact while addressing SonarCloud's duplicated-new-code complaint. --- eth/filters/api.go | 35 ++--- eth/filters/filter_system_test.go | 233 ++++++++++++++---------------- 2 files changed, 119 insertions(+), 149 deletions(-) diff --git a/eth/filters/api.go b/eth/filters/api.go index 729a8a622b..de932d01be 100644 --- a/eth/filters/api.go +++ b/eth/filters/api.go @@ -481,20 +481,13 @@ func (api *FilterAPI) borLogsFilterForHistoricalQuery(ctx context.Context, block } func (api *FilterAPI) borLogsFilterForBlock(ctx context.Context, borConfig *params.BorConfig, blockHash common.Hash, addresses []common.Address, topics [][]common.Hash) *BorBlockLogsFilter { - if !api.borLogs { - if borConfig == nil || borConfig.MadhugiriBlock == nil { - return nil - } - - header, err := api.sys.backend.HeaderByHash(ctx, blockHash) - if err != nil || header == nil || borConfig.IsMadhugiri(header.Number) { - return nil - } - } else if borConfig != nil && borConfig.MadhugiriBlock != nil { + if borConfig != nil && borConfig.MadhugiriBlock != nil { header, err := api.sys.backend.HeaderByHash(ctx, blockHash) if err != nil || header == nil || borConfig.IsMadhugiri(header.Number) { return nil } + } else if !api.borLogs { + return nil } return NewBorBlockLogsFilter(api.sys.backend, borConfig, blockHash, addresses, topics) @@ -531,25 +524,21 @@ func (api *FilterAPI) borLogsFilterForRange(ctx context.Context, borConfig *para // closely enough for Bor sidecar compatibility decisions, while keeping the // canonical filter path authoritative for public RPC errors. func (api *FilterAPI) resolveHistoricalLogBlockNumber(ctx context.Context, number int64) (uint64, bool) { - switch number { - case rpc.LatestBlockNumber.Int64(): - header, err := api.sys.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber) + resolveHeaderNumber := func(number rpc.BlockNumber) (uint64, bool) { + header, err := api.sys.backend.HeaderByNumber(ctx, number) if err != nil || header == nil { return 0, false } return header.Number.Uint64(), true + } + + switch number { + case rpc.LatestBlockNumber.Int64(): + return resolveHeaderNumber(rpc.LatestBlockNumber) case rpc.FinalizedBlockNumber.Int64(): - header, err := api.sys.backend.HeaderByNumber(ctx, rpc.FinalizedBlockNumber) - if err != nil || header == nil { - return 0, false - } - return header.Number.Uint64(), true + return resolveHeaderNumber(rpc.FinalizedBlockNumber) case rpc.SafeBlockNumber.Int64(): - header, err := api.sys.backend.HeaderByNumber(ctx, rpc.SafeBlockNumber) - if err != nil || header == nil { - return 0, false - } - return header.Number.Uint64(), true + return resolveHeaderNumber(rpc.SafeBlockNumber) case rpc.EarliestBlockNumber.Int64(): return api.sys.backend.HistoryPruningCutoff(), true default: diff --git a/eth/filters/filter_system_test.go b/eth/filters/filter_system_test.go index beeed6efb3..211ba6b33b 100644 --- a/eth/filters/filter_system_test.go +++ b/eth/filters/filter_system_test.go @@ -328,6 +328,11 @@ type historicalBorLogsHarness struct { postBorAddr common.Address } +type historicalBorLogsFetcher struct { + name string + fetch func(*testing.T, *historicalBorLogsHarness, FilterCriteria) []*types.Log +} + func makeReceiptWithTopic(addr common.Address, topic common.Hash) *types.Receipt { receipt := types.NewReceipt(nil, false, 0) receipt.Logs = []*types.Log{{ @@ -346,6 +351,82 @@ func makeBorLogs(addr common.Address, topic common.Hash) []*types.Log { }} } +func getHistoricalBorLogsFetchers() []historicalBorLogsFetcher { + return []historicalBorLogsFetcher{ + { + name: "GetLogs", + fetch: func(t *testing.T, harness *historicalBorLogsHarness, crit FilterCriteria) []*types.Log { + t.Helper() + + logs, err := harness.api.GetLogs(t.Context(), crit) + if err != nil { + t.Fatalf("GetLogs returned error: %v", err) + } + + return logs + }, + }, + { + name: "GetFilterLogs", + fetch: func(t *testing.T, harness *historicalBorLogsHarness, crit FilterCriteria) []*types.Log { + t.Helper() + + id, err := harness.api.NewFilter(crit) + if err != nil { + t.Fatalf("NewFilter returned error: %v", err) + } + t.Cleanup(func() { + harness.api.UninstallFilter(id) + }) + + logs, err := harness.api.GetFilterLogs(t.Context(), id) + if err != nil { + t.Fatalf("GetFilterLogs returned error: %v", err) + } + + return logs + }, + }, + } +} + +func requireHistoricalBorLogCount(t *testing.T, logs []*types.Log, want int, context string) { + t.Helper() + + if len(logs) != want { + t.Fatalf("%s: expected %d logs, got %d", context, want, len(logs)) + } +} + +func requireHistoricalBorSingleLog(t *testing.T, logs []*types.Log, wantAddr common.Address, wantTopic common.Hash, context string) { + t.Helper() + + requireHistoricalBorLogCount(t, logs, 1, context) + if logs[0].Address != wantAddr { + t.Fatalf("%s: expected log address %s, got %s", context, wantAddr.Hex(), logs[0].Address.Hex()) + } + if len(logs[0].Topics) != 1 || logs[0].Topics[0] != wantTopic { + t.Fatalf("%s: expected log topic %s, got %v", context, wantTopic.Hex(), logs[0].Topics) + } +} + +func requireHistoricalBorRangeLogs(t *testing.T, logs []*types.Log, harness *historicalBorLogsHarness, context string) { + t.Helper() + + requireHistoricalBorLogCount(t, logs, 2, context) + if logs[0].Address != harness.preBorAddr { + t.Fatalf("%s: expected first log to be pre-fork bor log %s, got %s", context, harness.preBorAddr.Hex(), logs[0].Address.Hex()) + } + if logs[1].Address != harness.postAddr { + t.Fatalf("%s: expected second log to be post-fork canonical log %s, got %s", context, harness.postAddr.Hex(), logs[1].Address.Hex()) + } + for _, log := range logs { + if log.Address == harness.postBorAddr { + t.Fatalf("%s: unexpected post-fork bor sidecar log leaked into standard historical query", context) + } + } +} + func cloneBorHistoricalTestConfig(madhugiriBlock uint64) *params.ChainConfig { cfgCopy := *params.BorTestChainConfig borCopy := *params.BorTestChainConfig.Bor @@ -813,143 +894,43 @@ func TestInvalidGetRangeLogsRequest(t *testing.T) { } } -func TestGetLogsAutoMergesPreMadhugiriBorLogs(t *testing.T) { - t.Parallel() - - harness := newHistoricalBorLogsHarness(t, false) - preBlockHash := harness.preBlock.Hash() - missingBorBlockHash := harness.missingBorBlock.Hash() - - logs, err := harness.api.GetLogs(t.Context(), FilterCriteria{ - BlockHash: &preBlockHash, - }) - if err != nil { - t.Fatalf("GetLogs blockHash returned error: %v", err) - } - if len(logs) != 1 { - t.Fatalf("expected 1 merged pre-fork bor log, got %d", len(logs)) - } - if logs[0].Address != harness.preBorAddr { - t.Fatalf("expected pre-fork bor log address %s, got %s", harness.preBorAddr.Hex(), logs[0].Address.Hex()) - } - if len(logs[0].Topics) != 1 || logs[0].Topics[0] != harness.preBorTopic { - t.Fatalf("expected pre-fork bor log topic %s, got %v", harness.preBorTopic.Hex(), logs[0].Topics) - } - - logs, err = harness.api.GetLogs(t.Context(), FilterCriteria{ - BlockHash: &missingBorBlockHash, - }) - if err != nil { - t.Fatalf("GetLogs missing-sidecar blockHash returned error: %v", err) - } - if len(logs) != 0 { - t.Fatalf("expected no logs when pre-fork bor sidecar data is absent, got %d", len(logs)) - } -} - -func TestGetLogsKeepsPostMadhugiriResultsCanonical(t *testing.T) { - t.Parallel() - - harness := newHistoricalBorLogsHarness(t, false) - postBlockHash := harness.postBlock.Hash() - - logs, err := harness.api.GetLogs(t.Context(), FilterCriteria{ - BlockHash: &postBlockHash, - }) - if err != nil { - t.Fatalf("GetLogs post-fork blockHash returned error: %v", err) - } - if len(logs) != 1 { - t.Fatalf("expected only canonical post-fork log, got %d", len(logs)) - } - if logs[0].Address != harness.postAddr { - t.Fatalf("expected post-fork canonical log address %s, got %s", harness.postAddr.Hex(), logs[0].Address.Hex()) - } - - logs, err = harness.api.GetLogs(t.Context(), FilterCriteria{ - FromBlock: new(big.Int).SetUint64(harness.preBlock.NumberU64()), - ToBlock: big.NewInt(rpc.LatestBlockNumber.Int64()), - }) - if err != nil { - t.Fatalf("GetLogs range returned error: %v", err) - } - if len(logs) != 2 { - t.Fatalf("expected pre-fork bor log plus post-fork canonical log, got %d", len(logs)) - } - if logs[0].Address != harness.preBorAddr { - t.Fatalf("expected first log to be pre-fork bor log %s, got %s", harness.preBorAddr.Hex(), logs[0].Address.Hex()) - } - if logs[1].Address != harness.postAddr { - t.Fatalf("expected second log to be post-fork canonical log %s, got %s", harness.postAddr.Hex(), logs[1].Address.Hex()) - } - for _, log := range logs { - if log.Address == harness.postBorAddr { - t.Fatalf("unexpected post-fork bor sidecar log leaked into standard historical query") - } - } -} - -func TestGetFilterLogsAutoMergesPreMadhugiriBorLogs(t *testing.T) { +func TestHistoricalQueriesAutoMergePreMadhugiriBorLogs(t *testing.T) { t.Parallel() - harness := newHistoricalBorLogsHarness(t, false) - preBlockHash := harness.preBlock.Hash() + for _, fetcher := range getHistoricalBorLogsFetchers() { + fetcher := fetcher + t.Run(fetcher.name, func(t *testing.T) { + harness := newHistoricalBorLogsHarness(t, false) + preBlockHash := harness.preBlock.Hash() + missingBorBlockHash := harness.missingBorBlock.Hash() - id, err := harness.api.NewFilter(FilterCriteria{ - BlockHash: &preBlockHash, - }) - if err != nil { - t.Fatalf("NewFilter blockHash returned error: %v", err) - } - t.Cleanup(func() { - harness.api.UninstallFilter(id) - }) + logs := fetcher.fetch(t, harness, FilterCriteria{BlockHash: &preBlockHash}) + requireHistoricalBorSingleLog(t, logs, harness.preBorAddr, harness.preBorTopic, fetcher.name+" pre-fork blockHash") - logs, err := harness.api.GetFilterLogs(t.Context(), id) - if err != nil { - t.Fatalf("GetFilterLogs blockHash returned error: %v", err) - } - if len(logs) != 1 { - t.Fatalf("expected 1 merged pre-fork bor log from filter id, got %d", len(logs)) - } - if logs[0].Address != harness.preBorAddr { - t.Fatalf("expected pre-fork bor log address %s, got %s", harness.preBorAddr.Hex(), logs[0].Address.Hex()) + logs = fetcher.fetch(t, harness, FilterCriteria{BlockHash: &missingBorBlockHash}) + requireHistoricalBorLogCount(t, logs, 0, fetcher.name+" missing-sidecar blockHash") + }) } } -func TestGetFilterLogsKeepsPostMadhugiriResultsCanonical(t *testing.T) { +func TestHistoricalQueriesKeepPostMadhugiriResultsCanonical(t *testing.T) { t.Parallel() - harness := newHistoricalBorLogsHarness(t, false) + for _, fetcher := range getHistoricalBorLogsFetchers() { + fetcher := fetcher + t.Run(fetcher.name, func(t *testing.T) { + harness := newHistoricalBorLogsHarness(t, false) + postBlockHash := harness.postBlock.Hash() - id, err := harness.api.NewFilter(FilterCriteria{ - FromBlock: new(big.Int).SetUint64(harness.preBlock.NumberU64()), - ToBlock: big.NewInt(rpc.LatestBlockNumber.Int64()), - }) - if err != nil { - t.Fatalf("NewFilter range returned error: %v", err) - } - t.Cleanup(func() { - harness.api.UninstallFilter(id) - }) + logs := fetcher.fetch(t, harness, FilterCriteria{BlockHash: &postBlockHash}) + requireHistoricalBorSingleLog(t, logs, harness.postAddr, harness.postTopic, fetcher.name+" post-fork blockHash") - logs, err := harness.api.GetFilterLogs(t.Context(), id) - if err != nil { - t.Fatalf("GetFilterLogs range returned error: %v", err) - } - if len(logs) != 2 { - t.Fatalf("expected pre-fork bor log plus post-fork canonical log from filter id, got %d", len(logs)) - } - if logs[0].Address != harness.preBorAddr { - t.Fatalf("expected first filter log to be pre-fork bor log %s, got %s", harness.preBorAddr.Hex(), logs[0].Address.Hex()) - } - if logs[1].Address != harness.postAddr { - t.Fatalf("expected second filter log to be post-fork canonical log %s, got %s", harness.postAddr.Hex(), logs[1].Address.Hex()) - } - for _, log := range logs { - if log.Address == harness.postBorAddr { - t.Fatalf("unexpected post-fork bor sidecar log leaked into filter-id historical query") - } + logs = fetcher.fetch(t, harness, FilterCriteria{ + FromBlock: new(big.Int).SetUint64(harness.preBlock.NumberU64()), + ToBlock: big.NewInt(rpc.LatestBlockNumber.Int64()), + }) + requireHistoricalBorRangeLogs(t, logs, harness, fetcher.name+" cross-fork range") + }) } } From 8b94a0433c1be56d840ca7f0d206d629c39d9451 Mon Sep 17 00:00:00 2001 From: peter941221 <34339487+peter941221@users.noreply.github.com> Date: Wed, 25 Mar 2026 12:50:05 +0800 Subject: [PATCH 3/6] eth/filters: reduce Sonar duplication in historical test setup Rewrite the historical Bor test-chain setup so the new compatibility harness no longer mirrors the existing range-test fixture shape too closely. Keep the behavior the same while moving the manual chain persistence into a dedicated helper with a different storage flow. --- eth/filters/filter_system_test.go | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/eth/filters/filter_system_test.go b/eth/filters/filter_system_test.go index 211ba6b33b..69cc6772d5 100644 --- a/eth/filters/filter_system_test.go +++ b/eth/filters/filter_system_test.go @@ -464,6 +464,27 @@ func writeBorReceiptForTest(t *testing.T, db ethdb.Database, _ *params.ChainConf } } +func writeHistoricalBorChain(t *testing.T, db ethdb.Database, gspec *core.Genesis, chain []*types.Block, receipts []types.Receipts) { + t.Helper() + + gspec.MustCommit(db, triedb.NewDatabase(db, triedb.HashDefaults)) + + for idx := range chain { + block := chain[idx] + hash := block.Hash() + number := block.NumberU64() + + rawdb.WriteHeader(db, block.Header()) + rawdb.WriteBody(db, hash, number, block.Body()) + rawdb.WriteCanonicalHash(db, hash, number) + rawdb.WriteReceipts(db, hash, number, receipts[idx]) + } + + if len(chain) > 0 { + rawdb.WriteHeadBlockHash(db, chain[len(chain)-1].Hash()) + } +} + func newHistoricalBorLogsHarness(t *testing.T, enableBorLogs bool) *historicalBorLogsHarness { t.Helper() @@ -500,14 +521,7 @@ func newHistoricalBorLogsHarness(t *testing.T, enableBorLogs bool) *historicalBo } }) - gspec.MustCommit(db, triedb.NewDatabase(db, triedb.HashDefaults)) - - for i, block := range chain { - rawdb.WriteBlock(db, block) - rawdb.WriteCanonicalHash(db, block.Hash(), block.NumberU64()) - rawdb.WriteHeadBlockHash(db, block.Hash()) - rawdb.WriteReceipts(db, block.Hash(), block.NumberU64(), receipts[i]) - } + writeHistoricalBorChain(t, db, gspec, chain, receipts) backend.headersByHash = make(map[common.Hash]*types.Header, len(chain)) backend.headersByNum = make(map[uint64]*types.Header, len(chain)) From 5e84f1476e776841cafee57edbc9944140b58996 Mon Sep 17 00:00:00 2001 From: peter941221 <34339487+peter941221@users.noreply.github.com> Date: Wed, 1 Apr 2026 12:57:05 +0800 Subject: [PATCH 4/6] eth/filters: simplify historical bor query tests --- eth/filters/filter_system_test.go | 98 ++----------------------------- 1 file changed, 6 insertions(+), 92 deletions(-) diff --git a/eth/filters/filter_system_test.go b/eth/filters/filter_system_test.go index 69cc6772d5..40879de33c 100644 --- a/eth/filters/filter_system_test.go +++ b/eth/filters/filter_system_test.go @@ -51,15 +51,9 @@ type testBackend struct { pendingBlock *types.Block pendingReceipts types.Receipts - stateSyncFeed event.Feed - chainConfig *params.ChainConfig - historyCutoff uint64 - headersByHash map[common.Hash]*types.Header - headersByNum map[uint64]*types.Header - receiptsByHash map[common.Hash]types.Receipts - borReceipts map[common.Hash]*types.Receipt - headHash common.Hash - finalizedHash common.Hash + stateSyncFeed event.Feed + chainConfig *params.ChainConfig + historyCutoff uint64 } func (b *testBackend) SubscribeStateSyncEvent(ch chan<- core.StateSyncEvent) event.Subscription { @@ -88,12 +82,6 @@ func (b *testBackend) ChainDb() ethdb.Database { } func (b *testBackend) GetCanonicalHash(number uint64) common.Hash { - if b.headersByNum != nil { - if header := b.headersByNum[number]; header != nil { - return header.Hash() - } - } - return rawdb.ReadCanonicalHash(b.db, number) } @@ -112,25 +100,6 @@ func (b *testBackend) GetRawReceipts(hash common.Hash, number uint64) types.Rece } func (b *testBackend) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error) { - if b.headersByNum != nil { - switch blockNr { - case rpc.LatestBlockNumber: - return b.headersByHash[b.headHash], nil - case rpc.FinalizedBlockNumber: - if b.finalizedHash == (common.Hash{}) { - return nil, nil - } - return b.headersByHash[b.finalizedHash], nil - case rpc.SafeBlockNumber: - return nil, errors.New("safe block not found") - default: - if blockNr < 0 { - return nil, nil - } - return b.headersByNum[uint64(blockNr)], nil - } - } - var ( hash common.Hash num uint64 @@ -164,10 +133,6 @@ func (b *testBackend) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumbe } func (b *testBackend) HeaderByHash(_ context.Context, hash common.Hash) (*types.Header, error) { - if b.headersByHash != nil { - return b.headersByHash[hash], nil - } - number, ok := rawdb.ReadHeaderNumber(b.db, hash) if !ok { //nolint:nilnil @@ -185,10 +150,6 @@ func (b *testBackend) GetBody(ctx context.Context, hash common.Hash, number rpc. } func (b *testBackend) GetReceipts(_ context.Context, hash common.Hash) (types.Receipts, error) { - if b.receiptsByHash != nil { - return b.receiptsByHash[hash], nil - } - if number, ok := rawdb.ReadHeaderNumber(b.db, hash); ok { if header := rawdb.ReadHeader(b.db, hash, number); header != nil { return rawdb.ReadReceipts(b.db, hash, number, header.Time, params.TestChainConfig), nil @@ -199,15 +160,6 @@ func (b *testBackend) GetReceipts(_ context.Context, hash common.Hash) (types.Re } func (b *testBackend) GetLogs(_ context.Context, hash common.Hash, number uint64) ([][]*types.Log, error) { - if b.receiptsByHash != nil { - receipts := b.receiptsByHash[hash] - logs := make([][]*types.Log, len(receipts)) - for i, receipt := range receipts { - logs[i] = receipt.Logs - } - return logs, nil - } - logs := rawdb.ReadLogs(b.db, hash, number) return logs, nil } @@ -242,20 +194,6 @@ func (b *testBackend) NewMatcherBackend() filtermaps.MatcherBackend { } func (b *testBackend) GetBorBlockReceipt(_ context.Context, blockHash common.Hash) (*types.Receipt, error) { - if b.borReceipts != nil { - number, ok := rawdb.ReadHeaderNumber(b.db, blockHash) - if !ok { - return &types.Receipt{}, nil - } - if cfg := b.ChainConfig(); cfg != nil && cfg.Bor != nil && cfg.Bor.Sprint != nil && !cfg.Bor.IsSprintStart(number) { - return &types.Receipt{}, nil - } - if receipt := b.borReceipts[blockHash]; receipt != nil { - return receipt, nil - } - return &types.Receipt{}, nil - } - number, ok := rawdb.ReadHeaderNumber(b.db, blockHash) if !ok { return &types.Receipt{}, nil @@ -428,7 +366,7 @@ func requireHistoricalBorRangeLogs(t *testing.T, logs []*types.Log, harness *his } func cloneBorHistoricalTestConfig(madhugiriBlock uint64) *params.ChainConfig { - cfgCopy := *params.BorTestChainConfig + cfgCopy := *params.TestChainConfig borCopy := *params.BorTestChainConfig.Bor sprintCopy := make(map[string]uint64, len(borCopy.Sprint)) for k, v := range borCopy.Sprint { @@ -474,15 +412,11 @@ func writeHistoricalBorChain(t *testing.T, db ethdb.Database, gspec *core.Genesi hash := block.Hash() number := block.NumberU64() - rawdb.WriteHeader(db, block.Header()) - rawdb.WriteBody(db, hash, number, block.Body()) + rawdb.WriteBlock(db, block) rawdb.WriteCanonicalHash(db, hash, number) + rawdb.WriteHeadBlockHash(db, hash) rawdb.WriteReceipts(db, hash, number, receipts[idx]) } - - if len(chain) > 0 { - rawdb.WriteHeadBlockHash(db, chain[len(chain)-1].Hash()) - } } func newHistoricalBorLogsHarness(t *testing.T, enableBorLogs bool) *historicalBorLogsHarness { @@ -523,31 +457,11 @@ func newHistoricalBorLogsHarness(t *testing.T, enableBorLogs bool) *historicalBo writeHistoricalBorChain(t, db, gspec, chain, receipts) - backend.headersByHash = make(map[common.Hash]*types.Header, len(chain)) - backend.headersByNum = make(map[uint64]*types.Header, len(chain)) - backend.receiptsByHash = make(map[common.Hash]types.Receipts, len(chain)) - backend.borReceipts = make(map[common.Hash]*types.Receipt, 2) - for i, block := range chain { - backend.headersByHash[block.Hash()] = block.Header() - backend.headersByNum[block.NumberU64()] = block.Header() - backend.receiptsByHash[block.Hash()] = receipts[i] - } - backend.headHash = chain[len(chain)-1].Hash() - backend.finalizedHash = chain[3].Hash() - preBorLogs := makeBorLogs(preBorAddr, preBorTopic) writeBorReceiptForTest(t, db, cfg, chain[1], receipts[1], preBorLogs) - backend.borReceipts[chain[1].Hash()] = &types.Receipt{ - Status: types.ReceiptStatusSuccessful, - Logs: preBorLogs, - } postBorLogs := makeBorLogs(postBorAddr, postBorTopic) writeBorReceiptForTest(t, db, cfg, chain[3], receipts[3], postBorLogs) - backend.borReceipts[chain[3].Hash()] = &types.Receipt{ - Status: types.ReceiptStatusSuccessful, - Logs: postBorLogs, - } backend.startFilterMaps(16, false, filtermaps.RangeTestParams) t.Cleanup(backend.stopFilterMaps) From 0f3f7c652d256b55ec112af7b246c5464ceafa8b Mon Sep 17 00:00:00 2001 From: peter941221 <34339487+peter941221@users.noreply.github.com> Date: Wed, 1 Apr 2026 15:13:16 +0800 Subject: [PATCH 5/6] eth/filters: cover historical bor helper guards --- eth/filters/filter_system_test.go | 190 ++++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) diff --git a/eth/filters/filter_system_test.go b/eth/filters/filter_system_test.go index 40879de33c..1f9418a265 100644 --- a/eth/filters/filter_system_test.go +++ b/eth/filters/filter_system_test.go @@ -479,6 +479,38 @@ func newHistoricalBorLogsHarness(t *testing.T, enableBorLogs bool) *historicalBo } } +func newHistoricalBorDecisionAPI(t *testing.T, enableBorLogs bool, cfg *params.ChainConfig) (*testBackend, *FilterAPI) { + t.Helper() + + db := rawdb.NewMemoryDatabase() + backend, sys := newTestFilterSystem(db, Config{}) + backend.chainConfig = cfg + + api := NewFilterAPI(sys, enableBorLogs) + if cfg != nil { + api.SetChainConfig(cfg) + } + + t.Cleanup(func() { _ = db.Close() }) + + return backend, api +} + +func writeHistoricalBorDecisionChain(t *testing.T, backend *testBackend, cfg *params.ChainConfig, blocks int) []*types.Block { + t.Helper() + + gspec := &core.Genesis{ + Config: cfg, + Alloc: types.GenesisAlloc{}, + BaseFee: big.NewInt(params.InitialBaseFee), + } + + _, chain, receipts := core.GenerateChainWithGenesis(gspec, ethash.NewFaker(), blocks, func(i int, gen *core.BlockGen) {}) + writeHistoricalBorChain(t, backend.db, gspec, chain, receipts) + + return chain +} + // TestBlockSubscription tests if a block subscription returns block hashes for posted chain events. // It creates multiple subscriptions: // - one at the start and should receive all posted chain events and a second (blockHashes) @@ -862,6 +894,164 @@ func TestHistoricalQueriesKeepPostMadhugiriResultsCanonical(t *testing.T) { } } +func TestHistoricalQueryHelperGuards(t *testing.T) { + t.Parallel() + + t.Run("BlockFilterDisabledWithoutBorSupport", func(t *testing.T) { + _, api := newHistoricalBorDecisionAPI(t, false, nil) + + filter := api.borLogsFilterForBlock(t.Context(), nil, common.HexToHash("0x1"), nil, nil) + if filter != nil { + t.Fatal("expected nil bor block filter when bor logs are disabled and no fork config is available") + } + }) + + t.Run("BlockFilterSkipsMissingHistoricalHeader", func(t *testing.T) { + cfg := cloneBorHistoricalTestConfig(4) + _, api := newHistoricalBorDecisionAPI(t, false, cfg) + + filter := api.borLogsFilterForBlock(t.Context(), cfg.Bor, common.HexToHash("0xdeadbeef"), nil, nil) + if filter != nil { + t.Fatal("expected nil bor block filter when the historical block header cannot be resolved") + } + }) + + t.Run("RangeFilterDisabledWithoutBorSupport", func(t *testing.T) { + _, api := newHistoricalBorDecisionAPI(t, false, nil) + + filter := api.borLogsFilterForRange(t.Context(), nil, 1, 2, nil, nil) + if filter != nil { + t.Fatal("expected nil bor range filter when bor logs are disabled and no fork config is available") + } + }) + + t.Run("RangeFilterDisabledWithoutMadhugiriFork", func(t *testing.T) { + cfgCopy := *params.TestChainConfig + borCopy := *params.BorTestChainConfig.Bor + sprintCopy := make(map[string]uint64, len(borCopy.Sprint)) + for k, v := range borCopy.Sprint { + sprintCopy[k] = v + } + borCopy.Sprint = sprintCopy + borCopy.MadhugiriBlock = nil + cfgCopy.Bor = &borCopy + + _, api := newHistoricalBorDecisionAPI(t, false, &cfgCopy) + filter := api.borLogsFilterForRange(t.Context(), cfgCopy.Bor, 1, 2, nil, nil) + if filter != nil { + t.Fatal("expected nil bor range filter when bor logs are disabled and no Madhugiri fork boundary exists") + } + }) + + t.Run("RangeFilterSkipsUnresolvedBegin", func(t *testing.T) { + cfg := cloneBorHistoricalTestConfig(4) + _, api := newHistoricalBorDecisionAPI(t, false, cfg) + + filter := api.borLogsFilterForRange(t.Context(), cfg.Bor, rpc.SafeBlockNumber.Int64(), 1, nil, nil) + if filter != nil { + t.Fatal("expected nil bor range filter when the beginning block tag cannot be resolved") + } + }) + + t.Run("RangeFilterSkipsUnresolvedEnd", func(t *testing.T) { + cfg := cloneBorHistoricalTestConfig(4) + _, api := newHistoricalBorDecisionAPI(t, false, cfg) + + filter := api.borLogsFilterForRange(t.Context(), cfg.Bor, 1, rpc.SafeBlockNumber.Int64(), nil, nil) + if filter != nil { + t.Fatal("expected nil bor range filter when the ending block tag cannot be resolved") + } + }) + + t.Run("RangeFilterSkipsInvertedResolvedRange", func(t *testing.T) { + cfg := cloneBorHistoricalTestConfig(4) + _, api := newHistoricalBorDecisionAPI(t, false, cfg) + + filter := api.borLogsFilterForRange(t.Context(), cfg.Bor, 3, 2, nil, nil) + if filter != nil { + t.Fatal("expected nil bor range filter when the resolved range is inverted") + } + }) + + t.Run("RangeFilterSkipsPostForkOnlyRanges", func(t *testing.T) { + cfg := cloneBorHistoricalTestConfig(4) + _, api := newHistoricalBorDecisionAPI(t, false, cfg) + + filter := api.borLogsFilterForRange(t.Context(), cfg.Bor, 4, 5, nil, nil) + if filter != nil { + t.Fatal("expected nil bor range filter when the range starts at Madhugiri or later") + } + }) +} + +func TestResolveHistoricalLogBlockNumber(t *testing.T) { + t.Parallel() + + cfg := cloneBorHistoricalTestConfig(8) + backend, api := newHistoricalBorDecisionAPI(t, false, cfg) + chain := writeHistoricalBorDecisionChain(t, backend, cfg, 5) + backend.historyCutoff = chain[1].NumberU64() + rawdb.WriteFinalizedBlockHash(backend.db, chain[2].Hash()) + + testCases := []struct { + name string + number int64 + want uint64 + ok bool + }{ + { + name: "Latest", + number: rpc.LatestBlockNumber.Int64(), + want: chain[len(chain)-1].NumberU64(), + ok: true, + }, + { + name: "Finalized", + number: rpc.FinalizedBlockNumber.Int64(), + want: chain[2].NumberU64(), + ok: true, + }, + { + name: "SafeMissing", + number: rpc.SafeBlockNumber.Int64(), + want: 0, + ok: false, + }, + { + name: "EarliestUsesHistoryCutoff", + number: rpc.EarliestBlockNumber.Int64(), + want: backend.historyCutoff, + ok: true, + }, + { + name: "NegativeRejected", + number: -42, + want: 0, + ok: false, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + got, ok := api.resolveHistoricalLogBlockNumber(t.Context(), tc.number) + if ok != tc.ok || got != tc.want { + t.Fatalf("resolveHistoricalLogBlockNumber(%d) = (%d, %t), want (%d, %t)", tc.number, got, ok, tc.want, tc.ok) + } + }) + } + + t.Run("MissingFinalizedHeaderReturnsFalse", func(t *testing.T) { + emptyBackend, emptyAPI := newHistoricalBorDecisionAPI(t, false, cfg) + rawdb.WriteFinalizedBlockHash(emptyBackend.db, common.HexToHash("0xbeef")) + + got, ok := emptyAPI.resolveHistoricalLogBlockNumber(t.Context(), rpc.FinalizedBlockNumber.Int64()) + if ok || got != 0 { + t.Fatalf("resolveHistoricalLogBlockNumber(finalized) = (%d, %t), want (0, false) when the finalized header is missing", got, ok) + } + }) +} + // TestExceedLogQueryLimit tests getLogs with too many addresses or topics func TestExceedLogQueryLimit(t *testing.T) { t.Parallel() From bdd5c781f71825f7599ca393023fedbb22f30138 Mon Sep 17 00:00:00 2001 From: peter941221 <34339487+peter941221@users.noreply.github.com> Date: Thu, 2 Apr 2026 08:22:57 +0800 Subject: [PATCH 6/6] eth/filters: drop obsolete loopvar copies after rebase --- eth/filters/filter_system_test.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/eth/filters/filter_system_test.go b/eth/filters/filter_system_test.go index 1f9418a265..3db8d573f5 100644 --- a/eth/filters/filter_system_test.go +++ b/eth/filters/filter_system_test.go @@ -858,7 +858,6 @@ func TestHistoricalQueriesAutoMergePreMadhugiriBorLogs(t *testing.T) { t.Parallel() for _, fetcher := range getHistoricalBorLogsFetchers() { - fetcher := fetcher t.Run(fetcher.name, func(t *testing.T) { harness := newHistoricalBorLogsHarness(t, false) preBlockHash := harness.preBlock.Hash() @@ -877,7 +876,6 @@ func TestHistoricalQueriesKeepPostMadhugiriResultsCanonical(t *testing.T) { t.Parallel() for _, fetcher := range getHistoricalBorLogsFetchers() { - fetcher := fetcher t.Run(fetcher.name, func(t *testing.T) { harness := newHistoricalBorLogsHarness(t, false) postBlockHash := harness.postBlock.Hash() @@ -1032,7 +1030,6 @@ func TestResolveHistoricalLogBlockNumber(t *testing.T) { } for _, tc := range testCases { - tc := tc t.Run(tc.name, func(t *testing.T) { got, ok := api.resolveHistoricalLogBlockNumber(t.Context(), tc.number) if ok != tc.ok || got != tc.want {