github.com/core-coin/go-core/v2@v2.1.9/core/state/snapshot/journal.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  	"errors"
    23  	"fmt"
    24  	"io"
    25  	"time"
    26  
    27  	"github.com/VictoriaMetrics/fastcache"
    28  
    29  	"github.com/core-coin/go-core/v2/xcbdb"
    30  
    31  	"github.com/core-coin/go-core/v2/common"
    32  	"github.com/core-coin/go-core/v2/core/rawdb"
    33  	"github.com/core-coin/go-core/v2/log"
    34  	"github.com/core-coin/go-core/v2/rlp"
    35  	"github.com/core-coin/go-core/v2/trie"
    36  )
    37  
    38  const journalVersion uint64 = 0
    39  
    40  // journalGenerator is a disk layer entry containing the generator progress marker.
    41  type journalGenerator struct {
    42  	Wiping   bool // Whether the database was in progress of being wiped
    43  	Done     bool // Whether the generator finished creating the snapshot
    44  	Marker   []byte
    45  	Accounts uint64
    46  	Slots    uint64
    47  	Storage  uint64
    48  }
    49  
    50  // journalDestruct is an account deletion entry in a diffLayer's disk journal.
    51  type journalDestruct struct {
    52  	Hash common.Hash
    53  }
    54  
    55  // journalAccount is an account entry in a diffLayer's disk journal.
    56  type journalAccount struct {
    57  	Hash common.Hash
    58  	Blob []byte
    59  }
    60  
    61  // journalStorage is an account's storage map in a diffLayer's disk journal.
    62  type journalStorage struct {
    63  	Hash common.Hash
    64  	Keys []common.Hash
    65  	Vals [][]byte
    66  }
    67  
    68  // loadAndParseLegacyJournal tries to parse the snapshot journal in legacy format.
    69  func loadAndParseLegacyJournal(db xcbdb.KeyValueStore, base *diskLayer) (snapshot, journalGenerator, error) {
    70  	// Retrieve the journal, for legacy journal it must exist since even for
    71  	// 0 layer it stores whether we've already generated the snapshot or are
    72  	// in progress only.
    73  	journal := rawdb.ReadSnapshotJournal(db)
    74  	if len(journal) == 0 {
    75  		return nil, journalGenerator{}, errors.New("missing or corrupted snapshot journal")
    76  	}
    77  	r := rlp.NewStream(bytes.NewReader(journal), 0)
    78  
    79  	// Read the snapshot generation progress for the disk layer
    80  	var generator journalGenerator
    81  	if err := r.Decode(&generator); err != nil {
    82  		return nil, journalGenerator{}, fmt.Errorf("failed to load snapshot progress marker: %v", err)
    83  	}
    84  	// Load all the snapshot diffs from the journal
    85  	snapshot, err := loadDiffLayer(base, r)
    86  	if err != nil {
    87  		return nil, generator, err
    88  	}
    89  	return snapshot, generator, nil
    90  }
    91  
    92  // loadAndParseJournal tries to parse the snapshot journal in latest format.
    93  func loadAndParseJournal(db xcbdb.KeyValueStore, base *diskLayer) (snapshot, journalGenerator, error) {
    94  	// Retrieve the disk layer generator. It must exist, no matter the
    95  	// snapshot is fully generated or not. Otherwise the entire disk
    96  	// layer is invalid.
    97  	generatorBlob := rawdb.ReadSnapshotGenerator(db)
    98  	if len(generatorBlob) == 0 {
    99  		return nil, journalGenerator{}, errors.New("missing snapshot generator")
   100  	}
   101  	var generator journalGenerator
   102  	if err := rlp.DecodeBytes(generatorBlob, &generator); err != nil {
   103  		return nil, journalGenerator{}, fmt.Errorf("failed to decode snapshot generator: %v", err)
   104  	}
   105  	// Retrieve the diff layer journal. It's possible that the journal is
   106  	// not existent, e.g. the disk layer is generating while that the Gocore
   107  	// crashes without persisting the diff journal.
   108  	// So if there is no journal, or the journal is invalid(e.g. the journal
   109  	// is not matched with disk layer; or the it's the legacy-format journal,
   110  	// etc.), we just discard all diffs and try to recover them later.
   111  	journal := rawdb.ReadSnapshotJournal(db)
   112  	if len(journal) == 0 {
   113  		log.Warn("Loaded snapshot journal", "diskroot", base.root, "diffs", "missing")
   114  		return base, generator, nil
   115  	}
   116  	r := rlp.NewStream(bytes.NewReader(journal), 0)
   117  
   118  	// Firstly, resolve the first element as the journal version
   119  	version, err := r.Uint()
   120  	if err != nil {
   121  		log.Warn("Failed to resolve the journal version", "error", err)
   122  		return base, generator, nil
   123  	}
   124  	if version != journalVersion {
   125  		log.Warn("Discarded the snapshot journal with wrong version", "required", journalVersion, "got", version)
   126  		return base, generator, nil
   127  	}
   128  	// Secondly, resolve the disk layer root, ensure it's continuous
   129  	// with disk layer. Note now we can ensure it's the snapshot journal
   130  	// correct version, so we expect everything can be resolved properly.
   131  	var root common.Hash
   132  	if err := r.Decode(&root); err != nil {
   133  		return nil, journalGenerator{}, errors.New("missing disk layer root")
   134  	}
   135  	// The diff journal is not matched with disk, discard them.
   136  	// It can happen that Gocore crashes without persisting the latest
   137  	// diff journal.
   138  	if !bytes.Equal(root.Bytes(), base.root.Bytes()) {
   139  		log.Warn("Loaded snapshot journal", "diskroot", base.root, "diffs", "unmatched")
   140  		return base, generator, nil
   141  	}
   142  	// Load all the snapshot diffs from the journal
   143  	snapshot, err := loadDiffLayer(base, r)
   144  	if err != nil {
   145  		return nil, journalGenerator{}, err
   146  	}
   147  	log.Debug("Loaded snapshot journal", "diskroot", base.root, "diffhead", snapshot.Root())
   148  	return snapshot, generator, nil
   149  }
   150  
   151  // loadSnapshot loads a pre-existing state snapshot backed by a key-value store.
   152  func loadSnapshot(diskdb xcbdb.KeyValueStore, triedb *trie.Database, cache int, root common.Hash, recovery bool) (snapshot, error) {
   153  	// Retrieve the block number and hash of the snapshot, failing if no snapshot
   154  	// is present in the database (or crashed mid-update).
   155  	baseRoot := rawdb.ReadSnapshotRoot(diskdb)
   156  	if baseRoot == (common.Hash{}) {
   157  		return nil, errors.New("missing or corrupted snapshot")
   158  	}
   159  	base := &diskLayer{
   160  		diskdb: diskdb,
   161  		triedb: triedb,
   162  		cache:  fastcache.New(cache * 1024 * 1024),
   163  		root:   baseRoot,
   164  	}
   165  	var legacy bool
   166  	snapshot, generator, err := loadAndParseJournal(diskdb, base)
   167  	if err != nil {
   168  		log.Warn("Failed to load new-format journal", "error", err)
   169  		snapshot, generator, err = loadAndParseLegacyJournal(diskdb, base)
   170  		legacy = true
   171  	}
   172  	if err != nil {
   173  		return nil, err
   174  	}
   175  	// Entire snapshot journal loaded, sanity check the head. If the loaded
   176  	// snapshot is not matched with current state root, print a warning log
   177  	// or discard the entire snapshot it's legacy snapshot.
   178  	//
   179  	// Possible scenario: Gocore was crashed without persisting journal and then
   180  	// restart, the head is rewound to the point with available state(trie)
   181  	// which is below the snapshot. In this case the snapshot can be recovered
   182  	// by re-executing blocks but right now it's unavailable.
   183  	if head := snapshot.Root(); head != root {
   184  		// If it's legacy snapshot, or it's new-format snapshot but
   185  		// it's not in recovery mode, returns the error here for
   186  		// rebuilding the entire snapshot forcibly.
   187  		if legacy || !recovery {
   188  			return nil, fmt.Errorf("head doesn't match snapshot: have %#x, want %#x", head, root)
   189  		}
   190  		// It's in snapshot recovery, the assumption is held that
   191  		// the disk layer is always higher than chain head. It can
   192  		// be eventually recovered when the chain head beyonds the
   193  		// disk layer.
   194  		log.Warn("Snapshot is not continuous with chain", "snaproot", head, "chainroot", root)
   195  	}
   196  	// Everything loaded correctly, resume any suspended operations
   197  	if !generator.Done {
   198  		// If the generator was still wiping, restart one from scratch (fine for
   199  		// now as it's rare and the wiper deletes the stuff it touches anyway, so
   200  		// restarting won't incur a lot of extra database hops.
   201  		var wiper chan struct{}
   202  		if generator.Wiping {
   203  			log.Info("Resuming previous snapshot wipe")
   204  			wiper = wipeSnapshot(diskdb, false)
   205  		}
   206  		// Whether or not wiping was in progress, load any generator progress too
   207  		base.genMarker = generator.Marker
   208  		if base.genMarker == nil {
   209  			base.genMarker = []byte{}
   210  		}
   211  		base.genPending = make(chan struct{})
   212  		base.genAbort = make(chan chan *generatorStats)
   213  
   214  		var origin uint64
   215  		if len(generator.Marker) >= 8 {
   216  			origin = binary.BigEndian.Uint64(generator.Marker)
   217  		}
   218  		go base.generate(&generatorStats{
   219  			wiping:   wiper,
   220  			origin:   origin,
   221  			start:    time.Now(),
   222  			accounts: generator.Accounts,
   223  			slots:    generator.Slots,
   224  			storage:  common.StorageSize(generator.Storage),
   225  		})
   226  	}
   227  	return snapshot, nil
   228  }
   229  
   230  // loadDiffLayer reads the next sections of a snapshot journal, reconstructing a new
   231  // diff and verifying that it can be linked to the requested parent.
   232  func loadDiffLayer(parent snapshot, r *rlp.Stream) (snapshot, error) {
   233  	// Read the next diff journal entry
   234  	var root common.Hash
   235  	if err := r.Decode(&root); err != nil {
   236  		// The first read may fail with EOF, marking the end of the journal
   237  		if err == io.EOF {
   238  			return parent, nil
   239  		}
   240  		return nil, fmt.Errorf("load diff root: %v", err)
   241  	}
   242  	var destructs []journalDestruct
   243  	if err := r.Decode(&destructs); err != nil {
   244  		return nil, fmt.Errorf("load diff destructs: %v", err)
   245  	}
   246  	destructSet := make(map[common.Hash]struct{})
   247  	for _, entry := range destructs {
   248  		destructSet[entry.Hash] = struct{}{}
   249  	}
   250  	var accounts []journalAccount
   251  	if err := r.Decode(&accounts); err != nil {
   252  		return nil, fmt.Errorf("load diff accounts: %v", err)
   253  	}
   254  	accountData := make(map[common.Hash][]byte)
   255  	for _, entry := range accounts {
   256  		if len(entry.Blob) > 0 { // RLP loses nil-ness, but `[]byte{}` is not a valid item, so reinterpret that
   257  			accountData[entry.Hash] = entry.Blob
   258  		} else {
   259  			accountData[entry.Hash] = nil
   260  		}
   261  	}
   262  	var storage []journalStorage
   263  	if err := r.Decode(&storage); err != nil {
   264  		return nil, fmt.Errorf("load diff storage: %v", err)
   265  	}
   266  	storageData := make(map[common.Hash]map[common.Hash][]byte)
   267  	for _, entry := range storage {
   268  		slots := make(map[common.Hash][]byte)
   269  		for i, key := range entry.Keys {
   270  			if len(entry.Vals[i]) > 0 { // RLP loses nil-ness, but `[]byte{}` is not a valid item, so reinterpret that
   271  				slots[key] = entry.Vals[i]
   272  			} else {
   273  				slots[key] = nil
   274  			}
   275  		}
   276  		storageData[entry.Hash] = slots
   277  	}
   278  	return loadDiffLayer(newDiffLayer(parent, root, destructSet, accountData, storageData), r)
   279  }
   280  
   281  // Journal terminates any in-progress snapshot generation, also implicitly pushing
   282  // the progress into the database.
   283  func (dl *diskLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) {
   284  	// If the snapshot is currently being generated, abort it
   285  	var stats *generatorStats
   286  	if dl.genAbort != nil {
   287  		abort := make(chan *generatorStats)
   288  		dl.genAbort <- abort
   289  
   290  		if stats = <-abort; stats != nil {
   291  			stats.Log("Journalling in-progress snapshot", dl.root, dl.genMarker)
   292  		}
   293  	}
   294  	// Ensure the layer didn't get stale
   295  	dl.lock.RLock()
   296  	defer dl.lock.RUnlock()
   297  
   298  	if dl.stale {
   299  		return common.Hash{}, ErrSnapshotStale
   300  	}
   301  	// Ensure the generator stats is written even if none was ran this cycle
   302  	journalProgress(dl.diskdb, dl.genMarker, stats)
   303  
   304  	log.Debug("Journalled disk layer", "root", dl.root)
   305  	return dl.root, nil
   306  }
   307  
   308  // Journal writes the memory layer contents into a buffer to be stored in the
   309  // database as the snapshot journal.
   310  func (dl *diffLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) {
   311  	// Journal the parent first
   312  	base, err := dl.parent.Journal(buffer)
   313  	if err != nil {
   314  		return common.Hash{}, err
   315  	}
   316  	// Ensure the layer didn't get stale
   317  	dl.lock.RLock()
   318  	defer dl.lock.RUnlock()
   319  
   320  	if dl.Stale() {
   321  		return common.Hash{}, ErrSnapshotStale
   322  	}
   323  	// Everything below was journalled, persist this layer too
   324  	if err := rlp.Encode(buffer, dl.root); err != nil {
   325  		return common.Hash{}, err
   326  	}
   327  	destructs := make([]journalDestruct, 0, len(dl.destructSet))
   328  	for hash := range dl.destructSet {
   329  		destructs = append(destructs, journalDestruct{Hash: hash})
   330  	}
   331  	if err := rlp.Encode(buffer, destructs); err != nil {
   332  		return common.Hash{}, err
   333  	}
   334  	accounts := make([]journalAccount, 0, len(dl.accountData))
   335  	for hash, blob := range dl.accountData {
   336  		accounts = append(accounts, journalAccount{Hash: hash, Blob: blob})
   337  	}
   338  	if err := rlp.Encode(buffer, accounts); err != nil {
   339  		return common.Hash{}, err
   340  	}
   341  	storage := make([]journalStorage, 0, len(dl.storageData))
   342  	for hash, slots := range dl.storageData {
   343  		keys := make([]common.Hash, 0, len(slots))
   344  		vals := make([][]byte, 0, len(slots))
   345  		for key, val := range slots {
   346  			keys = append(keys, key)
   347  			vals = append(vals, val)
   348  		}
   349  		storage = append(storage, journalStorage{Hash: hash, Keys: keys, Vals: vals})
   350  	}
   351  	if err := rlp.Encode(buffer, storage); err != nil {
   352  		return common.Hash{}, err
   353  	}
   354  	log.Debug("Journalled diff layer", "root", dl.root, "parent", dl.parent.Root())
   355  	return base, nil
   356  }
   357  
   358  // LegacyJournal writes the persistent layer generator stats into a buffer
   359  // to be stored in the database as the snapshot journal.
   360  //
   361  // Note it's the legacy version which is only used in testing right now.
   362  func (dl *diskLayer) LegacyJournal(buffer *bytes.Buffer) (common.Hash, error) {
   363  	// If the snapshot is currently being generated, abort it
   364  	var stats *generatorStats
   365  	if dl.genAbort != nil {
   366  		abort := make(chan *generatorStats)
   367  		dl.genAbort <- abort
   368  
   369  		if stats = <-abort; stats != nil {
   370  			stats.Log("Journalling in-progress snapshot", dl.root, dl.genMarker)
   371  		}
   372  	}
   373  	// Ensure the layer didn't get stale
   374  	dl.lock.RLock()
   375  	defer dl.lock.RUnlock()
   376  
   377  	if dl.stale {
   378  		return common.Hash{}, ErrSnapshotStale
   379  	}
   380  	// Write out the generator marker
   381  	entry := journalGenerator{
   382  		Done:   dl.genMarker == nil,
   383  		Marker: dl.genMarker,
   384  	}
   385  	if stats != nil {
   386  		entry.Wiping = (stats.wiping != nil)
   387  		entry.Accounts = stats.accounts
   388  		entry.Slots = stats.slots
   389  		entry.Storage = uint64(stats.storage)
   390  	}
   391  	log.Debug("Legacy journalled disk layer", "root", dl.root)
   392  	if err := rlp.Encode(buffer, entry); err != nil {
   393  		return common.Hash{}, err
   394  	}
   395  	return dl.root, nil
   396  }
   397  
   398  // Journal writes the memory layer contents into a buffer to be stored in the
   399  // database as the snapshot journal.
   400  //
   401  // Note it's the legacy version which is only used in testing right now.
   402  func (dl *diffLayer) LegacyJournal(buffer *bytes.Buffer) (common.Hash, error) {
   403  	// Journal the parent first
   404  	base, err := dl.parent.LegacyJournal(buffer)
   405  	if err != nil {
   406  		return common.Hash{}, err
   407  	}
   408  	// Ensure the layer didn't get stale
   409  	dl.lock.RLock()
   410  	defer dl.lock.RUnlock()
   411  
   412  	if dl.Stale() {
   413  		return common.Hash{}, ErrSnapshotStale
   414  	}
   415  	// Everything below was journalled, persist this layer too
   416  	if err := rlp.Encode(buffer, dl.root); err != nil {
   417  		return common.Hash{}, err
   418  	}
   419  	destructs := make([]journalDestruct, 0, len(dl.destructSet))
   420  	for hash := range dl.destructSet {
   421  		destructs = append(destructs, journalDestruct{Hash: hash})
   422  	}
   423  	if err := rlp.Encode(buffer, destructs); err != nil {
   424  		return common.Hash{}, err
   425  	}
   426  	accounts := make([]journalAccount, 0, len(dl.accountData))
   427  	for hash, blob := range dl.accountData {
   428  		accounts = append(accounts, journalAccount{Hash: hash, Blob: blob})
   429  	}
   430  	if err := rlp.Encode(buffer, accounts); err != nil {
   431  		return common.Hash{}, err
   432  	}
   433  	storage := make([]journalStorage, 0, len(dl.storageData))
   434  	for hash, slots := range dl.storageData {
   435  		keys := make([]common.Hash, 0, len(slots))
   436  		vals := make([][]byte, 0, len(slots))
   437  		for key, val := range slots {
   438  			keys = append(keys, key)
   439  			vals = append(vals, val)
   440  		}
   441  		storage = append(storage, journalStorage{Hash: hash, Keys: keys, Vals: vals})
   442  	}
   443  	if err := rlp.Encode(buffer, storage); err != nil {
   444  		return common.Hash{}, err
   445  	}
   446  	log.Debug("Legacy journalled disk layer", "root", dl.root, "parent", dl.parent.Root())
   447  	return base, nil
   448  }