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