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