github.com/ethereum/go-ethereum@v1.16.1/triedb/pathdb/context.go (about) 1 // Copyright 2024 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 "bytes" 21 "encoding/binary" 22 "errors" 23 "math" 24 "time" 25 26 "github.com/ethereum/go-ethereum/common" 27 "github.com/ethereum/go-ethereum/core/rawdb" 28 "github.com/ethereum/go-ethereum/ethdb" 29 "github.com/ethereum/go-ethereum/ethdb/memorydb" 30 "github.com/ethereum/go-ethereum/log" 31 ) 32 33 const ( 34 snapAccount = "account" // Identifier of account snapshot generation 35 snapStorage = "storage" // Identifier of storage snapshot generation 36 ) 37 38 // generatorStats is a collection of statistics gathered by the snapshot generator 39 // for logging purposes. This data structure is used throughout the entire 40 // lifecycle of the snapshot generation process and is shared across multiple 41 // generation cycles. 42 type generatorStats struct { 43 origin uint64 // Origin prefix where generation started 44 start time.Time // Timestamp when generation started 45 accounts uint64 // Number of accounts indexed(generated or recovered) 46 slots uint64 // Number of storage slots indexed(generated or recovered) 47 dangling uint64 // Number of dangling storage slots 48 storage common.StorageSize // Total account and storage slot size(generation or recovery) 49 } 50 51 // log creates a contextual log with the given message and the context pulled 52 // from the internally maintained statistics. 53 func (gs *generatorStats) log(msg string, root common.Hash, marker []byte) { 54 var ctx []interface{} 55 if root != (common.Hash{}) { 56 ctx = append(ctx, []interface{}{"root", root}...) 57 } 58 // Figure out whether we're after or within an account 59 switch len(marker) { 60 case common.HashLength: 61 ctx = append(ctx, []interface{}{"at", common.BytesToHash(marker)}...) 62 case 2 * common.HashLength: 63 ctx = append(ctx, []interface{}{ 64 "in", common.BytesToHash(marker[:common.HashLength]), 65 "at", common.BytesToHash(marker[common.HashLength:]), 66 }...) 67 } 68 // Add the usual measurements 69 ctx = append(ctx, []interface{}{ 70 "accounts", gs.accounts, 71 "slots", gs.slots, 72 "storage", gs.storage, 73 "dangling", gs.dangling, 74 "elapsed", common.PrettyDuration(time.Since(gs.start)), 75 }...) 76 // Calculate the estimated indexing time based on current stats 77 if len(marker) > 0 { 78 if done := binary.BigEndian.Uint64(marker[:8]) - gs.origin; done > 0 { 79 left := math.MaxUint64 - binary.BigEndian.Uint64(marker[:8]) 80 81 speed := done/uint64(time.Since(gs.start)/time.Millisecond+1) + 1 // +1s to avoid division by zero 82 ctx = append(ctx, []interface{}{ 83 "eta", common.PrettyDuration(time.Duration(left/speed) * time.Millisecond), 84 }...) 85 } 86 } 87 log.Info(msg, ctx...) 88 } 89 90 // generatorContext holds several global fields that are used throughout the 91 // current generation cycle. It must be recreated if the generation cycle is 92 // restarted. 93 type generatorContext struct { 94 root common.Hash // State root of the generation target 95 account *holdableIterator // Iterator of account snapshot data 96 storage *holdableIterator // Iterator of storage snapshot data 97 db ethdb.KeyValueStore // Key-value store containing the snapshot data 98 batch ethdb.Batch // Database batch for writing data atomically 99 logged time.Time // The timestamp when last generation progress was displayed 100 } 101 102 // newGeneratorContext initializes the context for generation. 103 func newGeneratorContext(root common.Hash, marker []byte, db ethdb.KeyValueStore) *generatorContext { 104 ctx := &generatorContext{ 105 root: root, 106 db: db, 107 batch: db.NewBatch(), 108 logged: time.Now(), 109 } 110 accMarker, storageMarker := splitMarker(marker) 111 ctx.openIterator(snapAccount, accMarker) 112 ctx.openIterator(snapStorage, storageMarker) 113 return ctx 114 } 115 116 // openIterator constructs global account and storage snapshot iterators 117 // at the interrupted position. These iterators should be reopened from time 118 // to time to avoid blocking leveldb compaction for a long time. 119 func (ctx *generatorContext) openIterator(kind string, start []byte) { 120 if kind == snapAccount { 121 iter := ctx.db.NewIterator(rawdb.SnapshotAccountPrefix, start) 122 ctx.account = newHoldableIterator(rawdb.NewKeyLengthIterator(iter, 1+common.HashLength)) 123 return 124 } 125 iter := ctx.db.NewIterator(rawdb.SnapshotStoragePrefix, start) 126 ctx.storage = newHoldableIterator(rawdb.NewKeyLengthIterator(iter, 1+2*common.HashLength)) 127 } 128 129 // reopenIterator releases the specified snapshot iterator and re-open it 130 // in the next position. It's aimed for not blocking leveldb compaction. 131 func (ctx *generatorContext) reopenIterator(kind string) { 132 // Shift iterator one more step, so that we can reopen 133 // the iterator at the right position. 134 var iter = ctx.account 135 if kind == snapStorage { 136 iter = ctx.storage 137 } 138 hasNext := iter.Next() 139 if !hasNext { 140 // Iterator exhausted, release forever and create an already exhausted virtual iterator 141 iter.Release() 142 if kind == snapAccount { 143 ctx.account = newHoldableIterator(memorydb.New().NewIterator(nil, nil)) 144 return 145 } 146 ctx.storage = newHoldableIterator(memorydb.New().NewIterator(nil, nil)) 147 return 148 } 149 next := iter.Key() 150 iter.Release() 151 ctx.openIterator(kind, next[1:]) 152 } 153 154 // close releases all the held resources. 155 func (ctx *generatorContext) close() { 156 ctx.account.Release() 157 ctx.storage.Release() 158 } 159 160 // iterator returns the corresponding iterator specified by the kind. 161 func (ctx *generatorContext) iterator(kind string) *holdableIterator { 162 if kind == snapAccount { 163 return ctx.account 164 } 165 return ctx.storage 166 } 167 168 // removeStorageBefore deletes all storage entries which are located before 169 // the specified account. When the iterator touches the storage entry which 170 // is located in or outside the given account, it stops and holds the current 171 // iterated element locally. 172 func (ctx *generatorContext) removeStorageBefore(account common.Hash) uint64 { 173 var ( 174 count uint64 175 start = time.Now() 176 iter = ctx.storage 177 ) 178 for iter.Next() { 179 key := iter.Key() 180 if bytes.Compare(key[1:1+common.HashLength], account.Bytes()) >= 0 { 181 iter.Hold() 182 break 183 } 184 count++ 185 ctx.batch.Delete(key) 186 if ctx.batch.ValueSize() > ethdb.IdealBatchSize { 187 ctx.batch.Write() 188 ctx.batch.Reset() 189 } 190 } 191 storageCleanCounter.Inc(time.Since(start).Nanoseconds()) 192 return count 193 } 194 195 // removeStorageAt deletes all storage entries which are located in the specified 196 // account. When the iterator touches the storage entry which is outside the given 197 // account, it stops and holds the current iterated element locally. An error will 198 // be returned if the initial position of iterator is not in the given account. 199 func (ctx *generatorContext) removeStorageAt(account common.Hash) error { 200 var ( 201 count int64 202 start = time.Now() 203 iter = ctx.storage 204 ) 205 for iter.Next() { 206 key := iter.Key() 207 cmp := bytes.Compare(key[1:1+common.HashLength], account.Bytes()) 208 if cmp < 0 { 209 return errors.New("invalid iterator position") 210 } 211 if cmp > 0 { 212 iter.Hold() 213 break 214 } 215 count++ 216 ctx.batch.Delete(key) 217 if ctx.batch.ValueSize() > ethdb.IdealBatchSize { 218 ctx.batch.Write() 219 ctx.batch.Reset() 220 } 221 } 222 wipedStorageMeter.Mark(count) 223 storageCleanCounter.Inc(time.Since(start).Nanoseconds()) 224 return nil 225 } 226 227 // removeRemainingStorage deletes all storage entries which are located after 228 // the current iterator position. 229 func (ctx *generatorContext) removeRemainingStorage() uint64 { 230 var ( 231 count uint64 232 start = time.Now() 233 iter = ctx.storage 234 ) 235 for iter.Next() { 236 count++ 237 ctx.batch.Delete(iter.Key()) 238 if ctx.batch.ValueSize() > ethdb.IdealBatchSize { 239 ctx.batch.Write() 240 ctx.batch.Reset() 241 } 242 } 243 danglingStorageMeter.Mark(int64(count)) 244 storageCleanCounter.Inc(time.Since(start).Nanoseconds()) 245 return count 246 }