Skip to content

Commit

Permalink
eth, core, internal, graphql: return error only if indexing is not fi…
Browse files Browse the repository at this point in the history
…nished
  • Loading branch information
rjl493456442 committed Jan 18, 2024
1 parent 0f3c1d0 commit 64196f9
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 53 deletions.
39 changes: 19 additions & 20 deletions core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,26 +186,25 @@ func DefaultCacheConfigWithScheme(scheme string) *CacheConfig {
}

// txLookup is wrapper over transaction lookup along with the corresponding
// transaction itself.
// transaction object.
type txLookup struct {
lookup *rawdb.LegacyTxLookupEntry
transaction *types.Transaction
}

// txIndexProgress is the struct describing the progress for transaction indexing.
type txIndexProgress struct {
tail uint64 // the oldest block indexed for transactions
head uint64 // the latest block indexed for transactions
limit uint64 // the number of blocks required for transaction indexing(0 means the whole chain)
head uint64 // the current chain head
indexed uint64 // the number of blocks have been indexed
limit uint64 // the number of blocks required for transaction indexing(0 means the whole chain)
}

// Error implements Error returning the progress in string format.
func (prog txIndexProgress) Error() string {
limit := "entire chain"
if prog.limit != 0 {
limit = fmt.Sprintf("last %d blocks", prog.limit)
// done returns an indicator if the transaction indexing is finished.
func (prog txIndexProgress) done() bool {
if prog.limit == 0 {
return prog.indexed == (prog.head + 1) // genesis included
}
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
Expand Down Expand Up @@ -2453,18 +2452,17 @@ func (bc *BlockChain) indexBlocks(tail *uint64, head uint64, done chan struct{})

// reportTxIndexProgress returns the tx indexing progress.
func (bc *BlockChain) reportTxIndexProgress(head uint64) txIndexProgress {
tail := rawdb.ReadTxIndexTail(bc.db)
if tail == nil {
return txIndexProgress{
tail: 0, // not indexed yet
head: 0, // not indexed yet
limit: bc.txLookupLimit,
}
var (
indexed uint64
tail = rawdb.ReadTxIndexTail(bc.db)
)
if tail != nil {
indexed = head - *tail + 1
}
return txIndexProgress{
tail: *tail,
head: head,
limit: bc.txLookupLimit,
head: head,
indexed: indexed,
limit: bc.txLookupLimit,
}
}

Expand Down Expand Up @@ -2510,6 +2508,7 @@ func (bc *BlockChain) maintainTxIndex() {
// indexer is never triggered.
if head := rawdb.ReadHeadBlock(bc.db); head != nil && head.Number().Uint64() != 0 {
done = make(chan struct{})
lastHead = head.Number().Uint64()
go bc.indexBlocks(rawdb.ReadTxIndexTail(bc.db), head.NumberU64(), done)
}
for {
Expand Down
27 changes: 20 additions & 7 deletions core/blockchain_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package core

import (
"errors"
"math/big"

"github.com/ethereum/go-ethereum/common"
Expand Down Expand Up @@ -254,23 +255,35 @@ func (bc *BlockChain) GetAncestor(hash common.Hash, number, ancestor uint64, max
return bc.hc.GetAncestor(hash, number, ancestor, maxNonCanonical)
}

// GetTransactionLookup retrieves the lookup along with the transaction itself
// associate with the given transaction hash. A non-nil error will be returned
// if the transaction is not found.
// GetTransactionLookup retrieves the lookup along with the transaction
// itself associate with the given transaction hash.
//
// 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) {
// Short circuit if the txlookup already in the cache, retrieve otherwise
if item, exist := bc.txLookupCache.Get(hash); exist {
return item.lookup, item.transaction, nil
}
tx, blockHash, blockNumber, txIndex := rawdb.ReadTransaction(bc.db, hash)
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()
if err != nil {
return nil, nil, err
return nil, nil, nil
}
// 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")
}
return nil, nil, progress
// 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{
BlockHash: blockHash,
Expand Down
21 changes: 15 additions & 6 deletions eth/api_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package eth
import (
"context"
"errors"
"fmt"
"math/big"
"time"

Expand Down Expand Up @@ -310,14 +309,24 @@ func (b *EthAPIBackend) GetPoolTransaction(hash common.Hash) *types.Transaction
}

// GetTransaction retrieves the lookup along with the transaction itself associate
// with the given transaction hash. A non-nil error will be returned if the
// transaction is not found.
func (b *EthAPIBackend) GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) {
// with the given transaction hash.
//
// 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)
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
}
if lookup == nil || tx == nil {
return false, nil, common.Hash{}, 0, 0, nil
}
return tx, lookup.BlockHash, lookup.BlockIndex, lookup.Index, nil
return true, tx, lookup.BlockHash, lookup.BlockIndex, lookup.Index, nil
}

func (b *EthAPIBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) {
Expand Down
6 changes: 3 additions & 3 deletions eth/tracers/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ type Backend interface {
HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error)
BlockByHash(ctx context.Context, hash common.Hash) (*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
ChainConfig() *params.ChainConfig
Engine() consensus.Engine
Expand Down Expand Up @@ -826,12 +826,12 @@ func containsTx(block *types.Block, hash common.Hash) bool {
// TraceTransaction returns the structured logs created during the execution of EVM
// and returns them as a JSON object.
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 {
return nil, err
}
// Only mined txes are supported
if tx == nil {
if !found {
return nil, errTxNotFound
}
// It shouldn't happen in practice.
Expand Down
4 changes: 2 additions & 2 deletions graphql/graphql.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,8 +230,8 @@ func (t *Transaction) resolve(ctx context.Context) (*types.Transaction, *Block)
return t.tx, t.block
}
// Try to return an already finalized transaction
tx, blockHash, _, index, err := t.r.backend.GetTransaction(ctx, t.hash)
if err == nil && tx != nil {
found, tx, blockHash, _, index, _ := t.r.backend.GetTransaction(ctx, t.hash)
if found {
t.tx = tx
blockNrOrHash := rpc.BlockNumberOrHashWithHash(blockHash, false)
t.block = &Block{
Expand Down
26 changes: 12 additions & 14 deletions internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1652,8 +1652,8 @@ func (s *TransactionAPI) GetTransactionCount(ctx context.Context, address common
// GetTransactionByHash returns the transaction for the given hash
func (s *TransactionAPI) GetTransactionByHash(ctx context.Context, hash common.Hash) (*RPCTransaction, error) {
// Try to return an already finalized transaction
tx, blockHash, blockNumber, index, err := s.b.GetTransaction(ctx, hash)
if err != nil {
found, tx, blockHash, blockNumber, index, err := s.b.GetTransaction(ctx, hash)
if !found {
// No finalized transaction, try to retrieve it from the pool
if tx := s.b.GetPoolTransaction(hash); tx != nil {
return NewRPCPendingTransaction(tx, s.b.CurrentHeader(), s.b.ChainConfig()), nil
Expand All @@ -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.
func (s *TransactionAPI) GetRawTransactionByHash(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) {
// Retrieve a finalized transaction, or a pooled otherwise
tx, _, _, _, err := s.b.GetTransaction(ctx, hash)
if err != nil {
found, tx, _, _, _, err := s.b.GetTransaction(ctx, hash)
if !found {
if tx = s.b.GetPoolTransaction(hash); tx == nil {
return nil, err
}
Expand All @@ -1682,11 +1682,12 @@ func (s *TransactionAPI) GetRawTransactionByHash(ctx context.Context, hash commo

// GetTransactionReceipt returns the transaction receipt for the given transaction hash.
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 {
// When the transaction doesn't exist or is not indexed yet,
// the RPC method should return JSON null as per specification.
return nil, err
return nil, err // transaction is not fully indexed
}
if !found {
return nil, nil // transaction is not existent or reachable
}
header, err := s.b.HeaderByHash(ctx, blockHash)
if err != nil {
Expand Down Expand Up @@ -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.
func (s *DebugAPI) GetRawTransaction(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) {
// Retrieve a finalized transaction, or a pooled otherwise
tx, _, _, _, err := s.b.GetTransaction(ctx, hash)
if err != nil {
return nil, err
}
if tx == nil {
found, tx, _, _, _, err := s.b.GetTransaction(ctx, hash)
if !found {
if tx = s.b.GetPoolTransaction(hash); tx == nil {
// Transaction not found anywhere, abort
return nil, nil
return nil, err
}
}
return tx.MarshalBinary()
Expand Down
2 changes: 1 addition & 1 deletion internal/ethapi/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ type Backend interface {

// Transaction pool API
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)
GetPoolTransaction(txHash common.Hash) *types.Transaction
GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error)
Expand Down

0 comments on commit 64196f9

Please sign in to comment.