github.com/aigarnetwork/aigar@v0.0.0-20191115204914-d59a6eb70f8e/core/rawdb/freezer.go (about) 1 // Copyright 2018 The go-ethereum Authors 2 // Copyright 2019 The go-aigar Authors 3 // This file is part of the go-aigar library. 4 // 5 // The go-aigar library is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Lesser General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // The go-aigar library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public License 16 // along with the go-aigar library. If not, see <http://www.gnu.org/licenses/>. 17 18 package rawdb 19 20 import ( 21 "errors" 22 "fmt" 23 "math" 24 "os" 25 "path/filepath" 26 "sync/atomic" 27 "time" 28 29 "github.com/AigarNetwork/aigar/common" 30 "github.com/AigarNetwork/aigar/ethdb" 31 "github.com/AigarNetwork/aigar/log" 32 "github.com/AigarNetwork/aigar/metrics" 33 "github.com/AigarNetwork/aigar/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 75 tables map[string]*freezerTable // Data tables for storing everything 76 instanceLock fileutil.Releaser // File-system lock to prevent double opens 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 } 106 for name, disableSnappy := range freezerNoSnappy { 107 table, err := newTable(datadir, name, readMeter, writeMeter, sizeGauge, disableSnappy) 108 if err != nil { 109 for _, table := range freezer.tables { 110 table.Close() 111 } 112 lock.Release() 113 return nil, err 114 } 115 freezer.tables[name] = table 116 } 117 if err := freezer.repair(); err != nil { 118 for _, table := range freezer.tables { 119 table.Close() 120 } 121 lock.Release() 122 return nil, err 123 } 124 log.Info("Opened ancient database", "database", datadir) 125 return freezer, nil 126 } 127 128 // Close terminates the chain freezer, unmapping all the data files. 129 func (f *freezer) Close() error { 130 var errs []error 131 for _, table := range f.tables { 132 if err := table.Close(); err != nil { 133 errs = append(errs, err) 134 } 135 } 136 if err := f.instanceLock.Release(); err != nil { 137 errs = append(errs, err) 138 } 139 if errs != nil { 140 return fmt.Errorf("%v", errs) 141 } 142 return nil 143 } 144 145 // HasAncient returns an indicator whether the specified ancient data exists 146 // in the freezer. 147 func (f *freezer) HasAncient(kind string, number uint64) (bool, error) { 148 if table := f.tables[kind]; table != nil { 149 return table.has(number), nil 150 } 151 return false, nil 152 } 153 154 // Ancient retrieves an ancient binary blob from the append-only immutable files. 155 func (f *freezer) Ancient(kind string, number uint64) ([]byte, error) { 156 if table := f.tables[kind]; table != nil { 157 return table.Retrieve(number) 158 } 159 return nil, errUnknownTable 160 } 161 162 // Ancients returns the length of the frozen items. 163 func (f *freezer) Ancients() (uint64, error) { 164 return atomic.LoadUint64(&f.frozen), nil 165 } 166 167 // AncientSize returns the ancient size of the specified category. 168 func (f *freezer) AncientSize(kind string) (uint64, error) { 169 if table := f.tables[kind]; table != nil { 170 return table.size() 171 } 172 return 0, errUnknownTable 173 } 174 175 // AppendAncient injects all binary blobs belong to block at the end of the 176 // append-only immutable table files. 177 // 178 // Notably, this function is lock free but kind of thread-safe. All out-of-order 179 // injection will be rejected. But if two injections with same number happen at 180 // the same time, we can get into the trouble. 181 func (f *freezer) AppendAncient(number uint64, hash, header, body, receipts, td []byte) (err error) { 182 // Ensure the binary blobs we are appending is continuous with freezer. 183 if atomic.LoadUint64(&f.frozen) != number { 184 return errOutOrderInsertion 185 } 186 // Rollback all inserted data if any insertion below failed to ensure 187 // the tables won't out of sync. 188 defer func() { 189 if err != nil { 190 rerr := f.repair() 191 if rerr != nil { 192 log.Crit("Failed to repair freezer", "err", rerr) 193 } 194 log.Info("Append ancient failed", "number", number, "err", err) 195 } 196 }() 197 // Inject all the components into the relevant data tables 198 if err := f.tables[freezerHashTable].Append(f.frozen, hash[:]); err != nil { 199 log.Error("Failed to append ancient hash", "number", f.frozen, "hash", hash, "err", err) 200 return err 201 } 202 if err := f.tables[freezerHeaderTable].Append(f.frozen, header); err != nil { 203 log.Error("Failed to append ancient header", "number", f.frozen, "hash", hash, "err", err) 204 return err 205 } 206 if err := f.tables[freezerBodiesTable].Append(f.frozen, body); err != nil { 207 log.Error("Failed to append ancient body", "number", f.frozen, "hash", hash, "err", err) 208 return err 209 } 210 if err := f.tables[freezerReceiptTable].Append(f.frozen, receipts); err != nil { 211 log.Error("Failed to append ancient receipts", "number", f.frozen, "hash", hash, "err", err) 212 return err 213 } 214 if err := f.tables[freezerDifficultyTable].Append(f.frozen, td); err != nil { 215 log.Error("Failed to append ancient difficulty", "number", f.frozen, "hash", hash, "err", err) 216 return err 217 } 218 atomic.AddUint64(&f.frozen, 1) // Only modify atomically 219 return nil 220 } 221 222 // Truncate discards any recent data above the provided threshold number. 223 func (f *freezer) TruncateAncients(items uint64) error { 224 if atomic.LoadUint64(&f.frozen) <= items { 225 return nil 226 } 227 for _, table := range f.tables { 228 if err := table.truncate(items); err != nil { 229 return err 230 } 231 } 232 atomic.StoreUint64(&f.frozen, items) 233 return nil 234 } 235 236 // sync flushes all data tables to disk. 237 func (f *freezer) Sync() error { 238 var errs []error 239 for _, table := range f.tables { 240 if err := table.Sync(); err != nil { 241 errs = append(errs, err) 242 } 243 } 244 if errs != nil { 245 return fmt.Errorf("%v", errs) 246 } 247 return nil 248 } 249 250 // freeze is a background thread that periodically checks the blockchain for any 251 // import progress and moves ancient data from the fast database into the freezer. 252 // 253 // This functionality is deliberately broken off from block importing to avoid 254 // incurring additional data shuffling delays on block propagation. 255 func (f *freezer) freeze(db ethdb.KeyValueStore) { 256 nfdb := &nofreezedb{KeyValueStore: db} 257 258 for { 259 // Retrieve the freezing threshold. 260 hash := ReadHeadBlockHash(nfdb) 261 if hash == (common.Hash{}) { 262 log.Debug("Current full block hash unavailable") // new chain, empty database 263 time.Sleep(freezerRecheckInterval) 264 continue 265 } 266 number := ReadHeaderNumber(nfdb, hash) 267 switch { 268 case number == nil: 269 log.Error("Current full block number unavailable", "hash", hash) 270 time.Sleep(freezerRecheckInterval) 271 continue 272 273 case *number < params.ImmutabilityThreshold: 274 log.Debug("Current full block not old enough", "number", *number, "hash", hash, "delay", params.ImmutabilityThreshold) 275 time.Sleep(freezerRecheckInterval) 276 continue 277 278 case *number-params.ImmutabilityThreshold <= f.frozen: 279 log.Debug("Ancient blocks frozen already", "number", *number, "hash", hash, "frozen", f.frozen) 280 time.Sleep(freezerRecheckInterval) 281 continue 282 } 283 head := ReadHeader(nfdb, hash, *number) 284 if head == nil { 285 log.Error("Current full block unavailable", "number", *number, "hash", hash) 286 time.Sleep(freezerRecheckInterval) 287 continue 288 } 289 // Seems we have data ready to be frozen, process in usable batches 290 limit := *number - params.ImmutabilityThreshold 291 if limit-f.frozen > freezerBatchLimit { 292 limit = f.frozen + freezerBatchLimit 293 } 294 var ( 295 start = time.Now() 296 first = f.frozen 297 ancients = make([]common.Hash, 0, limit) 298 ) 299 for f.frozen < limit { 300 // Retrieves all the components of the canonical block 301 hash := ReadCanonicalHash(nfdb, f.frozen) 302 if hash == (common.Hash{}) { 303 log.Error("Canonical hash missing, can't freeze", "number", f.frozen) 304 break 305 } 306 header := ReadHeaderRLP(nfdb, hash, f.frozen) 307 if len(header) == 0 { 308 log.Error("Block header missing, can't freeze", "number", f.frozen, "hash", hash) 309 break 310 } 311 body := ReadBodyRLP(nfdb, hash, f.frozen) 312 if len(body) == 0 { 313 log.Error("Block body missing, can't freeze", "number", f.frozen, "hash", hash) 314 break 315 } 316 receipts := ReadReceiptsRLP(nfdb, hash, f.frozen) 317 if len(receipts) == 0 { 318 log.Error("Block receipts missing, can't freeze", "number", f.frozen, "hash", hash) 319 break 320 } 321 td := ReadTdRLP(nfdb, hash, f.frozen) 322 if len(td) == 0 { 323 log.Error("Total difficulty missing, can't freeze", "number", f.frozen, "hash", hash) 324 break 325 } 326 log.Trace("Deep froze ancient block", "number", f.frozen, "hash", hash) 327 // Inject all the components into the relevant data tables 328 if err := f.AppendAncient(f.frozen, hash[:], header, body, receipts, td); err != nil { 329 break 330 } 331 ancients = append(ancients, hash) 332 } 333 // Batch of blocks have been frozen, flush them before wiping from leveldb 334 if err := f.Sync(); err != nil { 335 log.Crit("Failed to flush frozen tables", "err", err) 336 } 337 // Wipe out all data from the active database 338 batch := db.NewBatch() 339 for i := 0; i < len(ancients); i++ { 340 // Always keep the genesis block in active database 341 if first+uint64(i) != 0 { 342 DeleteBlockWithoutNumber(batch, ancients[i], first+uint64(i)) 343 DeleteCanonicalHash(batch, first+uint64(i)) 344 } 345 } 346 if err := batch.Write(); err != nil { 347 log.Crit("Failed to delete frozen canonical blocks", "err", err) 348 } 349 batch.Reset() 350 // Wipe out side chain also. 351 for number := first; number < f.frozen; number++ { 352 // Always keep the genesis block in active database 353 if number != 0 { 354 for _, hash := range ReadAllHashes(db, number) { 355 DeleteBlock(batch, hash, number) 356 } 357 } 358 } 359 if err := batch.Write(); err != nil { 360 log.Crit("Failed to delete frozen side blocks", "err", err) 361 } 362 // Log something friendly for the user 363 context := []interface{}{ 364 "blocks", f.frozen - first, "elapsed", common.PrettyDuration(time.Since(start)), "number", f.frozen - 1, 365 } 366 if n := len(ancients); n > 0 { 367 context = append(context, []interface{}{"hash", ancients[n-1]}...) 368 } 369 log.Info("Deep froze chain segment", context...) 370 371 // Avoid database thrashing with tiny writes 372 if f.frozen-first < freezerBatchLimit { 373 time.Sleep(freezerRecheckInterval) 374 } 375 } 376 } 377 378 // repair truncates all data tables to the same length. 379 func (f *freezer) repair() error { 380 min := uint64(math.MaxUint64) 381 for _, table := range f.tables { 382 items := atomic.LoadUint64(&table.items) 383 if min > items { 384 min = items 385 } 386 } 387 for _, table := range f.tables { 388 if err := table.truncate(min); err != nil { 389 return err 390 } 391 } 392 atomic.StoreUint64(&f.frozen, min) 393 return nil 394 }