github.com/cryptogateway/go-paymex@v0.0.0-20210204174735-96277fb1e602/core/state/snapshot/conversion.go (about)

     1  // Copyright 2020 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  	"fmt"
    22  	"sync"
    23  	"time"
    24  
    25  	"github.com/cryptogateway/go-paymex/common"
    26  	"github.com/cryptogateway/go-paymex/ethdb/memorydb"
    27  	"github.com/cryptogateway/go-paymex/log"
    28  	"github.com/cryptogateway/go-paymex/rlp"
    29  	"github.com/cryptogateway/go-paymex/trie"
    30  )
    31  
    32  // trieKV represents a trie key-value pair
    33  type trieKV struct {
    34  	key   common.Hash
    35  	value []byte
    36  }
    37  
    38  type (
    39  	// trieGeneratorFn is the interface of trie generation which can
    40  	// be implemented by different trie algorithm.
    41  	trieGeneratorFn func(in chan trieKV, out chan common.Hash)
    42  
    43  	// leafCallbackFn is the callback invoked at the leaves of the trie,
    44  	// returns the subtrie root with the specified subtrie identifier.
    45  	leafCallbackFn func(hash common.Hash, stat *generateStats) common.Hash
    46  )
    47  
    48  // GenerateAccountTrieRoot takes an account iterator and reproduces the root hash.
    49  func GenerateAccountTrieRoot(it AccountIterator) (common.Hash, error) {
    50  	return generateTrieRoot(it, common.Hash{}, stdGenerate, nil, &generateStats{start: time.Now()}, true)
    51  }
    52  
    53  // GenerateStorageTrieRoot takes a storage iterator and reproduces the root hash.
    54  func GenerateStorageTrieRoot(account common.Hash, it StorageIterator) (common.Hash, error) {
    55  	return generateTrieRoot(it, account, stdGenerate, nil, &generateStats{start: time.Now()}, true)
    56  }
    57  
    58  // VerifyState takes the whole snapshot tree as the input, traverses all the accounts
    59  // as well as the corresponding storages and compares the re-computed hash with the
    60  // original one(state root and the storage root).
    61  func VerifyState(snaptree *Tree, root common.Hash) error {
    62  	acctIt, err := snaptree.AccountIterator(root, common.Hash{})
    63  	if err != nil {
    64  		return err
    65  	}
    66  	defer acctIt.Release()
    67  
    68  	got, err := generateTrieRoot(acctIt, common.Hash{}, stdGenerate, func(account common.Hash, stat *generateStats) common.Hash {
    69  		storageIt, err := snaptree.StorageIterator(root, account, common.Hash{})
    70  		if err != nil {
    71  			return common.Hash{}
    72  		}
    73  		defer storageIt.Release()
    74  
    75  		hash, err := generateTrieRoot(storageIt, account, stdGenerate, nil, stat, false)
    76  		if err != nil {
    77  			return common.Hash{}
    78  		}
    79  		return hash
    80  	}, &generateStats{start: time.Now()}, true)
    81  
    82  	if err != nil {
    83  		return err
    84  	}
    85  	if got != root {
    86  		return fmt.Errorf("state root hash mismatch: got %x, want %x", got, root)
    87  	}
    88  	return nil
    89  }
    90  
    91  // generateStats is a collection of statistics gathered by the trie generator
    92  // for logging purposes.
    93  type generateStats struct {
    94  	accounts   uint64
    95  	slots      uint64
    96  	curAccount common.Hash
    97  	curSlot    common.Hash
    98  	start      time.Time
    99  	lock       sync.RWMutex
   100  }
   101  
   102  // progress records the progress trie generator made recently.
   103  func (stat *generateStats) progress(accounts, slots uint64, curAccount common.Hash, curSlot common.Hash) {
   104  	stat.lock.Lock()
   105  	defer stat.lock.Unlock()
   106  
   107  	stat.accounts += accounts
   108  	stat.slots += slots
   109  	stat.curAccount = curAccount
   110  	stat.curSlot = curSlot
   111  }
   112  
   113  // report prints the cumulative progress statistic smartly.
   114  func (stat *generateStats) report() {
   115  	stat.lock.RLock()
   116  	defer stat.lock.RUnlock()
   117  
   118  	var ctx []interface{}
   119  	if stat.curSlot != (common.Hash{}) {
   120  		ctx = append(ctx, []interface{}{
   121  			"in", stat.curAccount,
   122  			"at", stat.curSlot,
   123  		}...)
   124  	} else {
   125  		ctx = append(ctx, []interface{}{"at", stat.curAccount}...)
   126  	}
   127  	// Add the usual measurements
   128  	ctx = append(ctx, []interface{}{"accounts", stat.accounts}...)
   129  	if stat.slots != 0 {
   130  		ctx = append(ctx, []interface{}{"slots", stat.slots}...)
   131  	}
   132  	ctx = append(ctx, []interface{}{"elapsed", common.PrettyDuration(time.Since(stat.start))}...)
   133  	log.Info("Generating trie hash from snapshot", ctx...)
   134  }
   135  
   136  // reportDone prints the last log when the whole generation is finished.
   137  func (stat *generateStats) reportDone() {
   138  	stat.lock.RLock()
   139  	defer stat.lock.RUnlock()
   140  
   141  	var ctx []interface{}
   142  	ctx = append(ctx, []interface{}{"accounts", stat.accounts}...)
   143  	if stat.slots != 0 {
   144  		ctx = append(ctx, []interface{}{"slots", stat.slots}...)
   145  	}
   146  	ctx = append(ctx, []interface{}{"elapsed", common.PrettyDuration(time.Since(stat.start))}...)
   147  	log.Info("Generated trie hash from snapshot", ctx...)
   148  }
   149  
   150  // generateTrieRoot generates the trie hash based on the snapshot iterator.
   151  // It can be used for generating account trie, storage trie or even the
   152  // whole state which connects the accounts and the corresponding storages.
   153  func generateTrieRoot(it Iterator, account common.Hash, generatorFn trieGeneratorFn, leafCallback leafCallbackFn, stats *generateStats, report bool) (common.Hash, error) {
   154  	var (
   155  		in      = make(chan trieKV)         // chan to pass leaves
   156  		out     = make(chan common.Hash, 1) // chan to collect result
   157  		stoplog = make(chan bool, 1)        // 1-size buffer, works when logging is not enabled
   158  		wg      sync.WaitGroup
   159  	)
   160  	// Spin up a go-routine for trie hash re-generation
   161  	wg.Add(1)
   162  	go func() {
   163  		defer wg.Done()
   164  		generatorFn(in, out)
   165  	}()
   166  
   167  	// Spin up a go-routine for progress logging
   168  	if report && stats != nil {
   169  		wg.Add(1)
   170  		go func() {
   171  			defer wg.Done()
   172  
   173  			timer := time.NewTimer(0)
   174  			defer timer.Stop()
   175  
   176  			for {
   177  				select {
   178  				case <-timer.C:
   179  					stats.report()
   180  					timer.Reset(time.Second * 8)
   181  				case success := <-stoplog:
   182  					if success {
   183  						stats.reportDone()
   184  					}
   185  					return
   186  				}
   187  			}
   188  		}()
   189  	}
   190  	// stop is a helper function to shutdown the background threads
   191  	// and return the re-generated trie hash.
   192  	stop := func(success bool) common.Hash {
   193  		close(in)
   194  		result := <-out
   195  		stoplog <- success
   196  		wg.Wait()
   197  		return result
   198  	}
   199  	var (
   200  		logged    = time.Now()
   201  		processed = uint64(0)
   202  		leaf      trieKV
   203  		last      common.Hash
   204  	)
   205  	// Start to feed leaves
   206  	for it.Next() {
   207  		if account == (common.Hash{}) {
   208  			var (
   209  				err      error
   210  				fullData []byte
   211  			)
   212  			if leafCallback == nil {
   213  				fullData, err = FullAccountRLP(it.(AccountIterator).Account())
   214  				if err != nil {
   215  					stop(false)
   216  					return common.Hash{}, err
   217  				}
   218  			} else {
   219  				account, err := FullAccount(it.(AccountIterator).Account())
   220  				if err != nil {
   221  					stop(false)
   222  					return common.Hash{}, err
   223  				}
   224  				// Apply the leaf callback. Normally the callback is used to traverse
   225  				// the storage trie and re-generate the subtrie root.
   226  				subroot := leafCallback(it.Hash(), stats)
   227  				if !bytes.Equal(account.Root, subroot.Bytes()) {
   228  					stop(false)
   229  					return common.Hash{}, fmt.Errorf("invalid subroot(%x), want %x, got %x", it.Hash(), account.Root, subroot)
   230  				}
   231  				fullData, err = rlp.EncodeToBytes(account)
   232  				if err != nil {
   233  					stop(false)
   234  					return common.Hash{}, err
   235  				}
   236  			}
   237  			leaf = trieKV{it.Hash(), fullData}
   238  		} else {
   239  			leaf = trieKV{it.Hash(), common.CopyBytes(it.(StorageIterator).Slot())}
   240  		}
   241  		in <- leaf
   242  
   243  		// Accumulate the generation statistic if it's required.
   244  		processed++
   245  		if time.Since(logged) > 3*time.Second && stats != nil {
   246  			if account == (common.Hash{}) {
   247  				stats.progress(processed, 0, it.Hash(), common.Hash{})
   248  			} else {
   249  				stats.progress(0, processed, account, it.Hash())
   250  			}
   251  			logged, processed = time.Now(), 0
   252  		}
   253  		last = it.Hash()
   254  	}
   255  	// Commit the last part statistic.
   256  	if processed > 0 && stats != nil {
   257  		if account == (common.Hash{}) {
   258  			stats.progress(processed, 0, last, common.Hash{})
   259  		} else {
   260  			stats.progress(0, processed, account, last)
   261  		}
   262  	}
   263  	result := stop(true)
   264  	return result, nil
   265  }
   266  
   267  // stdGenerate is a very basic hexary trie builder which uses the same Trie
   268  // as the rest of geth, with no enhancements or optimizations
   269  func stdGenerate(in chan trieKV, out chan common.Hash) {
   270  	t, _ := trie.New(common.Hash{}, trie.NewDatabase(memorydb.New()))
   271  	for leaf := range in {
   272  		t.TryUpdate(leaf.key[:], leaf.value)
   273  	}
   274  	out <- t.Hash()
   275  }