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