Skip to content

rpc: eth_getLogs and trace_filter resolve block tags on the overlay view but read data from the committed view #23194

Description

@yperbasis

#23193 fixes debug_getModifiedAccountsByNumber, which resolved latest/pending on the overlay (fork-choice) view while scanning history on the committed view. The review of that PR found the same class of bug in two more endpoints.

Common setup: during an FCU background-commit window — after PublishOverlay makes block N+1 visible to overlay-aware readers, but before the batch is committed to the DB — overlay-aware tag resolution returns N+1 while the data reads only reach block N. The window is short-lived but recurs on every FCU.

eth_getLogs: spurious errors on the latest tag

resolveLogsRange resolves its baseline latest on the committed view (nil filters), but resolves user-supplied tags on the overlay view (api.filters):

latest, _, _, err := rpchelper.GetBlockNumber(ctx, rpc.BlockNumberOrHashWithNumber(rpc.LatestExecutedBlockNumber), tx, api._blockReader, nil)
if err != nil {
return 0, 0, err
}
begin = latest
if crit.FromBlock != nil {
fromBlock := crit.FromBlock.Int64()
if fromBlock > 0 {
begin = uint64(fromBlock)
} else {
blockNum := rpc.BlockNumber(fromBlock)
begin, _, _, err = rpchelper.GetBlockNumber(ctx, rpc.BlockNumberOrHashWithNumber(blockNum), tx, api._blockReader, api.filters)
if err != nil {
return 0, 0, err
}
}
if checkFuture && begin > latest {
return 0, 0, &rpc.CustomError{Message: ErrBlockRangeIntoFuture, Code: rpc.ErrCodeInvalidParams}
}
}
end = latest
if crit.ToBlock != nil {
toBlock := crit.ToBlock.Int64()
if toBlock > 0 {
end = uint64(toBlock)
} else {
blockNum := rpc.BlockNumber(toBlock)
end, _, _, err = rpchelper.GetBlockNumber(ctx, rpc.BlockNumberOrHashWithNumber(blockNum), tx, api._blockReader, api.filters)
if err != nil {
return 0, 0, err
}
}
if checkFuture && end > latest {
return 0, 0, &rpc.CustomError{Message: ErrBlockRangeIntoFuture, Code: rpc.ErrCodeInvalidParams}
}
}

With overlay head N+1 and committed execution progress N:

  • eth_getLogs({"fromBlock":"latest"}) resolves begin = N+1 (api.filters, line 170) but latest = N (nil filters, line 158), so the checkFuture guard returns ErrBlockRangeIntoFuture.
  • Even without that guard, GetLogs rejects begin > latestExecuted with "node is still syncing" (eth_receipts.go line 247).

So a plain latest logs poll — the most common eth_getLogs shape — fails transiently during every commit window.

Suggested fix, same as #23193: pass nil filters at lines 170 and 187 so user tags resolve on the same committed view that the guards and the log scan use. Trade-off (same as accepted in #23193): pending then resolves to the latest executed block instead of the txpool's pending block.

trace_filter: silently omits the overlay-head block

TraceAPIImpl.Filter resolves fromBlock/toBlock tags on the overlay view, then scans txnum indexes on the committed tx without any executed-progress guard:

if req.FromBlock == nil {
fromBlock = 0
} else {
fromBlock, _, _, err = rpchelper.GetBlockNumber(ctx, *req.FromBlock, dbtx, api._blockReader, api.filters)
if err != nil {
if errors.As(err, &rpc.BlockNotFoundErr{}) {
stream.WriteEmptyArray()
return nil // waiting for spec: not error for historical reasons
}
return err
}
}
if req.ToBlock == nil {
headNumber, err := api._blockReader.HeaderNumber(ctx, dbtx, rawdb.ReadHeadHeaderHash(dbtx))
if err != nil {
return err
}
toBlock = *headNumber
} else {
toBlock, _, _, err = rpchelper.GetBlockNumber(ctx, *req.ToBlock, dbtx, api._blockReader, api.filters)
if err != nil {
if errors.As(err, &rpc.BlockNotFoundErr{}) {
stream.WriteEmptyArray()
return nil // waiting for spec: not error for historical reasons
}
return err
}
}
if fromBlock > toBlock {
return errors.New("invalid parameters: fromBlock cannot be greater than toBlock")
}

TxNums.Min/Max silently clamp a missing block to the last available txnum (see db/kv/rawdbv3/txnum.go). With overlay head N+1:

  • trace_filter({"toBlock":"latest"}) resolves toBlock = N+1, clamps to block N's last txnum, and returns traces only up to block N. The response is a bare array that does not echo the resolved range, so an omitted head block looks identical to an empty one. An incremental indexer that advances its cursor to the requested head permanently skips block N+1's traces.
  • {"fromBlock":"latest"} combined with an older numeric toBlock errors with "fromBlock cannot be greater than toBlock" instead.

trace_block/trace_transaction guard against this via rpchelper.CheckBlockExecuted in callBlock/callTransaction; Filter alone lacks the guard.

Suggested fix: resolve the tags with nil filters like #23193, and optionally add an executed-progress guard so an explicit out-of-range block number errors instead of silently clamping.

Related

debug_getModifiedAccountsByHash is missing the startNum > latestBlock guard its ByNumber twin has (only the two-param path checks endNum), so it returns a silent empty result for a not-yet-executed block where ByNumber errors. Worth aligning while touching this area.

Metadata

Metadata

Labels

Type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions