github.com/ethereum/go-ethereum@v1.16.1/core/state/snapshot/journal.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  	"errors"
    23  	"fmt"
    24  	"io"
    25  	"time"
    26  
    27  	"github.com/VictoriaMetrics/fastcache"
    28  	"github.com/ethereum/go-ethereum/common"
    29  	"github.com/ethereum/go-ethereum/core/rawdb"
    30  	"github.com/ethereum/go-ethereum/ethdb"
    31  	"github.com/ethereum/go-ethereum/log"
    32  	"github.com/ethereum/go-ethereum/rlp"
    33  	"github.com/ethereum/go-ethereum/triedb"
    34  )
    35  
    36  const (
    37  	journalV0             uint64 = 0 // initial version
    38  	journalV1             uint64 = 1 // current version, with destruct flag (in diff layers) removed
    39  	journalCurrentVersion        = journalV1
    40  )
    41  
    42  // journalGenerator is a disk layer entry containing the generator progress marker.
    43  type journalGenerator struct {
    44  	// Indicator that whether the database was in progress of being wiped.
    45  	// It's deprecated but keep it here for background compatibility.
    46  	Wiping bool
    47  
    48  	Done     bool // Whether the generator finished creating the snapshot
    49  	Marker   []byte
    50  	Accounts uint64
    51  	Slots    uint64
    52  	Storage  uint64
    53  }
    54  
    55  // journalDestruct is an account deletion entry in a diffLayer's disk journal.
    56  type journalDestruct struct {
    57  	Hash common.Hash
    58  }
    59  
    60  // journalAccount is an account entry in a diffLayer's disk journal.
    61  type journalAccount struct {
    62  	Hash common.Hash
    63  	Blob []byte
    64  }
    65  
    66  // journalStorage is an account's storage map in a diffLayer's disk journal.
    67  type journalStorage struct {
    68  	Hash common.Hash
    69  	Keys []common.Hash
    70  	Vals [][]byte
    71  }
    72  
    73  func ParseGeneratorStatus(generatorBlob []byte) string {
    74  	if len(generatorBlob) == 0 {
    75  		return ""
    76  	}
    77  	var generator journalGenerator
    78  	if err := rlp.DecodeBytes(generatorBlob, &generator); err != nil {
    79  		log.Warn("failed to decode snapshot generator", "err", err)
    80  		return ""
    81  	}
    82  	// Figure out whether we're after or within an account
    83  	var m string
    84  	switch marker := generator.Marker; len(marker) {
    85  	case common.HashLength:
    86  		m = fmt.Sprintf("at %#x", marker)
    87  	case 2 * common.HashLength:
    88  		m = fmt.Sprintf("in %#x at %#x", marker[:common.HashLength], marker[common.HashLength:])
    89  	default:
    90  		m = fmt.Sprintf("%#x", marker)
    91  	}
    92  	return fmt.Sprintf(`Done: %v, Accounts: %d, Slots: %d, Storage: %d, Marker: %s`,
    93  		generator.Done, generator.Accounts, generator.Slots, generator.Storage, m)
    94  }
    95  
    96  // loadAndParseJournal tries to parse the snapshot journal in latest format.
    97  func loadAndParseJournal(db ethdb.KeyValueStore, base *diskLayer) (snapshot, journalGenerator, error) {
    98  	// Retrieve the disk layer generator. It must exist, no matter the
    99  	// snapshot is fully generated or not. Otherwise the entire disk
   100  	// layer is invalid.
   101  	generatorBlob := rawdb.ReadSnapshotGenerator(db)
   102  	if len(generatorBlob) == 0 {
   103  		return nil, journalGenerator{}, errors.New("missing snapshot generator")
   104  	}
   105  	var generator journalGenerator
   106  	if err := rlp.DecodeBytes(generatorBlob, &generator); err != nil {
   107  		return nil, journalGenerator{}, fmt.Errorf("failed to decode snapshot generator: %v", err)
   108  	}
   109  	// Retrieve the diff layer journal. It's possible that the journal is
   110  	// not existent, e.g. the disk layer is generating while that the Geth
   111  	// crashes without persisting the diff journal.
   112  	// So if there is no journal, or the journal is invalid(e.g. the journal
   113  	// is not matched with disk layer; or the it's the legacy-format journal,
   114  	// etc.), we just discard all diffs and try to recover them later.
   115  	var current snapshot = base
   116  	err := iterateJournal(db, func(parent common.Hash, root common.Hash, accountData map[common.Hash][]byte, storageData map[common.Hash]map[common.Hash][]byte) error {
   117  		current = newDiffLayer(current, root, accountData, storageData)
   118  		return nil
   119  	})
   120  	if err != nil {
   121  		return base, generator, nil
   122  	}
   123  	return current, generator, nil
   124  }
   125  
   126  // loadSnapshot loads a pre-existing state snapshot backed by a key-value store.
   127  func loadSnapshot(diskdb ethdb.KeyValueStore, triedb *triedb.Database, root common.Hash, cache int, recovery bool, noBuild bool) (snapshot, bool, error) {
   128  	// If snapshotting is disabled (initial sync in progress), don't do anything,
   129  	// wait for the chain to permit us to do something meaningful
   130  	if rawdb.ReadSnapshotDisabled(diskdb) {
   131  		return nil, true, nil
   132  	}
   133  	// Retrieve the block number and hash of the snapshot, failing if no snapshot
   134  	// is present in the database (or crashed mid-update).
   135  	baseRoot := rawdb.ReadSnapshotRoot(diskdb)
   136  	if baseRoot == (common.Hash{}) {
   137  		return nil, false, errors.New("missing or corrupted snapshot")
   138  	}
   139  	base := &diskLayer{
   140  		diskdb: diskdb,
   141  		triedb: triedb,
   142  		cache:  fastcache.New(cache * 1024 * 1024),
   143  		root:   baseRoot,
   144  	}
   145  	snapshot, generator, err := loadAndParseJournal(diskdb, base)
   146  	if err != nil {
   147  		log.Warn("Failed to load journal", "error", err)
   148  		return nil, false, err
   149  	}
   150  	// Entire snapshot journal loaded, sanity check the head. If the loaded
   151  	// snapshot is not matched with current state root, print a warning log
   152  	// or discard the entire snapshot it's legacy snapshot.
   153  	//
   154  	// Possible scenario: Geth was crashed without persisting journal and then
   155  	// restart, the head is rewound to the point with available state(trie)
   156  	// which is below the snapshot. In this case the snapshot can be recovered
   157  	// by re-executing blocks but right now it's unavailable.
   158  	if head := snapshot.Root(); head != root {
   159  		// If it's legacy snapshot, or it's new-format snapshot but
   160  		// it's not in recovery mode, returns the error here for
   161  		// rebuilding the entire snapshot forcibly.
   162  		if !recovery {
   163  			return nil, false, fmt.Errorf("head doesn't match snapshot: have %#x, want %#x", head, root)
   164  		}
   165  		// It's in snapshot recovery, the assumption is held that
   166  		// the disk layer is always higher than chain head. It can
   167  		// be eventually recovered when the chain head beyonds the
   168  		// disk layer.
   169  		log.Warn("Snapshot is not continuous with chain", "snaproot", head, "chainroot", root)
   170  	}
   171  	// Load the disk layer status from the generator if it's not complete
   172  	if !generator.Done {
   173  		base.genMarker = generator.Marker
   174  		if base.genMarker == nil {
   175  			base.genMarker = []byte{}
   176  		}
   177  	}
   178  	// Everything loaded correctly, resume any suspended operations
   179  	// if the background generation is allowed
   180  	if !generator.Done && !noBuild {
   181  		base.genPending = make(chan struct{})
   182  		base.genAbort = make(chan chan *generatorStats)
   183  
   184  		var origin uint64
   185  		if len(generator.Marker) >= 8 {
   186  			origin = binary.BigEndian.Uint64(generator.Marker)
   187  		}
   188  		go base.generate(&generatorStats{
   189  			origin:   origin,
   190  			start:    time.Now(),
   191  			accounts: generator.Accounts,
   192  			slots:    generator.Slots,
   193  			storage:  common.StorageSize(generator.Storage),
   194  		})
   195  	}
   196  	return snapshot, false, nil
   197  }
   198  
   199  // Journal terminates any in-progress snapshot generation, also implicitly pushing
   200  // the progress into the database.
   201  func (dl *diskLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) {
   202  	// If the snapshot is currently being generated, abort it
   203  	var stats *generatorStats
   204  	if dl.genAbort != nil {
   205  		abort := make(chan *generatorStats)
   206  		dl.genAbort <- abort
   207  
   208  		if stats = <-abort; stats != nil {
   209  			stats.Log("Journalling in-progress snapshot", dl.root, dl.genMarker)
   210  		}
   211  	}
   212  	// Ensure the layer didn't get stale
   213  	dl.lock.RLock()
   214  	defer dl.lock.RUnlock()
   215  
   216  	if dl.stale {
   217  		return common.Hash{}, ErrSnapshotStale
   218  	}
   219  	// Ensure the generator stats is written even if none was ran this cycle
   220  	journalProgress(dl.diskdb, dl.genMarker, stats)
   221  
   222  	log.Debug("Journalled disk layer", "root", dl.root)
   223  	return dl.root, nil
   224  }
   225  
   226  // Journal writes the memory layer contents into a buffer to be stored in the
   227  // database as the snapshot journal.
   228  func (dl *diffLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) {
   229  	// Journal the parent first
   230  	base, err := dl.parent.Journal(buffer)
   231  	if err != nil {
   232  		return common.Hash{}, err
   233  	}
   234  	// Ensure the layer didn't get stale
   235  	dl.lock.RLock()
   236  	defer dl.lock.RUnlock()
   237  
   238  	if dl.Stale() {
   239  		return common.Hash{}, ErrSnapshotStale
   240  	}
   241  	// Everything below was journalled, persist this layer too
   242  	if err := rlp.Encode(buffer, dl.root); err != nil {
   243  		return common.Hash{}, err
   244  	}
   245  	accounts := make([]journalAccount, 0, len(dl.accountData))
   246  	for hash, blob := range dl.accountData {
   247  		accounts = append(accounts, journalAccount{
   248  			Hash: hash,
   249  			Blob: blob,
   250  		})
   251  	}
   252  	if err := rlp.Encode(buffer, accounts); err != nil {
   253  		return common.Hash{}, err
   254  	}
   255  	storage := make([]journalStorage, 0, len(dl.storageData))
   256  	for hash, slots := range dl.storageData {
   257  		keys := make([]common.Hash, 0, len(slots))
   258  		vals := make([][]byte, 0, len(slots))
   259  		for key, val := range slots {
   260  			keys = append(keys, key)
   261  			vals = append(vals, val)
   262  		}
   263  		storage = append(storage, journalStorage{Hash: hash, Keys: keys, Vals: vals})
   264  	}
   265  	if err := rlp.Encode(buffer, storage); err != nil {
   266  		return common.Hash{}, err
   267  	}
   268  	log.Debug("Journalled diff layer", "root", dl.root, "parent", dl.parent.Root())
   269  	return base, nil
   270  }
   271  
   272  // journalCallback is a function which is invoked by iterateJournal, every
   273  // time a difflayer is loaded from disk.
   274  type journalCallback = func(parent common.Hash, root common.Hash, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) error
   275  
   276  // iterateJournal iterates through the journalled difflayers, loading them from
   277  // the database, and invoking the callback for each loaded layer.
   278  // The order is incremental; starting with the bottom-most difflayer, going towards
   279  // the most recent layer.
   280  // This method returns error either if there was some error reading from disk,
   281  // OR if the callback returns an error when invoked.
   282  func iterateJournal(db ethdb.KeyValueReader, callback journalCallback) error {
   283  	journal := rawdb.ReadSnapshotJournal(db)
   284  	if len(journal) == 0 {
   285  		log.Warn("Loaded snapshot journal", "diffs", "missing")
   286  		return nil
   287  	}
   288  	r := rlp.NewStream(bytes.NewReader(journal), 0)
   289  	// Firstly, resolve the first element as the journal version
   290  	version, err := r.Uint64()
   291  	if err != nil {
   292  		log.Warn("Failed to resolve the journal version", "error", err)
   293  		return errors.New("failed to resolve journal version")
   294  	}
   295  	if version != journalV0 && version != journalCurrentVersion {
   296  		log.Warn("Discarded journal with wrong version", "required", journalCurrentVersion, "got", version)
   297  		return errors.New("wrong journal version")
   298  	}
   299  	// Secondly, resolve the disk layer root, ensure it's continuous
   300  	// with disk layer. Note now we can ensure it's the snapshot journal
   301  	// correct version, so we expect everything can be resolved properly.
   302  	var parent common.Hash
   303  	if err := r.Decode(&parent); err != nil {
   304  		return errors.New("missing disk layer root")
   305  	}
   306  	if baseRoot := rawdb.ReadSnapshotRoot(db); baseRoot != parent {
   307  		log.Warn("Loaded snapshot journal", "diskroot", baseRoot, "diffs", "unmatched")
   308  		return errors.New("mismatched disk and diff layers")
   309  	}
   310  	for {
   311  		var (
   312  			root        common.Hash
   313  			accounts    []journalAccount
   314  			storage     []journalStorage
   315  			accountData = make(map[common.Hash][]byte)
   316  			storageData = make(map[common.Hash]map[common.Hash][]byte)
   317  		)
   318  		// Read the next diff journal entry
   319  		if err := r.Decode(&root); err != nil {
   320  			// The first read may fail with EOF, marking the end of the journal
   321  			if errors.Is(err, io.EOF) {
   322  				return nil
   323  			}
   324  			return fmt.Errorf("load diff root: %v", err)
   325  		}
   326  		// If a legacy journal is detected, decode the destruct set from the stream.
   327  		// The destruct set has been deprecated. If the journal contains non-empty
   328  		// destruct set, then it is deemed incompatible.
   329  		//
   330  		// Since self-destruction has been deprecated following the cancun fork,
   331  		// the destruct set is expected to be nil for layers above the fork block.
   332  		// However, an exception occurs during contract deployment: pre-funded accounts
   333  		// may self-destruct, causing accounts with non-zero balances to be removed
   334  		// from the state. For example,
   335  		// https://etherscan.io/tx/0xa087333d83f0cd63b96bdafb686462e1622ce25f40bd499e03efb1051f31fe49).
   336  		//
   337  		// For nodes with a fully synced state, the legacy journal is likely compatible
   338  		// with the updated definition, eliminating the need for regeneration. Unfortunately,
   339  		// nodes performing a full sync of historical chain segments or encountering
   340  		// pre-funded account deletions may face incompatibilities, leading to automatic
   341  		// snapshot regeneration.
   342  		//
   343  		// This approach minimizes snapshot regeneration for Geth nodes upgrading from a
   344  		// legacy version that are already synced. The workaround can be safely removed
   345  		// after the next hard fork.
   346  		if version == journalV0 {
   347  			var destructs []journalDestruct
   348  			if err := r.Decode(&destructs); err != nil {
   349  				return fmt.Errorf("load diff destructs: %v", err)
   350  			}
   351  			if len(destructs) > 0 {
   352  				log.Warn("Incompatible legacy journal detected", "version", journalV0)
   353  				return fmt.Errorf("incompatible legacy journal detected")
   354  			}
   355  		}
   356  		if err := r.Decode(&accounts); err != nil {
   357  			return fmt.Errorf("load diff accounts: %v", err)
   358  		}
   359  		if err := r.Decode(&storage); err != nil {
   360  			return fmt.Errorf("load diff storage: %v", err)
   361  		}
   362  		for _, entry := range accounts {
   363  			if len(entry.Blob) > 0 { // RLP loses nil-ness, but `[]byte{}` is not a valid item, so reinterpret that
   364  				accountData[entry.Hash] = entry.Blob
   365  			} else {
   366  				accountData[entry.Hash] = nil
   367  			}
   368  		}
   369  		for _, entry := range storage {
   370  			slots := make(map[common.Hash][]byte)
   371  			for i, key := range entry.Keys {
   372  				if len(entry.Vals[i]) > 0 { // RLP loses nil-ness, but `[]byte{}` is not a valid item, so reinterpret that
   373  					slots[key] = entry.Vals[i]
   374  				} else {
   375  					slots[key] = nil
   376  				}
   377  			}
   378  			storageData[entry.Hash] = slots
   379  		}
   380  		if err := callback(parent, root, accountData, storageData); err != nil {
   381  			return err
   382  		}
   383  		parent = root
   384  	}
   385  }