github.com/jimmyx0x/go-ethereum@v1.10.28/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 "fmt" 21 "sync" 22 "sync/atomic" 23 "time" 24 25 "github.com/ethereum/go-ethereum/common" 26 "github.com/ethereum/go-ethereum/ethdb" 27 "github.com/ethereum/go-ethereum/log" 28 "github.com/ethereum/go-ethereum/params" 29 ) 30 31 const ( 32 // freezerRecheckInterval is the frequency to check the key-value database for 33 // chain progression that might permit new blocks to be frozen into immutable 34 // storage. 35 freezerRecheckInterval = time.Minute 36 37 // freezerBatchLimit is the maximum number of blocks to freeze in one batch 38 // before doing an fsync and deleting it from the key-value store. 39 freezerBatchLimit = 30000 40 ) 41 42 // chainFreezer is a wrapper of freezer with additional chain freezing feature. 43 // The background thread will keep moving ancient chain segments from key-value 44 // database to flat files for saving space on live database. 45 type chainFreezer struct { 46 // WARNING: The `threshold` field is accessed atomically. On 32 bit platforms, only 47 // 64-bit aligned fields can be atomic. The struct is guaranteed to be so aligned, 48 // so take advantage of that (https://golang.org/pkg/sync/atomic/#pkg-note-BUG). 49 threshold uint64 // Number of recent blocks not to freeze (params.FullImmutabilityThreshold apart from tests) 50 51 *Freezer 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 data. 58 func newChainFreezer(datadir string, namespace string, readonly bool, maxTableSize uint32, tables map[string]bool) (*chainFreezer, error) { 59 freezer, err := NewFreezer(datadir, namespace, readonly, maxTableSize, tables) 60 if err != nil { 61 return nil, err 62 } 63 return &chainFreezer{ 64 Freezer: freezer, 65 threshold: params.FullImmutabilityThreshold, 66 quit: make(chan struct{}), 67 trigger: make(chan chan struct{}), 68 }, nil 69 } 70 71 // Close closes the chain freezer instance and terminates the background thread. 72 func (f *chainFreezer) Close() error { 73 err := f.Freezer.Close() 74 select { 75 case <-f.quit: 76 default: 77 close(f.quit) 78 } 79 f.wg.Wait() 80 return err 81 } 82 83 // freeze is a background thread that periodically checks the blockchain for any 84 // import progress and moves ancient data from the fast database into the freezer. 85 // 86 // This functionality is deliberately broken off from block importing to avoid 87 // incurring additional data shuffling delays on block propagation. 88 func (f *chainFreezer) freeze(db ethdb.KeyValueStore) { 89 nfdb := &nofreezedb{KeyValueStore: db} 90 91 var ( 92 backoff bool 93 triggered chan struct{} // Used in tests 94 ) 95 timer := time.NewTimer(freezerRecheckInterval) 96 defer timer.Stop() 97 for { 98 select { 99 case <-f.quit: 100 log.Info("Freezer shutting down") 101 return 102 default: 103 } 104 if backoff { 105 // If we were doing a manual trigger, notify it 106 if triggered != nil { 107 triggered <- struct{}{} 108 triggered = nil 109 } 110 select { 111 case <-timer.C: 112 backoff = false 113 timer.Reset(freezerRecheckInterval) 114 case triggered = <-f.trigger: 115 backoff = false 116 case <-f.quit: 117 return 118 } 119 } 120 // Retrieve the freezing threshold. 121 hash := ReadHeadBlockHash(nfdb) 122 if hash == (common.Hash{}) { 123 log.Debug("Current full block hash unavailable") // new chain, empty database 124 backoff = true 125 continue 126 } 127 number := ReadHeaderNumber(nfdb, hash) 128 threshold := atomic.LoadUint64(&f.threshold) 129 frozen := atomic.LoadUint64(&f.frozen) 130 switch { 131 case number == nil: 132 log.Error("Current full block number unavailable", "hash", hash) 133 backoff = true 134 continue 135 136 case *number < threshold: 137 log.Debug("Current full block not old enough", "number", *number, "hash", hash, "delay", threshold) 138 backoff = true 139 continue 140 141 case *number-threshold <= frozen: 142 log.Debug("Ancient blocks frozen already", "number", *number, "hash", hash, "frozen", frozen) 143 backoff = true 144 continue 145 } 146 head := ReadHeader(nfdb, hash, *number) 147 if head == nil { 148 log.Error("Current full block unavailable", "number", *number, "hash", hash) 149 backoff = true 150 continue 151 } 152 153 // Seems we have data ready to be frozen, process in usable batches 154 var ( 155 start = time.Now() 156 first, _ = f.Ancients() 157 limit = *number - threshold 158 ) 159 if limit-first > freezerBatchLimit { 160 limit = first + freezerBatchLimit 161 } 162 ancients, err := f.freezeRange(nfdb, first, limit) 163 if err != nil { 164 log.Error("Error in block freeze operation", "err", err) 165 backoff = true 166 continue 167 } 168 169 // Batch of blocks have been frozen, flush them before wiping from leveldb 170 if err := f.Sync(); err != nil { 171 log.Crit("Failed to flush frozen tables", "err", err) 172 } 173 174 // Wipe out all data from the active database 175 batch := db.NewBatch() 176 for i := 0; i < len(ancients); i++ { 177 // Always keep the genesis block in active database 178 if first+uint64(i) != 0 { 179 DeleteBlockWithoutNumber(batch, ancients[i], first+uint64(i)) 180 DeleteCanonicalHash(batch, first+uint64(i)) 181 } 182 } 183 if err := batch.Write(); err != nil { 184 log.Crit("Failed to delete frozen canonical blocks", "err", err) 185 } 186 batch.Reset() 187 188 // Wipe out side chains also and track dangling side chains 189 var dangling []common.Hash 190 frozen = atomic.LoadUint64(&f.frozen) // Needs reload after during freezeRange 191 for number := first; number < frozen; number++ { 192 // Always keep the genesis block in active database 193 if number != 0 { 194 dangling = ReadAllHashes(db, number) 195 for _, hash := range dangling { 196 log.Trace("Deleting side chain", "number", number, "hash", hash) 197 DeleteBlock(batch, hash, number) 198 } 199 } 200 } 201 if err := batch.Write(); err != nil { 202 log.Crit("Failed to delete frozen side blocks", "err", err) 203 } 204 batch.Reset() 205 206 // Step into the future and delete and dangling side chains 207 if frozen > 0 { 208 tip := frozen 209 for len(dangling) > 0 { 210 drop := make(map[common.Hash]struct{}) 211 for _, hash := range dangling { 212 log.Debug("Dangling parent from Freezer", "number", tip-1, "hash", hash) 213 drop[hash] = struct{}{} 214 } 215 children := ReadAllHashes(db, tip) 216 for i := 0; i < len(children); i++ { 217 // Dig up the child and ensure it's dangling 218 child := ReadHeader(nfdb, children[i], tip) 219 if child == nil { 220 log.Error("Missing dangling header", "number", tip, "hash", children[i]) 221 continue 222 } 223 if _, ok := drop[child.ParentHash]; !ok { 224 children = append(children[:i], children[i+1:]...) 225 i-- 226 continue 227 } 228 // Delete all block data associated with the child 229 log.Debug("Deleting dangling block", "number", tip, "hash", children[i], "parent", child.ParentHash) 230 DeleteBlock(batch, children[i], tip) 231 } 232 dangling = children 233 tip++ 234 } 235 if err := batch.Write(); err != nil { 236 log.Crit("Failed to delete dangling side blocks", "err", err) 237 } 238 } 239 240 // Log something friendly for the user 241 context := []interface{}{ 242 "blocks", frozen - first, "elapsed", common.PrettyDuration(time.Since(start)), "number", frozen - 1, 243 } 244 if n := len(ancients); n > 0 { 245 context = append(context, []interface{}{"hash", ancients[n-1]}...) 246 } 247 log.Debug("Deep froze chain segment", context...) 248 249 // Avoid database thrashing with tiny writes 250 if frozen-first < freezerBatchLimit { 251 backoff = true 252 } 253 } 254 } 255 256 func (f *chainFreezer) freezeRange(nfdb *nofreezedb, number, limit uint64) (hashes []common.Hash, err error) { 257 hashes = make([]common.Hash, 0, limit-number) 258 259 _, err = f.ModifyAncients(func(op ethdb.AncientWriteOp) error { 260 for ; number <= limit; number++ { 261 // Retrieve all the components of the canonical block. 262 hash := ReadCanonicalHash(nfdb, number) 263 if hash == (common.Hash{}) { 264 return fmt.Errorf("canonical hash missing, can't freeze block %d", number) 265 } 266 header := ReadHeaderRLP(nfdb, hash, number) 267 if len(header) == 0 { 268 return fmt.Errorf("block header missing, can't freeze block %d", number) 269 } 270 body := ReadBodyRLP(nfdb, hash, number) 271 if len(body) == 0 { 272 return fmt.Errorf("block body missing, can't freeze block %d", number) 273 } 274 receipts := ReadReceiptsRLP(nfdb, hash, number) 275 if len(receipts) == 0 { 276 return fmt.Errorf("block receipts missing, can't freeze block %d", number) 277 } 278 td := ReadTdRLP(nfdb, hash, number) 279 if len(td) == 0 { 280 return fmt.Errorf("total difficulty missing, can't freeze block %d", number) 281 } 282 283 // Write to the batch. 284 if err := op.AppendRaw(chainFreezerHashTable, number, hash[:]); err != nil { 285 return fmt.Errorf("can't write hash to Freezer: %v", err) 286 } 287 if err := op.AppendRaw(chainFreezerHeaderTable, number, header); err != nil { 288 return fmt.Errorf("can't write header to Freezer: %v", err) 289 } 290 if err := op.AppendRaw(chainFreezerBodiesTable, number, body); err != nil { 291 return fmt.Errorf("can't write body to Freezer: %v", err) 292 } 293 if err := op.AppendRaw(chainFreezerReceiptTable, number, receipts); err != nil { 294 return fmt.Errorf("can't write receipts to Freezer: %v", err) 295 } 296 if err := op.AppendRaw(chainFreezerDifficultyTable, number, td); err != nil { 297 return fmt.Errorf("can't write td to Freezer: %v", err) 298 } 299 300 hashes = append(hashes, hash) 301 } 302 return nil 303 }) 304 305 return hashes, err 306 }