github.com/snowblossomcoin/go-ethereum@v1.9.25/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/ethereum/go-ethereum/common" 26 "github.com/ethereum/go-ethereum/ethdb/memorydb" 27 "github.com/ethereum/go-ethereum/log" 28 "github.com/ethereum/go-ethereum/rlp" 29 "github.com/ethereum/go-ethereum/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 generaation 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 }