github.com/core-coin/go-core/v2@v2.1.9/core/state/snapshot/generate.go (about)

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