github.com/klaytn/klaytn@v1.12.1/snapshot/journal.go (about)

     1  // Modifications Copyright 2021 The klaytn Authors
     2  // Copyright 2019 The go-ethereum Authors
     3  // This file is part of the go-ethereum library.
     4  //
     5  // The go-ethereum library is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Lesser General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // The go-ethereum library is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    13  // GNU Lesser General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Lesser General Public License
    16  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    17  //
    18  // This file is derived from core/state/snapshot/journal.go (2021/10/21).
    19  // Modified and improved for the klaytn development.
    20  
    21  package snapshot
    22  
    23  import (
    24  	"bytes"
    25  	"encoding/binary"
    26  	"errors"
    27  	"fmt"
    28  	"io"
    29  	"time"
    30  
    31  	"github.com/klaytn/klaytn/common"
    32  	"github.com/klaytn/klaytn/rlp"
    33  	"github.com/klaytn/klaytn/storage/database"
    34  	"github.com/klaytn/klaytn/storage/statedb"
    35  
    36  	"github.com/VictoriaMetrics/fastcache"
    37  )
    38  
    39  const journalVersion uint64 = 0
    40  
    41  // journalGenerator is a disk layer entry containing the generator progress marker.
    42  type journalGenerator struct {
    43  	// Indicator that whether the database was in progress of being wiped.
    44  	// It's deprecated but keep it here for background compatibility.
    45  	Wiping bool
    46  
    47  	Done     bool // Whether the generator finished creating the snapshot
    48  	Marker   []byte
    49  	Accounts uint64
    50  	Slots    uint64
    51  	Storage  uint64
    52  }
    53  
    54  // journalDestruct is an account deletion entry in a diffLayer's disk journal.
    55  type journalDestruct struct {
    56  	Hash common.Hash
    57  }
    58  
    59  // journalAccount is an account entry in a diffLayer's disk journal.
    60  type journalAccount struct {
    61  	Hash common.Hash
    62  	Blob []byte
    63  }
    64  
    65  // journalStorage is an account's storage map in a diffLayer's disk journal.
    66  type journalStorage struct {
    67  	Hash common.Hash
    68  	Keys []common.Hash
    69  	Vals [][]byte
    70  }
    71  
    72  // loadAndParseJournal tries to parse the snapshot journal in latest format.
    73  func loadAndParseJournal(db database.DBManager, base *diskLayer) (snapshot, journalGenerator, error) {
    74  	// Retrieve the disk layer generator. It must exist, no matter the
    75  	// snapshot is fully generated or not. Otherwise the entire disk
    76  	// layer is invalid.
    77  	generatorBlob := db.ReadSnapshotGenerator()
    78  	if len(generatorBlob) == 0 {
    79  		return nil, journalGenerator{}, errors.New("missing snapshot generator")
    80  	}
    81  	var generator journalGenerator
    82  	if err := rlp.DecodeBytes(generatorBlob, &generator); err != nil {
    83  		return nil, journalGenerator{}, fmt.Errorf("failed to decode snapshot generator: %v", err)
    84  	}
    85  	// Retrieve the diff layer journal. It's possible that the journal is
    86  	// not existent, e.g. the disk layer is generating while that the Geth
    87  	// crashes without persisting the diff journal.
    88  	// So if there is no journal, or the journal is invalid(e.g. the journal
    89  	// is not matched with disk layer; or the it's the legacy-format journal,
    90  	// etc.), we just discard all diffs and try to recover them later.
    91  	journal := db.ReadSnapshotJournal()
    92  	if len(journal) == 0 {
    93  		logger.Warn("Loaded snapshot journal", "diskroot", base.root, "diffs", "missing")
    94  		return base, generator, nil
    95  	}
    96  	r := rlp.NewStream(bytes.NewReader(journal), 0)
    97  
    98  	// Firstly, resolve the first element as the journal version
    99  	version, err := r.Uint()
   100  	if err != nil {
   101  		logger.Warn("Failed to resolve the journal version", "error", err)
   102  		return base, generator, nil
   103  	}
   104  	if version != journalVersion {
   105  		logger.Warn("Discarded the snapshot journal with wrong version", "required", journalVersion, "got", version)
   106  		return base, generator, nil
   107  	}
   108  	// Secondly, resolve the disk layer root, ensure it's continuous
   109  	// with disk layer. Note now we can ensure it's the snapshot journal
   110  	// correct version, so we expect everything can be resolved properly.
   111  	var root common.Hash
   112  	if err := r.Decode(&root); err != nil {
   113  		return nil, journalGenerator{}, errors.New("missing disk layer root")
   114  	}
   115  	// The diff journal is not matched with disk, discard them.
   116  	// It can happen that Geth crashes without persisting the latest
   117  	// diff journal.
   118  	if !bytes.Equal(root.Bytes(), base.root.Bytes()) {
   119  		logger.Warn("Loaded snapshot journal", "diskroot", base.root, "diffs", "unmatched")
   120  		return base, generator, nil
   121  	}
   122  	// Load all the snapshot diffs from the journal
   123  	snapshot, err := loadDiffLayer(base, r)
   124  	if err != nil {
   125  		return nil, journalGenerator{}, err
   126  	}
   127  	logger.Debug("Loaded snapshot journal", "diskroot", base.root, "diffhead", snapshot.Root())
   128  	return snapshot, generator, nil
   129  }
   130  
   131  // loadSnapshot loads a pre-existing state snapshot backed by a key-value store.
   132  func loadSnapshot(diskdb database.DBManager, triedb *statedb.Database, cache int, root common.Hash, recovery bool) (snapshot, bool, error) {
   133  	// If snapshotting is disabled (initial sync in progress), don't do anything,
   134  	// wait for the chain to permit us to do something meaningful
   135  	if diskdb.ReadSnapshotDisabled() {
   136  		return nil, true, nil
   137  	}
   138  	// Retrieve the block number and hash of the snapshot, failing if no snapshot
   139  	// is present in the database (or crashed mid-update).
   140  	baseRoot := diskdb.ReadSnapshotRoot()
   141  	if baseRoot == (common.Hash{}) {
   142  		return nil, false, errors.New("missing or corrupted snapshot")
   143  	}
   144  	base := &diskLayer{
   145  		diskdb: diskdb,
   146  		triedb: triedb,
   147  		cache:  fastcache.New(cache * 1024 * 1024),
   148  		root:   baseRoot,
   149  	}
   150  	snapshot, generator, err := loadAndParseJournal(diskdb, base)
   151  	if err != nil {
   152  		logger.Warn("Failed to load new-format journal", "error", err)
   153  		return nil, false, err
   154  	}
   155  	// Entire snapshot journal loaded, sanity check the head. If the loaded
   156  	// snapshot is not matched with current state root, print a warning log
   157  	// or discard the entire snapshot it's legacy snapshot.
   158  	//
   159  	// Possible scenario: Geth was crashed without persisting journal and then
   160  	// restart, the head is rewound to the point with available state(trie)
   161  	// which is below the snapshot. In this case the snapshot can be recovered
   162  	// by re-executing blocks but right now it's unavailable.
   163  	if head := snapshot.Root(); head != root {
   164  		// If it's legacy snapshot, or it's new-format snapshot but
   165  		// it's not in recovery mode, returns the error here for
   166  		// rebuilding the entire snapshot forcibly.
   167  		if !recovery {
   168  			return nil, false, fmt.Errorf("head doesn't match snapshot: have %#x, want %#x", head, root)
   169  		}
   170  		// It's in snapshot recovery, the assumption is held that
   171  		// the disk layer is always higher than chain head. It can
   172  		// be eventually recovered when the chain head beyonds the
   173  		// disk layer.
   174  		logger.Warn("Snapshot is not continuous with chain", "snaproot", head, "chainroot", root)
   175  	}
   176  	// Everything loaded correctly, resume any suspended operations
   177  	if !generator.Done {
   178  		// Whether or not wiping was in progress, load any generator progress too
   179  		base.genMarker = generator.Marker
   180  		if base.genMarker == nil {
   181  			base.genMarker = []byte{}
   182  		}
   183  		base.genPending = make(chan struct{})
   184  		base.genAbort = make(chan chan *generatorStats)
   185  
   186  		var origin uint64
   187  		if len(generator.Marker) >= 8 {
   188  			origin = binary.BigEndian.Uint64(generator.Marker)
   189  		}
   190  		go base.generate(&generatorStats{
   191  			origin:   origin,
   192  			start:    time.Now(),
   193  			accounts: generator.Accounts,
   194  			slots:    generator.Slots,
   195  			storage:  common.StorageSize(generator.Storage),
   196  		})
   197  	}
   198  	return snapshot, false, nil
   199  }
   200  
   201  // loadDiffLayer reads the next sections of a snapshot journal, reconstructing a new
   202  // diff and verifying that it can be linked to the requested parent.
   203  func loadDiffLayer(parent snapshot, r *rlp.Stream) (snapshot, error) {
   204  	// Read the next diff journal entry
   205  	var root common.Hash
   206  	if err := r.Decode(&root); err != nil {
   207  		// The first read may fail with EOF, marking the end of the journal
   208  		if err == io.EOF {
   209  			return parent, nil
   210  		}
   211  		return nil, fmt.Errorf("load diff root: %v", err)
   212  	}
   213  	var destructs []journalDestruct
   214  	if err := r.Decode(&destructs); err != nil {
   215  		return nil, fmt.Errorf("load diff destructs: %v", err)
   216  	}
   217  	destructSet := make(map[common.Hash]struct{})
   218  	for _, entry := range destructs {
   219  		destructSet[entry.Hash] = struct{}{}
   220  	}
   221  	var accounts []journalAccount
   222  	if err := r.Decode(&accounts); err != nil {
   223  		return nil, fmt.Errorf("load diff accounts: %v", err)
   224  	}
   225  	accountData := make(map[common.Hash][]byte)
   226  	for _, entry := range accounts {
   227  		if len(entry.Blob) > 0 { // RLP loses nil-ness, but `[]byte{}` is not a valid item, so reinterpret that
   228  			accountData[entry.Hash] = entry.Blob
   229  		} else {
   230  			accountData[entry.Hash] = nil
   231  		}
   232  	}
   233  	var storage []journalStorage
   234  	if err := r.Decode(&storage); err != nil {
   235  		return nil, fmt.Errorf("load diff storage: %v", err)
   236  	}
   237  	storageData := make(map[common.Hash]map[common.Hash][]byte)
   238  	for _, entry := range storage {
   239  		slots := make(map[common.Hash][]byte)
   240  		for i, key := range entry.Keys {
   241  			if len(entry.Vals[i]) > 0 { // RLP loses nil-ness, but `[]byte{}` is not a valid item, so reinterpret that
   242  				slots[key] = entry.Vals[i]
   243  			} else {
   244  				slots[key] = nil
   245  			}
   246  		}
   247  		storageData[entry.Hash] = slots
   248  	}
   249  	return loadDiffLayer(newDiffLayer(parent, root, destructSet, accountData, storageData), r)
   250  }
   251  
   252  // Journal terminates any in-progress snapshot generation, also implicitly pushing
   253  // the progress into the database.
   254  func (dl *diskLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) {
   255  	// If the snapshot is currently being generated, abort it
   256  	var stats *generatorStats
   257  	if dl.genAbort != nil {
   258  		abort := make(chan *generatorStats)
   259  		dl.genAbort <- abort
   260  
   261  		if stats = <-abort; stats != nil {
   262  			stats.Log("Journalling in-progress snapshot", dl.root, dl.genMarker)
   263  		}
   264  	}
   265  	// Ensure the layer didn't get stale
   266  	dl.lock.RLock()
   267  	defer dl.lock.RUnlock()
   268  
   269  	if dl.stale {
   270  		return common.Hash{}, ErrSnapshotStale
   271  	}
   272  	// Ensure the generator stats is written even if none was ran this cycle
   273  	journalProgress(dl.diskdb.GetSnapshotDB(), dl.genMarker, stats)
   274  
   275  	logger.Debug("Journalled disk layer", "root", dl.root)
   276  	return dl.root, nil
   277  }
   278  
   279  // Journal writes the memory layer contents into a buffer to be stored in the
   280  // database as the snapshot journal.
   281  func (dl *diffLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) {
   282  	// Journal the parent first
   283  	base, err := dl.parent.Journal(buffer)
   284  	if err != nil {
   285  		return common.Hash{}, err
   286  	}
   287  	// Ensure the layer didn't get stale
   288  	dl.lock.RLock()
   289  	defer dl.lock.RUnlock()
   290  
   291  	if dl.Stale() {
   292  		return common.Hash{}, ErrSnapshotStale
   293  	}
   294  	// Everything below was journalled, persist this layer too
   295  	if err := rlp.Encode(buffer, dl.root); err != nil {
   296  		return common.Hash{}, err
   297  	}
   298  	destructs := make([]journalDestruct, 0, len(dl.destructSet))
   299  	for hash := range dl.destructSet {
   300  		destructs = append(destructs, journalDestruct{Hash: hash})
   301  	}
   302  	if err := rlp.Encode(buffer, destructs); err != nil {
   303  		return common.Hash{}, err
   304  	}
   305  	accounts := make([]journalAccount, 0, len(dl.accountData))
   306  	for hash, blob := range dl.accountData {
   307  		accounts = append(accounts, journalAccount{Hash: hash, Blob: blob})
   308  	}
   309  	if err := rlp.Encode(buffer, accounts); err != nil {
   310  		return common.Hash{}, err
   311  	}
   312  	storage := make([]journalStorage, 0, len(dl.storageData))
   313  	for hash, slots := range dl.storageData {
   314  		keys := make([]common.Hash, 0, len(slots))
   315  		vals := make([][]byte, 0, len(slots))
   316  		for key, val := range slots {
   317  			keys = append(keys, key)
   318  			vals = append(vals, val)
   319  		}
   320  		storage = append(storage, journalStorage{Hash: hash, Keys: keys, Vals: vals})
   321  	}
   322  	if err := rlp.Encode(buffer, storage); err != nil {
   323  		return common.Hash{}, err
   324  	}
   325  	logger.Debug("Journalled diff layer", "root", dl.root, "parent", dl.parent.Root())
   326  	return base, nil
   327  }