github.com/dim4egster/coreth@v0.10.2/core/state/snapshot/generate.go (about)

     1  // (c) 2019-2020, 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 2019 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 snapshot
    28  
    29  import (
    30  	"bytes"
    31  	"encoding/binary"
    32  	"fmt"
    33  	"math/big"
    34  	"time"
    35  
    36  	"github.com/VictoriaMetrics/fastcache"
    37  	"github.com/dim4egster/coreth/core/rawdb"
    38  	"github.com/dim4egster/coreth/ethdb"
    39  	"github.com/dim4egster/coreth/trie"
    40  	"github.com/ethereum/go-ethereum/common"
    41  	"github.com/ethereum/go-ethereum/common/math"
    42  	"github.com/ethereum/go-ethereum/crypto"
    43  	"github.com/ethereum/go-ethereum/log"
    44  	"github.com/ethereum/go-ethereum/rlp"
    45  )
    46  
    47  var (
    48  	// emptyRoot is the known root hash of an empty trie.
    49  	emptyRoot = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
    50  
    51  	// emptyCode is the known hash of the empty EVM bytecode.
    52  	emptyCode = crypto.Keccak256Hash(nil)
    53  )
    54  
    55  // generatorStats is a collection of statistics gathered by the snapshot generator
    56  // for logging purposes.
    57  type generatorStats struct {
    58  	wiping   chan struct{}      // Notification channel if wiping is in progress
    59  	origin   uint64             // Origin prefix where generation started
    60  	start    time.Time          // Timestamp when generation started
    61  	accounts uint64             // Number of accounts indexed(generated or recovered)
    62  	slots    uint64             // Number of storage slots indexed(generated or recovered)
    63  	storage  common.StorageSize // Total account and storage slot size(generation or recovery)
    64  }
    65  
    66  // Info creates an contextual info-level log with the given message and the context pulled
    67  // from the internally maintained statistics.
    68  func (gs *generatorStats) Info(msg string, root common.Hash, marker []byte) {
    69  	gs.log(log.LvlInfo, msg, root, marker)
    70  }
    71  
    72  // Debug creates an contextual debug-level log with the given message and the context pulled
    73  // from the internally maintained statistics.
    74  func (gs *generatorStats) Debug(msg string, root common.Hash, marker []byte) {
    75  	gs.log(log.LvlDebug, msg, root, marker)
    76  }
    77  
    78  // log creates an contextual log with the given message and the context pulled
    79  // from the internally maintained statistics.
    80  func (gs *generatorStats) log(level log.Lvl, msg string, root common.Hash, marker []byte) {
    81  	var ctx []interface{}
    82  	if root != (common.Hash{}) {
    83  		ctx = append(ctx, []interface{}{"root", root}...)
    84  	}
    85  	// Figure out whether we're after or within an account
    86  	switch len(marker) {
    87  	case common.HashLength:
    88  		ctx = append(ctx, []interface{}{"at", common.BytesToHash(marker)}...)
    89  	case 2 * common.HashLength:
    90  		ctx = append(ctx, []interface{}{
    91  			"in", common.BytesToHash(marker[:common.HashLength]),
    92  			"at", common.BytesToHash(marker[common.HashLength:]),
    93  		}...)
    94  	}
    95  	// Add the usual measurements
    96  	ctx = append(ctx, []interface{}{
    97  		"accounts", gs.accounts,
    98  		"slots", gs.slots,
    99  		"storage", gs.storage,
   100  		"elapsed", common.PrettyDuration(time.Since(gs.start)),
   101  	}...)
   102  	// Calculate the estimated indexing time based on current stats
   103  	if len(marker) > 0 {
   104  		if done := binary.BigEndian.Uint64(marker[:8]) - gs.origin; done > 0 {
   105  			left := math.MaxUint64 - binary.BigEndian.Uint64(marker[:8])
   106  
   107  			speed := done/uint64(time.Since(gs.start)/time.Millisecond+1) + 1 // +1s to avoid division by zero
   108  			ctx = append(ctx, []interface{}{
   109  				"eta", common.PrettyDuration(time.Duration(left/speed) * time.Millisecond),
   110  			}...)
   111  		}
   112  	}
   113  
   114  	switch level {
   115  	case log.LvlTrace:
   116  		log.Trace(msg, ctx...)
   117  	case log.LvlDebug:
   118  		log.Debug(msg, ctx...)
   119  	case log.LvlInfo:
   120  		log.Info(msg, ctx...)
   121  	case log.LvlWarn:
   122  		log.Warn(msg, ctx...)
   123  	case log.LvlError:
   124  		log.Error(msg, ctx...)
   125  	case log.LvlCrit:
   126  		log.Crit(msg, ctx...)
   127  	default:
   128  		log.Error(fmt.Sprintf("log with invalid log level %s: %s", level, msg), ctx...)
   129  	}
   130  }
   131  
   132  // generateSnapshot regenerates a brand new snapshot based on an existing state
   133  // database and head block asynchronously. The snapshot is returned immediately
   134  // and generation is continued in the background until done.
   135  func generateSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, blockHash, root common.Hash, wiper chan struct{}) *diskLayer {
   136  	// Wipe any previously existing snapshot from the database if no wiper is
   137  	// currently in progress.
   138  	if wiper == nil {
   139  		wiper = WipeSnapshot(diskdb, true)
   140  	}
   141  	// Create a new disk layer with an initialized state marker at zero
   142  	var (
   143  		stats     = &generatorStats{wiping: wiper, start: time.Now()}
   144  		batch     = diskdb.NewBatch()
   145  		genMarker = []byte{} // Initialized but empty!
   146  	)
   147  	rawdb.WriteSnapshotBlockHash(batch, blockHash)
   148  	rawdb.WriteSnapshotRoot(batch, root)
   149  	journalProgress(batch, genMarker, stats)
   150  	if err := batch.Write(); err != nil {
   151  		log.Crit("Failed to write initialized state marker", "err", err)
   152  	}
   153  	base := &diskLayer{
   154  		diskdb:     diskdb,
   155  		triedb:     triedb,
   156  		blockHash:  blockHash,
   157  		root:       root,
   158  		cache:      fastcache.New(cache * 1024 * 1024),
   159  		genMarker:  genMarker,
   160  		genPending: make(chan struct{}),
   161  		genAbort:   make(chan chan struct{}),
   162  		created:    time.Now(),
   163  	}
   164  	go base.generate(stats)
   165  	log.Debug("Start snapshot generation", "root", root)
   166  	return base
   167  }
   168  
   169  // journalProgress persists the generator stats into the database to resume later.
   170  func journalProgress(db ethdb.KeyValueWriter, marker []byte, stats *generatorStats) {
   171  	// Write out the generator marker. Note it's a standalone disk layer generator
   172  	// which is not mixed with journal. It's ok if the generator is persisted while
   173  	// journal is not.
   174  	entry := journalGenerator{
   175  		Done:   marker == nil,
   176  		Marker: marker,
   177  	}
   178  	if stats != nil {
   179  		entry.Wiping = (stats.wiping != nil)
   180  		entry.Accounts = stats.accounts
   181  		entry.Slots = stats.slots
   182  		entry.Storage = uint64(stats.storage)
   183  	}
   184  	blob, err := rlp.EncodeToBytes(entry)
   185  	if err != nil {
   186  		panic(err) // Cannot happen, here to catch dev errors
   187  	}
   188  	var logstr string
   189  	switch {
   190  	case marker == nil:
   191  		logstr = "done"
   192  	case bytes.Equal(marker, []byte{}):
   193  		logstr = "empty"
   194  	case len(marker) == common.HashLength:
   195  		logstr = fmt.Sprintf("%#x", marker)
   196  	default:
   197  		logstr = fmt.Sprintf("%#x:%#x", marker[:common.HashLength], marker[common.HashLength:])
   198  	}
   199  	log.Debug("Journalled generator progress", "progress", logstr)
   200  	rawdb.WriteSnapshotGenerator(db, blob)
   201  }
   202  
   203  // checkAndFlush checks to see if snapshot generation has been aborted or if
   204  // the current batch size is greater than ethdb.IdealBatchSize. If so, it saves
   205  // the current progress to disk and returns true. Else, it could log current
   206  // progress and returns true.
   207  func (dl *diskLayer) checkAndFlush(batch ethdb.Batch, stats *generatorStats, currentLocation []byte) bool {
   208  	// If we've exceeded our batch allowance or termination was requested, flush to disk
   209  	var abort chan struct{}
   210  	select {
   211  	case abort = <-dl.genAbort:
   212  	default:
   213  	}
   214  	if batch.ValueSize() > ethdb.IdealBatchSize || abort != nil {
   215  		if bytes.Compare(currentLocation, dl.genMarker) < 0 {
   216  			log.Error("Snapshot generator went backwards",
   217  				"currentLocation", fmt.Sprintf("%x", currentLocation),
   218  				"genMarker", fmt.Sprintf("%x", dl.genMarker))
   219  		}
   220  		// Flush out the batch anyway no matter it's empty or not.
   221  		// It's possible that all the states are recovered and the
   222  		// generation indeed makes progress.
   223  		journalProgress(batch, currentLocation, stats)
   224  
   225  		if err := batch.Write(); err != nil {
   226  			log.Error("Failed to flush batch", "err", err)
   227  			if abort == nil {
   228  				abort = <-dl.genAbort
   229  			}
   230  			dl.genStats = stats
   231  			close(abort)
   232  			return true
   233  		}
   234  		batch.Reset()
   235  
   236  		dl.lock.Lock()
   237  		dl.genMarker = currentLocation
   238  		dl.lock.Unlock()
   239  
   240  		if abort != nil {
   241  			stats.Debug("Aborting state snapshot generation", dl.root, currentLocation)
   242  			dl.genStats = stats
   243  			close(abort)
   244  			return true
   245  		}
   246  	}
   247  	if time.Since(dl.logged) > 8*time.Second {
   248  		stats.Info("Generating state snapshot", dl.root, currentLocation)
   249  		dl.logged = time.Now()
   250  	}
   251  	return false
   252  }
   253  
   254  // generate is a background thread that iterates over the state and storage tries,
   255  // constructing the state snapshot. All the arguments are purely for statistics
   256  // gathering and logging, since the method surfs the blocks as they arrive, often
   257  // being restarted.
   258  func (dl *diskLayer) generate(stats *generatorStats) {
   259  	// If a database wipe is in operation, wait until it's done
   260  	if stats.wiping != nil {
   261  		stats.Info("Wiper running, state snapshotting paused", common.Hash{}, dl.genMarker)
   262  		select {
   263  		// If wiper is done, resume normal mode of operation
   264  		case <-stats.wiping:
   265  			stats.wiping = nil
   266  			stats.start = time.Now()
   267  
   268  		// If generator was aborted during wipe, return
   269  		case abort := <-dl.genAbort:
   270  			stats.Debug("Aborting state snapshot generation", dl.root, dl.genMarker)
   271  			dl.genStats = stats
   272  			close(abort)
   273  			return
   274  		}
   275  	}
   276  	// Create an account and state iterator pointing to the current generator marker
   277  	accTrie, err := trie.NewStateTrie(common.Hash{}, dl.root, dl.triedb)
   278  	if err != nil {
   279  		// The account trie is missing (GC), surf the chain until one becomes available
   280  		stats.Info("Trie missing, state snapshotting paused", dl.root, dl.genMarker)
   281  		abort := <-dl.genAbort
   282  		dl.genStats = stats
   283  		close(abort)
   284  		return
   285  	}
   286  	stats.Debug("Resuming state snapshot generation", dl.root, dl.genMarker)
   287  
   288  	var accMarker []byte
   289  	if len(dl.genMarker) > 0 { // []byte{} is the start, use nil for that
   290  		accMarker = dl.genMarker[:common.HashLength]
   291  	}
   292  	accIt := trie.NewIterator(accTrie.NodeIterator(accMarker))
   293  	batch := dl.diskdb.NewBatch()
   294  
   295  	// Iterate from the previous marker and continue generating the state snapshot
   296  	dl.logged = time.Now()
   297  	for accIt.Next() {
   298  		// Retrieve the current account and flatten it into the internal format
   299  		accountHash := common.BytesToHash(accIt.Key)
   300  
   301  		var acc struct {
   302  			Nonce       uint64
   303  			Balance     *big.Int
   304  			Root        common.Hash
   305  			CodeHash    []byte
   306  			IsMultiCoin bool
   307  		}
   308  		if err := rlp.DecodeBytes(accIt.Value, &acc); err != nil {
   309  			log.Crit("Invalid account encountered during snapshot creation", "err", err)
   310  		}
   311  		data := SlimAccountRLP(acc.Nonce, acc.Balance, acc.Root, acc.CodeHash, acc.IsMultiCoin)
   312  
   313  		// If the account is not yet in-progress, write it out
   314  		if accMarker == nil || !bytes.Equal(accountHash[:], accMarker) {
   315  			rawdb.WriteAccountSnapshot(batch, accountHash, data)
   316  			stats.storage += common.StorageSize(1 + common.HashLength + len(data))
   317  			stats.accounts++
   318  		}
   319  		marker := accountHash[:]
   320  		// If the snap generation goes here after interrupted, genMarker may go backward
   321  		// when last genMarker is consisted of accountHash and storageHash
   322  		if accMarker != nil && bytes.Equal(marker, accMarker) && len(dl.genMarker) > common.HashLength {
   323  			marker = dl.genMarker[:]
   324  		}
   325  		if dl.checkAndFlush(batch, stats, marker) {
   326  			// checkAndFlush handles abort
   327  			return
   328  		}
   329  		// If the iterated account is a contract, iterate through corresponding contract
   330  		// storage to generate snapshot entries.
   331  		if acc.Root != emptyRoot {
   332  			storeTrie, err := trie.NewStateTrie(accountHash, acc.Root, dl.triedb)
   333  			if err != nil {
   334  				log.Error("Generator failed to access storage trie", "root", dl.root, "account", accountHash, "stroot", acc.Root, "err", err)
   335  				abort := <-dl.genAbort
   336  				dl.genStats = stats
   337  				close(abort)
   338  				return
   339  			}
   340  			var storeMarker []byte
   341  			if accMarker != nil && bytes.Equal(accountHash[:], accMarker) && len(dl.genMarker) > common.HashLength {
   342  				storeMarker = dl.genMarker[common.HashLength:]
   343  			}
   344  			storeIt := trie.NewIterator(storeTrie.NodeIterator(storeMarker))
   345  			for storeIt.Next() {
   346  				rawdb.WriteStorageSnapshot(batch, accountHash, common.BytesToHash(storeIt.Key), storeIt.Value)
   347  				stats.storage += common.StorageSize(1 + 2*common.HashLength + len(storeIt.Value))
   348  				stats.slots++
   349  
   350  				if dl.checkAndFlush(batch, stats, append(accountHash[:], storeIt.Key...)) {
   351  					// checkAndFlush handles abort
   352  					return
   353  				}
   354  			}
   355  			if err := storeIt.Err; err != nil {
   356  				log.Error("Generator failed to iterate storage trie", "accroot", dl.root, "acchash", common.BytesToHash(accIt.Key), "stroot", acc.Root, "err", err)
   357  				abort := <-dl.genAbort
   358  				dl.genStats = stats
   359  				close(abort)
   360  				return
   361  			}
   362  		}
   363  		if time.Since(dl.logged) > 8*time.Second {
   364  			stats.Info("Generating state snapshot", dl.root, accIt.Key)
   365  			dl.logged = time.Now()
   366  		}
   367  		// Some account processed, unmark the marker
   368  		accMarker = nil
   369  	}
   370  	if err := accIt.Err; err != nil {
   371  		log.Error("Generator failed to iterate account trie", "root", dl.root, "err", err)
   372  		abort := <-dl.genAbort
   373  		dl.genStats = stats
   374  		close(abort)
   375  		return
   376  	}
   377  	// Snapshot fully generated, set the marker to nil.
   378  	// Note even there is nothing to commit, persist the
   379  	// generator anyway to mark the snapshot is complete.
   380  	journalProgress(batch, nil, stats)
   381  	if err := batch.Write(); err != nil {
   382  		log.Error("Failed to flush batch", "err", err)
   383  		abort := <-dl.genAbort
   384  		dl.genStats = stats
   385  		close(abort)
   386  		return
   387  	}
   388  
   389  	log.Info("Generated state snapshot", "accounts", stats.accounts, "slots", stats.slots,
   390  		"storage", stats.storage, "elapsed", common.PrettyDuration(time.Since(stats.start)))
   391  
   392  	dl.lock.Lock()
   393  	dl.genMarker = nil
   394  	dl.genStats = stats
   395  	close(dl.genPending)
   396  	dl.lock.Unlock()
   397  
   398  	// Someone will be looking for us, wait it out
   399  	abort := <-dl.genAbort
   400  	close(abort)
   401  }