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