eth, eth/filters: implement API error code for pruned blocks (#31361)

Implements #31275

---------

Co-authored-by: Jared Wasinger <j-wasinger@hotmail.com>
Co-authored-by: Felix Lange <fjl@twurst.com>
This commit is contained in:
Sina M 2025-04-01 13:42:01 +02:00 committed by GitHub
parent f0cdc40ceb
commit bc36f2de83
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 128 additions and 38 deletions

View file

@ -86,6 +86,11 @@ func (bc *BlockChain) GetHeaderByNumber(number uint64) *types.Header {
return bc.hc.GetHeaderByNumber(number)
}
// GetBlockNumber retrieves the block number associated with a block hash.
func (bc *BlockChain) GetBlockNumber(hash common.Hash) *uint64 {
return bc.hc.GetBlockNumber(hash)
}
// GetHeadersFrom returns a contiguous segment of headers, in rlp-form, going
// backwards from the given number.
func (bc *BlockChain) GetHeadersFrom(number, count uint64) []rlp.RawValue {

View file

@ -34,6 +34,7 @@ import (
"github.com/ethereum/go-ethereum/core/txpool"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/eth/gasprice"
"github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/ethdb"
@ -91,7 +92,13 @@ func (b *EthAPIBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumb
}
return block, nil
}
return b.eth.blockchain.GetHeaderByNumber(uint64(number)), nil
var bn uint64
if number == rpc.EarliestBlockNumber {
bn = b.eth.blockchain.HistoryPruningCutoff()
} else {
bn = uint64(number)
}
return b.eth.blockchain.GetHeaderByNumber(bn), nil
}
func (b *EthAPIBackend) HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error) {
@ -143,11 +150,27 @@ func (b *EthAPIBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumbe
}
return b.eth.blockchain.GetBlock(header.Hash(), header.Number.Uint64()), nil
}
return b.eth.blockchain.GetBlockByNumber(uint64(number)), nil
bn := uint64(number) // the resolved number
if number == rpc.EarliestBlockNumber {
bn = b.eth.blockchain.HistoryPruningCutoff()
}
block := b.eth.blockchain.GetBlockByNumber(bn)
if block == nil && bn < b.eth.blockchain.HistoryPruningCutoff() {
return nil, &ethconfig.PrunedHistoryError{}
}
return block, nil
}
func (b *EthAPIBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
return b.eth.blockchain.GetBlockByHash(hash), nil
number := b.eth.blockchain.GetBlockNumber(hash)
if number == nil {
return nil, nil
}
block := b.eth.blockchain.GetBlock(hash, *number)
if block == nil && *number < b.eth.blockchain.HistoryPruningCutoff() {
return nil, &ethconfig.PrunedHistoryError{}
}
return block, nil
}
// GetBody returns body of a block. It does not resolve special block numbers.
@ -155,10 +178,14 @@ func (b *EthAPIBackend) GetBody(ctx context.Context, hash common.Hash, number rp
if number < 0 || hash == (common.Hash{}) {
return nil, errors.New("invalid arguments; expect hash and no special block numbers")
}
if body := b.eth.blockchain.GetBody(hash); body != nil {
return body, nil
body := b.eth.blockchain.GetBody(hash)
if body == nil {
if uint64(number) < b.eth.blockchain.HistoryPruningCutoff() {
return nil, &ethconfig.PrunedHistoryError{}
}
return nil, errors.New("block body not found")
}
return nil, errors.New("block body not found")
return body, nil
}
func (b *EthAPIBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) {
@ -175,6 +202,9 @@ func (b *EthAPIBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash r
}
block := b.eth.blockchain.GetBlock(hash, header.Number.Uint64())
if block == nil {
if header.Number.Uint64() < b.eth.blockchain.HistoryPruningCutoff() {
return nil, &ethconfig.PrunedHistoryError{}
}
return nil, errors.New("header found, but block body is missing")
}
return block, nil
@ -234,6 +264,10 @@ func (b *EthAPIBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockN
return nil, nil, errors.New("invalid arguments; neither block nor hash specified")
}
func (b *EthAPIBackend) HistoryPruningCutoff() uint64 {
return b.eth.blockchain.HistoryPruningCutoff()
}
func (b *EthAPIBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
return b.eth.blockchain.GetReceiptsByHash(hash), nil
}

View file

@ -90,3 +90,9 @@ var HistoryPrunePoints = map[common.Hash]*HistoryPrunePoint{
BlockHash: common.HexToHash("0x229f6b18ca1552f1d5146deceb5387333f40dc6275aebee3f2c5c4ece07d02db"),
},
}
// PrunedHistoryError is returned when the requested history is pruned.
type PrunedHistoryError struct{}
func (e *PrunedHistoryError) Error() string { return "pruned history unavailable" }
func (e *PrunedHistoryError) ErrorCode() int { return 4444 }

View file

@ -29,6 +29,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/rpc"
)
@ -354,9 +355,13 @@ func (api *FilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*type
if crit.ToBlock != nil {
end = crit.ToBlock.Int64()
}
// Block numbers below 0 are special cases.
if begin > 0 && end > 0 && begin > end {
return nil, errInvalidBlockRange
}
if begin > 0 && begin < int64(api.events.backend.HistoryPruningCutoff()) {
return nil, &ethconfig.PrunedHistoryError{}
}
// Construct the range filter
filter = api.sys.NewRangeFilter(begin, end, crit.Addresses, crit.Topics)
}

View file

@ -27,6 +27,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/filtermaps"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
)
@ -86,6 +87,9 @@ func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) {
if header == nil {
return nil, errors.New("unknown block")
}
if header.Number.Uint64() < f.sys.backend.HistoryPruningCutoff() {
return nil, &ethconfig.PrunedHistoryError{}
}
return f.blockLogs(ctx, header)
}
@ -114,11 +118,19 @@ func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) {
return 0, errors.New("safe header not found")
}
return hdr.Number.Uint64(), nil
case rpc.EarliestBlockNumber.Int64():
earliest := f.sys.backend.HistoryPruningCutoff()
hdr, _ := f.sys.backend.HeaderByNumber(ctx, rpc.BlockNumber(earliest))
if hdr == nil {
return 0, errors.New("earliest header not found")
}
return hdr.Number.Uint64(), nil
default:
if number < 0 {
return 0, errors.New("negative block number")
}
return uint64(number), nil
}
if number < 0 {
return 0, errors.New("negative block number")
}
return uint64(number), nil
}
// range query need to resolve the special begin/end block number

View file

@ -31,6 +31,7 @@ import (
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/filtermaps"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
@ -64,6 +65,7 @@ type Backend interface {
CurrentHeader() *types.Header
ChainConfig() *params.ChainConfig
HistoryPruningCutoff() uint64
SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription
SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription
SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription
@ -304,6 +306,14 @@ func (es *EventSystem) SubscribeLogs(crit ethereum.FilterQuery, logs chan []*typ
return nil, errPendingLogsUnsupported
}
if from == rpc.EarliestBlockNumber {
from = rpc.BlockNumber(es.backend.HistoryPruningCutoff())
}
// Queries beyond the pruning cutoff are not supported.
if uint64(from) < es.backend.HistoryPruningCutoff() {
return nil, &ethconfig.PrunedHistoryError{}
}
// only interested in new mined logs
if from == rpc.LatestBlockNumber && to == rpc.LatestBlockNumber {
return es.subscribeLogs(crit, logs), nil

View file

@ -181,6 +181,10 @@ func (b *testBackend) setPending(block *types.Block, receipts types.Receipts) {
b.pendingReceipts = receipts
}
func (b *testBackend) HistoryPruningCutoff() uint64 {
return 0
}
func newTestFilterSystem(db ethdb.Database, cfg Config) (*testBackend, *FilterSystem) {
backend := &testBackend{db: db}
sys := NewFilterSystem(backend, cfg)

View file

@ -549,21 +549,23 @@ func (api *BlockChainAPI) GetUncleByBlockHashAndIndex(ctx context.Context, block
}
// GetUncleCountByBlockNumber returns number of uncles in the block for the given block number
func (api *BlockChainAPI) GetUncleCountByBlockNumber(ctx context.Context, blockNr rpc.BlockNumber) *hexutil.Uint {
if block, _ := api.b.BlockByNumber(ctx, blockNr); block != nil {
func (api *BlockChainAPI) GetUncleCountByBlockNumber(ctx context.Context, blockNr rpc.BlockNumber) (*hexutil.Uint, error) {
block, err := api.b.BlockByNumber(ctx, blockNr)
if block != nil {
n := hexutil.Uint(len(block.Uncles()))
return &n
return &n, nil
}
return nil
return nil, err
}
// GetUncleCountByBlockHash returns number of uncles in the block for the given block hash
func (api *BlockChainAPI) GetUncleCountByBlockHash(ctx context.Context, blockHash common.Hash) *hexutil.Uint {
if block, _ := api.b.BlockByHash(ctx, blockHash); block != nil {
func (api *BlockChainAPI) GetUncleCountByBlockHash(ctx context.Context, blockHash common.Hash) (*hexutil.Uint, error) {
block, err := api.b.BlockByHash(ctx, blockHash)
if block != nil {
n := hexutil.Uint(len(block.Uncles()))
return &n
return &n, nil
}
return nil
return nil, err
}
// GetCode returns the code stored at the given address in the state for the given block number.
@ -596,9 +598,7 @@ func (api *BlockChainAPI) GetStorageAt(ctx context.Context, address common.Addre
func (api *BlockChainAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) ([]map[string]interface{}, error) {
block, err := api.b.BlockByNumberOrHash(ctx, blockNrOrHash)
if block == nil || err != nil {
// When the block doesn't exist, the RPC method should return JSON null
// as per specification.
return nil, nil
return nil, err
}
receipts, err := api.b.GetReceipts(ctx, block.Hash())
if err != nil {
@ -1258,37 +1258,41 @@ func NewTransactionAPI(b Backend, nonceLock *AddrLocker) *TransactionAPI {
}
// GetBlockTransactionCountByNumber returns the number of transactions in the block with the given block number.
func (api *TransactionAPI) GetBlockTransactionCountByNumber(ctx context.Context, blockNr rpc.BlockNumber) *hexutil.Uint {
if block, _ := api.b.BlockByNumber(ctx, blockNr); block != nil {
func (api *TransactionAPI) GetBlockTransactionCountByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*hexutil.Uint, error) {
block, err := api.b.BlockByNumber(ctx, blockNr)
if block != nil {
n := hexutil.Uint(len(block.Transactions()))
return &n
return &n, nil
}
return nil
return nil, err
}
// GetBlockTransactionCountByHash returns the number of transactions in the block with the given hash.
func (api *TransactionAPI) GetBlockTransactionCountByHash(ctx context.Context, blockHash common.Hash) *hexutil.Uint {
if block, _ := api.b.BlockByHash(ctx, blockHash); block != nil {
func (api *TransactionAPI) GetBlockTransactionCountByHash(ctx context.Context, blockHash common.Hash) (*hexutil.Uint, error) {
block, err := api.b.BlockByHash(ctx, blockHash)
if block != nil {
n := hexutil.Uint(len(block.Transactions()))
return &n
return &n, nil
}
return nil
return nil, err
}
// GetTransactionByBlockNumberAndIndex returns the transaction for the given block number and index.
func (api *TransactionAPI) GetTransactionByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index hexutil.Uint) *RPCTransaction {
if block, _ := api.b.BlockByNumber(ctx, blockNr); block != nil {
return newRPCTransactionFromBlockIndex(block, uint64(index), api.b.ChainConfig())
func (api *TransactionAPI) GetTransactionByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index hexutil.Uint) (*RPCTransaction, error) {
block, err := api.b.BlockByNumber(ctx, blockNr)
if block != nil {
return newRPCTransactionFromBlockIndex(block, uint64(index), api.b.ChainConfig()), nil
}
return nil
return nil, err
}
// GetTransactionByBlockHashAndIndex returns the transaction for the given block hash and index.
func (api *TransactionAPI) GetTransactionByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) *RPCTransaction {
if block, _ := api.b.BlockByHash(ctx, blockHash); block != nil {
return newRPCTransactionFromBlockIndex(block, uint64(index), api.b.ChainConfig())
func (api *TransactionAPI) GetTransactionByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) (*RPCTransaction, error) {
block, err := api.b.BlockByHash(ctx, blockHash)
if block != nil {
return newRPCTransactionFromBlockIndex(block, uint64(index), api.b.ChainConfig()), nil
}
return nil
return nil, err
}
// GetRawTransactionByBlockNumberAndIndex returns the bytes of the transaction for the given block number and index.

View file

@ -520,8 +520,12 @@ func (b testBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber)
if number == rpc.PendingBlockNumber {
return b.pending, nil
}
if number == rpc.EarliestBlockNumber {
number = 0
}
return b.chain.GetBlockByNumber(uint64(number)), nil
}
func (b testBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
return b.chain.GetBlockByHash(hash), nil
}
@ -618,6 +622,9 @@ func (b testBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscripti
func (b testBackend) NewMatcherBackend() filtermaps.MatcherBackend {
panic("implement me")
}
func (b testBackend) HistoryPruningCutoff() uint64 { return b.chain.HistoryPruningCutoff() }
func TestEstimateGas(t *testing.T) {
t.Parallel()
// Initialize test accounts

View file

@ -85,6 +85,7 @@ type Backend interface {
ChainConfig() *params.ChainConfig
Engine() consensus.Engine
HistoryPruningCutoff() uint64
// This is copied from filters.Backend
// eth/filters needs to be initialized from this backend type, so methods needed by

View file

@ -402,3 +402,5 @@ func (b *backendMock) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent)
func (b *backendMock) Engine() consensus.Engine { return nil }
func (b *backendMock) NewMatcherBackend() filtermaps.MatcherBackend { return nil }
func (b *backendMock) HistoryPruningCutoff() uint64 { return 0 }

View file

@ -63,11 +63,11 @@ type jsonWriter interface {
type BlockNumber int64
const (
EarliestBlockNumber = BlockNumber(-5)
SafeBlockNumber = BlockNumber(-4)
FinalizedBlockNumber = BlockNumber(-3)
LatestBlockNumber = BlockNumber(-2)
PendingBlockNumber = BlockNumber(-1)
EarliestBlockNumber = BlockNumber(0)
)
// UnmarshalJSON parses the given JSON fragment into a BlockNumber. It supports: