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) 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 // GetHeadersFrom returns a contiguous segment of headers, in rlp-form, going
// backwards from the given number. // backwards from the given number.
func (bc *BlockChain) GetHeadersFrom(number, count uint64) []rlp.RawValue { 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/txpool"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "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/gasprice"
"github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
@ -91,7 +92,13 @@ func (b *EthAPIBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumb
} }
return block, nil 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) { 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.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) { 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. // 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{}) { if number < 0 || hash == (common.Hash{}) {
return nil, errors.New("invalid arguments; expect hash and no special block numbers") return nil, errors.New("invalid arguments; expect hash and no special block numbers")
} }
if body := b.eth.blockchain.GetBody(hash); body != nil { body := b.eth.blockchain.GetBody(hash)
return body, nil 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) { 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()) block := b.eth.blockchain.GetBlock(hash, header.Number.Uint64())
if block == nil { 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 nil, errors.New("header found, but block body is missing")
} }
return block, nil 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") 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) { func (b *EthAPIBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
return b.eth.blockchain.GetReceiptsByHash(hash), nil return b.eth.blockchain.GetReceiptsByHash(hash), nil
} }

View file

@ -90,3 +90,9 @@ var HistoryPrunePoints = map[common.Hash]*HistoryPrunePoint{
BlockHash: common.HexToHash("0x229f6b18ca1552f1d5146deceb5387333f40dc6275aebee3f2c5c4ece07d02db"), 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"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types" "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/internal/ethapi"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
) )
@ -354,9 +355,13 @@ func (api *FilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*type
if crit.ToBlock != nil { if crit.ToBlock != nil {
end = crit.ToBlock.Int64() end = crit.ToBlock.Int64()
} }
// Block numbers below 0 are special cases.
if begin > 0 && end > 0 && begin > end { if begin > 0 && end > 0 && begin > end {
return nil, errInvalidBlockRange return nil, errInvalidBlockRange
} }
if begin > 0 && begin < int64(api.events.backend.HistoryPruningCutoff()) {
return nil, &ethconfig.PrunedHistoryError{}
}
// Construct the range filter // Construct the range filter
filter = api.sys.NewRangeFilter(begin, end, crit.Addresses, crit.Topics) 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/common"
"github.com/ethereum/go-ethereum/core/filtermaps" "github.com/ethereum/go-ethereum/core/filtermaps"
"github.com/ethereum/go-ethereum/core/types" "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/log"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
) )
@ -86,6 +87,9 @@ func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) {
if header == nil { if header == nil {
return nil, errors.New("unknown block") return nil, errors.New("unknown block")
} }
if header.Number.Uint64() < f.sys.backend.HistoryPruningCutoff() {
return nil, &ethconfig.PrunedHistoryError{}
}
return f.blockLogs(ctx, header) 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 0, errors.New("safe header not found")
} }
return hdr.Number.Uint64(), nil 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 // 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"
"github.com/ethereum/go-ethereum/core/filtermaps" "github.com/ethereum/go-ethereum/core/filtermaps"
"github.com/ethereum/go-ethereum/core/types" "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/ethdb"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
@ -64,6 +65,7 @@ type Backend interface {
CurrentHeader() *types.Header CurrentHeader() *types.Header
ChainConfig() *params.ChainConfig ChainConfig() *params.ChainConfig
HistoryPruningCutoff() uint64
SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription
SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription
SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) 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 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 // only interested in new mined logs
if from == rpc.LatestBlockNumber && to == rpc.LatestBlockNumber { if from == rpc.LatestBlockNumber && to == rpc.LatestBlockNumber {
return es.subscribeLogs(crit, logs), nil 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 b.pendingReceipts = receipts
} }
func (b *testBackend) HistoryPruningCutoff() uint64 {
return 0
}
func newTestFilterSystem(db ethdb.Database, cfg Config) (*testBackend, *FilterSystem) { func newTestFilterSystem(db ethdb.Database, cfg Config) (*testBackend, *FilterSystem) {
backend := &testBackend{db: db} backend := &testBackend{db: db}
sys := NewFilterSystem(backend, cfg) 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 // 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 { func (api *BlockChainAPI) GetUncleCountByBlockNumber(ctx context.Context, blockNr rpc.BlockNumber) (*hexutil.Uint, error) {
if block, _ := api.b.BlockByNumber(ctx, blockNr); block != nil { block, err := api.b.BlockByNumber(ctx, blockNr)
if block != nil {
n := hexutil.Uint(len(block.Uncles())) 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 // 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 { func (api *BlockChainAPI) GetUncleCountByBlockHash(ctx context.Context, blockHash common.Hash) (*hexutil.Uint, error) {
if block, _ := api.b.BlockByHash(ctx, blockHash); block != nil { block, err := api.b.BlockByHash(ctx, blockHash)
if block != nil {
n := hexutil.Uint(len(block.Uncles())) 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. // 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) { func (api *BlockChainAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) ([]map[string]interface{}, error) {
block, err := api.b.BlockByNumberOrHash(ctx, blockNrOrHash) block, err := api.b.BlockByNumberOrHash(ctx, blockNrOrHash)
if block == nil || err != nil { if block == nil || err != nil {
// When the block doesn't exist, the RPC method should return JSON null return nil, err
// as per specification.
return nil, nil
} }
receipts, err := api.b.GetReceipts(ctx, block.Hash()) receipts, err := api.b.GetReceipts(ctx, block.Hash())
if err != nil { 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. // 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 { func (api *TransactionAPI) GetBlockTransactionCountByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*hexutil.Uint, error) {
if block, _ := api.b.BlockByNumber(ctx, blockNr); block != nil { block, err := api.b.BlockByNumber(ctx, blockNr)
if block != nil {
n := hexutil.Uint(len(block.Transactions())) 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. // 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 { func (api *TransactionAPI) GetBlockTransactionCountByHash(ctx context.Context, blockHash common.Hash) (*hexutil.Uint, error) {
if block, _ := api.b.BlockByHash(ctx, blockHash); block != nil { block, err := api.b.BlockByHash(ctx, blockHash)
if block != nil {
n := hexutil.Uint(len(block.Transactions())) 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. // 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 { func (api *TransactionAPI) GetTransactionByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index hexutil.Uint) (*RPCTransaction, error) {
if block, _ := api.b.BlockByNumber(ctx, blockNr); block != nil { block, err := api.b.BlockByNumber(ctx, blockNr)
return newRPCTransactionFromBlockIndex(block, uint64(index), api.b.ChainConfig()) 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. // 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 { func (api *TransactionAPI) GetTransactionByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) (*RPCTransaction, error) {
if block, _ := api.b.BlockByHash(ctx, blockHash); block != nil { block, err := api.b.BlockByHash(ctx, blockHash)
return newRPCTransactionFromBlockIndex(block, uint64(index), api.b.ChainConfig()) 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. // 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 { if number == rpc.PendingBlockNumber {
return b.pending, nil return b.pending, nil
} }
if number == rpc.EarliestBlockNumber {
number = 0
}
return b.chain.GetBlockByNumber(uint64(number)), nil return b.chain.GetBlockByNumber(uint64(number)), nil
} }
func (b testBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { func (b testBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
return b.chain.GetBlockByHash(hash), nil 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 { func (b testBackend) NewMatcherBackend() filtermaps.MatcherBackend {
panic("implement me") panic("implement me")
} }
func (b testBackend) HistoryPruningCutoff() uint64 { return b.chain.HistoryPruningCutoff() }
func TestEstimateGas(t *testing.T) { func TestEstimateGas(t *testing.T) {
t.Parallel() t.Parallel()
// Initialize test accounts // Initialize test accounts

View file

@ -85,6 +85,7 @@ type Backend interface {
ChainConfig() *params.ChainConfig ChainConfig() *params.ChainConfig
Engine() consensus.Engine Engine() consensus.Engine
HistoryPruningCutoff() uint64
// This is copied from filters.Backend // This is copied from filters.Backend
// eth/filters needs to be initialized from this backend type, so methods needed by // 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) Engine() consensus.Engine { return nil }
func (b *backendMock) NewMatcherBackend() filtermaps.MatcherBackend { 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 type BlockNumber int64
const ( const (
EarliestBlockNumber = BlockNumber(-5)
SafeBlockNumber = BlockNumber(-4) SafeBlockNumber = BlockNumber(-4)
FinalizedBlockNumber = BlockNumber(-3) FinalizedBlockNumber = BlockNumber(-3)
LatestBlockNumber = BlockNumber(-2) LatestBlockNumber = BlockNumber(-2)
PendingBlockNumber = BlockNumber(-1) PendingBlockNumber = BlockNumber(-1)
EarliestBlockNumber = BlockNumber(0)
) )
// UnmarshalJSON parses the given JSON fragment into a BlockNumber. It supports: // UnmarshalJSON parses the given JSON fragment into a BlockNumber. It supports: