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