github.com/ethereum/go-ethereum@v1.16.1/triedb/pathdb/context.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 pathdb
    18  
    19  import (
    20  	"bytes"
    21  	"encoding/binary"
    22  	"errors"
    23  	"math"
    24  	"time"
    25  
    26  	"github.com/ethereum/go-ethereum/common"
    27  	"github.com/ethereum/go-ethereum/core/rawdb"
    28  	"github.com/ethereum/go-ethereum/ethdb"
    29  	"github.com/ethereum/go-ethereum/ethdb/memorydb"
    30  	"github.com/ethereum/go-ethereum/log"
    31  )
    32  
    33  const (
    34  	snapAccount = "account" // Identifier of account snapshot generation
    35  	snapStorage = "storage" // Identifier of storage snapshot generation
    36  )
    37  
    38  // generatorStats is a collection of statistics gathered by the snapshot generator
    39  // for logging purposes. This data structure is used throughout the entire
    40  // lifecycle of the snapshot generation process and is shared across multiple
    41  // generation cycles.
    42  type generatorStats struct {
    43  	origin   uint64             // Origin prefix where generation started
    44  	start    time.Time          // Timestamp when generation started
    45  	accounts uint64             // Number of accounts indexed(generated or recovered)
    46  	slots    uint64             // Number of storage slots indexed(generated or recovered)
    47  	dangling uint64             // Number of dangling storage slots
    48  	storage  common.StorageSize // Total account and storage slot size(generation or recovery)
    49  }
    50  
    51  // log creates a contextual log with the given message and the context pulled
    52  // from the internally maintained statistics.
    53  func (gs *generatorStats) log(msg string, root common.Hash, marker []byte) {
    54  	var ctx []interface{}
    55  	if root != (common.Hash{}) {
    56  		ctx = append(ctx, []interface{}{"root", root}...)
    57  	}
    58  	// Figure out whether we're after or within an account
    59  	switch len(marker) {
    60  	case common.HashLength:
    61  		ctx = append(ctx, []interface{}{"at", common.BytesToHash(marker)}...)
    62  	case 2 * common.HashLength:
    63  		ctx = append(ctx, []interface{}{
    64  			"in", common.BytesToHash(marker[:common.HashLength]),
    65  			"at", common.BytesToHash(marker[common.HashLength:]),
    66  		}...)
    67  	}
    68  	// Add the usual measurements
    69  	ctx = append(ctx, []interface{}{
    70  		"accounts", gs.accounts,
    71  		"slots", gs.slots,
    72  		"storage", gs.storage,
    73  		"dangling", gs.dangling,
    74  		"elapsed", common.PrettyDuration(time.Since(gs.start)),
    75  	}...)
    76  	// Calculate the estimated indexing time based on current stats
    77  	if len(marker) > 0 {
    78  		if done := binary.BigEndian.Uint64(marker[:8]) - gs.origin; done > 0 {
    79  			left := math.MaxUint64 - binary.BigEndian.Uint64(marker[:8])
    80  
    81  			speed := done/uint64(time.Since(gs.start)/time.Millisecond+1) + 1 // +1s to avoid division by zero
    82  			ctx = append(ctx, []interface{}{
    83  				"eta", common.PrettyDuration(time.Duration(left/speed) * time.Millisecond),
    84  			}...)
    85  		}
    86  	}
    87  	log.Info(msg, ctx...)
    88  }
    89  
    90  // generatorContext holds several global fields that are used throughout the
    91  // current generation cycle. It must be recreated if the generation cycle is
    92  // restarted.
    93  type generatorContext struct {
    94  	root    common.Hash         // State root of the generation target
    95  	account *holdableIterator   // Iterator of account snapshot data
    96  	storage *holdableIterator   // Iterator of storage snapshot data
    97  	db      ethdb.KeyValueStore // Key-value store containing the snapshot data
    98  	batch   ethdb.Batch         // Database batch for writing data atomically
    99  	logged  time.Time           // The timestamp when last generation progress was displayed
   100  }
   101  
   102  // newGeneratorContext initializes the context for generation.
   103  func newGeneratorContext(root common.Hash, marker []byte, db ethdb.KeyValueStore) *generatorContext {
   104  	ctx := &generatorContext{
   105  		root:   root,
   106  		db:     db,
   107  		batch:  db.NewBatch(),
   108  		logged: time.Now(),
   109  	}
   110  	accMarker, storageMarker := splitMarker(marker)
   111  	ctx.openIterator(snapAccount, accMarker)
   112  	ctx.openIterator(snapStorage, storageMarker)
   113  	return ctx
   114  }
   115  
   116  // openIterator constructs global account and storage snapshot iterators
   117  // at the interrupted position. These iterators should be reopened from time
   118  // to time to avoid blocking leveldb compaction for a long time.
   119  func (ctx *generatorContext) openIterator(kind string, start []byte) {
   120  	if kind == snapAccount {
   121  		iter := ctx.db.NewIterator(rawdb.SnapshotAccountPrefix, start)
   122  		ctx.account = newHoldableIterator(rawdb.NewKeyLengthIterator(iter, 1+common.HashLength))
   123  		return
   124  	}
   125  	iter := ctx.db.NewIterator(rawdb.SnapshotStoragePrefix, start)
   126  	ctx.storage = newHoldableIterator(rawdb.NewKeyLengthIterator(iter, 1+2*common.HashLength))
   127  }
   128  
   129  // reopenIterator releases the specified snapshot iterator and re-open it
   130  // in the next position. It's aimed for not blocking leveldb compaction.
   131  func (ctx *generatorContext) reopenIterator(kind string) {
   132  	// Shift iterator one more step, so that we can reopen
   133  	// the iterator at the right position.
   134  	var iter = ctx.account
   135  	if kind == snapStorage {
   136  		iter = ctx.storage
   137  	}
   138  	hasNext := iter.Next()
   139  	if !hasNext {
   140  		// Iterator exhausted, release forever and create an already exhausted virtual iterator
   141  		iter.Release()
   142  		if kind == snapAccount {
   143  			ctx.account = newHoldableIterator(memorydb.New().NewIterator(nil, nil))
   144  			return
   145  		}
   146  		ctx.storage = newHoldableIterator(memorydb.New().NewIterator(nil, nil))
   147  		return
   148  	}
   149  	next := iter.Key()
   150  	iter.Release()
   151  	ctx.openIterator(kind, next[1:])
   152  }
   153  
   154  // close releases all the held resources.
   155  func (ctx *generatorContext) close() {
   156  	ctx.account.Release()
   157  	ctx.storage.Release()
   158  }
   159  
   160  // iterator returns the corresponding iterator specified by the kind.
   161  func (ctx *generatorContext) iterator(kind string) *holdableIterator {
   162  	if kind == snapAccount {
   163  		return ctx.account
   164  	}
   165  	return ctx.storage
   166  }
   167  
   168  // removeStorageBefore deletes all storage entries which are located before
   169  // the specified account. When the iterator touches the storage entry which
   170  // is located in or outside the given account, it stops and holds the current
   171  // iterated element locally.
   172  func (ctx *generatorContext) removeStorageBefore(account common.Hash) uint64 {
   173  	var (
   174  		count uint64
   175  		start = time.Now()
   176  		iter  = ctx.storage
   177  	)
   178  	for iter.Next() {
   179  		key := iter.Key()
   180  		if bytes.Compare(key[1:1+common.HashLength], account.Bytes()) >= 0 {
   181  			iter.Hold()
   182  			break
   183  		}
   184  		count++
   185  		ctx.batch.Delete(key)
   186  		if ctx.batch.ValueSize() > ethdb.IdealBatchSize {
   187  			ctx.batch.Write()
   188  			ctx.batch.Reset()
   189  		}
   190  	}
   191  	storageCleanCounter.Inc(time.Since(start).Nanoseconds())
   192  	return count
   193  }
   194  
   195  // removeStorageAt deletes all storage entries which are located in the specified
   196  // account. When the iterator touches the storage entry which is outside the given
   197  // account, it stops and holds the current iterated element locally. An error will
   198  // be returned if the initial position of iterator is not in the given account.
   199  func (ctx *generatorContext) removeStorageAt(account common.Hash) error {
   200  	var (
   201  		count int64
   202  		start = time.Now()
   203  		iter  = ctx.storage
   204  	)
   205  	for iter.Next() {
   206  		key := iter.Key()
   207  		cmp := bytes.Compare(key[1:1+common.HashLength], account.Bytes())
   208  		if cmp < 0 {
   209  			return errors.New("invalid iterator position")
   210  		}
   211  		if cmp > 0 {
   212  			iter.Hold()
   213  			break
   214  		}
   215  		count++
   216  		ctx.batch.Delete(key)
   217  		if ctx.batch.ValueSize() > ethdb.IdealBatchSize {
   218  			ctx.batch.Write()
   219  			ctx.batch.Reset()
   220  		}
   221  	}
   222  	wipedStorageMeter.Mark(count)
   223  	storageCleanCounter.Inc(time.Since(start).Nanoseconds())
   224  	return nil
   225  }
   226  
   227  // removeRemainingStorage deletes all storage entries which are located after
   228  // the current iterator position.
   229  func (ctx *generatorContext) removeRemainingStorage() uint64 {
   230  	var (
   231  		count uint64
   232  		start = time.Now()
   233  		iter  = ctx.storage
   234  	)
   235  	for iter.Next() {
   236  		count++
   237  		ctx.batch.Delete(iter.Key())
   238  		if ctx.batch.ValueSize() > ethdb.IdealBatchSize {
   239  			ctx.batch.Write()
   240  			ctx.batch.Reset()
   241  		}
   242  	}
   243  	danglingStorageMeter.Mark(int64(count))
   244  	storageCleanCounter.Inc(time.Since(start).Nanoseconds())
   245  	return count
   246  }