diff --git a/core/blockchain.go b/core/blockchain.go index 4195bcc95f..fb7adf2444 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -186,7 +186,7 @@ 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 @@ -194,18 +194,17 @@ type txLookup struct { // 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 @@ -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, } } @@ -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 { diff --git a/core/blockchain_reader.go b/core/blockchain_reader.go index 9457d1e672..b761876b3b 100644 --- a/core/blockchain_reader.go +++ b/core/blockchain_reader.go @@ -17,6 +17,7 @@ package core import ( + "errors" "math/big" "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) } -// 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 { @@ -264,13 +272,18 @@ func (bc *BlockChain) GetTransactionLookup(hash common.Hash) (*rawdb.LegacyTxLoo } 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 } - 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{ BlockHash: blockHash, diff --git a/eth/api_backend.go b/eth/api_backend.go index 3cc22dcfe0..c2019c3dd3 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -19,7 +19,6 @@ package eth import ( "context" "errors" - "fmt" "math/big" "time" @@ -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 } - 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) { diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 7c0028601d..67b7d21a40 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -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 @@ -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. diff --git a/graphql/graphql.go b/graphql/graphql.go index 49be23af69..e76ddac6bc 100644 --- a/graphql/graphql.go +++ b/graphql/graphql.go @@ -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{ diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index bef323c090..6d33f2053d 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -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 @@ -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 } @@ -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 { @@ -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() diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index 50f338f5ca..5f408ba20b 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -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)