github.com/dominant-strategies/go-quai@v0.28.2/core/rawdb/database.go (about) 1 // Copyright 2018 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 "bytes" 21 "errors" 22 "fmt" 23 "os" 24 "path/filepath" 25 "sync/atomic" 26 "time" 27 28 "github.com/dominant-strategies/go-quai/common" 29 "github.com/dominant-strategies/go-quai/ethdb" 30 "github.com/dominant-strategies/go-quai/ethdb/leveldb" 31 "github.com/dominant-strategies/go-quai/ethdb/memorydb" 32 "github.com/dominant-strategies/go-quai/log" 33 "github.com/olekukonko/tablewriter" 34 ) 35 36 // freezerdb is a database wrapper that enabled freezer data retrievals. 37 type freezerdb struct { 38 ethdb.KeyValueStore 39 ethdb.AncientStore 40 } 41 42 // Close implements io.Closer, closing both the fast key-value store as well as 43 // the slow ancient tables. 44 func (frdb *freezerdb) Close() error { 45 var errs []error 46 if err := frdb.AncientStore.Close(); err != nil { 47 errs = append(errs, err) 48 } 49 if err := frdb.KeyValueStore.Close(); err != nil { 50 errs = append(errs, err) 51 } 52 if len(errs) != 0 { 53 return fmt.Errorf("%v", errs) 54 } 55 return nil 56 } 57 58 // Freeze is a helper method used for external testing to trigger and block until 59 // a freeze cycle completes, without having to sleep for a minute to trigger the 60 // automatic background run. 61 func (frdb *freezerdb) Freeze(threshold uint64) error { 62 if frdb.AncientStore.(*freezer).readonly { 63 return errReadOnly 64 } 65 // Set the freezer threshold to a temporary value 66 defer func(old uint64) { 67 atomic.StoreUint64(&frdb.AncientStore.(*freezer).threshold, old) 68 }(atomic.LoadUint64(&frdb.AncientStore.(*freezer).threshold)) 69 atomic.StoreUint64(&frdb.AncientStore.(*freezer).threshold, threshold) 70 71 // Trigger a freeze cycle and block until it's done 72 trigger := make(chan struct{}, 1) 73 frdb.AncientStore.(*freezer).trigger <- trigger 74 <-trigger 75 return nil 76 } 77 78 // nofreezedb is a database wrapper that disables freezer data retrievals. 79 type nofreezedb struct { 80 ethdb.KeyValueStore 81 } 82 83 // HasAncient returns an error as we don't have a backing chain freezer. 84 func (db *nofreezedb) HasAncient(kind string, number uint64) (bool, error) { 85 return false, errNotSupported 86 } 87 88 // Ancient returns an error as we don't have a backing chain freezer. 89 func (db *nofreezedb) Ancient(kind string, number uint64) ([]byte, error) { 90 return nil, errNotSupported 91 } 92 93 // Ancients returns an error as we don't have a backing chain freezer. 94 func (db *nofreezedb) Ancients() (uint64, error) { 95 return 0, errNotSupported 96 } 97 98 // AncientSize returns an error as we don't have a backing chain freezer. 99 func (db *nofreezedb) AncientSize(kind string) (uint64, error) { 100 return 0, errNotSupported 101 } 102 103 // AppendAncient returns an error as we don't have a backing chain freezer. 104 func (db *nofreezedb) AppendAncient(number uint64, hash, header, body, receipts, etxSet []byte) error { 105 return errNotSupported 106 } 107 108 // TruncateAncients returns an error as we don't have a backing chain freezer. 109 func (db *nofreezedb) TruncateAncients(items uint64) error { 110 return errNotSupported 111 } 112 113 // Sync returns an error as we don't have a backing chain freezer. 114 func (db *nofreezedb) Sync() error { 115 return errNotSupported 116 } 117 118 // NewDatabase creates a high level database on top of a given key-value data 119 // store without a freezer moving immutable chain segments into cold storage. 120 func NewDatabase(db ethdb.KeyValueStore) ethdb.Database { 121 return &nofreezedb{ 122 KeyValueStore: db, 123 } 124 } 125 126 // NewDatabaseWithFreezer creates a high level database on top of a given key- 127 // value data store with a freezer moving immutable chain segments into cold 128 // storage. 129 func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace string, readonly bool) (ethdb.Database, error) { 130 // Create the idle freezer instance 131 frdb, err := newFreezer(freezer, namespace, readonly) 132 if err != nil { 133 return nil, err 134 } 135 // Since the freezer can be stored separately from the user's key-value database, 136 // there's a fairly high probability that the user requests invalid combinations 137 // of the freezer and database. Ensure that we don't shoot ourselves in the foot 138 // by serving up conflicting data, leading to both datastores getting corrupted. 139 // 140 // - If both the freezer and key-value store is empty (no genesis), we just 141 // initialized a new empty freezer, so everything's fine. 142 // - If the key-value store is empty, but the freezer is not, we need to make 143 // sure the user's genesis matches the freezer. That will be checked in the 144 // blockchain, since we don't have the genesis block here (nor should we at 145 // this point care, the key-value/freezer combo is valid). 146 // - If neither the key-value store nor the freezer is empty, cross validate 147 // the genesis hashes to make sure they are compatible. If they are, also 148 // ensure that there's no gap between the freezer and sunsequently leveldb. 149 // - If the key-value store is not empty, but the freezer is we might just be 150 // upgrading to the freezer release, or we might have had a small chain and 151 // not frozen anything yet. Ensure that no blocks are missing yet from the 152 // key-value store, since that would mean we already had an old freezer. 153 154 // If the genesis hash is empty, we have a new key-value store, so nothing to 155 // validate in this method. If, however, the genesis hash is not nil, compare 156 // it to the freezer content. 157 if kvgenesis, _ := db.Get(headerHashKey(0)); len(kvgenesis) > 0 { 158 if frozen, _ := frdb.Ancients(); frozen > 0 { 159 // If the freezer already contains something, ensure that the genesis blocks 160 // match, otherwise we might mix up freezers across chains and destroy both 161 // the freezer and the key-value store. 162 frgenesis, err := frdb.Ancient(freezerHashTable, 0) 163 if err != nil { 164 return nil, fmt.Errorf("failed to retrieve genesis from ancient %v", err) 165 } else if !bytes.Equal(kvgenesis, frgenesis) { 166 return nil, fmt.Errorf("genesis mismatch: %#x (leveldb) != %#x (ancients)", kvgenesis, frgenesis) 167 } 168 // Key-value store and freezer belong to the same network. Ensure that they 169 // are contiguous, otherwise we might end up with a non-functional freezer. 170 if kvhash, _ := db.Get(headerHashKey(frozen)); len(kvhash) == 0 { 171 // Subsequent header after the freezer limit is missing from the database. 172 // Reject startup is the database has a more recent head. 173 if *ReadHeaderNumber(db, ReadHeadHeaderHash(db)) > frozen-1 { 174 return nil, fmt.Errorf("gap (#%d) in the chain between ancients and leveldb", frozen) 175 } 176 // Database contains only older data than the freezer, this happens if the 177 // state was wiped and reinited from an existing freezer. 178 } 179 // Otherwise, key-value store continues where the freezer left off, all is fine. 180 // We might have duplicate blocks (crash after freezer write but before key-value 181 // store deletion, but that's fine). 182 } else { 183 // If the freezer is empty, ensure nothing was moved yet from the key-value 184 // store, otherwise we'll end up missing data. We check block #1 to decide 185 // if we froze anything previously or not, but do take care of databases with 186 // only the genesis block. 187 if ReadHeadHeaderHash(db) != common.BytesToHash(kvgenesis) { 188 // Key-value store contains more data than the genesis block, make sure we 189 // didn't freeze anything yet. 190 if kvblob, _ := db.Get(headerHashKey(1)); len(kvblob) == 0 { 191 return nil, errors.New("ancient chain segments already extracted, please set --datadir.ancient to the correct path") 192 } 193 // Block #1 is still in the database, we're allowed to init a new feezer 194 } 195 // Otherwise, the head header is still the genesis, we're allowed to init a new 196 // feezer. 197 } 198 } 199 return &freezerdb{ 200 KeyValueStore: db, 201 AncientStore: frdb, 202 }, nil 203 } 204 205 // NewMemoryDatabase creates an ephemeral in-memory key-value database without a 206 // freezer moving immutable chain segments into cold storage. 207 func NewMemoryDatabase() ethdb.Database { 208 return NewDatabase(memorydb.New()) 209 } 210 211 // NewMemoryDatabaseWithCap creates an ephemeral in-memory key-value database 212 // with an initial starting capacity, but without a freezer moving immutable 213 // chain segments into cold storage. 214 func NewMemoryDatabaseWithCap(size int) ethdb.Database { 215 return NewDatabase(memorydb.NewWithCap(size)) 216 } 217 218 // NewLevelDBDatabase creates a persistent key-value database without a freezer 219 // moving immutable chain segments into cold storage. 220 func NewLevelDBDatabase(file string, cache int, handles int, namespace string, readonly bool) (ethdb.Database, error) { 221 db, err := leveldb.New(file, cache, handles, namespace, readonly) 222 if err != nil { 223 return nil, err 224 } 225 log.Info("Using LevelDB as the backing database") 226 return NewDatabase(db), nil 227 } 228 229 const ( 230 dbPebble = "pebble" 231 dbLeveldb = "leveldb" 232 ) 233 234 // hasPreexistingDb checks the given data directory whether a database is already 235 // instantiated at that location, and if so, returns the type of database (or the 236 // empty string). 237 func hasPreexistingDb(path string) string { 238 if _, err := os.Stat(filepath.Join(path, "CURRENT")); err != nil { 239 return "" // No pre-existing db 240 } 241 if matches, err := filepath.Glob(filepath.Join(path, "OPTIONS*")); len(matches) > 0 || err != nil { 242 if err != nil { 243 panic(err) // only possible if the pattern is malformed 244 } 245 return dbPebble 246 } 247 return dbLeveldb 248 } 249 250 // OpenOptions contains the options to apply when opening a database. 251 // OBS: If AncientsDirectory is empty, it indicates that no freezer is to be used. 252 type OpenOptions struct { 253 Type string // "leveldb" | "pebble" 254 Directory string // the datadir 255 AncientsDirectory string // the ancients-dir 256 Namespace string // the namespace for database relevant metrics 257 Cache int // the capacity(in megabytes) of the data caching 258 Handles int // number of files to be open simultaneously 259 ReadOnly bool 260 } 261 262 // openKeyValueDatabase opens a disk-based key-value database, e.g. leveldb or pebble. 263 // 264 // type == null type != null 265 // +---------------------------------------- 266 // db is non-existent | leveldb default | specified type 267 // db is existent | from db | specified type (if compatible) 268 func openKeyValueDatabase(o OpenOptions) (ethdb.Database, error) { 269 existingDb := hasPreexistingDb(o.Directory) 270 if len(existingDb) != 0 && len(o.Type) != 0 && o.Type != existingDb { 271 return nil, fmt.Errorf("db.engine choice was %v but found pre-existing %v database in specified data directory", o.Type, existingDb) 272 } 273 if o.Type == dbPebble || existingDb == dbPebble { 274 if PebbleEnabled { 275 log.Info("Using pebble as the backing database") 276 return NewPebbleDBDatabase(o.Directory, o.Cache, o.Handles, o.Namespace, o.ReadOnly) 277 } else { 278 return nil, errors.New("db.engine 'pebble' not supported on this platform") 279 } 280 } 281 if len(o.Type) != 0 && o.Type != dbLeveldb { 282 return nil, fmt.Errorf("unknown db.engine %v", o.Type) 283 } 284 log.Info("Using leveldb as the backing database") 285 // Use leveldb, either as default (no explicit choice), or pre-existing, or chosen explicitly 286 return NewLevelDBDatabase(o.Directory, o.Cache, o.Handles, o.Namespace, o.ReadOnly) 287 } 288 289 // Open opens both a disk-based key-value database such as leveldb or pebble, but also 290 // integrates it with a freezer database -- if the AncientDir option has been 291 // set on the provided OpenOptions. 292 // The passed o.AncientDir indicates the path of root ancient directory where 293 // the chain freezer can be opened. 294 func Open(o OpenOptions) (ethdb.Database, error) { 295 kvdb, err := openKeyValueDatabase(o) 296 if err != nil { 297 return nil, err 298 } 299 if len(o.AncientsDirectory) == 0 { 300 return kvdb, nil 301 } 302 frdb, err := NewDatabaseWithFreezer(kvdb, o.AncientsDirectory, o.Namespace, o.ReadOnly) 303 if err != nil { 304 kvdb.Close() 305 return nil, err 306 } 307 return frdb, nil 308 } 309 310 type counter uint64 311 312 func (c counter) String() string { 313 return fmt.Sprintf("%d", c) 314 } 315 316 func (c counter) Percentage(current uint64) string { 317 return fmt.Sprintf("%d", current*100/uint64(c)) 318 } 319 320 // stat stores sizes and count for a parameter 321 type stat struct { 322 size common.StorageSize 323 count counter 324 } 325 326 // Add size to the stat and increase the counter by 1 327 func (s *stat) Add(size common.StorageSize) { 328 s.size += size 329 s.count++ 330 } 331 332 func (s *stat) Size() string { 333 return s.size.String() 334 } 335 336 func (s *stat) Count() string { 337 return s.count.String() 338 } 339 340 // InspectDatabase traverses the entire database and checks the size 341 // of all different categories of data. 342 func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { 343 it := db.NewIterator(keyPrefix, keyStart) 344 defer it.Release() 345 346 var ( 347 count int64 348 start = time.Now() 349 logged = time.Now() 350 351 // Key-value store statistics 352 headers stat 353 bodies stat 354 receipts stat 355 tds stat 356 numHashPairings stat 357 hashNumPairings stat 358 tries stat 359 codes stat 360 txLookups stat 361 accountSnaps stat 362 storageSnaps stat 363 preimages stat 364 bloomBits stat 365 366 // Ancient store statistics 367 ancientHeadersSize common.StorageSize 368 ancientBodiesSize common.StorageSize 369 ancientReceiptsSize common.StorageSize 370 ancientTdsSize common.StorageSize 371 ancientHashesSize common.StorageSize 372 373 // Les statistic 374 chtTrieNodes stat 375 bloomTrieNodes stat 376 377 // Meta- and unaccounted data 378 metadata stat 379 unaccounted stat 380 381 // Totals 382 total common.StorageSize 383 ) 384 // Inspect key-value database first. 385 for it.Next() { 386 var ( 387 key = it.Key() 388 size = common.StorageSize(len(key) + len(it.Value())) 389 ) 390 total += size 391 switch { 392 case bytes.HasPrefix(key, headerPrefix) && len(key) == (len(headerPrefix)+8+common.HashLength): 393 headers.Add(size) 394 case bytes.HasPrefix(key, blockBodyPrefix) && len(key) == (len(blockBodyPrefix)+8+common.HashLength): 395 bodies.Add(size) 396 case bytes.HasPrefix(key, blockReceiptsPrefix) && len(key) == (len(blockReceiptsPrefix)+8+common.HashLength): 397 receipts.Add(size) 398 case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerTDSuffix): 399 tds.Add(size) 400 case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerHashSuffix): 401 numHashPairings.Add(size) 402 case bytes.HasPrefix(key, headerNumberPrefix) && len(key) == (len(headerNumberPrefix)+common.HashLength): 403 hashNumPairings.Add(size) 404 case len(key) == common.HashLength: 405 tries.Add(size) 406 case bytes.HasPrefix(key, CodePrefix) && len(key) == len(CodePrefix)+common.HashLength: 407 codes.Add(size) 408 case bytes.HasPrefix(key, txLookupPrefix) && len(key) == (len(txLookupPrefix)+common.HashLength): 409 txLookups.Add(size) 410 case bytes.HasPrefix(key, SnapshotAccountPrefix) && len(key) == (len(SnapshotAccountPrefix)+common.HashLength): 411 accountSnaps.Add(size) 412 case bytes.HasPrefix(key, SnapshotStoragePrefix) && len(key) == (len(SnapshotStoragePrefix)+2*common.HashLength): 413 storageSnaps.Add(size) 414 case bytes.HasPrefix(key, preimagePrefix) && len(key) == (len(preimagePrefix)+common.HashLength): 415 preimages.Add(size) 416 case bytes.HasPrefix(key, configPrefix) && len(key) == (len(configPrefix)+common.HashLength): 417 metadata.Add(size) 418 case bytes.HasPrefix(key, bloomBitsPrefix) && len(key) == (len(bloomBitsPrefix)+10+common.HashLength): 419 bloomBits.Add(size) 420 case bytes.HasPrefix(key, BloomBitsIndexPrefix): 421 bloomBits.Add(size) 422 case bytes.HasPrefix(key, []byte("cht-")) || 423 bytes.HasPrefix(key, []byte("chtIndexV2-")) || 424 bytes.HasPrefix(key, []byte("chtRootV2-")): // Canonical hash trie 425 chtTrieNodes.Add(size) 426 case bytes.HasPrefix(key, []byte("blt-")) || 427 bytes.HasPrefix(key, []byte("bltIndex-")) || 428 bytes.HasPrefix(key, []byte("bltRoot-")): // Bloomtrie sub 429 bloomTrieNodes.Add(size) 430 default: 431 var accounted bool 432 for _, meta := range [][]byte{ 433 databaseVersionKey, headHeaderKey, headBlockKey, lastPivotKey, 434 fastTrieProgressKey, snapshotDisabledKey, snapshotRootKey, snapshotJournalKey, 435 snapshotGeneratorKey, snapshotRecoveryKey, txIndexTailKey, fastTxLookupLimitKey, 436 uncleanShutdownKey, badBlockKey, 437 } { 438 if bytes.Equal(key, meta) { 439 metadata.Add(size) 440 accounted = true 441 break 442 } 443 } 444 if !accounted { 445 unaccounted.Add(size) 446 } 447 } 448 count++ 449 if count%1000 == 0 && time.Since(logged) > 8*time.Second { 450 log.Info("Inspecting database", "count", count, "elapsed", common.PrettyDuration(time.Since(start))) 451 logged = time.Now() 452 } 453 } 454 // Inspect append-only file store then. 455 ancientSizes := []*common.StorageSize{&ancientHeadersSize, &ancientBodiesSize, &ancientReceiptsSize, &ancientHashesSize, &ancientTdsSize} 456 for i, category := range []string{freezerHeaderTable, freezerBodiesTable, freezerReceiptTable, freezerHashTable, freezerDifficultyTable, freezerEtxSetsTable} { 457 if size, err := db.AncientSize(category); err == nil { 458 *ancientSizes[i] += common.StorageSize(size) 459 total += common.StorageSize(size) 460 } 461 } 462 // Get number of ancient rows inside the freezer 463 ancients := counter(0) 464 if count, err := db.Ancients(); err == nil { 465 ancients = counter(count) 466 } 467 // Display the database statistic. 468 stats := [][]string{ 469 {"Key-Value store", "Headers", headers.Size(), headers.Count()}, 470 {"Key-Value store", "Bodies", bodies.Size(), bodies.Count()}, 471 {"Key-Value store", "Receipt lists", receipts.Size(), receipts.Count()}, 472 {"Key-Value store", "Difficulties", tds.Size(), tds.Count()}, 473 {"Key-Value store", "Block number->hash", numHashPairings.Size(), numHashPairings.Count()}, 474 {"Key-Value store", "Block hash->number", hashNumPairings.Size(), hashNumPairings.Count()}, 475 {"Key-Value store", "Transaction index", txLookups.Size(), txLookups.Count()}, 476 {"Key-Value store", "Bloombit index", bloomBits.Size(), bloomBits.Count()}, 477 {"Key-Value store", "Contract codes", codes.Size(), codes.Count()}, 478 {"Key-Value store", "Trie nodes", tries.Size(), tries.Count()}, 479 {"Key-Value store", "Trie preimages", preimages.Size(), preimages.Count()}, 480 {"Key-Value store", "Account snapshot", accountSnaps.Size(), accountSnaps.Count()}, 481 {"Key-Value store", "Storage snapshot", storageSnaps.Size(), storageSnaps.Count()}, 482 {"Key-Value store", "Singleton metadata", metadata.Size(), metadata.Count()}, 483 {"Ancient store", "Headers", ancientHeadersSize.String(), ancients.String()}, 484 {"Ancient store", "Bodies", ancientBodiesSize.String(), ancients.String()}, 485 {"Ancient store", "Receipt lists", ancientReceiptsSize.String(), ancients.String()}, 486 {"Ancient store", "Difficulties", ancientTdsSize.String(), ancients.String()}, 487 {"Ancient store", "Block number->hash", ancientHashesSize.String(), ancients.String()}, 488 {"Light client", "CHT trie nodes", chtTrieNodes.Size(), chtTrieNodes.Count()}, 489 {"Light client", "Bloom trie nodes", bloomTrieNodes.Size(), bloomTrieNodes.Count()}, 490 } 491 table := tablewriter.NewWriter(os.Stdout) 492 table.SetHeader([]string{"Database", "Category", "Size", "Items"}) 493 table.SetFooter([]string{"", "Total", total.String(), " "}) 494 table.AppendBulk(stats) 495 table.Render() 496 497 if unaccounted.size > 0 { 498 log.Error("Database contains unaccounted data", "size", unaccounted.size, "count", unaccounted.count) 499 } 500 501 return nil 502 }