Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions rpc/jsonrpc/debug_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
13 changes: 0 additions & 13 deletions rpc/jsonrpc/eth_api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down
97 changes: 39 additions & 58 deletions rpc/jsonrpc/eth_receipts.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -167,7 +166,8 @@ 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: 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
}
Expand All @@ -184,7 +184,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
}
Expand Down Expand Up @@ -368,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)
Expand Down Expand Up @@ -400,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
}
Expand All @@ -467,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
Expand Down
118 changes: 118 additions & 0 deletions rpc/jsonrpc/eth_receipts_test.go
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.

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)
}
Loading
Loading