github.com/MetalBlockchain/subnet-evm@v0.4.9/core/rawdb/chain_iterator.go (about)

     1  // (c) 2019-2022, Ava Labs, Inc.
     2  //
     3  // This file is a derived work, based on the go-ethereum library whose original
     4  // notices appear below.
     5  //
     6  // It is distributed under a license compatible with the licensing terms of the
     7  // original code from which it is derived.
     8  //
     9  // Much love to the original authors for their work.
    10  // **********
    11  // Copyright 2020 The go-ethereum Authors
    12  // This file is part of the go-ethereum library.
    13  //
    14  // The go-ethereum library is free software: you can redistribute it and/or modify
    15  // it under the terms of the GNU Lesser General Public License as published by
    16  // the Free Software Foundation, either version 3 of the License, or
    17  // (at your option) any later version.
    18  //
    19  // The go-ethereum library is distributed in the hope that it will be useful,
    20  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    21  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    22  // GNU Lesser General Public License for more details.
    23  //
    24  // You should have received a copy of the GNU Lesser General Public License
    25  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    26  
    27  package rawdb
    28  
    29  import (
    30  	"runtime"
    31  	"sync/atomic"
    32  	"time"
    33  
    34  	"github.com/MetalBlockchain/subnet-evm/core/types"
    35  	"github.com/MetalBlockchain/subnet-evm/ethdb"
    36  	"github.com/ethereum/go-ethereum/common"
    37  	"github.com/ethereum/go-ethereum/common/prque"
    38  	"github.com/ethereum/go-ethereum/log"
    39  	"github.com/ethereum/go-ethereum/rlp"
    40  )
    41  
    42  type blockTxHashes struct {
    43  	number uint64
    44  	hashes []common.Hash
    45  }
    46  
    47  // iterateTransactions iterates over all transactions in the (canon) block
    48  // number(s) given, and yields the hashes on a channel. If there is a signal
    49  // received from interrupt channel, the iteration will be aborted and result
    50  // channel will be closed.
    51  // Iterates blocks in the range [from, to)
    52  func iterateTransactions(db ethdb.Database, from uint64, to uint64, reverse bool, interrupt chan struct{}) chan *blockTxHashes {
    53  	// One thread sequentially reads data from db
    54  	type numberRlp struct {
    55  		number uint64
    56  		rlp    rlp.RawValue
    57  	}
    58  	if to == from {
    59  		return nil
    60  	}
    61  	threads := to - from
    62  	if cpus := runtime.NumCPU(); threads > uint64(cpus) {
    63  		threads = uint64(cpus)
    64  	}
    65  	var (
    66  		rlpCh    = make(chan *numberRlp, threads*2)     // we send raw rlp over this channel
    67  		hashesCh = make(chan *blockTxHashes, threads*2) // send hashes over hashesCh
    68  	)
    69  	// lookup runs in one instance
    70  	lookup := func() {
    71  		n, end := from, to
    72  		if reverse {
    73  			n, end = to-1, from-1
    74  		}
    75  		defer close(rlpCh)
    76  		for n != end {
    77  			data := ReadCanonicalBodyRLP(db, n)
    78  			// Feed the block to the aggregator, or abort on interrupt
    79  			select {
    80  			case rlpCh <- &numberRlp{n, data}:
    81  			case <-interrupt:
    82  				return
    83  			}
    84  			if reverse {
    85  				n--
    86  			} else {
    87  				n++
    88  			}
    89  		}
    90  	}
    91  	// process runs in parallel
    92  	nThreadsAlive := int32(threads)
    93  	process := func() {
    94  		defer func() {
    95  			// Last processor closes the result channel
    96  			if atomic.AddInt32(&nThreadsAlive, -1) == 0 {
    97  				close(hashesCh)
    98  			}
    99  		}()
   100  		for data := range rlpCh {
   101  			var body types.Body
   102  			if err := rlp.DecodeBytes(data.rlp, &body); err != nil {
   103  				log.Warn("Failed to decode block body", "block", data.number, "error", err)
   104  				return
   105  			}
   106  			var hashes []common.Hash
   107  			for _, tx := range body.Transactions {
   108  				hashes = append(hashes, tx.Hash())
   109  			}
   110  			result := &blockTxHashes{
   111  				hashes: hashes,
   112  				number: data.number,
   113  			}
   114  			// Feed the block to the aggregator, or abort on interrupt
   115  			select {
   116  			case hashesCh <- result:
   117  			case <-interrupt:
   118  				return
   119  			}
   120  		}
   121  	}
   122  	go lookup() // start the sequential db accessor
   123  	for i := 0; i < int(threads); i++ {
   124  		go process()
   125  	}
   126  	return hashesCh
   127  }
   128  
   129  // indexTransactions creates txlookup indices of the specified block range.
   130  //
   131  // This function iterates canonical chain in reverse order, it has one main advantage:
   132  // We can write tx index tail flag periodically even without the whole indexing
   133  // procedure is finished. So that we can resume indexing procedure next time quickly.
   134  //
   135  // There is a passed channel, the whole procedure will be interrupted if any
   136  // signal received.
   137  func indexTransactions(db ethdb.Database, from uint64, to uint64, interrupt chan struct{}, hook func(uint64) bool) {
   138  	// short circuit for invalid range
   139  	if from >= to {
   140  		return
   141  	}
   142  	var (
   143  		hashesCh = iterateTransactions(db, from, to, true, interrupt)
   144  		batch    = db.NewBatch()
   145  		start    = time.Now()
   146  		logged   = start.Add(-7 * time.Second)
   147  		// Since we iterate in reverse, we expect the first number to come
   148  		// in to be [to-1]. Therefore, setting lastNum to means that the
   149  		// prqueue gap-evaluation will work correctly
   150  		lastNum = to
   151  		queue   = prque.New(nil)
   152  		// for stats reporting
   153  		blocks, txs = 0, 0
   154  	)
   155  	for chanDelivery := range hashesCh {
   156  		// Push the delivery into the queue and process contiguous ranges.
   157  		// Since we iterate in reverse, so lower numbers have lower prio, and
   158  		// we can use the number directly as prio marker
   159  		queue.Push(chanDelivery, int64(chanDelivery.number))
   160  		for !queue.Empty() {
   161  			// If the next available item is gapped, return
   162  			if _, priority := queue.Peek(); priority != int64(lastNum-1) {
   163  				break
   164  			}
   165  			// For testing
   166  			if hook != nil && !hook(lastNum-1) {
   167  				break
   168  			}
   169  			// Next block available, pop it off and index it
   170  			delivery := queue.PopItem().(*blockTxHashes)
   171  			lastNum = delivery.number
   172  			WriteTxLookupEntries(batch, delivery.number, delivery.hashes)
   173  			blocks++
   174  			txs += len(delivery.hashes)
   175  			// If enough data was accumulated in memory or we're at the last block, dump to disk
   176  			if batch.ValueSize() > ethdb.IdealBatchSize {
   177  				WriteTxIndexTail(batch, lastNum) // Also write the tail here
   178  				if err := batch.Write(); err != nil {
   179  					log.Crit("Failed writing batch to db", "error", err)
   180  					return
   181  				}
   182  				batch.Reset()
   183  			}
   184  			// If we've spent too much time already, notify the user of what we're doing
   185  			if time.Since(logged) > 8*time.Second {
   186  				log.Info("Indexing transactions", "blocks", blocks, "txs", txs, "tail", lastNum, "total", to-from, "elapsed", common.PrettyDuration(time.Since(start)))
   187  				logged = time.Now()
   188  			}
   189  		}
   190  	}
   191  	// Flush the new indexing tail and the last committed data. It can also happen
   192  	// that the last batch is empty because nothing to index, but the tail has to
   193  	// be flushed anyway.
   194  	WriteTxIndexTail(batch, lastNum)
   195  	if err := batch.Write(); err != nil {
   196  		log.Crit("Failed writing batch to db", "error", err)
   197  		return
   198  	}
   199  	select {
   200  	case <-interrupt:
   201  		log.Debug("Transaction indexing interrupted", "blocks", blocks, "txs", txs, "tail", lastNum, "elapsed", common.PrettyDuration(time.Since(start)))
   202  	default:
   203  		log.Info("Indexed transactions", "blocks", blocks, "txs", txs, "tail", lastNum, "elapsed", common.PrettyDuration(time.Since(start)))
   204  	}
   205  }
   206  
   207  // // IndexTransactions creates txlookup indices of the specified block range. The from
   208  // // is included while to is excluded.
   209  // //
   210  // // This function iterates canonical chain in reverse order, it has one main advantage:
   211  // // We can write tx index tail flag periodically even without the whole indexing
   212  // // procedure is finished. So that we can resume indexing procedure next time quickly.
   213  // //
   214  // // There is a passed channel, the whole procedure will be interrupted if any
   215  // // signal received.
   216  // func IndexTransactions(db ethdb.Database, from uint64, to uint64, interrupt chan struct{}) {
   217  // 	indexTransactions(db, from, to, interrupt, nil)
   218  // }
   219  
   220  // indexTransactionsForTesting is the internal debug version with an additional hook.
   221  func indexTransactionsForTesting(db ethdb.Database, from uint64, to uint64, interrupt chan struct{}, hook func(uint64) bool) {
   222  	indexTransactions(db, from, to, interrupt, hook)
   223  }
   224  
   225  // unindexTransactions removes txlookup indices of the specified block range.
   226  //
   227  // There is a passed channel, the whole procedure will be interrupted if any
   228  // signal received.
   229  func unindexTransactions(db ethdb.Database, from uint64, to uint64, interrupt chan struct{}, hook func(uint64) bool) {
   230  	// short circuit for invalid range
   231  	if from >= to {
   232  		return
   233  	}
   234  	var (
   235  		hashesCh = iterateTransactions(db, from, to, false, interrupt)
   236  		batch    = db.NewBatch()
   237  		start    = time.Now()
   238  		logged   = start.Add(-7 * time.Second)
   239  		// we expect the first number to come in to be [from]. Therefore, setting
   240  		// nextNum to from means that the prqueue gap-evaluation will work correctly
   241  		nextNum = from
   242  		queue   = prque.New(nil)
   243  		// for stats reporting
   244  		blocks, txs = 0, 0
   245  	)
   246  	// Otherwise spin up the concurrent iterator and unindexer
   247  	for delivery := range hashesCh {
   248  		// Push the delivery into the queue and process contiguous ranges.
   249  		queue.Push(delivery, -int64(delivery.number))
   250  		for !queue.Empty() {
   251  			// If the next available item is gapped, return
   252  			if _, priority := queue.Peek(); -priority != int64(nextNum) {
   253  				break
   254  			}
   255  			// For testing
   256  			if hook != nil && !hook(nextNum) {
   257  				break
   258  			}
   259  			delivery := queue.PopItem().(*blockTxHashes)
   260  			nextNum = delivery.number + 1
   261  			DeleteTxLookupEntries(batch, delivery.hashes)
   262  			txs += len(delivery.hashes)
   263  			blocks++
   264  
   265  			// If enough data was accumulated in memory or we're at the last block, dump to disk
   266  			// A batch counts the size of deletion as '1', so we need to flush more
   267  			// often than that.
   268  			if blocks%1000 == 0 {
   269  				WriteTxIndexTail(batch, nextNum)
   270  				if err := batch.Write(); err != nil {
   271  					log.Crit("Failed writing batch to db", "error", err)
   272  					return
   273  				}
   274  				batch.Reset()
   275  			}
   276  			// If we've spent too much time already, notify the user of what we're doing
   277  			if time.Since(logged) > 8*time.Second {
   278  				log.Info("Unindexing transactions", "blocks", blocks, "txs", txs, "total", to-from, "elapsed", common.PrettyDuration(time.Since(start)))
   279  				logged = time.Now()
   280  			}
   281  		}
   282  	}
   283  	// Flush the new indexing tail and the last committed data. It can also happen
   284  	// that the last batch is empty because nothing to unindex, but the tail has to
   285  	// be flushed anyway.
   286  	WriteTxIndexTail(batch, nextNum)
   287  	if err := batch.Write(); err != nil {
   288  		log.Crit("Failed writing batch to db", "error", err)
   289  		return
   290  	}
   291  	select {
   292  	case <-interrupt:
   293  		log.Debug("Transaction unindexing interrupted", "blocks", blocks, "txs", txs, "tail", to, "elapsed", common.PrettyDuration(time.Since(start)))
   294  	default:
   295  		log.Info("Unindexed transactions", "blocks", blocks, "txs", txs, "tail", to, "elapsed", common.PrettyDuration(time.Since(start)))
   296  	}
   297  }
   298  
   299  // UnindexTransactions removes txlookup indices of the specified block range.
   300  // The from is included while to is excluded.
   301  //
   302  // There is a passed channel, the whole procedure will be interrupted if any
   303  // signal received.
   304  func UnindexTransactions(db ethdb.Database, from uint64, to uint64, interrupt chan struct{}) {
   305  	unindexTransactions(db, from, to, interrupt, nil)
   306  }
   307  
   308  // unindexTransactionsForTesting is the internal debug version with an additional hook.
   309  func unindexTransactionsForTesting(db ethdb.Database, from uint64, to uint64, interrupt chan struct{}, hook func(uint64) bool) {
   310  	unindexTransactions(db, from, to, interrupt, hook)
   311  }