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