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