diff --git a/src/coins.cpp b/src/coins.cpp index b2e33abf33..02f424fad6 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -55,6 +55,7 @@ uint256 CCoinsViewBacked::GetBestBlock() const { return base->GetBestBlock(); } void CCoinsViewBacked::SetBackend(CCoinsView &viewIn) { base = &viewIn; } bool CCoinsViewBacked::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return base->BatchWrite(mapCoins, hashBlock); } CCoinsViewCursor *CCoinsViewBacked::Cursor() const { return base->Cursor(); } +size_t CCoinsViewBacked::EstimateSize() const { return base->EstimateSize(); } SaltedTxidHasher::SaltedTxidHasher() : k0(GetRand(std::numeric_limits::max())), k1(GetRand(std::numeric_limits::max())) {} diff --git a/src/coins.h b/src/coins.h index 12cfd6bf6b..47ef90114b 100644 --- a/src/coins.h +++ b/src/coins.h @@ -319,6 +319,9 @@ public: //! As we use CCoinsViews polymorphically, have a virtual destructor virtual ~CCoinsView() {} + + //! Estimate database size (0 if not implemented) + virtual size_t EstimateSize() const { return 0; } }; @@ -336,6 +339,7 @@ public: void SetBackend(CCoinsView &viewIn); bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock); CCoinsViewCursor *Cursor() const; + size_t EstimateSize() const override; }; diff --git a/src/dbwrapper.h b/src/dbwrapper.h index 442f37b420..24ef71bfbf 100644 --- a/src/dbwrapper.h +++ b/src/dbwrapper.h @@ -305,7 +305,22 @@ public: * Return true if the database managed by this class contains no entries. */ bool IsEmpty(); + + template + size_t EstimateSize(const K& key_begin, const K& key_end) const + { + CDataStream ssKey1(SER_DISK, CLIENT_VERSION), ssKey2(SER_DISK, CLIENT_VERSION); + ssKey1.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); + ssKey2.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); + ssKey1 << key_begin; + ssKey2 << key_end; + leveldb::Slice slKey1(ssKey1.data(), ssKey1.size()); + leveldb::Slice slKey2(ssKey2.data(), ssKey2.size()); + uint64_t size = 0; + leveldb::Range range(slKey1, slKey2); + pdb->GetApproximateSizes(&range, 1, &size); + return size; + } }; #endif // BITCOIN_DBWRAPPER_H - diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index d2f955fb32..e59b9b86bb 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -782,6 +782,7 @@ struct CCoinsStats uint64_t nTransactions; uint64_t nTransactionOutputs; uint256 hashSerialized; + uint64_t nDiskSize; CAmount nTotalAmount; CCoinsStats() : nHeight(0), nTransactions(0), nTransactionOutputs(0), nTotalAmount(0) {} @@ -826,6 +827,7 @@ static bool GetUTXOStats(CCoinsView *view, CCoinsStats &stats) } stats.hashSerialized = ss.GetHash(); stats.nTotalAmount = nTotalAmount; + stats.nDiskSize = view->EstimateSize(); return true; } @@ -892,6 +894,7 @@ UniValue gettxoutsetinfo(const JSONRPCRequest& request) " \"transactions\": n, (numeric) The number of transactions\n" " \"txouts\": n, (numeric) The number of output transactions\n" " \"hash_serialized\": \"hash\", (string) The serialized hash\n" + " \"disk_size\": n, (numeric) The estimated size of the chainstate on disk\n" " \"total_amount\": x.xxx (numeric) The total amount\n" "}\n" "\nExamples:\n" @@ -909,6 +912,7 @@ UniValue gettxoutsetinfo(const JSONRPCRequest& request) ret.push_back(Pair("transactions", (int64_t)stats.nTransactions)); ret.push_back(Pair("txouts", (int64_t)stats.nTransactionOutputs)); ret.push_back(Pair("hash_serialized_2", stats.hashSerialized.GetHex())); + ret.push_back(Pair("disk_size", stats.nDiskSize)); ret.push_back(Pair("total_amount", ValueFromAmount(stats.nTotalAmount))); } else { throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set"); diff --git a/src/txdb.cpp b/src/txdb.cpp index 76aab23983..f139384a22 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -67,6 +67,11 @@ bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return db.WriteBatch(batch); } +size_t CCoinsViewDB::EstimateSize() const +{ + return db.EstimateSize(DB_COINS, (char)(DB_COINS+1)); +} + CBlockTreeDB::CBlockTreeDB(size_t nCacheSize, bool fMemory, bool fWipe) : CDBWrapper(GetDataDir() / "blocks" / "index", nCacheSize, fMemory, fWipe) { } diff --git a/src/txdb.h b/src/txdb.h index 117e7201fb..df164cb820 100644 --- a/src/txdb.h +++ b/src/txdb.h @@ -78,6 +78,8 @@ public: uint256 GetBestBlock() const; bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock); CCoinsViewCursor *Cursor() const; + + size_t EstimateSize() const override; }; /** Specialization of CCoinsViewCursor to iterate over a CCoinsViewDB */ diff --git a/test/functional/blockchain.py b/test/functional/blockchain.py index b0faea9b38..4bfd3ee677 100755 --- a/test/functional/blockchain.py +++ b/test/functional/blockchain.py @@ -50,6 +50,9 @@ class BlockchainTest(BitcoinTestFramework): assert_equal(res['height'], 200) assert_equal(res['txouts'], 200) assert_equal(res['bestblock'], node.getblockhash(200)) + size = res['disk_size'] + assert size > 6400 + assert size < 64000 assert_equal(len(res['bestblock']), 64) assert_equal(len(res['hash_serialized_2']), 64)