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