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