github.com/ethw3/go-ethereuma@v0.0.0-20221013053120-c14602a4c23c/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/ethw3/go-ethereuma/common" 26 "github.com/ethw3/go-ethereuma/ethdb" 27 "github.com/ethw3/go-ethereuma/log" 28 "github.com/ethw3/go-ethereuma/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 for { 96 select { 97 case <-f.quit: 98 log.Info("Freezer shutting down") 99 return 100 default: 101 } 102 if backoff { 103 // If we were doing a manual trigger, notify it 104 if triggered != nil { 105 triggered <- struct{}{} 106 triggered = nil 107 } 108 select { 109 case <-time.NewTimer(freezerRecheckInterval).C: 110 backoff = false 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 := atomic.LoadUint64(&f.threshold) 126 frozen := atomic.LoadUint64(&f.frozen) 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 = atomic.LoadUint64(&f.frozen) // 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 ethdb.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 }