eth, core, internal, graphql: return error only if indexing is not finished

This commit is contained in:
Gary Rong 2024-01-18 16:04:28 +08:00
parent 0f3c1d0d48
commit 64196f9657
7 changed files with 72 additions and 53 deletions

View file

@ -186,7 +186,7 @@ func DefaultCacheConfigWithScheme(scheme string) *CacheConfig {
} }
// txLookup is wrapper over transaction lookup along with the corresponding // txLookup is wrapper over transaction lookup along with the corresponding
// transaction itself. // transaction object.
type txLookup struct { type txLookup struct {
lookup *rawdb.LegacyTxLookupEntry lookup *rawdb.LegacyTxLookupEntry
transaction *types.Transaction transaction *types.Transaction
@ -194,18 +194,17 @@ type txLookup struct {
// txIndexProgress is the struct describing the progress for transaction indexing. // txIndexProgress is the struct describing the progress for transaction indexing.
type txIndexProgress struct { type txIndexProgress struct {
tail uint64 // the oldest block indexed for transactions head uint64 // the current chain head
head uint64 // the latest block indexed for transactions indexed uint64 // the number of blocks have been indexed
limit uint64 // the number of blocks required for transaction indexing(0 means the whole chain) limit uint64 // the number of blocks required for transaction indexing(0 means the whole chain)
} }
// Error implements Error returning the progress in string format. // done returns an indicator if the transaction indexing is finished.
func (prog txIndexProgress) Error() string { func (prog txIndexProgress) done() bool {
limit := "entire chain" if prog.limit == 0 {
if prog.limit != 0 { return prog.indexed == (prog.head + 1) // genesis included
limit = fmt.Sprintf("last %d blocks", prog.limit)
} }
return fmt.Sprintf("index-tail: %d, index-head: %d, limit: %s", prog.tail, prog.head, limit) return prog.indexed >= prog.limit
} }
// BlockChain represents the canonical chain given a database with a genesis // BlockChain represents the canonical chain given a database with a genesis
@ -2453,18 +2452,17 @@ func (bc *BlockChain) indexBlocks(tail *uint64, head uint64, done chan struct{})
// reportTxIndexProgress returns the tx indexing progress. // reportTxIndexProgress returns the tx indexing progress.
func (bc *BlockChain) reportTxIndexProgress(head uint64) txIndexProgress { func (bc *BlockChain) reportTxIndexProgress(head uint64) txIndexProgress {
tail := rawdb.ReadTxIndexTail(bc.db) var (
if tail == nil { indexed uint64
return txIndexProgress{ tail = rawdb.ReadTxIndexTail(bc.db)
tail: 0, // not indexed yet )
head: 0, // not indexed yet if tail != nil {
limit: bc.txLookupLimit, indexed = head - *tail + 1
}
} }
return txIndexProgress{ return txIndexProgress{
tail: *tail, head: head,
head: head, indexed: indexed,
limit: bc.txLookupLimit, limit: bc.txLookupLimit,
} }
} }
@ -2510,6 +2508,7 @@ func (bc *BlockChain) maintainTxIndex() {
// indexer is never triggered. // indexer is never triggered.
if head := rawdb.ReadHeadBlock(bc.db); head != nil && head.Number().Uint64() != 0 { if head := rawdb.ReadHeadBlock(bc.db); head != nil && head.Number().Uint64() != 0 {
done = make(chan struct{}) done = make(chan struct{})
lastHead = head.Number().Uint64()
go bc.indexBlocks(rawdb.ReadTxIndexTail(bc.db), head.NumberU64(), done) go bc.indexBlocks(rawdb.ReadTxIndexTail(bc.db), head.NumberU64(), done)
} }
for { for {

View file

@ -17,6 +17,7 @@
package core package core
import ( import (
"errors"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -254,9 +255,16 @@ func (bc *BlockChain) GetAncestor(hash common.Hash, number, ancestor uint64, max
return bc.hc.GetAncestor(hash, number, ancestor, maxNonCanonical) return bc.hc.GetAncestor(hash, number, ancestor, maxNonCanonical)
} }
// GetTransactionLookup retrieves the lookup along with the transaction itself // GetTransactionLookup retrieves the lookup along with the transaction
// associate with the given transaction hash. A non-nil error will be returned // itself associate with the given transaction hash.
// if the transaction is not found. //
// An error will be returned if the transaction is not found, and background
// indexing for transactions is still in progress. The transaction might be
// reachable shortly once it's indexed.
//
// A null will be returned in the transaction is not found and background
// transaction indexing is already finished. The transaction is not existent
// from the node's perspective.
func (bc *BlockChain) GetTransactionLookup(hash common.Hash) (*rawdb.LegacyTxLookupEntry, *types.Transaction, error) { func (bc *BlockChain) GetTransactionLookup(hash common.Hash) (*rawdb.LegacyTxLookupEntry, *types.Transaction, error) {
// Short circuit if the txlookup already in the cache, retrieve otherwise // Short circuit if the txlookup already in the cache, retrieve otherwise
if item, exist := bc.txLookupCache.Get(hash); exist { if item, exist := bc.txLookupCache.Get(hash); exist {
@ -264,13 +272,18 @@ func (bc *BlockChain) GetTransactionLookup(hash common.Hash) (*rawdb.LegacyTxLoo
} }
tx, blockHash, blockNumber, txIndex := rawdb.ReadTransaction(bc.db, hash) tx, blockHash, blockNumber, txIndex := rawdb.ReadTransaction(bc.db, hash)
if tx == nil { if tx == nil {
// The transaction can either be non-existent, or just not indexed
// yet. Return the tx indexing progress as well for better UX.
progress, err := bc.askTxIndexProgress() progress, err := bc.askTxIndexProgress()
if err != nil { if err != nil {
return nil, nil, err return nil, nil, nil
} }
return nil, nil, progress // The transaction indexing is not finished yet, returning an
// error to explicitly indicate it.
if !progress.done() {
return nil, nil, errors.New("transaction is still indexing")
}
// The transaction is already indexed, the transaction is either
// not existent or not in the range of index, returning null.
return nil, nil, nil
} }
lookup := &rawdb.LegacyTxLookupEntry{ lookup := &rawdb.LegacyTxLookupEntry{
BlockHash: blockHash, BlockHash: blockHash,

View file

@ -19,7 +19,6 @@ package eth
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"math/big" "math/big"
"time" "time"
@ -310,14 +309,24 @@ func (b *EthAPIBackend) GetPoolTransaction(hash common.Hash) *types.Transaction
} }
// GetTransaction retrieves the lookup along with the transaction itself associate // GetTransaction retrieves the lookup along with the transaction itself associate
// with the given transaction hash. A non-nil error will be returned if the // with the given transaction hash.
// transaction is not found. //
func (b *EthAPIBackend) GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) { // An error will be returned if the transaction is not found, and background
// indexing for transactions is still in progress. The error is used to indicate the
// scenario explicitly that the transaction might be reachable shortly.
//
// A null will be returned in the transaction is not found and background transaction
// indexing is already finished. The transaction is not existent from the perspective
// of node.
func (b *EthAPIBackend) GetTransaction(ctx context.Context, txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64, error) {
lookup, tx, err := b.eth.blockchain.GetTransactionLookup(txHash) lookup, tx, err := b.eth.blockchain.GetTransactionLookup(txHash)
if err != nil { if err != nil {
return nil, common.Hash{}, 0, 0, fmt.Errorf("tx is not existent or not indexed, %w", err) return false, nil, common.Hash{}, 0, 0, err
} }
return tx, lookup.BlockHash, lookup.BlockIndex, lookup.Index, nil if lookup == nil || tx == nil {
return false, nil, common.Hash{}, 0, 0, nil
}
return true, tx, lookup.BlockHash, lookup.BlockIndex, lookup.Index, nil
} }
func (b *EthAPIBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) { func (b *EthAPIBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) {

View file

@ -80,7 +80,7 @@ type Backend interface {
HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error)
BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error)
BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error)
GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) GetTransaction(ctx context.Context, txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64, error)
RPCGasCap() uint64 RPCGasCap() uint64
ChainConfig() *params.ChainConfig ChainConfig() *params.ChainConfig
Engine() consensus.Engine Engine() consensus.Engine
@ -826,12 +826,12 @@ func containsTx(block *types.Block, hash common.Hash) bool {
// TraceTransaction returns the structured logs created during the execution of EVM // TraceTransaction returns the structured logs created during the execution of EVM
// and returns them as a JSON object. // and returns them as a JSON object.
func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *TraceConfig) (interface{}, error) { func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *TraceConfig) (interface{}, error) {
tx, blockHash, blockNumber, index, err := api.backend.GetTransaction(ctx, hash) found, _, blockHash, blockNumber, index, err := api.backend.GetTransaction(ctx, hash)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Only mined txes are supported // Only mined txes are supported
if tx == nil { if !found {
return nil, errTxNotFound return nil, errTxNotFound
} }
// It shouldn't happen in practice. // It shouldn't happen in practice.

View file

@ -230,8 +230,8 @@ func (t *Transaction) resolve(ctx context.Context) (*types.Transaction, *Block)
return t.tx, t.block return t.tx, t.block
} }
// Try to return an already finalized transaction // Try to return an already finalized transaction
tx, blockHash, _, index, err := t.r.backend.GetTransaction(ctx, t.hash) found, tx, blockHash, _, index, _ := t.r.backend.GetTransaction(ctx, t.hash)
if err == nil && tx != nil { if found {
t.tx = tx t.tx = tx
blockNrOrHash := rpc.BlockNumberOrHashWithHash(blockHash, false) blockNrOrHash := rpc.BlockNumberOrHashWithHash(blockHash, false)
t.block = &Block{ t.block = &Block{

View file

@ -1652,8 +1652,8 @@ func (s *TransactionAPI) GetTransactionCount(ctx context.Context, address common
// GetTransactionByHash returns the transaction for the given hash // GetTransactionByHash returns the transaction for the given hash
func (s *TransactionAPI) GetTransactionByHash(ctx context.Context, hash common.Hash) (*RPCTransaction, error) { func (s *TransactionAPI) GetTransactionByHash(ctx context.Context, hash common.Hash) (*RPCTransaction, error) {
// Try to return an already finalized transaction // Try to return an already finalized transaction
tx, blockHash, blockNumber, index, err := s.b.GetTransaction(ctx, hash) found, tx, blockHash, blockNumber, index, err := s.b.GetTransaction(ctx, hash)
if err != nil { if !found {
// No finalized transaction, try to retrieve it from the pool // No finalized transaction, try to retrieve it from the pool
if tx := s.b.GetPoolTransaction(hash); tx != nil { if tx := s.b.GetPoolTransaction(hash); tx != nil {
return NewRPCPendingTransaction(tx, s.b.CurrentHeader(), s.b.ChainConfig()), nil return NewRPCPendingTransaction(tx, s.b.CurrentHeader(), s.b.ChainConfig()), nil
@ -1670,8 +1670,8 @@ func (s *TransactionAPI) GetTransactionByHash(ctx context.Context, hash common.H
// GetRawTransactionByHash returns the bytes of the transaction for the given hash. // GetRawTransactionByHash returns the bytes of the transaction for the given hash.
func (s *TransactionAPI) GetRawTransactionByHash(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) { func (s *TransactionAPI) GetRawTransactionByHash(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) {
// Retrieve a finalized transaction, or a pooled otherwise // Retrieve a finalized transaction, or a pooled otherwise
tx, _, _, _, err := s.b.GetTransaction(ctx, hash) found, tx, _, _, _, err := s.b.GetTransaction(ctx, hash)
if err != nil { if !found {
if tx = s.b.GetPoolTransaction(hash); tx == nil { if tx = s.b.GetPoolTransaction(hash); tx == nil {
return nil, err return nil, err
} }
@ -1682,11 +1682,12 @@ func (s *TransactionAPI) GetRawTransactionByHash(ctx context.Context, hash commo
// GetTransactionReceipt returns the transaction receipt for the given transaction hash. // GetTransactionReceipt returns the transaction receipt for the given transaction hash.
func (s *TransactionAPI) GetTransactionReceipt(ctx context.Context, hash common.Hash) (map[string]interface{}, error) { func (s *TransactionAPI) GetTransactionReceipt(ctx context.Context, hash common.Hash) (map[string]interface{}, error) {
tx, blockHash, blockNumber, index, err := s.b.GetTransaction(ctx, hash) found, tx, blockHash, blockNumber, index, err := s.b.GetTransaction(ctx, hash)
if err != nil { if err != nil {
// When the transaction doesn't exist or is not indexed yet, return nil, err // transaction is not fully indexed
// the RPC method should return JSON null as per specification. }
return nil, err if !found {
return nil, nil // transaction is not existent or reachable
} }
header, err := s.b.HeaderByHash(ctx, blockHash) header, err := s.b.HeaderByHash(ctx, blockHash)
if err != nil { if err != nil {
@ -2076,14 +2077,11 @@ func (api *DebugAPI) GetRawReceipts(ctx context.Context, blockNrOrHash rpc.Block
// GetRawTransaction returns the bytes of the transaction for the given hash. // GetRawTransaction returns the bytes of the transaction for the given hash.
func (s *DebugAPI) GetRawTransaction(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) { func (s *DebugAPI) GetRawTransaction(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) {
// Retrieve a finalized transaction, or a pooled otherwise // Retrieve a finalized transaction, or a pooled otherwise
tx, _, _, _, err := s.b.GetTransaction(ctx, hash) found, tx, _, _, _, err := s.b.GetTransaction(ctx, hash)
if err != nil { if !found {
return nil, err
}
if tx == nil {
if tx = s.b.GetPoolTransaction(hash); tx == nil { if tx = s.b.GetPoolTransaction(hash); tx == nil {
// Transaction not found anywhere, abort // Transaction not found anywhere, abort
return nil, nil return nil, err
} }
} }
return tx.MarshalBinary() return tx.MarshalBinary()

View file

@ -75,7 +75,7 @@ type Backend interface {
// Transaction pool API // Transaction pool API
SendTx(ctx context.Context, signedTx *types.Transaction) error SendTx(ctx context.Context, signedTx *types.Transaction) error
GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) GetTransaction(ctx context.Context, txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64, error)
GetPoolTransactions() (types.Transactions, error) GetPoolTransactions() (types.Transactions, error)
GetPoolTransaction(txHash common.Hash) *types.Transaction GetPoolTransaction(txHash common.Hash) *types.Transaction
GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error)