github.com/DxChainNetwork/dxc@v0.8.1-0.20220824085222-1162e304b6e7/core/rawdb/freezer.go (about) 1 // Copyright 2019 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 "math" 23 "os" 24 "path/filepath" 25 "sync" 26 "sync/atomic" 27 "time" 28 29 "github.com/DxChainNetwork/dxc/common" 30 "github.com/DxChainNetwork/dxc/ethdb" 31 "github.com/DxChainNetwork/dxc/log" 32 "github.com/DxChainNetwork/dxc/metrics" 33 "github.com/DxChainNetwork/dxc/params" 34 "github.com/prometheus/tsdb/fileutil" 35 ) 36 37 var ( 38 // errReadOnly is returned if the freezer is opened in read only mode. All the 39 // mutations are disallowed. 40 errReadOnly = errors.New("read only") 41 42 // errUnknownTable is returned if the user attempts to read from a table that is 43 // not tracked by the freezer. 44 errUnknownTable = errors.New("unknown table") 45 46 // errOutOrderInsertion is returned if the user attempts to inject out-of-order 47 // binary blobs into the freezer. 48 errOutOrderInsertion = errors.New("the append operation is out-order") 49 50 // errSymlinkDatadir is returned if the ancient directory specified by user 51 // is a symbolic link. 52 errSymlinkDatadir = errors.New("symbolic link datadir is not supported") 53 ) 54 55 const ( 56 // freezerRecheckInterval is the frequency to check the key-value database for 57 // chain progression that might permit new blocks to be frozen into immutable 58 // storage. 59 freezerRecheckInterval = time.Minute 60 61 // freezerBatchLimit is the maximum number of blocks to freeze in one batch 62 // before doing an fsync and deleting it from the key-value store. 63 freezerBatchLimit = 30000 64 ) 65 66 // freezer is an memory mapped append-only database to store immutable chain data 67 // into flat files: 68 // 69 // - The append only nature ensures that disk writes are minimized. 70 // - The memory mapping ensures we can max out system memory for caching without 71 // reserving it for go-ethereum. This would also reduce the memory requirements 72 // of Geth, and thus also GC overhead. 73 type freezer struct { 74 // WARNING: The `frozen` field is accessed atomically. On 32 bit platforms, only 75 // 64-bit aligned fields can be atomic. The struct is guaranteed to be so aligned, 76 // so take advantage of that (https://golang.org/pkg/sync/atomic/#pkg-note-BUG). 77 frozen uint64 // Number of blocks already frozen 78 threshold uint64 // Number of recent blocks not to freeze (params.FullImmutabilityThreshold apart from tests) 79 80 readonly bool 81 tables map[string]*freezerTable // Data tables for storing everything 82 instanceLock fileutil.Releaser // File-system lock to prevent double opens 83 84 trigger chan chan struct{} // Manual blocking freeze trigger, test determinism 85 86 quit chan struct{} 87 wg sync.WaitGroup 88 closeOnce sync.Once 89 } 90 91 // newFreezer creates a chain freezer that moves ancient chain data into 92 // append-only flat file containers. 93 func newFreezer(datadir string, namespace string, readonly bool) (*freezer, error) { 94 // Create the initial freezer object 95 var ( 96 readMeter = metrics.NewRegisteredMeter(namespace+"ancient/read", nil) 97 writeMeter = metrics.NewRegisteredMeter(namespace+"ancient/write", nil) 98 sizeGauge = metrics.NewRegisteredGauge(namespace+"ancient/size", nil) 99 ) 100 // Ensure the datadir is not a symbolic link if it exists. 101 if info, err := os.Lstat(datadir); !os.IsNotExist(err) { 102 if info.Mode()&os.ModeSymlink != 0 { 103 log.Warn("Symbolic link ancient database is not supported", "path", datadir) 104 return nil, errSymlinkDatadir 105 } 106 } 107 // Leveldb uses LOCK as the filelock filename. To prevent the 108 // name collision, we use FLOCK as the lock name. 109 lock, _, err := fileutil.Flock(filepath.Join(datadir, "FLOCK")) 110 if err != nil { 111 return nil, err 112 } 113 // Open all the supported data tables 114 freezer := &freezer{ 115 readonly: readonly, 116 threshold: params.FullImmutabilityThreshold, 117 tables: make(map[string]*freezerTable), 118 instanceLock: lock, 119 trigger: make(chan chan struct{}), 120 quit: make(chan struct{}), 121 } 122 for name, disableSnappy := range FreezerNoSnappy { 123 table, err := newTable(datadir, name, readMeter, writeMeter, sizeGauge, disableSnappy) 124 if err != nil { 125 for _, table := range freezer.tables { 126 table.Close() 127 } 128 lock.Release() 129 return nil, err 130 } 131 freezer.tables[name] = table 132 } 133 if err := freezer.repair(); err != nil { 134 for _, table := range freezer.tables { 135 table.Close() 136 } 137 lock.Release() 138 return nil, err 139 } 140 log.Info("Opened ancient database", "database", datadir, "readonly", readonly) 141 return freezer, nil 142 } 143 144 // Close terminates the chain freezer, unmapping all the data files. 145 func (f *freezer) Close() error { 146 var errs []error 147 f.closeOnce.Do(func() { 148 close(f.quit) 149 // Wait for any background freezing to stop 150 f.wg.Wait() 151 for _, table := range f.tables { 152 if err := table.Close(); err != nil { 153 errs = append(errs, err) 154 } 155 } 156 if err := f.instanceLock.Release(); err != nil { 157 errs = append(errs, err) 158 } 159 }) 160 if errs != nil { 161 return fmt.Errorf("%v", errs) 162 } 163 return nil 164 } 165 166 // HasAncient returns an indicator whether the specified ancient data exists 167 // in the freezer. 168 func (f *freezer) HasAncient(kind string, number uint64) (bool, error) { 169 if table := f.tables[kind]; table != nil { 170 return table.has(number), nil 171 } 172 return false, nil 173 } 174 175 // Ancient retrieves an ancient binary blob from the append-only immutable files. 176 func (f *freezer) Ancient(kind string, number uint64) ([]byte, error) { 177 if table := f.tables[kind]; table != nil { 178 return table.Retrieve(number) 179 } 180 return nil, errUnknownTable 181 } 182 183 // ReadAncients retrieves multiple items in sequence, starting from the index 'start'. 184 // It will return 185 // - at most 'max' items, 186 // - at least 1 item (even if exceeding the maxByteSize), but will otherwise 187 // return as many items as fit into maxByteSize. 188 func (f *freezer) ReadAncients(kind string, start, count, maxBytes uint64) ([][]byte, error) { 189 if table := f.tables[kind]; table != nil { 190 return table.RetrieveItems(start, count, maxBytes) 191 } 192 return nil, errUnknownTable 193 } 194 195 // Ancients returns the length of the frozen items. 196 func (f *freezer) Ancients() (uint64, error) { 197 return atomic.LoadUint64(&f.frozen), nil 198 } 199 200 // AncientSize returns the ancient size of the specified category. 201 func (f *freezer) AncientSize(kind string) (uint64, error) { 202 if table := f.tables[kind]; table != nil { 203 return table.size() 204 } 205 return 0, errUnknownTable 206 } 207 208 // AppendAncient injects all binary blobs belong to block at the end of the 209 // append-only immutable table files. 210 // 211 // Notably, this function is lock free but kind of thread-safe. All out-of-order 212 // injection will be rejected. But if two injections with same number happen at 213 // the same time, we can get into the trouble. 214 func (f *freezer) AppendAncient(number uint64, hash, header, body, receipts, td []byte) (err error) { 215 if f.readonly { 216 return errReadOnly 217 } 218 // Ensure the binary blobs we are appending is continuous with freezer. 219 if atomic.LoadUint64(&f.frozen) != number { 220 return errOutOrderInsertion 221 } 222 // Rollback all inserted data if any insertion below failed to ensure 223 // the tables won't out of sync. 224 defer func() { 225 if err != nil { 226 rerr := f.repair() 227 if rerr != nil { 228 log.Crit("Failed to repair freezer", "err", rerr) 229 } 230 log.Info("Append ancient failed", "number", number, "err", err) 231 } 232 }() 233 // Inject all the components into the relevant data tables 234 if err := f.tables[freezerHashTable].Append(f.frozen, hash[:]); err != nil { 235 log.Error("Failed to append ancient hash", "number", f.frozen, "hash", hash, "err", err) 236 return err 237 } 238 if err := f.tables[freezerHeaderTable].Append(f.frozen, header); err != nil { 239 log.Error("Failed to append ancient header", "number", f.frozen, "hash", hash, "err", err) 240 return err 241 } 242 if err := f.tables[freezerBodiesTable].Append(f.frozen, body); err != nil { 243 log.Error("Failed to append ancient body", "number", f.frozen, "hash", hash, "err", err) 244 return err 245 } 246 if err := f.tables[freezerReceiptTable].Append(f.frozen, receipts); err != nil { 247 log.Error("Failed to append ancient receipts", "number", f.frozen, "hash", hash, "err", err) 248 return err 249 } 250 if err := f.tables[freezerDifficultyTable].Append(f.frozen, td); err != nil { 251 log.Error("Failed to append ancient difficulty", "number", f.frozen, "hash", hash, "err", err) 252 return err 253 } 254 atomic.AddUint64(&f.frozen, 1) // Only modify atomically 255 return nil 256 } 257 258 // TruncateAncients discards any recent data above the provided threshold number. 259 func (f *freezer) TruncateAncients(items uint64) error { 260 if f.readonly { 261 return errReadOnly 262 } 263 if atomic.LoadUint64(&f.frozen) <= items { 264 return nil 265 } 266 for _, table := range f.tables { 267 if err := table.truncate(items); err != nil { 268 return err 269 } 270 } 271 atomic.StoreUint64(&f.frozen, items) 272 return nil 273 } 274 275 // Sync flushes all data tables to disk. 276 func (f *freezer) Sync() error { 277 var errs []error 278 for _, table := range f.tables { 279 if err := table.Sync(); err != nil { 280 errs = append(errs, err) 281 } 282 } 283 if errs != nil { 284 return fmt.Errorf("%v", errs) 285 } 286 return nil 287 } 288 289 // freeze is a background thread that periodically checks the blockchain for any 290 // import progress and moves ancient data from the fast database into the freezer. 291 // 292 // This functionality is deliberately broken off from block importing to avoid 293 // incurring additional data shuffling delays on block propagation. 294 func (f *freezer) freeze(db ethdb.KeyValueStore) { 295 nfdb := &nofreezedb{KeyValueStore: db} 296 297 var ( 298 backoff bool 299 triggered chan struct{} // Used in tests 300 ) 301 for { 302 select { 303 case <-f.quit: 304 log.Info("Freezer shutting down") 305 return 306 default: 307 } 308 if backoff { 309 // If we were doing a manual trigger, notify it 310 if triggered != nil { 311 triggered <- struct{}{} 312 triggered = nil 313 } 314 select { 315 case <-time.NewTimer(freezerRecheckInterval).C: 316 backoff = false 317 case triggered = <-f.trigger: 318 backoff = false 319 case <-f.quit: 320 return 321 } 322 } 323 // Retrieve the freezing threshold. 324 hash := ReadHeadBlockHash(nfdb) 325 if hash == (common.Hash{}) { 326 log.Debug("Current full block hash unavailable") // new chain, empty database 327 backoff = true 328 continue 329 } 330 number := ReadHeaderNumber(nfdb, hash) 331 threshold := atomic.LoadUint64(&f.threshold) 332 333 switch { 334 case number == nil: 335 log.Error("Current full block number unavailable", "hash", hash) 336 backoff = true 337 continue 338 339 case *number < threshold: 340 log.Debug("Current full block not old enough", "number", *number, "hash", hash, "delay", threshold) 341 backoff = true 342 continue 343 344 case *number-threshold <= f.frozen: 345 log.Debug("Ancient blocks frozen already", "number", *number, "hash", hash, "frozen", f.frozen) 346 backoff = true 347 continue 348 } 349 head := ReadHeader(nfdb, hash, *number) 350 if head == nil { 351 log.Error("Current full block unavailable", "number", *number, "hash", hash) 352 backoff = true 353 continue 354 } 355 // Seems we have data ready to be frozen, process in usable batches 356 limit := *number - threshold 357 if limit-f.frozen > freezerBatchLimit { 358 limit = f.frozen + freezerBatchLimit 359 } 360 var ( 361 start = time.Now() 362 first = f.frozen 363 ancients = make([]common.Hash, 0, limit-f.frozen) 364 ) 365 for f.frozen <= limit { 366 // Retrieves all the components of the canonical block 367 hash := ReadCanonicalHash(nfdb, f.frozen) 368 if hash == (common.Hash{}) { 369 log.Error("Canonical hash missing, can't freeze", "number", f.frozen) 370 break 371 } 372 header := ReadHeaderRLP(nfdb, hash, f.frozen) 373 if len(header) == 0 { 374 log.Error("Block header missing, can't freeze", "number", f.frozen, "hash", hash) 375 break 376 } 377 body := ReadBodyRLP(nfdb, hash, f.frozen) 378 if len(body) == 0 { 379 log.Error("Block body missing, can't freeze", "number", f.frozen, "hash", hash) 380 break 381 } 382 receipts := ReadReceiptsRLP(nfdb, hash, f.frozen) 383 if len(receipts) == 0 { 384 log.Error("Block receipts missing, can't freeze", "number", f.frozen, "hash", hash) 385 break 386 } 387 td := ReadTdRLP(nfdb, hash, f.frozen) 388 if len(td) == 0 { 389 log.Error("Total difficulty missing, can't freeze", "number", f.frozen, "hash", hash) 390 break 391 } 392 log.Trace("Deep froze ancient block", "number", f.frozen, "hash", hash) 393 // Inject all the components into the relevant data tables 394 if err := f.AppendAncient(f.frozen, hash[:], header, body, receipts, td); err != nil { 395 break 396 } 397 ancients = append(ancients, hash) 398 } 399 // Batch of blocks have been frozen, flush them before wiping from leveldb 400 if err := f.Sync(); err != nil { 401 log.Crit("Failed to flush frozen tables", "err", err) 402 } 403 // Wipe out all data from the active database 404 batch := db.NewBatch() 405 for i := 0; i < len(ancients); i++ { 406 // Always keep the genesis block in active database 407 if first+uint64(i) != 0 { 408 DeleteBlockWithoutNumber(batch, ancients[i], first+uint64(i)) 409 DeleteCanonicalHash(batch, first+uint64(i)) 410 } 411 } 412 if err := batch.Write(); err != nil { 413 log.Crit("Failed to delete frozen canonical blocks", "err", err) 414 } 415 batch.Reset() 416 417 // Wipe out side chains also and track dangling side chains 418 var dangling []common.Hash 419 for number := first; number < f.frozen; number++ { 420 // Always keep the genesis block in active database 421 if number != 0 { 422 dangling = ReadAllHashes(db, number) 423 for _, hash := range dangling { 424 log.Trace("Deleting side chain", "number", number, "hash", hash) 425 DeleteBlock(batch, hash, number) 426 } 427 } 428 } 429 if err := batch.Write(); err != nil { 430 log.Crit("Failed to delete frozen side blocks", "err", err) 431 } 432 batch.Reset() 433 434 // Step into the future and delete and dangling side chains 435 if f.frozen > 0 { 436 tip := f.frozen 437 for len(dangling) > 0 { 438 drop := make(map[common.Hash]struct{}) 439 for _, hash := range dangling { 440 log.Debug("Dangling parent from freezer", "number", tip-1, "hash", hash) 441 drop[hash] = struct{}{} 442 } 443 children := ReadAllHashes(db, tip) 444 for i := 0; i < len(children); i++ { 445 // Dig up the child and ensure it's dangling 446 child := ReadHeader(nfdb, children[i], tip) 447 if child == nil { 448 log.Error("Missing dangling header", "number", tip, "hash", children[i]) 449 continue 450 } 451 if _, ok := drop[child.ParentHash]; !ok { 452 children = append(children[:i], children[i+1:]...) 453 i-- 454 continue 455 } 456 // Delete all block data associated with the child 457 log.Debug("Deleting dangling block", "number", tip, "hash", children[i], "parent", child.ParentHash) 458 DeleteBlock(batch, children[i], tip) 459 } 460 dangling = children 461 tip++ 462 } 463 if err := batch.Write(); err != nil { 464 log.Crit("Failed to delete dangling side blocks", "err", err) 465 } 466 } 467 // Log something friendly for the user 468 context := []interface{}{ 469 "blocks", f.frozen - first, "elapsed", common.PrettyDuration(time.Since(start)), "number", f.frozen - 1, 470 } 471 if n := len(ancients); n > 0 { 472 context = append(context, []interface{}{"hash", ancients[n-1]}...) 473 } 474 log.Info("Deep froze chain segment", context...) 475 476 // Avoid database thrashing with tiny writes 477 if f.frozen-first < freezerBatchLimit { 478 backoff = true 479 } 480 } 481 } 482 483 // repair truncates all data tables to the same length. 484 func (f *freezer) repair() error { 485 min := uint64(math.MaxUint64) 486 for _, table := range f.tables { 487 items := atomic.LoadUint64(&table.items) 488 if min > items { 489 min = items 490 } 491 } 492 for _, table := range f.tables { 493 if err := table.truncate(min); err != nil { 494 return err 495 } 496 } 497 atomic.StoreUint64(&f.frozen, min) 498 return nil 499 }