1
0
Fork 0
forked from forks/go-ethereum
go-ethereum-modded-tocallarg/eth/api_backend.go
Sina M 615d29f7c2
core: reduce load on txindexer from API (#31752)
Fixes https://github.com/ethereum/go-ethereum/issues/31732.

This logic was removed in the recent refactoring in the txindexer to
handle history cutoff (#31393). It was first introduced in this PR:
https://github.com/ethereum/go-ethereum/pull/28908.

I have tested it and it works as an alternative to #31745.

This PR packs 3 changes to the flow of fetching txs from the API:

- It caches the indexer tail after each run is over to avoid hitting the
db all the time as was done originally in #28908.

- Changes `backend.GetTransaction`. It doesn't return an error anymore
when tx indexer is in progress. It shifts the responsibility to the
caller to check the progress. The reason is that in most cases we anyway
check the txpool for the tx. If it was indeed a pending tx we can avoid
the indexer progress check.

---------

Co-authored-by: Gary Rong <garyrong0905@gmail.com>
2025-05-05 10:07:55 +08:00

470 lines
16 KiB
Go

// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library 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.
//
// The go-ethereum library 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 the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package eth
import (
"context"
"errors"
"math/big"
"time"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/filtermaps"
"github.com/ethereum/go-ethereum/core/history"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/txpool"
"github.com/ethereum/go-ethereum/core/txpool/locals"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/gasprice"
"github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
)
// EthAPIBackend implements ethapi.Backend and tracers.Backend for full nodes
type EthAPIBackend struct {
extRPCEnabled bool
allowUnprotectedTxs bool
eth *Ethereum
gpo *gasprice.Oracle
}
// ChainConfig returns the active chain configuration.
func (b *EthAPIBackend) ChainConfig() *params.ChainConfig {
return b.eth.blockchain.Config()
}
func (b *EthAPIBackend) CurrentBlock() *types.Header {
return b.eth.blockchain.CurrentBlock()
}
func (b *EthAPIBackend) SetHead(number uint64) {
b.eth.handler.downloader.Cancel()
b.eth.blockchain.SetHead(number)
}
func (b *EthAPIBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) {
// Pending block is only known by the miner
if number == rpc.PendingBlockNumber {
block, _, _ := b.eth.miner.Pending()
if block == nil {
return nil, errors.New("pending block is not available")
}
return block.Header(), nil
}
// Otherwise resolve and return the block
if number == rpc.LatestBlockNumber {
return b.eth.blockchain.CurrentBlock(), nil
}
if number == rpc.FinalizedBlockNumber {
block := b.eth.blockchain.CurrentFinalBlock()
if block == nil {
return nil, errors.New("finalized block not found")
}
return block, nil
}
if number == rpc.SafeBlockNumber {
block := b.eth.blockchain.CurrentSafeBlock()
if block == nil {
return nil, errors.New("safe block not found")
}
return block, nil
}
var bn uint64
if number == rpc.EarliestBlockNumber {
bn = b.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) {
if blockNr, ok := blockNrOrHash.Number(); ok {
return b.HeaderByNumber(ctx, blockNr)
}
if hash, ok := blockNrOrHash.Hash(); ok {
header := b.eth.blockchain.GetHeaderByHash(hash)
if header == nil {
return nil, errors.New("header for hash not found")
}
if blockNrOrHash.RequireCanonical && b.eth.blockchain.GetCanonicalHash(header.Number.Uint64()) != hash {
return nil, errors.New("hash is not currently canonical")
}
return header, nil
}
return nil, errors.New("invalid arguments; neither block nor hash specified")
}
func (b *EthAPIBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
return b.eth.blockchain.GetHeaderByHash(hash), nil
}
func (b *EthAPIBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) {
// Pending block is only known by the miner
if number == rpc.PendingBlockNumber {
block, _, _ := b.eth.miner.Pending()
if block == nil {
return nil, errors.New("pending block is not available")
}
return block, nil
}
// Otherwise resolve and return the block
if number == rpc.LatestBlockNumber {
header := b.eth.blockchain.CurrentBlock()
return b.eth.blockchain.GetBlock(header.Hash(), header.Number.Uint64()), nil
}
if number == rpc.FinalizedBlockNumber {
header := b.eth.blockchain.CurrentFinalBlock()
if header == nil {
return nil, errors.New("finalized block not found")
}
return b.eth.blockchain.GetBlock(header.Hash(), header.Number.Uint64()), nil
}
if number == rpc.SafeBlockNumber {
header := b.eth.blockchain.CurrentSafeBlock()
if header == nil {
return nil, errors.New("safe block not found")
}
return b.eth.blockchain.GetBlock(header.Hash(), header.Number.Uint64()), nil
}
bn := uint64(number) // the resolved number
if number == rpc.EarliestBlockNumber {
bn = b.HistoryPruningCutoff()
}
block := b.eth.blockchain.GetBlockByNumber(bn)
if block == nil && bn < b.HistoryPruningCutoff() {
return nil, &history.PrunedHistoryError{}
}
return block, nil
}
func (b *EthAPIBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
number := b.eth.blockchain.GetBlockNumber(hash)
if number == nil {
return nil, nil
}
block := b.eth.blockchain.GetBlock(hash, *number)
if block == nil && *number < b.HistoryPruningCutoff() {
return nil, &history.PrunedHistoryError{}
}
return block, nil
}
// GetBody returns body of a block. It does not resolve special block numbers.
func (b *EthAPIBackend) GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error) {
if number < 0 || hash == (common.Hash{}) {
return nil, errors.New("invalid arguments; expect hash and no special block numbers")
}
body := b.eth.blockchain.GetBody(hash)
if body == nil {
if uint64(number) < b.HistoryPruningCutoff() {
return nil, &history.PrunedHistoryError{}
}
return nil, errors.New("block body not found")
}
return body, nil
}
func (b *EthAPIBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) {
if blockNr, ok := blockNrOrHash.Number(); ok {
return b.BlockByNumber(ctx, blockNr)
}
if hash, ok := blockNrOrHash.Hash(); ok {
header := b.eth.blockchain.GetHeaderByHash(hash)
if header == nil {
return nil, errors.New("header for hash not found")
}
if blockNrOrHash.RequireCanonical && b.eth.blockchain.GetCanonicalHash(header.Number.Uint64()) != hash {
return nil, errors.New("hash is not currently canonical")
}
block := b.eth.blockchain.GetBlock(hash, header.Number.Uint64())
if block == nil {
if header.Number.Uint64() < b.HistoryPruningCutoff() {
return nil, &history.PrunedHistoryError{}
}
return nil, errors.New("header found, but block body is missing")
}
return block, nil
}
return nil, errors.New("invalid arguments; neither block nor hash specified")
}
func (b *EthAPIBackend) Pending() (*types.Block, types.Receipts, *state.StateDB) {
return b.eth.miner.Pending()
}
func (b *EthAPIBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) {
// Pending state is only known by the miner
if number == rpc.PendingBlockNumber {
block, _, state := b.eth.miner.Pending()
if block == nil || state == nil {
return nil, nil, errors.New("pending state is not available")
}
return state, block.Header(), nil
}
// Otherwise resolve the block number and return its state
header, err := b.HeaderByNumber(ctx, number)
if err != nil {
return nil, nil, err
}
if header == nil {
return nil, nil, errors.New("header not found")
}
stateDb, err := b.eth.BlockChain().StateAt(header.Root)
if err != nil {
return nil, nil, err
}
return stateDb, header, nil
}
func (b *EthAPIBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) {
if blockNr, ok := blockNrOrHash.Number(); ok {
return b.StateAndHeaderByNumber(ctx, blockNr)
}
if hash, ok := blockNrOrHash.Hash(); ok {
header, err := b.HeaderByHash(ctx, hash)
if err != nil {
return nil, nil, err
}
if header == nil {
return nil, nil, errors.New("header for hash not found")
}
if blockNrOrHash.RequireCanonical && b.eth.blockchain.GetCanonicalHash(header.Number.Uint64()) != hash {
return nil, nil, errors.New("hash is not currently canonical")
}
stateDb, err := b.eth.BlockChain().StateAt(header.Root)
if err != nil {
return nil, nil, err
}
return stateDb, header, nil
}
return nil, nil, errors.New("invalid arguments; neither block nor hash specified")
}
func (b *EthAPIBackend) HistoryPruningCutoff() uint64 {
bn, _ := b.eth.blockchain.HistoryPruningCutoff()
return bn
}
func (b *EthAPIBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
return b.eth.blockchain.GetReceiptsByHash(hash), nil
}
func (b *EthAPIBackend) GetLogs(ctx context.Context, hash common.Hash, number uint64) ([][]*types.Log, error) {
return rawdb.ReadLogs(b.eth.chainDb, hash, number), nil
}
func (b *EthAPIBackend) GetEVM(ctx context.Context, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) *vm.EVM {
if vmConfig == nil {
vmConfig = b.eth.blockchain.GetVMConfig()
}
var context vm.BlockContext
if blockCtx != nil {
context = *blockCtx
} else {
context = core.NewEVMBlockContext(header, b.eth.BlockChain(), nil)
}
return vm.NewEVM(context, state, b.ChainConfig(), *vmConfig)
}
func (b *EthAPIBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription {
return b.eth.BlockChain().SubscribeRemovedLogsEvent(ch)
}
func (b *EthAPIBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription {
return b.eth.BlockChain().SubscribeChainEvent(ch)
}
func (b *EthAPIBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription {
return b.eth.BlockChain().SubscribeChainHeadEvent(ch)
}
func (b *EthAPIBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription {
return b.eth.BlockChain().SubscribeLogsEvent(ch)
}
func (b *EthAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error {
err := b.eth.txPool.Add([]*types.Transaction{signedTx}, false)[0]
// If the local transaction tracker is not configured, returns whatever
// returned from the txpool.
if b.eth.localTxTracker == nil {
return err
}
// If the transaction fails with an error indicating it is invalid, or if there is
// very little chance it will be accepted later (e.g., the gas price is below the
// configured minimum, or the sender has insufficient funds to cover the cost),
// propagate the error to the user.
if err != nil && !locals.IsTemporaryReject(err) {
return err
}
// No error will be returned to user if the transaction fails with a temporary
// error and might be accepted later (e.g., the transaction pool is full).
// Locally submitted transactions will be resubmitted later via the local tracker.
b.eth.localTxTracker.Track(signedTx)
return nil
}
func (b *EthAPIBackend) GetPoolTransactions() (types.Transactions, error) {
pending := b.eth.txPool.Pending(txpool.PendingFilter{})
var txs types.Transactions
for _, batch := range pending {
for _, lazy := range batch {
if tx := lazy.Resolve(); tx != nil {
txs = append(txs, tx)
}
}
}
return txs, nil
}
func (b *EthAPIBackend) GetPoolTransaction(hash common.Hash) *types.Transaction {
return b.eth.txPool.Get(hash)
}
// GetTransaction retrieves the lookup along with the transaction itself associate
// with the given transaction hash.
//
// A null will be returned if the transaction is not found. The transaction is not
// existent from the node's perspective. This can be due to the transaction indexer
// not being finished. The caller must explicitly check the indexer progress.
func (b *EthAPIBackend) GetTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) {
lookup, tx := b.eth.blockchain.GetTransactionLookup(txHash)
if lookup == nil || tx == nil {
return false, nil, common.Hash{}, 0, 0
}
return true, tx, lookup.BlockHash, lookup.BlockIndex, lookup.Index
}
// TxIndexDone returns true if the transaction indexer has finished indexing.
func (b *EthAPIBackend) TxIndexDone() bool {
return b.eth.blockchain.TxIndexDone()
}
func (b *EthAPIBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) {
return b.eth.txPool.PoolNonce(addr), nil
}
func (b *EthAPIBackend) Stats() (runnable int, blocked int) {
return b.eth.txPool.Stats()
}
func (b *EthAPIBackend) TxPoolContent() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction) {
return b.eth.txPool.Content()
}
func (b *EthAPIBackend) TxPoolContentFrom(addr common.Address) ([]*types.Transaction, []*types.Transaction) {
return b.eth.txPool.ContentFrom(addr)
}
func (b *EthAPIBackend) TxPool() *txpool.TxPool {
return b.eth.txPool
}
func (b *EthAPIBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription {
return b.eth.txPool.SubscribeTransactions(ch, true)
}
func (b *EthAPIBackend) SyncProgress(ctx context.Context) ethereum.SyncProgress {
prog := b.eth.Downloader().Progress()
if txProg, err := b.eth.blockchain.TxIndexProgress(); err == nil {
prog.TxIndexFinishedBlocks = txProg.Indexed
prog.TxIndexRemainingBlocks = txProg.Remaining
}
return prog
}
func (b *EthAPIBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) {
return b.gpo.SuggestTipCap(ctx)
}
func (b *EthAPIBackend) FeeHistory(ctx context.Context, blockCount uint64, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (firstBlock *big.Int, reward [][]*big.Int, baseFee []*big.Int, gasUsedRatio []float64, baseFeePerBlobGas []*big.Int, blobGasUsedRatio []float64, err error) {
return b.gpo.FeeHistory(ctx, blockCount, lastBlock, rewardPercentiles)
}
func (b *EthAPIBackend) BlobBaseFee(ctx context.Context) *big.Int {
if excess := b.CurrentHeader().ExcessBlobGas; excess != nil {
return eip4844.CalcBlobFee(b.ChainConfig(), b.CurrentHeader())
}
return nil
}
func (b *EthAPIBackend) ChainDb() ethdb.Database {
return b.eth.ChainDb()
}
func (b *EthAPIBackend) AccountManager() *accounts.Manager {
return b.eth.AccountManager()
}
func (b *EthAPIBackend) ExtRPCEnabled() bool {
return b.extRPCEnabled
}
func (b *EthAPIBackend) UnprotectedAllowed() bool {
return b.allowUnprotectedTxs
}
func (b *EthAPIBackend) RPCGasCap() uint64 {
return b.eth.config.RPCGasCap
}
func (b *EthAPIBackend) RPCEVMTimeout() time.Duration {
return b.eth.config.RPCEVMTimeout
}
func (b *EthAPIBackend) RPCTxFeeCap() float64 {
return b.eth.config.RPCTxFeeCap
}
func (b *EthAPIBackend) CurrentView() *filtermaps.ChainView {
head := b.eth.blockchain.CurrentBlock()
if head == nil {
return nil
}
return filtermaps.NewChainView(b.eth.blockchain, head.Number.Uint64(), head.Hash())
}
func (b *EthAPIBackend) NewMatcherBackend() filtermaps.MatcherBackend {
return b.eth.filterMaps.NewMatcherBackend()
}
func (b *EthAPIBackend) Engine() consensus.Engine {
return b.eth.engine
}
func (b *EthAPIBackend) CurrentHeader() *types.Header {
return b.eth.blockchain.CurrentHeader()
}
func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, readOnly bool, preferDisk bool) (*state.StateDB, tracers.StateReleaseFunc, error) {
return b.eth.stateAtBlock(ctx, block, reexec, base, readOnly, preferDisk)
}
func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*types.Transaction, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) {
return b.eth.stateAtTransaction(ctx, block, txIndex, reexec)
}