github.com/klaytn/klaytn@v1.12.1/snapshot/journal.go (about) 1 // Modifications Copyright 2021 The klaytn Authors 2 // Copyright 2019 The go-ethereum Authors 3 // This file is part of the go-ethereum library. 4 // 5 // The go-ethereum library is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Lesser General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // The go-ethereum library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public License 16 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 17 // 18 // This file is derived from core/state/snapshot/journal.go (2021/10/21). 19 // Modified and improved for the klaytn development. 20 21 package snapshot 22 23 import ( 24 "bytes" 25 "encoding/binary" 26 "errors" 27 "fmt" 28 "io" 29 "time" 30 31 "github.com/klaytn/klaytn/common" 32 "github.com/klaytn/klaytn/rlp" 33 "github.com/klaytn/klaytn/storage/database" 34 "github.com/klaytn/klaytn/storage/statedb" 35 36 "github.com/VictoriaMetrics/fastcache" 37 ) 38 39 const journalVersion uint64 = 0 40 41 // journalGenerator is a disk layer entry containing the generator progress marker. 42 type journalGenerator struct { 43 // Indicator that whether the database was in progress of being wiped. 44 // It's deprecated but keep it here for background compatibility. 45 Wiping bool 46 47 Done bool // Whether the generator finished creating the snapshot 48 Marker []byte 49 Accounts uint64 50 Slots uint64 51 Storage uint64 52 } 53 54 // journalDestruct is an account deletion entry in a diffLayer's disk journal. 55 type journalDestruct struct { 56 Hash common.Hash 57 } 58 59 // journalAccount is an account entry in a diffLayer's disk journal. 60 type journalAccount struct { 61 Hash common.Hash 62 Blob []byte 63 } 64 65 // journalStorage is an account's storage map in a diffLayer's disk journal. 66 type journalStorage struct { 67 Hash common.Hash 68 Keys []common.Hash 69 Vals [][]byte 70 } 71 72 // loadAndParseJournal tries to parse the snapshot journal in latest format. 73 func loadAndParseJournal(db database.DBManager, base *diskLayer) (snapshot, journalGenerator, error) { 74 // Retrieve the disk layer generator. It must exist, no matter the 75 // snapshot is fully generated or not. Otherwise the entire disk 76 // layer is invalid. 77 generatorBlob := db.ReadSnapshotGenerator() 78 if len(generatorBlob) == 0 { 79 return nil, journalGenerator{}, errors.New("missing snapshot generator") 80 } 81 var generator journalGenerator 82 if err := rlp.DecodeBytes(generatorBlob, &generator); err != nil { 83 return nil, journalGenerator{}, fmt.Errorf("failed to decode snapshot generator: %v", err) 84 } 85 // Retrieve the diff layer journal. It's possible that the journal is 86 // not existent, e.g. the disk layer is generating while that the Geth 87 // crashes without persisting the diff journal. 88 // So if there is no journal, or the journal is invalid(e.g. the journal 89 // is not matched with disk layer; or the it's the legacy-format journal, 90 // etc.), we just discard all diffs and try to recover them later. 91 journal := db.ReadSnapshotJournal() 92 if len(journal) == 0 { 93 logger.Warn("Loaded snapshot journal", "diskroot", base.root, "diffs", "missing") 94 return base, generator, nil 95 } 96 r := rlp.NewStream(bytes.NewReader(journal), 0) 97 98 // Firstly, resolve the first element as the journal version 99 version, err := r.Uint() 100 if err != nil { 101 logger.Warn("Failed to resolve the journal version", "error", err) 102 return base, generator, nil 103 } 104 if version != journalVersion { 105 logger.Warn("Discarded the snapshot journal with wrong version", "required", journalVersion, "got", version) 106 return base, generator, nil 107 } 108 // Secondly, resolve the disk layer root, ensure it's continuous 109 // with disk layer. Note now we can ensure it's the snapshot journal 110 // correct version, so we expect everything can be resolved properly. 111 var root common.Hash 112 if err := r.Decode(&root); err != nil { 113 return nil, journalGenerator{}, errors.New("missing disk layer root") 114 } 115 // The diff journal is not matched with disk, discard them. 116 // It can happen that Geth crashes without persisting the latest 117 // diff journal. 118 if !bytes.Equal(root.Bytes(), base.root.Bytes()) { 119 logger.Warn("Loaded snapshot journal", "diskroot", base.root, "diffs", "unmatched") 120 return base, generator, nil 121 } 122 // Load all the snapshot diffs from the journal 123 snapshot, err := loadDiffLayer(base, r) 124 if err != nil { 125 return nil, journalGenerator{}, err 126 } 127 logger.Debug("Loaded snapshot journal", "diskroot", base.root, "diffhead", snapshot.Root()) 128 return snapshot, generator, nil 129 } 130 131 // loadSnapshot loads a pre-existing state snapshot backed by a key-value store. 132 func loadSnapshot(diskdb database.DBManager, triedb *statedb.Database, cache int, root common.Hash, recovery bool) (snapshot, bool, error) { 133 // If snapshotting is disabled (initial sync in progress), don't do anything, 134 // wait for the chain to permit us to do something meaningful 135 if diskdb.ReadSnapshotDisabled() { 136 return nil, true, nil 137 } 138 // Retrieve the block number and hash of the snapshot, failing if no snapshot 139 // is present in the database (or crashed mid-update). 140 baseRoot := diskdb.ReadSnapshotRoot() 141 if baseRoot == (common.Hash{}) { 142 return nil, false, errors.New("missing or corrupted snapshot") 143 } 144 base := &diskLayer{ 145 diskdb: diskdb, 146 triedb: triedb, 147 cache: fastcache.New(cache * 1024 * 1024), 148 root: baseRoot, 149 } 150 snapshot, generator, err := loadAndParseJournal(diskdb, base) 151 if err != nil { 152 logger.Warn("Failed to load new-format journal", "error", err) 153 return nil, false, err 154 } 155 // Entire snapshot journal loaded, sanity check the head. If the loaded 156 // snapshot is not matched with current state root, print a warning log 157 // or discard the entire snapshot it's legacy snapshot. 158 // 159 // Possible scenario: Geth was crashed without persisting journal and then 160 // restart, the head is rewound to the point with available state(trie) 161 // which is below the snapshot. In this case the snapshot can be recovered 162 // by re-executing blocks but right now it's unavailable. 163 if head := snapshot.Root(); head != root { 164 // If it's legacy snapshot, or it's new-format snapshot but 165 // it's not in recovery mode, returns the error here for 166 // rebuilding the entire snapshot forcibly. 167 if !recovery { 168 return nil, false, fmt.Errorf("head doesn't match snapshot: have %#x, want %#x", head, root) 169 } 170 // It's in snapshot recovery, the assumption is held that 171 // the disk layer is always higher than chain head. It can 172 // be eventually recovered when the chain head beyonds the 173 // disk layer. 174 logger.Warn("Snapshot is not continuous with chain", "snaproot", head, "chainroot", root) 175 } 176 // Everything loaded correctly, resume any suspended operations 177 if !generator.Done { 178 // Whether or not wiping was in progress, load any generator progress too 179 base.genMarker = generator.Marker 180 if base.genMarker == nil { 181 base.genMarker = []byte{} 182 } 183 base.genPending = make(chan struct{}) 184 base.genAbort = make(chan chan *generatorStats) 185 186 var origin uint64 187 if len(generator.Marker) >= 8 { 188 origin = binary.BigEndian.Uint64(generator.Marker) 189 } 190 go base.generate(&generatorStats{ 191 origin: origin, 192 start: time.Now(), 193 accounts: generator.Accounts, 194 slots: generator.Slots, 195 storage: common.StorageSize(generator.Storage), 196 }) 197 } 198 return snapshot, false, nil 199 } 200 201 // loadDiffLayer reads the next sections of a snapshot journal, reconstructing a new 202 // diff and verifying that it can be linked to the requested parent. 203 func loadDiffLayer(parent snapshot, r *rlp.Stream) (snapshot, error) { 204 // Read the next diff journal entry 205 var root common.Hash 206 if err := r.Decode(&root); err != nil { 207 // The first read may fail with EOF, marking the end of the journal 208 if err == io.EOF { 209 return parent, nil 210 } 211 return nil, fmt.Errorf("load diff root: %v", err) 212 } 213 var destructs []journalDestruct 214 if err := r.Decode(&destructs); err != nil { 215 return nil, fmt.Errorf("load diff destructs: %v", err) 216 } 217 destructSet := make(map[common.Hash]struct{}) 218 for _, entry := range destructs { 219 destructSet[entry.Hash] = struct{}{} 220 } 221 var accounts []journalAccount 222 if err := r.Decode(&accounts); err != nil { 223 return nil, fmt.Errorf("load diff accounts: %v", err) 224 } 225 accountData := make(map[common.Hash][]byte) 226 for _, entry := range accounts { 227 if len(entry.Blob) > 0 { // RLP loses nil-ness, but `[]byte{}` is not a valid item, so reinterpret that 228 accountData[entry.Hash] = entry.Blob 229 } else { 230 accountData[entry.Hash] = nil 231 } 232 } 233 var storage []journalStorage 234 if err := r.Decode(&storage); err != nil { 235 return nil, fmt.Errorf("load diff storage: %v", err) 236 } 237 storageData := make(map[common.Hash]map[common.Hash][]byte) 238 for _, entry := range storage { 239 slots := make(map[common.Hash][]byte) 240 for i, key := range entry.Keys { 241 if len(entry.Vals[i]) > 0 { // RLP loses nil-ness, but `[]byte{}` is not a valid item, so reinterpret that 242 slots[key] = entry.Vals[i] 243 } else { 244 slots[key] = nil 245 } 246 } 247 storageData[entry.Hash] = slots 248 } 249 return loadDiffLayer(newDiffLayer(parent, root, destructSet, accountData, storageData), r) 250 } 251 252 // Journal terminates any in-progress snapshot generation, also implicitly pushing 253 // the progress into the database. 254 func (dl *diskLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) { 255 // If the snapshot is currently being generated, abort it 256 var stats *generatorStats 257 if dl.genAbort != nil { 258 abort := make(chan *generatorStats) 259 dl.genAbort <- abort 260 261 if stats = <-abort; stats != nil { 262 stats.Log("Journalling in-progress snapshot", dl.root, dl.genMarker) 263 } 264 } 265 // Ensure the layer didn't get stale 266 dl.lock.RLock() 267 defer dl.lock.RUnlock() 268 269 if dl.stale { 270 return common.Hash{}, ErrSnapshotStale 271 } 272 // Ensure the generator stats is written even if none was ran this cycle 273 journalProgress(dl.diskdb.GetSnapshotDB(), dl.genMarker, stats) 274 275 logger.Debug("Journalled disk layer", "root", dl.root) 276 return dl.root, nil 277 } 278 279 // Journal writes the memory layer contents into a buffer to be stored in the 280 // database as the snapshot journal. 281 func (dl *diffLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) { 282 // Journal the parent first 283 base, err := dl.parent.Journal(buffer) 284 if err != nil { 285 return common.Hash{}, err 286 } 287 // Ensure the layer didn't get stale 288 dl.lock.RLock() 289 defer dl.lock.RUnlock() 290 291 if dl.Stale() { 292 return common.Hash{}, ErrSnapshotStale 293 } 294 // Everything below was journalled, persist this layer too 295 if err := rlp.Encode(buffer, dl.root); err != nil { 296 return common.Hash{}, err 297 } 298 destructs := make([]journalDestruct, 0, len(dl.destructSet)) 299 for hash := range dl.destructSet { 300 destructs = append(destructs, journalDestruct{Hash: hash}) 301 } 302 if err := rlp.Encode(buffer, destructs); err != nil { 303 return common.Hash{}, err 304 } 305 accounts := make([]journalAccount, 0, len(dl.accountData)) 306 for hash, blob := range dl.accountData { 307 accounts = append(accounts, journalAccount{Hash: hash, Blob: blob}) 308 } 309 if err := rlp.Encode(buffer, accounts); err != nil { 310 return common.Hash{}, err 311 } 312 storage := make([]journalStorage, 0, len(dl.storageData)) 313 for hash, slots := range dl.storageData { 314 keys := make([]common.Hash, 0, len(slots)) 315 vals := make([][]byte, 0, len(slots)) 316 for key, val := range slots { 317 keys = append(keys, key) 318 vals = append(vals, val) 319 } 320 storage = append(storage, journalStorage{Hash: hash, Keys: keys, Vals: vals}) 321 } 322 if err := rlp.Encode(buffer, storage); err != nil { 323 return common.Hash{}, err 324 } 325 logger.Debug("Journalled diff layer", "root", dl.root, "parent", dl.parent.Root()) 326 return base, nil 327 }