github.com/ethereum/go-ethereum@v1.16.1/core/rawdb/chain_freezer.go (about) 1 // Copyright 2022 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 rawdb 18 19 import ( 20 "errors" 21 "fmt" 22 "sync" 23 "time" 24 25 "github.com/ethereum/go-ethereum/common" 26 "github.com/ethereum/go-ethereum/core/rawdb/eradb" 27 "github.com/ethereum/go-ethereum/ethdb" 28 "github.com/ethereum/go-ethereum/log" 29 "github.com/ethereum/go-ethereum/params" 30 ) 31 32 const ( 33 // freezerRecheckInterval is the frequency to check the key-value database for 34 // chain progression that might permit new blocks to be frozen into immutable 35 // storage. 36 freezerRecheckInterval = time.Minute 37 38 // freezerBatchLimit is the maximum number of blocks to freeze in one batch 39 // before doing an fsync and deleting it from the key-value store. 40 freezerBatchLimit = 30000 41 ) 42 43 // chainFreezer is a wrapper of chain ancient store with additional chain freezing 44 // feature. The background thread will keep moving ancient chain segments from 45 // key-value database to flat files for saving space on live database. 46 type chainFreezer struct { 47 ancients ethdb.AncientStore // Ancient store for storing cold chain segment 48 49 // Optional Era database used as a backup for the pruned chain. 50 eradb *eradb.Store 51 52 quit chan struct{} 53 wg sync.WaitGroup 54 trigger chan chan struct{} // Manual blocking freeze trigger, test determinism 55 } 56 57 // newChainFreezer initializes the freezer for ancient chain segment. 58 // 59 // - if the empty directory is given, initializes the pure in-memory 60 // state freezer (e.g. dev mode). 61 // - if non-empty directory is given, initializes the regular file-based 62 // state freezer. 63 func newChainFreezer(datadir string, eraDir string, namespace string, readonly bool) (*chainFreezer, error) { 64 if datadir == "" { 65 return &chainFreezer{ 66 ancients: NewMemoryFreezer(readonly, chainFreezerTableConfigs), 67 quit: make(chan struct{}), 68 trigger: make(chan chan struct{}), 69 }, nil 70 } 71 freezer, err := NewFreezer(datadir, namespace, readonly, freezerTableSize, chainFreezerTableConfigs) 72 if err != nil { 73 return nil, err 74 } 75 edb, err := eradb.New(resolveChainEraDir(datadir, eraDir)) 76 if err != nil { 77 return nil, err 78 } 79 return &chainFreezer{ 80 ancients: freezer, 81 eradb: edb, 82 quit: make(chan struct{}), 83 trigger: make(chan chan struct{}), 84 }, nil 85 } 86 87 // Close closes the chain freezer instance and terminates the background thread. 88 func (f *chainFreezer) Close() error { 89 select { 90 case <-f.quit: 91 default: 92 close(f.quit) 93 } 94 f.wg.Wait() 95 96 if f.eradb != nil { 97 f.eradb.Close() 98 } 99 return f.ancients.Close() 100 } 101 102 // readHeadNumber returns the number of chain head block. 0 is returned if the 103 // block is unknown or not available yet. 104 func (f *chainFreezer) readHeadNumber(db ethdb.KeyValueReader) uint64 { 105 hash := ReadHeadBlockHash(db) 106 if hash == (common.Hash{}) { 107 log.Error("Head block is not reachable") 108 return 0 109 } 110 number := ReadHeaderNumber(db, hash) 111 if number == nil { 112 log.Error("Number of head block is missing") 113 return 0 114 } 115 return *number 116 } 117 118 // readFinalizedNumber returns the number of finalized block. 0 is returned 119 // if the block is unknown or not available yet. 120 func (f *chainFreezer) readFinalizedNumber(db ethdb.KeyValueReader) uint64 { 121 hash := ReadFinalizedBlockHash(db) 122 if hash == (common.Hash{}) { 123 return 0 124 } 125 number := ReadHeaderNumber(db, hash) 126 if number == nil { 127 log.Error("Number of finalized block is missing") 128 return 0 129 } 130 return *number 131 } 132 133 // freezeThreshold returns the threshold for chain freezing. It's determined 134 // by formula: max(finality, HEAD-params.FullImmutabilityThreshold). 135 func (f *chainFreezer) freezeThreshold(db ethdb.KeyValueReader) (uint64, error) { 136 var ( 137 head = f.readHeadNumber(db) 138 final = f.readFinalizedNumber(db) 139 headLimit uint64 140 ) 141 if head > params.FullImmutabilityThreshold { 142 headLimit = head - params.FullImmutabilityThreshold 143 } 144 if final == 0 && headLimit == 0 { 145 return 0, errors.New("freezing threshold is not available") 146 } 147 if final > headLimit { 148 return final, nil 149 } 150 return headLimit, nil 151 } 152 153 // freeze is a background thread that periodically checks the blockchain for any 154 // import progress and moves ancient data from the fast database into the freezer. 155 // 156 // This functionality is deliberately broken off from block importing to avoid 157 // incurring additional data shuffling delays on block propagation. 158 func (f *chainFreezer) freeze(db ethdb.KeyValueStore) { 159 var ( 160 backoff bool 161 triggered chan struct{} // Used in tests 162 nfdb = &nofreezedb{KeyValueStore: db} 163 ) 164 timer := time.NewTimer(freezerRecheckInterval) 165 defer timer.Stop() 166 167 for { 168 select { 169 case <-f.quit: 170 log.Info("Freezer shutting down") 171 return 172 default: 173 } 174 if backoff { 175 // If we were doing a manual trigger, notify it 176 if triggered != nil { 177 triggered <- struct{}{} 178 triggered = nil 179 } 180 select { 181 case <-timer.C: 182 backoff = false 183 timer.Reset(freezerRecheckInterval) 184 case triggered = <-f.trigger: 185 backoff = false 186 case <-f.quit: 187 return 188 } 189 } 190 threshold, err := f.freezeThreshold(nfdb) 191 if err != nil { 192 backoff = true 193 log.Debug("Current full block not old enough to freeze", "err", err) 194 continue 195 } 196 frozen, _ := f.Ancients() // no error will occur, safe to ignore 197 198 // Short circuit if the blocks below threshold are already frozen. 199 if frozen != 0 && frozen-1 >= threshold { 200 backoff = true 201 log.Debug("Ancient blocks frozen already", "threshold", threshold, "frozen", frozen) 202 continue 203 } 204 // Seems we have data ready to be frozen, process in usable batches 205 var ( 206 start = time.Now() 207 first = frozen // the first block to freeze 208 last = threshold // the last block to freeze 209 ) 210 if last-first+1 > freezerBatchLimit { 211 last = freezerBatchLimit + first - 1 212 } 213 ancients, err := f.freezeRange(nfdb, first, last) 214 if err != nil { 215 log.Error("Error in block freeze operation", "err", err) 216 backoff = true 217 continue 218 } 219 // Batch of blocks have been frozen, flush them before wiping from key-value store 220 if err := f.SyncAncient(); err != nil { 221 log.Crit("Failed to flush frozen tables", "err", err) 222 } 223 // Wipe out all data from the active database 224 batch := db.NewBatch() 225 for i := 0; i < len(ancients); i++ { 226 // Always keep the genesis block in active database 227 if first+uint64(i) != 0 { 228 DeleteBlockWithoutNumber(batch, ancients[i], first+uint64(i)) 229 DeleteCanonicalHash(batch, first+uint64(i)) 230 } 231 } 232 if err := batch.Write(); err != nil { 233 log.Crit("Failed to delete frozen canonical blocks", "err", err) 234 } 235 batch.Reset() 236 237 // Wipe out side chains also and track dangling side chains 238 var dangling []common.Hash 239 frozen, _ = f.Ancients() // Needs reload after during freezeRange 240 for number := first; number < frozen; number++ { 241 // Always keep the genesis block in active database 242 if number != 0 { 243 dangling = ReadAllHashes(db, number) 244 for _, hash := range dangling { 245 log.Trace("Deleting side chain", "number", number, "hash", hash) 246 DeleteBlock(batch, hash, number) 247 } 248 } 249 } 250 if err := batch.Write(); err != nil { 251 log.Crit("Failed to delete frozen side blocks", "err", err) 252 } 253 batch.Reset() 254 255 // Step into the future and delete any dangling side chains 256 if frozen > 0 { 257 tip := frozen 258 for len(dangling) > 0 { 259 drop := make(map[common.Hash]struct{}) 260 for _, hash := range dangling { 261 log.Debug("Dangling parent from Freezer", "number", tip-1, "hash", hash) 262 drop[hash] = struct{}{} 263 } 264 children := ReadAllHashes(db, tip) 265 for i := 0; i < len(children); i++ { 266 // Dig up the child and ensure it's dangling 267 child := ReadHeader(nfdb, children[i], tip) 268 if child == nil { 269 log.Error("Missing dangling header", "number", tip, "hash", children[i]) 270 continue 271 } 272 if _, ok := drop[child.ParentHash]; !ok { 273 children = append(children[:i], children[i+1:]...) 274 i-- 275 continue 276 } 277 // Delete all block data associated with the child 278 log.Debug("Deleting dangling block", "number", tip, "hash", children[i], "parent", child.ParentHash) 279 DeleteBlock(batch, children[i], tip) 280 } 281 dangling = children 282 tip++ 283 } 284 if err := batch.Write(); err != nil { 285 log.Crit("Failed to delete dangling side blocks", "err", err) 286 } 287 } 288 289 // Log something friendly for the user 290 context := []interface{}{ 291 "blocks", frozen - first, "elapsed", common.PrettyDuration(time.Since(start)), "number", frozen - 1, 292 } 293 if n := len(ancients); n > 0 { 294 context = append(context, []interface{}{"hash", ancients[n-1]}...) 295 } 296 log.Debug("Deep froze chain segment", context...) 297 298 // Avoid database thrashing with tiny writes 299 if frozen-first < freezerBatchLimit { 300 backoff = true 301 } 302 } 303 } 304 305 // freezeRange moves a batch of chain segments from the fast database to the freezer. 306 // The parameters (number, limit) specify the relevant block range, both of which 307 // are included. 308 func (f *chainFreezer) freezeRange(nfdb *nofreezedb, number, limit uint64) (hashes []common.Hash, err error) { 309 hashes = make([]common.Hash, 0, limit-number+1) 310 311 _, err = f.ModifyAncients(func(op ethdb.AncientWriteOp) error { 312 for ; number <= limit; number++ { 313 // Retrieve all the components of the canonical block. 314 hash := ReadCanonicalHash(nfdb, number) 315 if hash == (common.Hash{}) { 316 return fmt.Errorf("canonical hash missing, can't freeze block %d", number) 317 } 318 header := ReadHeaderRLP(nfdb, hash, number) 319 if len(header) == 0 { 320 return fmt.Errorf("block header missing, can't freeze block %d", number) 321 } 322 body := ReadBodyRLP(nfdb, hash, number) 323 if len(body) == 0 { 324 return fmt.Errorf("block body missing, can't freeze block %d", number) 325 } 326 receipts := ReadReceiptsRLP(nfdb, hash, number) 327 if len(receipts) == 0 { 328 return fmt.Errorf("block receipts missing, can't freeze block %d", number) 329 } 330 // Write to the batch. 331 if err := op.AppendRaw(ChainFreezerHashTable, number, hash[:]); err != nil { 332 return fmt.Errorf("can't write hash to Freezer: %v", err) 333 } 334 if err := op.AppendRaw(ChainFreezerHeaderTable, number, header); err != nil { 335 return fmt.Errorf("can't write header to Freezer: %v", err) 336 } 337 if err := op.AppendRaw(ChainFreezerBodiesTable, number, body); err != nil { 338 return fmt.Errorf("can't write body to Freezer: %v", err) 339 } 340 if err := op.AppendRaw(ChainFreezerReceiptTable, number, receipts); err != nil { 341 return fmt.Errorf("can't write receipts to Freezer: %v", err) 342 } 343 hashes = append(hashes, hash) 344 } 345 return nil 346 }) 347 return hashes, err 348 } 349 350 // Ancient retrieves an ancient binary blob from the append-only immutable files. 351 func (f *chainFreezer) Ancient(kind string, number uint64) ([]byte, error) { 352 // Lookup the entry in the underlying ancient store, assuming that 353 // headers and hashes are always available. 354 if kind == ChainFreezerHeaderTable || kind == ChainFreezerHashTable { 355 return f.ancients.Ancient(kind, number) 356 } 357 tail, err := f.ancients.Tail() 358 if err != nil { 359 return nil, err 360 } 361 // Lookup the entry in the underlying ancient store if it's not pruned 362 if number >= tail { 363 return f.ancients.Ancient(kind, number) 364 } 365 // Lookup the entry in the optional era backend 366 if f.eradb == nil { 367 return nil, errOutOfBounds 368 } 369 switch kind { 370 case ChainFreezerBodiesTable: 371 return f.eradb.GetRawBody(number) 372 case ChainFreezerReceiptTable: 373 return f.eradb.GetRawReceipts(number) 374 } 375 return nil, errUnknownTable 376 } 377 378 // ReadAncients executes an operation while preventing mutations to the freezer, 379 // i.e. if fn performs multiple reads, they will be consistent with each other. 380 func (f *chainFreezer) ReadAncients(fn func(ethdb.AncientReaderOp) error) (err error) { 381 if store, ok := f.ancients.(*Freezer); ok { 382 store.writeLock.Lock() 383 defer store.writeLock.Unlock() 384 } 385 return fn(f) 386 } 387 388 // Methods below are just pass-through to the underlying ancient store. 389 390 func (f *chainFreezer) Ancients() (uint64, error) { 391 return f.ancients.Ancients() 392 } 393 394 func (f *chainFreezer) Tail() (uint64, error) { 395 return f.ancients.Tail() 396 } 397 398 func (f *chainFreezer) AncientSize(kind string) (uint64, error) { 399 return f.ancients.AncientSize(kind) 400 } 401 402 func (f *chainFreezer) AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error) { 403 return f.ancients.AncientRange(kind, start, count, maxBytes) 404 } 405 406 func (f *chainFreezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (int64, error) { 407 return f.ancients.ModifyAncients(fn) 408 } 409 410 func (f *chainFreezer) TruncateHead(items uint64) (uint64, error) { 411 return f.ancients.TruncateHead(items) 412 } 413 414 func (f *chainFreezer) TruncateTail(items uint64) (uint64, error) { 415 return f.ancients.TruncateTail(items) 416 } 417 418 func (f *chainFreezer) SyncAncient() error { 419 return f.ancients.SyncAncient() 420 }