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