core, core/rawdb, eth/sync: no tx indexing during snap sync (#28703)
This change simplifies the logic for indexing transactions and enhances the UX when transaction is not found by returning more information to users. Transaction indexing is now considered as a part of the initial sync, and `eth.syncing` will thus be `true` if transaction indexing is not yet finished. API consumers can use the syncing status to determine if the node is ready to serve users.
This commit is contained in:
+25
-4
@@ -308,9 +308,25 @@ func (b *EthAPIBackend) GetPoolTransaction(hash common.Hash) *types.Transaction
|
||||
return b.eth.txPool.Get(hash)
|
||||
}
|
||||
|
||||
func (b *EthAPIBackend) GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) {
|
||||
tx, blockHash, blockNumber, index := rawdb.ReadTransaction(b.eth.ChainDb(), txHash)
|
||||
return tx, blockHash, blockNumber, index, nil
|
||||
// GetTransaction 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 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 false, nil, common.Hash{}, 0, 0, err
|
||||
}
|
||||
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) {
|
||||
@@ -338,7 +354,12 @@ func (b *EthAPIBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.S
|
||||
}
|
||||
|
||||
func (b *EthAPIBackend) SyncProgress() ethereum.SyncProgress {
|
||||
return b.eth.Downloader().Progress()
|
||||
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) {
|
||||
|
||||
+1
-1
@@ -322,7 +322,7 @@ func (s *Ethereum) APIs() []rpc.API {
|
||||
Service: NewMinerAPI(s),
|
||||
}, {
|
||||
Namespace: "eth",
|
||||
Service: downloader.NewDownloaderAPI(s.handler.downloader, s.eventMux),
|
||||
Service: downloader.NewDownloaderAPI(s.handler.downloader, s.blockchain, s.eventMux),
|
||||
}, {
|
||||
Namespace: "admin",
|
||||
Service: NewAdminAPI(s),
|
||||
|
||||
+56
-16
@@ -19,16 +19,20 @@ package downloader
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
// DownloaderAPI provides an API which gives information about the current synchronisation status.
|
||||
// It offers only methods that operates on data that can be available to anyone without security risks.
|
||||
// DownloaderAPI provides an API which gives information about the current
|
||||
// synchronisation status. It offers only methods that operates on data that
|
||||
// can be available to anyone without security risks.
|
||||
type DownloaderAPI struct {
|
||||
d *Downloader
|
||||
chain *core.BlockChain
|
||||
mux *event.TypeMux
|
||||
installSyncSubscription chan chan interface{}
|
||||
uninstallSyncSubscription chan *uninstallSyncSubscriptionRequest
|
||||
@@ -38,31 +42,57 @@ type DownloaderAPI struct {
|
||||
// listens for events from the downloader through the global event mux. In case it receives one of
|
||||
// these events it broadcasts it to all syncing subscriptions that are installed through the
|
||||
// installSyncSubscription channel.
|
||||
func NewDownloaderAPI(d *Downloader, m *event.TypeMux) *DownloaderAPI {
|
||||
func NewDownloaderAPI(d *Downloader, chain *core.BlockChain, m *event.TypeMux) *DownloaderAPI {
|
||||
api := &DownloaderAPI{
|
||||
d: d,
|
||||
chain: chain,
|
||||
mux: m,
|
||||
installSyncSubscription: make(chan chan interface{}),
|
||||
uninstallSyncSubscription: make(chan *uninstallSyncSubscriptionRequest),
|
||||
}
|
||||
|
||||
go api.eventLoop()
|
||||
|
||||
return api
|
||||
}
|
||||
|
||||
// eventLoop runs a loop until the event mux closes. It will install and uninstall new
|
||||
// sync subscriptions and broadcasts sync status updates to the installed sync subscriptions.
|
||||
// eventLoop runs a loop until the event mux closes. It will install and uninstall
|
||||
// new sync subscriptions and broadcasts sync status updates to the installed sync
|
||||
// subscriptions.
|
||||
//
|
||||
// The sync status pushed to subscriptions can be a stream like:
|
||||
// >>> {Syncing: true, Progress: {...}}
|
||||
// >>> {false}
|
||||
//
|
||||
// If the node is already synced up, then only a single event subscribers will
|
||||
// receive is {false}.
|
||||
func (api *DownloaderAPI) eventLoop() {
|
||||
var (
|
||||
sub = api.mux.Subscribe(StartEvent{}, DoneEvent{}, FailedEvent{})
|
||||
sub = api.mux.Subscribe(StartEvent{})
|
||||
syncSubscriptions = make(map[chan interface{}]struct{})
|
||||
checkInterval = time.Second * 60
|
||||
checkTimer = time.NewTimer(checkInterval)
|
||||
|
||||
// status flags
|
||||
started bool
|
||||
done bool
|
||||
|
||||
getProgress = func() ethereum.SyncProgress {
|
||||
prog := api.d.Progress()
|
||||
if txProg, err := api.chain.TxIndexProgress(); err == nil {
|
||||
prog.TxIndexFinishedBlocks = txProg.Indexed
|
||||
prog.TxIndexRemainingBlocks = txProg.Remaining
|
||||
}
|
||||
return prog
|
||||
}
|
||||
)
|
||||
defer checkTimer.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case i := <-api.installSyncSubscription:
|
||||
syncSubscriptions[i] = struct{}{}
|
||||
if done {
|
||||
i <- false
|
||||
}
|
||||
case u := <-api.uninstallSyncSubscription:
|
||||
delete(syncSubscriptions, u.c)
|
||||
close(u.uninstalled)
|
||||
@@ -70,21 +100,31 @@ func (api *DownloaderAPI) eventLoop() {
|
||||
if event == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var notification interface{}
|
||||
switch event.Data.(type) {
|
||||
case StartEvent:
|
||||
notification = &SyncingResult{
|
||||
started = true
|
||||
}
|
||||
case <-checkTimer.C:
|
||||
if !started {
|
||||
checkTimer.Reset(checkInterval)
|
||||
continue
|
||||
}
|
||||
prog := getProgress()
|
||||
if !prog.Done() {
|
||||
notification := &SyncingResult{
|
||||
Syncing: true,
|
||||
Status: api.d.Progress(),
|
||||
Status: prog,
|
||||
}
|
||||
case DoneEvent, FailedEvent:
|
||||
notification = false
|
||||
for c := range syncSubscriptions {
|
||||
c <- notification
|
||||
}
|
||||
checkTimer.Reset(checkInterval)
|
||||
continue
|
||||
}
|
||||
// broadcast
|
||||
for c := range syncSubscriptions {
|
||||
c <- notification
|
||||
c <- false
|
||||
}
|
||||
done = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-18
@@ -228,24 +228,6 @@ func (cs *chainSyncer) startSync(op *chainSyncOp) {
|
||||
|
||||
// doSync synchronizes the local blockchain with a remote peer.
|
||||
func (h *handler) doSync(op *chainSyncOp) error {
|
||||
if op.mode == downloader.SnapSync {
|
||||
// Before launch the snap sync, we have to ensure user uses the same
|
||||
// txlookup limit.
|
||||
// The main concern here is: during the snap sync Geth won't index the
|
||||
// block(generate tx indices) before the HEAD-limit. But if user changes
|
||||
// the limit in the next snap sync(e.g. user kill Geth manually and
|
||||
// restart) then it will be hard for Geth to figure out the oldest block
|
||||
// has been indexed. So here for the user-experience wise, it's non-optimal
|
||||
// that user can't change limit during the snap sync. If changed, Geth
|
||||
// will just blindly use the original one.
|
||||
limit := h.chain.TxLookupLimit()
|
||||
if stored := rawdb.ReadFastTxLookupLimit(h.database); stored == nil {
|
||||
rawdb.WriteFastTxLookupLimit(h.database, limit)
|
||||
} else if *stored != limit {
|
||||
h.chain.SetTxLookupLimit(*stored)
|
||||
log.Warn("Update txLookup limit", "provided", limit, "updated", *stored)
|
||||
}
|
||||
}
|
||||
// Run the sync cycle, and disable snap sync if we're past the pivot block
|
||||
err := h.downloader.LegacySync(op.peer.ID(), op.head, op.td, h.chain.Config().TerminalTotalDifficulty, op.mode)
|
||||
if err != nil {
|
||||
|
||||
+4
-4
@@ -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
|
||||
return nil, ethapi.NewTxIndexingError()
|
||||
}
|
||||
// Only mined txes are supported
|
||||
if tx == nil {
|
||||
if !found {
|
||||
return nil, errTxNotFound
|
||||
}
|
||||
// It shouldn't happen in practice.
|
||||
|
||||
@@ -113,9 +113,9 @@ func (b *testBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber)
|
||||
return b.chain.GetBlockByNumber(uint64(number)), nil
|
||||
}
|
||||
|
||||
func (b *testBackend) GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) {
|
||||
func (b *testBackend) GetTransaction(ctx context.Context, txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64, error) {
|
||||
tx, hash, blockNumber, index := rawdb.ReadTransaction(b.chaindb, txHash)
|
||||
return tx, hash, blockNumber, index, nil
|
||||
return tx != nil, tx, hash, blockNumber, index, nil
|
||||
}
|
||||
|
||||
func (b *testBackend) RPCGasCap() uint64 {
|
||||
|
||||
Reference in New Issue
Block a user