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