github.com/core-coin/go-core/v2@v2.1.9/core/state/snapshot/conversion.go (about) 1 // Copyright 2020 by the Authors 2 // This file is part of the go-core library. 3 // 4 // The go-core 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-core 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-core 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/core-coin/go-core/v2/xcbdb/memorydb" 26 27 "github.com/core-coin/go-core/v2/common" 28 "github.com/core-coin/go-core/v2/log" 29 "github.com/core-coin/go-core/v2/rlp" 30 "github.com/core-coin/go-core/v2/trie" 31 ) 32 33 // trieKV represents a trie key-value pair 34 type trieKV struct { 35 key common.Hash 36 value []byte 37 } 38 39 type ( 40 // trieGeneratorFn is the interface of trie generation which can 41 // be implemented by different trie algorithm. 42 trieGeneratorFn func(in chan (trieKV), out chan (common.Hash)) 43 44 // leafCallbackFn is the callback invoked at the leaves of the trie, 45 // returns the subtrie root with the specified subtrie identifier. 46 leafCallbackFn func(hash common.Hash, stat *generateStats) common.Hash 47 ) 48 49 // GenerateAccountTrieRoot takes an account iterator and reproduces the root hash. 50 func GenerateAccountTrieRoot(it AccountIterator) (common.Hash, error) { 51 return generateTrieRoot(it, common.Hash{}, stdGenerate, nil, &generateStats{start: time.Now()}, true) 52 } 53 54 // GenerateStorageTrieRoot takes a storage iterator and reproduces the root hash. 55 func GenerateStorageTrieRoot(account common.Hash, it StorageIterator) (common.Hash, error) { 56 return generateTrieRoot(it, account, stdGenerate, nil, &generateStats{start: time.Now()}, true) 57 } 58 59 // VerifyState takes the whole snapshot tree as the input, traverses all the accounts 60 // as well as the corresponding storages and compares the re-computed hash with the 61 // original one(state root and the storage root). 62 func VerifyState(snaptree *Tree, root common.Hash) error { 63 acctIt, err := snaptree.AccountIterator(root, common.Hash{}) 64 if err != nil { 65 return err 66 } 67 defer acctIt.Release() 68 69 got, err := generateTrieRoot(acctIt, common.Hash{}, stdGenerate, func(account common.Hash, stat *generateStats) common.Hash { 70 storageIt, err := snaptree.StorageIterator(root, account, common.Hash{}) 71 if err != nil { 72 return common.Hash{} 73 } 74 defer storageIt.Release() 75 76 hash, err := generateTrieRoot(storageIt, account, stdGenerate, nil, stat, false) 77 if err != nil { 78 return common.Hash{} 79 } 80 return hash 81 }, &generateStats{start: time.Now()}, 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 accounts uint64 96 slots uint64 97 curAccount common.Hash 98 curSlot common.Hash 99 start time.Time 100 lock sync.RWMutex 101 } 102 103 // progress records the progress trie generator made recently. 104 func (stat *generateStats) progress(accounts, slots uint64, curAccount common.Hash, curSlot common.Hash) { 105 stat.lock.Lock() 106 defer stat.lock.Unlock() 107 108 stat.accounts += accounts 109 stat.slots += slots 110 stat.curAccount = curAccount 111 stat.curSlot = curSlot 112 } 113 114 // report prints the cumulative progress statistic smartly. 115 func (stat *generateStats) report() { 116 stat.lock.RLock() 117 defer stat.lock.RUnlock() 118 119 var ctx []interface{} 120 if stat.curSlot != (common.Hash{}) { 121 ctx = append(ctx, []interface{}{ 122 "in", stat.curAccount, 123 "at", stat.curSlot, 124 }...) 125 } else { 126 ctx = append(ctx, []interface{}{"at", stat.curAccount}...) 127 } 128 // Add the usual measurements 129 ctx = append(ctx, []interface{}{"accounts", stat.accounts}...) 130 if stat.slots != 0 { 131 ctx = append(ctx, []interface{}{"slots", stat.slots}...) 132 } 133 ctx = append(ctx, []interface{}{"elapsed", common.PrettyDuration(time.Since(stat.start))}...) 134 log.Info("Generating trie hash from snapshot", ctx...) 135 } 136 137 // reportDone prints the last log when the whole generation is finished. 138 func (stat *generateStats) reportDone() { 139 stat.lock.RLock() 140 defer stat.lock.RUnlock() 141 142 var ctx []interface{} 143 ctx = append(ctx, []interface{}{"accounts", stat.accounts}...) 144 if stat.slots != 0 { 145 ctx = append(ctx, []interface{}{"slots", stat.slots}...) 146 } 147 ctx = append(ctx, []interface{}{"elapsed", common.PrettyDuration(time.Since(stat.start))}...) 148 log.Info("Generated trie hash from snapshot", ctx...) 149 } 150 151 // generateTrieRoot generates the trie hash based on the snapshot iterator. 152 // It can be used for generating account trie, storage trie or even the 153 // whole state which connects the accounts and the corresponding storages. 154 func generateTrieRoot(it Iterator, account common.Hash, generatorFn trieGeneratorFn, leafCallback leafCallbackFn, stats *generateStats, report bool) (common.Hash, error) { 155 var ( 156 in = make(chan trieKV) // chan to pass leaves 157 out = make(chan common.Hash, 1) // chan to collect result 158 stoplog = make(chan bool, 1) // 1-size buffer, works when logging is not enabled 159 wg sync.WaitGroup 160 ) 161 // Spin up a go-routine for trie hash re-generation 162 wg.Add(1) 163 go func() { 164 defer wg.Done() 165 generatorFn(in, out) 166 }() 167 168 // Spin up a go-routine for progress logging 169 if report && stats != nil { 170 wg.Add(1) 171 go func() { 172 defer wg.Done() 173 174 timer := time.NewTimer(0) 175 defer timer.Stop() 176 177 for { 178 select { 179 case <-timer.C: 180 stats.report() 181 timer.Reset(time.Second * 8) 182 case success := <-stoplog: 183 if success { 184 stats.reportDone() 185 } 186 return 187 } 188 } 189 }() 190 } 191 // stop is a helper function to shutdown the background threads 192 // and return the re-generated trie hash. 193 stop := func(success bool) common.Hash { 194 close(in) 195 result := <-out 196 stoplog <- success 197 wg.Wait() 198 return result 199 } 200 var ( 201 logged = time.Now() 202 processed = uint64(0) 203 leaf trieKV 204 last common.Hash 205 ) 206 // Start to feed leaves 207 for it.Next() { 208 if account == (common.Hash{}) { 209 var ( 210 err error 211 fullData []byte 212 ) 213 if leafCallback == nil { 214 fullData, err = FullAccountRLP(it.(AccountIterator).Account()) 215 if err != nil { 216 stop(false) 217 return common.Hash{}, err 218 } 219 } else { 220 account, err := FullAccount(it.(AccountIterator).Account()) 221 if err != nil { 222 stop(false) 223 return common.Hash{}, err 224 } 225 // Apply the leaf callback. Normally the callback is used to traverse 226 // the storage trie and re-generate the subtrie root. 227 subroot := leafCallback(it.Hash(), stats) 228 if !bytes.Equal(account.Root, subroot.Bytes()) { 229 stop(false) 230 return common.Hash{}, fmt.Errorf("invalid subroot(%x), want %x, got %x", it.Hash(), account.Root, subroot) 231 } 232 fullData, err = rlp.EncodeToBytes(account) 233 if err != nil { 234 stop(false) 235 return common.Hash{}, err 236 } 237 } 238 leaf = trieKV{it.Hash(), fullData} 239 } else { 240 leaf = trieKV{it.Hash(), common.CopyBytes(it.(StorageIterator).Slot())} 241 } 242 in <- leaf 243 244 // Accumulate the generaation statistic if it's required. 245 processed++ 246 if time.Since(logged) > 3*time.Second && stats != nil { 247 if account == (common.Hash{}) { 248 stats.progress(processed, 0, it.Hash(), common.Hash{}) 249 } else { 250 stats.progress(0, processed, account, it.Hash()) 251 } 252 logged, processed = time.Now(), 0 253 } 254 last = it.Hash() 255 } 256 // Commit the last part statistic. 257 if processed > 0 && stats != nil { 258 if account == (common.Hash{}) { 259 stats.progress(processed, 0, last, common.Hash{}) 260 } else { 261 stats.progress(0, processed, account, last) 262 } 263 } 264 result := stop(true) 265 return result, nil 266 } 267 268 // stdGenerate is a very basic hexary trie builder which uses the same Trie 269 // as the rest of gocore, with no enhancements or optimizations 270 func stdGenerate(in chan (trieKV), out chan (common.Hash)) { 271 t, _ := trie.New(common.Hash{}, trie.NewDatabase(memorydb.New())) 272 for leaf := range in { 273 t.TryUpdate(leaf.key[:], leaf.value) 274 } 275 out <- t.Hash() 276 }