github.1485827954.workers.dev/ethereum/go-ethereum@v1.14.3/core/txindexer.go (about)

     1  // Copyright 2024 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>
    16  
    17  package core
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  
    23  	"github.com/ethereum/go-ethereum/core/rawdb"
    24  	"github.com/ethereum/go-ethereum/ethdb"
    25  	"github.com/ethereum/go-ethereum/log"
    26  )
    27  
    28  // TxIndexProgress is the struct describing the progress for transaction indexing.
    29  type TxIndexProgress struct {
    30  	Indexed   uint64 // number of blocks whose transactions are indexed
    31  	Remaining uint64 // number of blocks whose transactions are not indexed yet
    32  }
    33  
    34  // Done returns an indicator if the transaction indexing is finished.
    35  func (progress TxIndexProgress) Done() bool {
    36  	return progress.Remaining == 0
    37  }
    38  
    39  // txIndexer is the module responsible for maintaining transaction indexes
    40  // according to the configured indexing range by users.
    41  type txIndexer struct {
    42  	// limit is the maximum number of blocks from head whose tx indexes
    43  	// are reserved:
    44  	//  * 0: means the entire chain should be indexed
    45  	//  * N: means the latest N blocks [HEAD-N+1, HEAD] should be indexed
    46  	//       and all others shouldn't.
    47  	limit    uint64
    48  	db       ethdb.Database
    49  	progress chan chan TxIndexProgress
    50  	term     chan chan struct{}
    51  	closed   chan struct{}
    52  }
    53  
    54  // newTxIndexer initializes the transaction indexer.
    55  func newTxIndexer(limit uint64, chain *BlockChain) *txIndexer {
    56  	indexer := &txIndexer{
    57  		limit:    limit,
    58  		db:       chain.db,
    59  		progress: make(chan chan TxIndexProgress),
    60  		term:     make(chan chan struct{}),
    61  		closed:   make(chan struct{}),
    62  	}
    63  	go indexer.loop(chain)
    64  
    65  	var msg string
    66  	if limit == 0 {
    67  		msg = "entire chain"
    68  	} else {
    69  		msg = fmt.Sprintf("last %d blocks", limit)
    70  	}
    71  	log.Info("Initialized transaction indexer", "range", msg)
    72  
    73  	return indexer
    74  }
    75  
    76  // run executes the scheduled indexing/unindexing task in a separate thread.
    77  // If the stop channel is closed, the task should be terminated as soon as
    78  // possible, the done channel will be closed once the task is finished.
    79  func (indexer *txIndexer) run(tail *uint64, head uint64, stop chan struct{}, done chan struct{}) {
    80  	defer func() { close(done) }()
    81  
    82  	// Short circuit if chain is empty and nothing to index.
    83  	if head == 0 {
    84  		return
    85  	}
    86  	// The tail flag is not existent, it means the node is just initialized
    87  	// and all blocks in the chain (part of them may from ancient store) are
    88  	// not indexed yet, index the chain according to the configured limit.
    89  	if tail == nil {
    90  		from := uint64(0)
    91  		if indexer.limit != 0 && head >= indexer.limit {
    92  			from = head - indexer.limit + 1
    93  		}
    94  		rawdb.IndexTransactions(indexer.db, from, head+1, stop, true)
    95  		return
    96  	}
    97  	// The tail flag is existent (which means indexes in [tail, head] should be
    98  	// present), while the whole chain are requested for indexing.
    99  	if indexer.limit == 0 || head < indexer.limit {
   100  		if *tail > 0 {
   101  			// It can happen when chain is rewound to a historical point which
   102  			// is even lower than the indexes tail, recap the indexing target
   103  			// to new head to avoid reading non-existent block bodies.
   104  			end := *tail
   105  			if end > head+1 {
   106  				end = head + 1
   107  			}
   108  			rawdb.IndexTransactions(indexer.db, 0, end, stop, true)
   109  		}
   110  		return
   111  	}
   112  	// The tail flag is existent, adjust the index range according to configured
   113  	// limit and the latest chain head.
   114  	if head-indexer.limit+1 < *tail {
   115  		// Reindex a part of missing indices and rewind index tail to HEAD-limit
   116  		rawdb.IndexTransactions(indexer.db, head-indexer.limit+1, *tail, stop, true)
   117  	} else {
   118  		// Unindex a part of stale indices and forward index tail to HEAD-limit
   119  		rawdb.UnindexTransactions(indexer.db, *tail, head-indexer.limit+1, stop, false)
   120  	}
   121  }
   122  
   123  // loop is the scheduler of the indexer, assigning indexing/unindexing tasks depending
   124  // on the received chain event.
   125  func (indexer *txIndexer) loop(chain *BlockChain) {
   126  	defer close(indexer.closed)
   127  
   128  	// Listening to chain events and manipulate the transaction indexes.
   129  	var (
   130  		stop     chan struct{}                       // Non-nil if background routine is active.
   131  		done     chan struct{}                       // Non-nil if background routine is active.
   132  		lastHead uint64                              // The latest announced chain head (whose tx indexes are assumed created)
   133  		lastTail = rawdb.ReadTxIndexTail(indexer.db) // The oldest indexed block, nil means nothing indexed
   134  
   135  		headCh = make(chan ChainHeadEvent)
   136  		sub    = chain.SubscribeChainHeadEvent(headCh)
   137  	)
   138  	defer sub.Unsubscribe()
   139  
   140  	// Launch the initial processing if chain is not empty (head != genesis).
   141  	// This step is useful in these scenarios that chain has no progress.
   142  	if head := rawdb.ReadHeadBlock(indexer.db); head != nil && head.Number().Uint64() != 0 {
   143  		stop = make(chan struct{})
   144  		done = make(chan struct{})
   145  		lastHead = head.Number().Uint64()
   146  		go indexer.run(rawdb.ReadTxIndexTail(indexer.db), head.NumberU64(), stop, done)
   147  	}
   148  	for {
   149  		select {
   150  		case head := <-headCh:
   151  			if done == nil {
   152  				stop = make(chan struct{})
   153  				done = make(chan struct{})
   154  				go indexer.run(rawdb.ReadTxIndexTail(indexer.db), head.Block.NumberU64(), stop, done)
   155  			}
   156  			lastHead = head.Block.NumberU64()
   157  		case <-done:
   158  			stop = nil
   159  			done = nil
   160  			lastTail = rawdb.ReadTxIndexTail(indexer.db)
   161  		case ch := <-indexer.progress:
   162  			ch <- indexer.report(lastHead, lastTail)
   163  		case ch := <-indexer.term:
   164  			if stop != nil {
   165  				close(stop)
   166  			}
   167  			if done != nil {
   168  				log.Info("Waiting background transaction indexer to exit")
   169  				<-done
   170  			}
   171  			close(ch)
   172  			return
   173  		}
   174  	}
   175  }
   176  
   177  // report returns the tx indexing progress.
   178  func (indexer *txIndexer) report(head uint64, tail *uint64) TxIndexProgress {
   179  	total := indexer.limit
   180  	if indexer.limit == 0 || total > head {
   181  		total = head + 1 // genesis included
   182  	}
   183  	var indexed uint64
   184  	if tail != nil {
   185  		indexed = head - *tail + 1
   186  	}
   187  	// The value of indexed might be larger than total if some blocks need
   188  	// to be unindexed, avoiding a negative remaining.
   189  	var remaining uint64
   190  	if indexed < total {
   191  		remaining = total - indexed
   192  	}
   193  	return TxIndexProgress{
   194  		Indexed:   indexed,
   195  		Remaining: remaining,
   196  	}
   197  }
   198  
   199  // txIndexProgress retrieves the tx indexing progress, or an error if the
   200  // background tx indexer is already stopped.
   201  func (indexer *txIndexer) txIndexProgress() (TxIndexProgress, error) {
   202  	ch := make(chan TxIndexProgress, 1)
   203  	select {
   204  	case indexer.progress <- ch:
   205  		return <-ch, nil
   206  	case <-indexer.closed:
   207  		return TxIndexProgress{}, errors.New("indexer is closed")
   208  	}
   209  }
   210  
   211  // close shutdown the indexer. Safe to be called for multiple times.
   212  func (indexer *txIndexer) close() {
   213  	ch := make(chan struct{})
   214  	select {
   215  	case indexer.term <- ch:
   216  		<-ch
   217  	case <-indexer.closed:
   218  	}
   219  }