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