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