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