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