github.com/juliankolbe/go-ethereum@v1.9.992/core/state/snapshot/generate.go (about)

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