diff --git a/doc/release-notes.md b/doc/release-notes.md --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -32,6 +32,11 @@ - `fundrawtransaction` now returns RPC_WALLET_ERROR if bitcoind is unable to create the transaction. The error message provides further details. Previously returned RPC_INTERNAL_ERROR. + - The `gettxoutsetinfo` response now contains `disk_size` and `bogosize` instead of + `bytes_serialized`. The first is a more accurate estimate of actual disk usage, but + is not deterministic. The second is unrelated to disk usage, but is a + database-independent metric of UTXO set size: it counts every UTXO entry as 50 + the + length of its scriptPubKey. Reserve block space for high priority transactions (D485) --------------------------------------------------------- diff --git a/qa/rpc-tests/blockchain.py b/qa/rpc-tests/blockchain.py --- a/qa/rpc-tests/blockchain.py +++ b/qa/rpc-tests/blockchain.py @@ -50,8 +50,11 @@ assert_equal(res['transactions'], 200) assert_equal(res['height'], 200) assert_equal(res['txouts'], 200) - assert_equal(res['bytes_serialized'], 13924) + assert_equal(res['bogosize'], 17000), 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']), 64) @@ -65,6 +68,7 @@ assert_equal(res2['total_amount'], Decimal('0')) assert_equal(res2['height'], 0) assert_equal(res2['txouts'], 0) + assert_equal(res2['bogosize'], 0), assert_equal(res2['bestblock'], node.getblockhash(0)) assert_equal(len(res2['hash_serialized']), 64) @@ -77,6 +81,7 @@ assert_equal(res['transactions'], res3['transactions']) assert_equal(res['height'], res3['height']) assert_equal(res['txouts'], res3['txouts']) + assert_equal(res['bogosize'], res3['bogosize']) assert_equal(res['bestblock'], res3['bestblock']) assert_equal(res['hash_serialized'], res3['hash_serialized']) @@ -106,5 +111,6 @@ assert isinstance(int(header['versionHex'], 16), int) assert isinstance(header['difficulty'], Decimal) + if __name__ == '__main__': BlockchainTest().main() diff --git a/src/coins.h b/src/coins.h --- a/src/coins.h +++ b/src/coins.h @@ -417,6 +417,9 @@ //! 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; } }; /** CCoinsView backed by another CCoinsView */ @@ -433,6 +436,7 @@ void SetBackend(CCoinsView &viewIn); bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock); CCoinsViewCursor *Cursor() const; + size_t EstimateSize() const override; }; /** diff --git a/src/coins.cpp b/src/coins.cpp --- a/src/coins.cpp +++ b/src/coins.cpp @@ -77,6 +77,9 @@ CCoinsViewCursor *CCoinsViewBacked::Cursor() const { return base->Cursor(); } +size_t CCoinsViewBacked::EstimateSize() const { + return base->EstimateSize(); +} SaltedTxidHasher::SaltedTxidHasher() : k0(GetRand(std::numeric_limits::max())), diff --git a/src/dbwrapper.h b/src/dbwrapper.h --- a/src/dbwrapper.h +++ b/src/dbwrapper.h @@ -273,6 +273,22 @@ * 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 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -857,13 +857,14 @@ uint256 hashBlock; uint64_t nTransactions; uint64_t nTransactionOutputs; - uint64_t nSerializedSize; + uint64_t nBogoSize; uint256 hashSerialized; + uint64_t nDiskSize; CAmount nTotalAmount; CCoinsStats() - : nHeight(0), nTransactions(0), nTransactionOutputs(0), - nSerializedSize(0), nTotalAmount(0) {} + : nHeight(0), nTransactions(0), nTransactionOutputs(0), nBogoSize(0), + nDiskSize(0), nTotalAmount(0) {} }; //! Calculate statistics about the unspent transaction output set @@ -885,6 +886,7 @@ if (pcursor->GetKey(key) && pcursor->GetValue(coins)) { stats.nTransactions++; ss << key; + ss << VARINT(coins.nHeight * 2 + coins.fCoinBase); for (size_t i = 0; i < coins.vout.size(); i++) { const CTxOut &out = coins.vout[i]; if (!out.IsNull()) { @@ -892,9 +894,13 @@ ss << VARINT(i + 1); ss << out; nTotalAmount += out.nValue; + stats.nBogoSize += + 32 /* txid */ + 4 /* vout index */ + + 4 /* height + coinbase */ + 8 /* amount */ + + 2 /* scriptPubKey len */ + + out.scriptPubKey.size() /* scriptPubKey */; } } - stats.nSerializedSize += 32 + pcursor->GetValueSize(); ss << VARINT(0); } else { return error("%s: unable to read value", __func__); @@ -903,6 +909,7 @@ } stats.hashSerialized = ss.GetHash(); stats.nTotalAmount = nTotalAmount; + stats.nDiskSize = view->EstimateSize(); return true; } @@ -982,8 +989,11 @@ " \"transactions\": n, (numeric) The number of transactions\n" " \"txouts\": n, (numeric) The number of output " "transactions\n" - " \"bytes_serialized\": n, (numeric) The serialized size\n" + " \"bogosize\": n, (numeric) A database-independent " + "metric for UTXO set size\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" + @@ -1000,8 +1010,9 @@ ret.push_back(Pair("bestblock", stats.hashBlock.GetHex())); ret.push_back(Pair("transactions", int64_t(stats.nTransactions))); ret.push_back(Pair("txouts", int64_t(stats.nTransactionOutputs))); - ret.push_back(Pair("bytes_serialized", int64_t(stats.nSerializedSize))); + ret.push_back(Pair("bogosize", int64_t(stats.nBogoSize))); ret.push_back(Pair("hash_serialized", stats.hashSerialized.GetHex())); + ret.push_back(Pair("disk_size", stats.nDiskSize)); ret.push_back( Pair("total_amount", ValueFromAmount(stats.nTotalAmount))); } else { diff --git a/src/txdb.h b/src/txdb.h --- a/src/txdb.h +++ b/src/txdb.h @@ -76,6 +76,8 @@ 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/src/txdb.cpp b/src/txdb.cpp --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -67,6 +67,10 @@ 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) {}