Skip to content
1 change: 1 addition & 0 deletions cmd/geth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ var (
utils.RPCGlobalGasCapFlag,
utils.RPCGlobalEVMTimeoutFlag,
utils.RPCGlobalTxFeeCapFlag,
utils.RPCGlobalLogQueryLimit,
utils.AllowUnprotectedTxs,
utils.MaxBlockRangeFlag,
utils.BatchRequestLimit,
Expand Down
1 change: 1 addition & 0 deletions cmd/geth/usage.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{
utils.RPCGlobalGasCapFlag,
utils.RPCGlobalEVMTimeoutFlag,
utils.RPCGlobalTxFeeCapFlag,
utils.RPCGlobalLogQueryLimit,
utils.AllowUnprotectedTxs,
utils.JSpathFlag,
utils.ExecFlag,
Expand Down
11 changes: 10 additions & 1 deletion cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,11 @@ var (
Usage: "Sets a cap on transaction fee (in ether) that can be sent via the RPC APIs (0 = no cap)",
Value: ethconfig.Defaults.RPCTxFeeCap,
}
RPCGlobalLogQueryLimit = &cli.IntFlag{
Name: "rpc.logquerylimit",
Usage: "Maximum number of alternative addresses or topics allowed per search position in eth_getLogs filter criteria (0 = no cap)",
Value: ethconfig.Defaults.LogQueryLimit,
}
// Logging and debug settings
EthStatsURLFlag = cli.StringFlag{
Name: "ethstats",
Expand Down Expand Up @@ -1707,6 +1712,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
if ctx.IsSet(CacheLogSizeFlag.Name) {
cfg.FilterLogCacheSize = ctx.Int(CacheLogSizeFlag.Name)
}
if ctx.IsSet(RPCGlobalLogQueryLimit.Name) {
cfg.LogQueryLimit = ctx.Int(RPCGlobalLogQueryLimit.Name)
}
Comment on lines +1715 to +1717
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject negative rpc.logquerylimit values during config sanitization.

At Line 1715, negative values can flow from CLI/config into cfg.LogQueryLimit; later checks treat any negative as “everything exceeds limit”, effectively breaking all log queries.

Proposed fix
 	if ctx.IsSet(RPCGlobalLogQueryLimit.Name) {
 		cfg.LogQueryLimit = ctx.Int(RPCGlobalLogQueryLimit.Name)
 	}
+	if cfg.LogQueryLimit < 0 {
+		Fatalf("--%s must be >= 0", RPCGlobalLogQueryLimit.Name)
+	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if ctx.IsSet(RPCGlobalLogQueryLimit.Name) {
cfg.LogQueryLimit = ctx.Int(RPCGlobalLogQueryLimit.Name)
}
if ctx.IsSet(RPCGlobalLogQueryLimit.Name) {
cfg.LogQueryLimit = ctx.Int(RPCGlobalLogQueryLimit.Name)
}
if cfg.LogQueryLimit < 0 {
Fatalf("--%s must be >= 0", RPCGlobalLogQueryLimit.Name)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/utils/flags.go` around lines 1715 - 1717, The CLI/config parsing
currently allows negative values for RPCGlobalLogQueryLimit which are assigned
into cfg.LogQueryLimit via ctx.IsSet(RPCGlobalLogQueryLimit.Name) and
ctx.Int(...); add validation there to reject negatives: when
ctx.IsSet(RPCGlobalLogQueryLimit.Name) read the int into a temp, if temp < 0
return a validation error (or propagate an existing sanitize error type)
explaining rpc.logquerylimit must be non-negative instead of assigning it to
cfg.LogQueryLimit; ensure references to RPCGlobalLogQueryLimit.Name,
ctx.Int(...) and cfg.LogQueryLimit are used so the correct branch is fixed.

if !ctx.Bool(SnapshotFlag.Name) {
// If snap-sync is requested, this flag is also required
if cfg.SyncMode == downloader.SnapSync {
Expand Down Expand Up @@ -1982,7 +1990,8 @@ func RegisterGraphQLService(stack *node.Node, backend ethapi.Backend, filterSyst
func RegisterFilterAPI(stack *node.Node, backend ethapi.Backend, ethcfg *ethconfig.Config) *filters.FilterSystem {
isLightClient := ethcfg.SyncMode == downloader.LightSync
filterSystem := filters.NewFilterSystem(backend, filters.Config{
LogCacheSize: ethcfg.FilterLogCacheSize,
LogCacheSize: ethcfg.FilterLogCacheSize,
LogQueryLimit: ethcfg.LogQueryLimit,
})
stack.RegisterAPIs([]rpc.API{{
Namespace: "eth",
Expand Down
5 changes: 4 additions & 1 deletion core/state/statedb_hooked.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,10 @@ func (s *hookedStateDB) SetCode(address common.Address, code []byte) []byte {
if len(prev) != 0 {
prevHash = crypto.Keccak256Hash(prev)
}
s.hooks.OnCodeChange(address, prevHash, prev, crypto.Keccak256Hash(code), code)
newHash := crypto.Keccak256Hash(code)
if prevHash != newHash {
s.hooks.OnCodeChange(address, prevHash, prev, newHash, code)
}
}
return prev
}
Expand Down
41 changes: 41 additions & 0 deletions core/state/statedb_hooked_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package state

import (
"testing"

"github.com/morph-l2/go-ethereum/common"
"github.com/morph-l2/go-ethereum/core/rawdb"
"github.com/morph-l2/go-ethereum/core/tracing"
)

func TestSetCodeNoChangeDoesNotHook(t *testing.T) {
statedb, err := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase()), nil)
if err != nil {
t.Fatal(err)
}
addr := common.HexToAddress("0x1111111111111111111111111111111111111111")
var calls int
hooked := NewHookedState(statedb, &tracing.Hooks{
OnCodeChange: func(common.Address, common.Hash, []byte, common.Hash, []byte) {
calls++
},
})

code := []byte{1, 2, 3}
hooked.SetCode(addr, code)
if calls != 1 {
t.Fatalf("first SetCode should hook once, got %d", calls)
}
hooked.SetCode(addr, append([]byte(nil), code...))
if calls != 1 {
t.Fatalf("unchanged SetCode should not hook, got %d", calls)
}
hooked.SetCode(addr, nil)
if calls != 2 {
t.Fatalf("clearing code should hook once, got %d", calls)
}
hooked.SetCode(addr, nil)
if calls != 2 {
t.Fatalf("repeated empty SetCode should not hook, got %d", calls)
}
}
4 changes: 3 additions & 1 deletion core/vm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,9 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
if err == nil {
createDataGas := uint64(len(ret)) * params.CreateDataGas
if contract.UseGas(createDataGas, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) {
evm.StateDB.SetCode(address, ret)
if len(ret) > 0 {
evm.StateDB.SetCode(address, ret)
}
} else {
err = ErrCodeStoreOutOfGas
}
Expand Down
86 changes: 86 additions & 0 deletions core/vm/evm_create_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package vm

import (
"math/big"
"testing"

"github.com/morph-l2/go-ethereum/common"
"github.com/morph-l2/go-ethereum/core/rawdb"
statedb "github.com/morph-l2/go-ethereum/core/state"
"github.com/morph-l2/go-ethereum/params"
)

type setCodeCountingStateDB struct {
StateDB
setCodeCalls int
}

func (db *setCodeCountingStateDB) SetCode(addr common.Address, code []byte) []byte {
db.setCodeCalls++
return db.StateDB.SetCode(addr, code)
}

func newCreateTestEVM(t *testing.T) (*EVM, *setCodeCountingStateDB, common.Address) {
t.Helper()

base, err := statedb.New(common.Hash{}, statedb.NewDatabase(rawdb.NewMemoryDatabase()), nil)
if err != nil {
t.Fatal(err)
}
caller := common.HexToAddress("0x1111111111111111111111111111111111111111")
base.CreateAccount(caller)

wrapped := &setCodeCountingStateDB{StateDB: base}
blockCtx := BlockContext{
CanTransfer: func(StateDB, common.Address, *big.Int) bool { return true },
Transfer: func(StateDB, common.Address, common.Address, *big.Int) {},
GetHash: func(uint64) common.Hash { return common.Hash{} },
BlockNumber: big.NewInt(0),
Time: big.NewInt(0),
GasLimit: 10_000_000,
BaseFee: big.NewInt(params.InitialBaseFee),
}
txCtx := TxContext{
Origin: caller,
GasPrice: big.NewInt(params.InitialBaseFee),
}
return NewEVM(blockCtx, txCtx, wrapped, params.TestChainConfig, Config{}), wrapped, caller
}

func TestCreateEmptyReturnSkipsSetCode(t *testing.T) {
evm, statedb, caller := newCreateTestEVM(t)

ret, _, _, err := evm.Create(AccountRef(caller), nil, 1_000_000, new(big.Int))
if err != nil {
t.Fatal(err)
}
if len(ret) != 0 {
t.Fatalf("expected empty create return, got %x", ret)
}
if statedb.setCodeCalls != 0 {
t.Fatalf("empty create called SetCode %d times", statedb.setCodeCalls)
}
}

func TestCreateNonEmptyReturnSetsCode(t *testing.T) {
evm, statedb, caller := newCreateTestEVM(t)

code := []byte{
byte(PUSH1), 0x2a,
byte(PUSH1), 0x00,
byte(MSTORE),
byte(PUSH1), 0x20,
byte(PUSH1), 0x00,
byte(RETURN),
}
ret, _, _, err := evm.Create(AccountRef(caller), code, 1_000_000, new(big.Int))
if err != nil {
t.Fatal(err)
}
if len(ret) != 32 {
t.Fatalf("expected non-empty create return, got %x", ret)
}
if statedb.setCodeCalls != 1 {
t.Fatalf("non-empty create called SetCode %d times", statedb.setCodeCalls)
}
}
4 changes: 4 additions & 0 deletions eth/ethconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ var Defaults = Config{
TrieTimeout: 60 * time.Minute,
SnapshotCache: 102,
FilterLogCacheSize: 32,
LogQueryLimit: 1000,
Miner: miner.DefaultConfig,
TxPool: core.DefaultTxPoolConfig,
RPCGasCap: 50000000,
Expand Down Expand Up @@ -169,6 +170,9 @@ type Config struct {
// This is the number of blocks for which logs will be cached in the filter system.
FilterLogCacheSize int

// This is the maximum number of addresses or topics allowed in filter criteria.
LogQueryLimit int

// Mining options
Miner miner.Config

Expand Down
6 changes: 6 additions & 0 deletions eth/ethconfig/gen_config.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions eth/fetcher/tx_fetcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,10 @@ func (f *TxFetcher) loop() {
if len(f.announced[hash]) == 0 {
delete(f.announced, hash)
}
delete(f.alternates[hash], drop.peer)
if len(f.alternates[hash]) == 0 {
delete(f.alternates, hash)
}
}
delete(f.announces, drop.peer)
}
Expand Down
38 changes: 38 additions & 0 deletions eth/fetcher/tx_fetcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1121,6 +1121,44 @@ func TestTransactionFetcherDropRescheduling(t *testing.T) {
})
}

func TestTransactionFetcherDropAlternates(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher {
return NewTxFetcher(
func(common.Hash) bool { return false },
func(txs []*types.Transaction) []error {
return make([]error, len(txs))
},
func(string, []common.Hash) error { return nil },
)
},
steps: []interface{}{
doTxNotify{peer: "A", hashes: []common.Hash{{0x01}}},
doWait{time: txArriveTimeout, step: true},
doTxNotify{peer: "B", hashes: []common.Hash{{0x01}}},
isScheduled{
tracking: map[string][]common.Hash{
"A": {{0x01}},
"B": {{0x01}},
},
fetching: map[string][]common.Hash{
"A": {{0x01}},
},
},
doDrop("B"),
isWaiting(nil),
isScheduled{
tracking: map[string][]common.Hash{
"A": {{0x01}},
},
fetching: map[string][]common.Hash{
"A": {{0x01}},
},
},
},
})
}

// This test reproduces a crash caught by the fuzzer. The root cause was a
// dangling transaction timing out and clashing on readd with a concurrently
// announced one.
Expand Down
Loading
Loading