github.com/electroneum/electroneum-sc@v0.0.0-20230105223411-3bc1d078281e/core/state/snapshot/dangling.go (about)

     1  // Copyright 2022 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  	"errors"
    22  	"fmt"
    23  	"io"
    24  	"time"
    25  
    26  	"github.com/electroneum/electroneum-sc/common"
    27  	"github.com/electroneum/electroneum-sc/core/rawdb"
    28  	"github.com/electroneum/electroneum-sc/ethdb"
    29  	"github.com/electroneum/electroneum-sc/log"
    30  	"github.com/electroneum/electroneum-sc/rlp"
    31  )
    32  
    33  // CheckDanglingStorage iterates the snap storage data, and verifies that all
    34  // storage also has corresponding account data.
    35  func CheckDanglingStorage(chaindb ethdb.KeyValueStore) error {
    36  	if err := checkDanglingDiskStorage(chaindb); err != nil {
    37  		return err
    38  	}
    39  	return checkDanglingMemStorage(chaindb)
    40  }
    41  
    42  // checkDanglingDiskStorage checks if there is any 'dangling' storage data in the
    43  // disk-backed snapshot layer.
    44  func checkDanglingDiskStorage(chaindb ethdb.KeyValueStore) error {
    45  	var (
    46  		lastReport = time.Now()
    47  		start      = time.Now()
    48  		lastKey    []byte
    49  		it         = rawdb.NewKeyLengthIterator(chaindb.NewIterator(rawdb.SnapshotStoragePrefix, nil), 1+2*common.HashLength)
    50  	)
    51  	log.Info("Checking dangling snapshot disk storage")
    52  
    53  	defer it.Release()
    54  	for it.Next() {
    55  		k := it.Key()
    56  		accKey := k[1:33]
    57  		if bytes.Equal(accKey, lastKey) {
    58  			// No need to look up for every slot
    59  			continue
    60  		}
    61  		lastKey = common.CopyBytes(accKey)
    62  		if time.Since(lastReport) > time.Second*8 {
    63  			log.Info("Iterating snap storage", "at", fmt.Sprintf("%#x", accKey), "elapsed", common.PrettyDuration(time.Since(start)))
    64  			lastReport = time.Now()
    65  		}
    66  		if data := rawdb.ReadAccountSnapshot(chaindb, common.BytesToHash(accKey)); len(data) == 0 {
    67  			log.Warn("Dangling storage - missing account", "account", fmt.Sprintf("%#x", accKey), "storagekey", fmt.Sprintf("%#x", k))
    68  			return fmt.Errorf("dangling snapshot storage account %#x", accKey)
    69  		}
    70  	}
    71  	log.Info("Verified the snapshot disk storage", "time", common.PrettyDuration(time.Since(start)), "err", it.Error())
    72  	return nil
    73  }
    74  
    75  // checkDanglingMemStorage checks if there is any 'dangling' storage in the journalled
    76  // snapshot difflayers.
    77  func checkDanglingMemStorage(db ethdb.KeyValueStore) error {
    78  	var (
    79  		start   = time.Now()
    80  		journal = rawdb.ReadSnapshotJournal(db)
    81  	)
    82  	if len(journal) == 0 {
    83  		log.Warn("Loaded snapshot journal", "diffs", "missing")
    84  		return nil
    85  	}
    86  	r := rlp.NewStream(bytes.NewReader(journal), 0)
    87  	// Firstly, resolve the first element as the journal version
    88  	version, err := r.Uint64()
    89  	if err != nil {
    90  		log.Warn("Failed to resolve the journal version", "error", err)
    91  		return nil
    92  	}
    93  	if version != journalVersion {
    94  		log.Warn("Discarded the snapshot journal with wrong version", "required", journalVersion, "got", version)
    95  		return nil
    96  	}
    97  	// Secondly, resolve the disk layer root, ensure it's continuous
    98  	// with disk layer. Note now we can ensure it's the snapshot journal
    99  	// correct version, so we expect everything can be resolved properly.
   100  	var root common.Hash
   101  	if err := r.Decode(&root); err != nil {
   102  		return errors.New("missing disk layer root")
   103  	}
   104  	// The diff journal is not matched with disk, discard them.
   105  	// It can happen that Geth crashes without persisting the latest
   106  	// diff journal.
   107  	// Load all the snapshot diffs from the journal
   108  	if err := checkDanglingJournalStorage(r); err != nil {
   109  		return err
   110  	}
   111  	log.Info("Verified the snapshot journalled storage", "time", common.PrettyDuration(time.Since(start)))
   112  	return nil
   113  }
   114  
   115  // loadDiffLayer reads the next sections of a snapshot journal, reconstructing a new
   116  // diff and verifying that it can be linked to the requested parent.
   117  func checkDanglingJournalStorage(r *rlp.Stream) error {
   118  	for {
   119  		// Read the next diff journal entry
   120  		var root common.Hash
   121  		if err := r.Decode(&root); err != nil {
   122  			// The first read may fail with EOF, marking the end of the journal
   123  			if err == io.EOF {
   124  				return nil
   125  			}
   126  			return fmt.Errorf("load diff root: %v", err)
   127  		}
   128  		var destructs []journalDestruct
   129  		if err := r.Decode(&destructs); err != nil {
   130  			return fmt.Errorf("load diff destructs: %v", err)
   131  		}
   132  		var accounts []journalAccount
   133  		if err := r.Decode(&accounts); err != nil {
   134  			return fmt.Errorf("load diff accounts: %v", err)
   135  		}
   136  		accountData := make(map[common.Hash][]byte)
   137  		for _, entry := range accounts {
   138  			if len(entry.Blob) > 0 { // RLP loses nil-ness, but `[]byte{}` is not a valid item, so reinterpret that
   139  				accountData[entry.Hash] = entry.Blob
   140  			} else {
   141  				accountData[entry.Hash] = nil
   142  			}
   143  		}
   144  		var storage []journalStorage
   145  		if err := r.Decode(&storage); err != nil {
   146  			return fmt.Errorf("load diff storage: %v", err)
   147  		}
   148  		for _, entry := range storage {
   149  			if _, ok := accountData[entry.Hash]; !ok {
   150  				log.Error("Dangling storage - missing account", "account", fmt.Sprintf("%#x", entry.Hash), "root", root)
   151  				return fmt.Errorf("dangling journal snapshot storage account %#x", entry.Hash)
   152  			}
   153  		}
   154  	}
   155  }