github.com/aidoskuneen/adk-node@v0.0.0-20220315131952-2e32567cb7f4/core/rawdb/database.go (about) 1 // Copyright 2021 The adkgo Authors 2 // This file is part of the adkgo library (adapted for adkgo from go--ethereum v1.10.8). 3 // 4 // the adkgo 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 adkgo 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 adkgo 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/aidoskuneen/adk-node/common" 28 "github.com/aidoskuneen/adk-node/ethdb" 29 "github.com/aidoskuneen/adk-node/ethdb/leveldb" 30 "github.com/aidoskuneen/adk-node/ethdb/memorydb" 31 "github.com/aidoskuneen/adk-node/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 312 // Ancient store statistics 313 ancientHeadersSize common.StorageSize 314 ancientBodiesSize common.StorageSize 315 ancientReceiptsSize common.StorageSize 316 ancientTdsSize common.StorageSize 317 ancientHashesSize common.StorageSize 318 319 // Les statistic 320 chtTrieNodes stat 321 bloomTrieNodes stat 322 323 // Meta- and unaccounted data 324 metadata stat 325 unaccounted stat 326 327 // Totals 328 total common.StorageSize 329 ) 330 // Inspect key-value database first. 331 for it.Next() { 332 var ( 333 key = it.Key() 334 size = common.StorageSize(len(key) + len(it.Value())) 335 ) 336 total += size 337 switch { 338 case bytes.HasPrefix(key, headerPrefix) && len(key) == (len(headerPrefix)+8+common.HashLength): 339 headers.Add(size) 340 case bytes.HasPrefix(key, blockBodyPrefix) && len(key) == (len(blockBodyPrefix)+8+common.HashLength): 341 bodies.Add(size) 342 case bytes.HasPrefix(key, blockReceiptsPrefix) && len(key) == (len(blockReceiptsPrefix)+8+common.HashLength): 343 receipts.Add(size) 344 case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerTDSuffix): 345 tds.Add(size) 346 case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerHashSuffix): 347 numHashPairings.Add(size) 348 case bytes.HasPrefix(key, headerNumberPrefix) && len(key) == (len(headerNumberPrefix)+common.HashLength): 349 hashNumPairings.Add(size) 350 case len(key) == common.HashLength: 351 tries.Add(size) 352 case bytes.HasPrefix(key, CodePrefix) && len(key) == len(CodePrefix)+common.HashLength: 353 codes.Add(size) 354 case bytes.HasPrefix(key, txLookupPrefix) && len(key) == (len(txLookupPrefix)+common.HashLength): 355 txLookups.Add(size) 356 case bytes.HasPrefix(key, SnapshotAccountPrefix) && len(key) == (len(SnapshotAccountPrefix)+common.HashLength): 357 accountSnaps.Add(size) 358 case bytes.HasPrefix(key, SnapshotStoragePrefix) && len(key) == (len(SnapshotStoragePrefix)+2*common.HashLength): 359 storageSnaps.Add(size) 360 case bytes.HasPrefix(key, preimagePrefix) && len(key) == (len(preimagePrefix)+common.HashLength): 361 preimages.Add(size) 362 case bytes.HasPrefix(key, configPrefix) && len(key) == (len(configPrefix)+common.HashLength): 363 metadata.Add(size) 364 case bytes.HasPrefix(key, bloomBitsPrefix) && len(key) == (len(bloomBitsPrefix)+10+common.HashLength): 365 bloomBits.Add(size) 366 case bytes.HasPrefix(key, BloomBitsIndexPrefix): 367 bloomBits.Add(size) 368 case bytes.HasPrefix(key, []byte("clique-")) && len(key) == 7+common.HashLength: 369 cliqueSnaps.Add(size) 370 case bytes.HasPrefix(key, []byte("cht-")) || 371 bytes.HasPrefix(key, []byte("chtIndexV2-")) || 372 bytes.HasPrefix(key, []byte("chtRootV2-")): // Canonical hash trie 373 chtTrieNodes.Add(size) 374 case bytes.HasPrefix(key, []byte("blt-")) || 375 bytes.HasPrefix(key, []byte("bltIndex-")) || 376 bytes.HasPrefix(key, []byte("bltRoot-")): // Bloomtrie sub 377 bloomTrieNodes.Add(size) 378 default: 379 var accounted bool 380 for _, meta := range [][]byte{ 381 databaseVersionKey, headHeaderKey, headBlockKey, headFastBlockKey, lastPivotKey, 382 fastTrieProgressKey, snapshotDisabledKey, snapshotRootKey, snapshotJournalKey, 383 snapshotGeneratorKey, snapshotRecoveryKey, txIndexTailKey, fastTxLookupLimitKey, 384 uncleanShutdownKey, badBlockKey, 385 } { 386 if bytes.Equal(key, meta) { 387 metadata.Add(size) 388 accounted = true 389 break 390 } 391 } 392 if !accounted { 393 unaccounted.Add(size) 394 } 395 } 396 count++ 397 if count%1000 == 0 && time.Since(logged) > 8*time.Second { 398 log.Info("Inspecting database", "count", count, "elapsed", common.PrettyDuration(time.Since(start))) 399 logged = time.Now() 400 } 401 } 402 // Inspect append-only file store then. 403 ancientSizes := []*common.StorageSize{&ancientHeadersSize, &ancientBodiesSize, &ancientReceiptsSize, &ancientHashesSize, &ancientTdsSize} 404 for i, category := range []string{freezerHeaderTable, freezerBodiesTable, freezerReceiptTable, freezerHashTable, freezerDifficultyTable} { 405 if size, err := db.AncientSize(category); err == nil { 406 *ancientSizes[i] += common.StorageSize(size) 407 total += common.StorageSize(size) 408 } 409 } 410 // Get number of ancient rows inside the freezer 411 ancients := counter(0) 412 if count, err := db.Ancients(); err == nil { 413 ancients = counter(count) 414 } 415 // Display the database statistic. 416 stats := [][]string{ 417 {"Key-Value store", "Headers", headers.Size(), headers.Count()}, 418 {"Key-Value store", "Bodies", bodies.Size(), bodies.Count()}, 419 {"Key-Value store", "Receipt lists", receipts.Size(), receipts.Count()}, 420 {"Key-Value store", "Difficulties", tds.Size(), tds.Count()}, 421 {"Key-Value store", "Block number->hash", numHashPairings.Size(), numHashPairings.Count()}, 422 {"Key-Value store", "Block hash->number", hashNumPairings.Size(), hashNumPairings.Count()}, 423 {"Key-Value store", "Transaction index", txLookups.Size(), txLookups.Count()}, 424 {"Key-Value store", "Bloombit index", bloomBits.Size(), bloomBits.Count()}, 425 {"Key-Value store", "Contract codes", codes.Size(), codes.Count()}, 426 {"Key-Value store", "Trie nodes", tries.Size(), tries.Count()}, 427 {"Key-Value store", "Trie preimages", preimages.Size(), preimages.Count()}, 428 {"Key-Value store", "Account snapshot", accountSnaps.Size(), accountSnaps.Count()}, 429 {"Key-Value store", "Storage snapshot", storageSnaps.Size(), storageSnaps.Count()}, 430 {"Key-Value store", "Clique snapshots", cliqueSnaps.Size(), cliqueSnaps.Count()}, 431 {"Key-Value store", "Singleton metadata", metadata.Size(), metadata.Count()}, 432 {"Ancient store", "Headers", ancientHeadersSize.String(), ancients.String()}, 433 {"Ancient store", "Bodies", ancientBodiesSize.String(), ancients.String()}, 434 {"Ancient store", "Receipt lists", ancientReceiptsSize.String(), ancients.String()}, 435 {"Ancient store", "Difficulties", ancientTdsSize.String(), ancients.String()}, 436 {"Ancient store", "Block number->hash", ancientHashesSize.String(), ancients.String()}, 437 {"Light client", "CHT trie nodes", chtTrieNodes.Size(), chtTrieNodes.Count()}, 438 {"Light client", "Bloom trie nodes", bloomTrieNodes.Size(), bloomTrieNodes.Count()}, 439 } 440 table := tablewriter.NewWriter(os.Stdout) 441 table.SetHeader([]string{"Database", "Category", "Size", "Items"}) 442 table.SetFooter([]string{"", "Total", total.String(), " "}) 443 table.AppendBulk(stats) 444 table.Render() 445 446 if unaccounted.size > 0 { 447 log.Error("Database contains unaccounted data", "size", unaccounted.size, "count", unaccounted.count) 448 } 449 450 return nil 451 }