github.com/ethereum/go-ethereum@v1.16.1/triedb/pathdb/verifier.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 pathdb 18 19 import ( 20 "encoding/binary" 21 "errors" 22 "fmt" 23 "math" 24 "runtime" 25 "sync" 26 "time" 27 28 "github.com/ethereum/go-ethereum/common" 29 "github.com/ethereum/go-ethereum/core/rawdb" 30 "github.com/ethereum/go-ethereum/core/types" 31 "github.com/ethereum/go-ethereum/log" 32 "github.com/ethereum/go-ethereum/rlp" 33 "github.com/ethereum/go-ethereum/trie" 34 ) 35 36 // trieKV represents a trie key-value pair 37 type trieKV struct { 38 key common.Hash 39 value []byte 40 } 41 42 type ( 43 // trieHasherFn is the interface of trie hasher which can be implemented 44 // by different trie algorithm. 45 trieHasherFn func(in chan trieKV, out chan common.Hash) 46 47 // leafCallbackFn is the callback invoked at the leaves of the trie, 48 // returns the subtrie root with the specified subtrie identifier. 49 leafCallbackFn func(accountHash, codeHash common.Hash, stat *generateStats) (common.Hash, error) 50 ) 51 52 // VerifyState traverses the flat states specified by the given state root and 53 // ensures they are matched with each other. 54 func (db *Database) VerifyState(root common.Hash) error { 55 acctIt, err := db.AccountIterator(root, common.Hash{}) 56 if err != nil { 57 return err // The required snapshot might not exist. 58 } 59 defer acctIt.Release() 60 61 got, err := generateTrieRoot(acctIt, common.Hash{}, stackTrieHasher, func(accountHash, codeHash common.Hash, stat *generateStats) (common.Hash, error) { 62 // Migrate the code first, commit the contract code into the tmp db. 63 if codeHash != types.EmptyCodeHash { 64 code := rawdb.ReadCode(db.diskdb, codeHash) 65 if len(code) == 0 { 66 return common.Hash{}, errors.New("failed to read contract code") 67 } 68 } 69 // Then migrate all storage trie nodes into the tmp db. 70 storageIt, err := db.StorageIterator(root, accountHash, common.Hash{}) 71 if err != nil { 72 return common.Hash{}, err 73 } 74 defer storageIt.Release() 75 76 hash, err := generateTrieRoot(storageIt, accountHash, stackTrieHasher, nil, stat, false) 77 if err != nil { 78 return common.Hash{}, err 79 } 80 return hash, nil 81 }, newGenerateStats(), true) 82 83 if err != nil { 84 return err 85 } 86 if got != root { 87 return fmt.Errorf("state root hash mismatch: got %x, want %x", got, root) 88 } 89 return nil 90 } 91 92 // generateStats is a collection of statistics gathered by the trie generator 93 // for logging purposes. 94 type generateStats struct { 95 head common.Hash 96 start time.Time 97 98 accounts uint64 // Number of accounts done (including those being crawled) 99 slots uint64 // Number of storage slots done (including those being crawled) 100 101 slotsStart map[common.Hash]time.Time // Start time for account slot crawling 102 slotsHead map[common.Hash]common.Hash // Slot head for accounts being crawled 103 104 lock sync.RWMutex 105 } 106 107 // newGenerateStats creates a new generator stats. 108 func newGenerateStats() *generateStats { 109 return &generateStats{ 110 slotsStart: make(map[common.Hash]time.Time), 111 slotsHead: make(map[common.Hash]common.Hash), 112 start: time.Now(), 113 } 114 } 115 116 // progressAccounts updates the generator stats for the account range. 117 func (stat *generateStats) progressAccounts(account common.Hash, done uint64) { 118 stat.lock.Lock() 119 defer stat.lock.Unlock() 120 121 stat.accounts += done 122 stat.head = account 123 } 124 125 // finishAccounts updates the generator stats for the finished account range. 126 func (stat *generateStats) finishAccounts(done uint64) { 127 stat.lock.Lock() 128 defer stat.lock.Unlock() 129 130 stat.accounts += done 131 } 132 133 // progressContract updates the generator stats for a specific in-progress contract. 134 func (stat *generateStats) progressContract(account common.Hash, slot common.Hash, done uint64) { 135 stat.lock.Lock() 136 defer stat.lock.Unlock() 137 138 stat.slots += done 139 stat.slotsHead[account] = slot 140 if _, ok := stat.slotsStart[account]; !ok { 141 stat.slotsStart[account] = time.Now() 142 } 143 } 144 145 // finishContract updates the generator stats for a specific just-finished contract. 146 func (stat *generateStats) finishContract(account common.Hash, done uint64) { 147 stat.lock.Lock() 148 defer stat.lock.Unlock() 149 150 stat.slots += done 151 delete(stat.slotsHead, account) 152 delete(stat.slotsStart, account) 153 } 154 155 // report prints the cumulative progress statistic smartly. 156 func (stat *generateStats) report() { 157 stat.lock.RLock() 158 defer stat.lock.RUnlock() 159 160 ctx := []interface{}{ 161 "accounts", stat.accounts, 162 "slots", stat.slots, 163 "elapsed", common.PrettyDuration(time.Since(stat.start)), 164 } 165 if stat.accounts > 0 { 166 // If there's progress on the account trie, estimate the time to finish crawling it 167 if done := binary.BigEndian.Uint64(stat.head[:8]) / stat.accounts; done > 0 { 168 var ( 169 left = (math.MaxUint64 - binary.BigEndian.Uint64(stat.head[:8])) / stat.accounts 170 speed = done/uint64(time.Since(stat.start)/time.Millisecond+1) + 1 // +1s to avoid division by zero 171 eta = time.Duration(left/speed) * time.Millisecond 172 ) 173 // If there are large contract crawls in progress, estimate their finish time 174 for acc, head := range stat.slotsHead { 175 start := stat.slotsStart[acc] 176 if done := binary.BigEndian.Uint64(head[:8]); done > 0 { 177 var ( 178 left = math.MaxUint64 - binary.BigEndian.Uint64(head[:8]) 179 speed = done/uint64(time.Since(start)/time.Millisecond+1) + 1 // +1s to avoid division by zero 180 ) 181 // Override the ETA if larger than the largest until now 182 if slotETA := time.Duration(left/speed) * time.Millisecond; eta < slotETA { 183 eta = slotETA 184 } 185 } 186 } 187 ctx = append(ctx, []interface{}{ 188 "eta", common.PrettyDuration(eta), 189 }...) 190 } 191 } 192 log.Info("Iterating state snapshot", ctx...) 193 } 194 195 // reportDone prints the last log when the whole generation is finished. 196 func (stat *generateStats) reportDone() { 197 stat.lock.RLock() 198 defer stat.lock.RUnlock() 199 200 var ctx []interface{} 201 ctx = append(ctx, []interface{}{"accounts", stat.accounts}...) 202 if stat.slots != 0 { 203 ctx = append(ctx, []interface{}{"slots", stat.slots}...) 204 } 205 ctx = append(ctx, []interface{}{"elapsed", common.PrettyDuration(time.Since(stat.start))}...) 206 log.Info("Iterated snapshot", ctx...) 207 } 208 209 // runReport periodically prints the progress information. 210 func runReport(stats *generateStats, stop chan bool) { 211 timer := time.NewTimer(0) 212 defer timer.Stop() 213 214 for { 215 select { 216 case <-timer.C: 217 stats.report() 218 timer.Reset(time.Second * 8) 219 case success := <-stop: 220 if success { 221 stats.reportDone() 222 } 223 return 224 } 225 } 226 } 227 228 // generateTrieRoot generates the trie hash based on the snapshot iterator. 229 // It can be used for generating account trie, storage trie or even the 230 // whole state which connects the accounts and the corresponding storages. 231 func generateTrieRoot(it Iterator, account common.Hash, generatorFn trieHasherFn, leafCallback leafCallbackFn, stats *generateStats, report bool) (common.Hash, error) { 232 var ( 233 in = make(chan trieKV) // chan to pass leaves 234 out = make(chan common.Hash, 1) // chan to collect result 235 stoplog = make(chan bool, 1) // 1-size buffer, works when logging is not enabled 236 wg sync.WaitGroup 237 ) 238 // Spin up a go-routine for trie hash re-generation 239 wg.Add(1) 240 go func() { 241 defer wg.Done() 242 generatorFn(in, out) 243 }() 244 // Spin up a go-routine for progress logging 245 if report && stats != nil { 246 wg.Add(1) 247 go func() { 248 defer wg.Done() 249 runReport(stats, stoplog) 250 }() 251 } 252 // Create a semaphore to assign tasks and collect results through. We'll pre- 253 // fill it with nils, thus using the same channel for both limiting concurrent 254 // processing and gathering results. 255 threads := runtime.NumCPU() 256 results := make(chan error, threads) 257 for i := 0; i < threads; i++ { 258 results <- nil // fill the semaphore 259 } 260 // stop is a helper function to shutdown the background threads 261 // and return the re-generated trie hash. 262 stop := func(fail error) (common.Hash, error) { 263 close(in) 264 result := <-out 265 for i := 0; i < threads; i++ { 266 if err := <-results; err != nil && fail == nil { 267 fail = err 268 } 269 } 270 stoplog <- fail == nil 271 272 wg.Wait() 273 return result, fail 274 } 275 var ( 276 logged = time.Now() 277 processed = uint64(0) 278 leaf trieKV 279 ) 280 // Start to feed leaves 281 for it.Next() { 282 if account == (common.Hash{}) { 283 var ( 284 err error 285 fullData []byte 286 ) 287 if leafCallback == nil { 288 fullData, err = types.FullAccountRLP(it.(AccountIterator).Account()) 289 if err != nil { 290 return stop(err) 291 } 292 } else { 293 // Wait until the semaphore allows us to continue, aborting if 294 // a sub-task failed 295 if err := <-results; err != nil { 296 results <- nil // stop will drain the results, add a noop back for this error we just consumed 297 return stop(err) 298 } 299 // Fetch the next account and process it concurrently 300 account, err := types.FullAccount(it.(AccountIterator).Account()) 301 if err != nil { 302 return stop(err) 303 } 304 go func(hash common.Hash) { 305 subroot, err := leafCallback(hash, common.BytesToHash(account.CodeHash), stats) 306 if err != nil { 307 results <- err 308 return 309 } 310 if account.Root != subroot { 311 results <- fmt.Errorf("invalid subroot(path %x), want %x, have %x", hash, account.Root, subroot) 312 return 313 } 314 results <- nil 315 }(it.Hash()) 316 fullData, err = rlp.EncodeToBytes(account) 317 if err != nil { 318 return stop(err) 319 } 320 } 321 leaf = trieKV{it.Hash(), fullData} 322 } else { 323 leaf = trieKV{it.Hash(), common.CopyBytes(it.(StorageIterator).Slot())} 324 } 325 in <- leaf 326 327 // Accumulate the generation statistic if it's required. 328 processed++ 329 if time.Since(logged) > 3*time.Second && stats != nil { 330 if account == (common.Hash{}) { 331 stats.progressAccounts(it.Hash(), processed) 332 } else { 333 stats.progressContract(account, it.Hash(), processed) 334 } 335 logged, processed = time.Now(), 0 336 } 337 } 338 // Commit the last part statistic. 339 if processed > 0 && stats != nil { 340 if account == (common.Hash{}) { 341 stats.finishAccounts(processed) 342 } else { 343 stats.finishContract(account, processed) 344 } 345 } 346 return stop(nil) 347 } 348 349 func stackTrieHasher(in chan trieKV, out chan common.Hash) { 350 t := trie.NewStackTrie(nil) 351 for leaf := range in { 352 t.Update(leaf.key[:], leaf.value) 353 } 354 out <- t.Hash() 355 }