diff options
author | Carl Dong <contact@carldong.me> | 2022-02-16 15:31:02 -0500 |
---|---|---|
committer | Carl Dong <contact@carldong.me> | 2022-05-23 14:53:31 -0400 |
commit | 35f73ce4b2efd7341fe55f77b334f27ad8aad090 (patch) | |
tree | 6c43c08e4c3da86c3dee3c1152bafb1435300a8c /src | |
parent | b7634fe02b6b030f5d62502c73db84ba9a276640 (diff) | |
download | bitcoin-35f73ce4b2efd7341fe55f77b334f27ad8aad090.tar.xz |
coinstats: Move hasher codepath to kernel/coinstats
As mentioned in a previous commit, the hashing codepath can now be moved
to a separate file. This decouples callers that only rely on the hashing
codepath from the indexing one.
This is key for libbitcoinkernel, which needs to have the CoinsStats
hashing codepath for AssumeUTXO, but does not wish to be coupled with
indexes.
Note that only the .cpp file is split in this commit, the header files
will be split in a subsequent commit and the #includes to
node/coinstats.h will be adjusted to only #include the necessary
headers.
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 2 | ||||
-rw-r--r-- | src/kernel/coinstats.cpp | 187 | ||||
-rw-r--r-- | src/node/coinstats.cpp | 176 | ||||
-rw-r--r-- | src/node/coinstats.h | 2 |
4 files changed, 191 insertions, 176 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 0fd15baed1..005a91912c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -357,6 +357,7 @@ libbitcoin_node_a_SOURCES = \ index/coinstatsindex.cpp \ index/txindex.cpp \ init.cpp \ + kernel/coinstats.cpp \ mapport.cpp \ net.cpp \ netgroup.cpp \ @@ -873,6 +874,7 @@ libbitcoinkernel_la_SOURCES = \ index/base.cpp \ index/coinstatsindex.cpp \ init/common.cpp \ + kernel/coinstats.cpp \ key.cpp \ logging.cpp \ node/blockstorage.cpp \ diff --git a/src/kernel/coinstats.cpp b/src/kernel/coinstats.cpp new file mode 100644 index 0000000000..15d5c3fbe6 --- /dev/null +++ b/src/kernel/coinstats.cpp @@ -0,0 +1,187 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <node/coinstats.h> + +#include <coins.h> +#include <crypto/muhash.h> +#include <hash.h> +#include <serialize.h> +#include <uint256.h> +#include <util/overflow.h> +#include <util/system.h> +#include <validation.h> + +#include <map> + +namespace node { + +CCoinsStats::CCoinsStats(int block_height, const uint256& block_hash) + : nHeight(block_height), + hashBlock(block_hash) {} + +// Database-independent metric indicating the UTXO set size +uint64_t GetBogoSize(const CScript& script_pub_key) +{ + return 32 /* txid */ + + 4 /* vout index */ + + 4 /* height + coinbase */ + + 8 /* amount */ + + 2 /* scriptPubKey len */ + + script_pub_key.size() /* scriptPubKey */; +} + +CDataStream TxOutSer(const COutPoint& outpoint, const Coin& coin) { + CDataStream ss(SER_DISK, PROTOCOL_VERSION); + ss << outpoint; + ss << static_cast<uint32_t>(coin.nHeight * 2 + coin.fCoinBase); + ss << coin.out; + return ss; +} + +//! Warning: be very careful when changing this! assumeutxo and UTXO snapshot +//! validation commitments are reliant on the hash constructed by this +//! function. +//! +//! If the construction of this hash is changed, it will invalidate +//! existing UTXO snapshots. This will not result in any kind of consensus +//! failure, but it will force clients that were expecting to make use of +//! assumeutxo to do traditional IBD instead. +//! +//! It is also possible, though very unlikely, that a change in this +//! construction could cause a previously invalid (and potentially malicious) +//! UTXO snapshot to be considered valid. +static void ApplyHash(CHashWriter& ss, const uint256& hash, const std::map<uint32_t, Coin>& outputs) +{ + for (auto it = outputs.begin(); it != outputs.end(); ++it) { + if (it == outputs.begin()) { + ss << hash; + ss << VARINT(it->second.nHeight * 2 + it->second.fCoinBase ? 1u : 0u); + } + + ss << VARINT(it->first + 1); + ss << it->second.out.scriptPubKey; + ss << VARINT_MODE(it->second.out.nValue, VarIntMode::NONNEGATIVE_SIGNED); + + if (it == std::prev(outputs.end())) { + ss << VARINT(0u); + } + } +} + +static void ApplyHash(std::nullptr_t, const uint256& hash, const std::map<uint32_t, Coin>& outputs) {} + +static void ApplyHash(MuHash3072& muhash, const uint256& hash, const std::map<uint32_t, Coin>& outputs) +{ + for (auto it = outputs.begin(); it != outputs.end(); ++it) { + COutPoint outpoint = COutPoint(hash, it->first); + Coin coin = it->second; + muhash.Insert(MakeUCharSpan(TxOutSer(outpoint, coin))); + } +} + +static void ApplyStats(CCoinsStats& stats, const uint256& hash, const std::map<uint32_t, Coin>& outputs) +{ + assert(!outputs.empty()); + stats.nTransactions++; + for (auto it = outputs.begin(); it != outputs.end(); ++it) { + stats.nTransactionOutputs++; + if (stats.total_amount.has_value()) { + stats.total_amount = CheckedAdd(*stats.total_amount, it->second.out.nValue); + } + stats.nBogoSize += GetBogoSize(it->second.out.scriptPubKey); + } +} + +//! Calculate statistics about the unspent transaction output set +template <typename T> +static bool ComputeUTXOStats(CCoinsView* view, CCoinsStats& stats, T hash_obj, const std::function<void()>& interruption_point) +{ + std::unique_ptr<CCoinsViewCursor> pcursor(view->Cursor()); + assert(pcursor); + + PrepareHash(hash_obj, stats); + + uint256 prevkey; + std::map<uint32_t, Coin> outputs; + while (pcursor->Valid()) { + interruption_point(); + COutPoint key; + Coin coin; + if (pcursor->GetKey(key) && pcursor->GetValue(coin)) { + if (!outputs.empty() && key.hash != prevkey) { + ApplyStats(stats, prevkey, outputs); + ApplyHash(hash_obj, prevkey, outputs); + outputs.clear(); + } + prevkey = key.hash; + outputs[key.n] = std::move(coin); + stats.coins_count++; + } else { + return error("%s: unable to read value", __func__); + } + pcursor->Next(); + } + if (!outputs.empty()) { + ApplyStats(stats, prevkey, outputs); + ApplyHash(hash_obj, prevkey, outputs); + } + + FinalizeHash(hash_obj, stats); + + stats.nDiskSize = view->EstimateSize(); + + return true; +} + +std::optional<CCoinsStats> ComputeUTXOStats(CoinStatsHashType hash_type, CCoinsView* view, BlockManager& blockman, const std::function<void()>& interruption_point) +{ + CBlockIndex* pindex = WITH_LOCK(::cs_main, return blockman.LookupBlockIndex(view->GetBestBlock())); + CCoinsStats stats{Assert(pindex)->nHeight, pindex->GetBlockHash()}; + + bool success = [&]() -> bool { + switch (hash_type) { + case(CoinStatsHashType::HASH_SERIALIZED): { + CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); + return ComputeUTXOStats(view, stats, ss, interruption_point); + } + case(CoinStatsHashType::MUHASH): { + MuHash3072 muhash; + return ComputeUTXOStats(view, stats, muhash, interruption_point); + } + case(CoinStatsHashType::NONE): { + return ComputeUTXOStats(view, stats, nullptr, interruption_point); + } + } // no default case, so the compiler can warn about missing cases + assert(false); + }(); + + if (!success) { + return std::nullopt; + } + return stats; +} + +// The legacy hash serializes the hashBlock +static void PrepareHash(CHashWriter& ss, const CCoinsStats& stats) +{ + ss << stats.hashBlock; +} +// MuHash does not need the prepare step +static void PrepareHash(MuHash3072& muhash, CCoinsStats& stats) {} +static void PrepareHash(std::nullptr_t, CCoinsStats& stats) {} + +static void FinalizeHash(CHashWriter& ss, CCoinsStats& stats) +{ + stats.hashSerialized = ss.GetHash(); +} +static void FinalizeHash(MuHash3072& muhash, CCoinsStats& stats) +{ + uint256 out; + muhash.Finalize(out); + stats.hashSerialized = out; +} +static void FinalizeHash(std::nullptr_t, CCoinsStats& stats) {} + +} // namespace node diff --git a/src/node/coinstats.cpp b/src/node/coinstats.cpp index 8851eb2c2d..12c8e7b0da 100644 --- a/src/node/coinstats.cpp +++ b/src/node/coinstats.cpp @@ -6,187 +6,11 @@ #include <node/coinstats.h> #include <coins.h> -#include <crypto/muhash.h> -#include <hash.h> #include <index/coinstatsindex.h> #include <optional> -#include <serialize.h> -#include <uint256.h> -#include <util/overflow.h> -#include <util/system.h> #include <validation.h> -#include <map> - namespace node { - -CCoinsStats::CCoinsStats(int block_height, const uint256& block_hash) - : nHeight(block_height), - hashBlock(block_hash) {} - -// Database-independent metric indicating the UTXO set size -uint64_t GetBogoSize(const CScript& script_pub_key) -{ - return 32 /* txid */ + - 4 /* vout index */ + - 4 /* height + coinbase */ + - 8 /* amount */ + - 2 /* scriptPubKey len */ + - script_pub_key.size() /* scriptPubKey */; -} - -CDataStream TxOutSer(const COutPoint& outpoint, const Coin& coin) { - CDataStream ss(SER_DISK, PROTOCOL_VERSION); - ss << outpoint; - ss << static_cast<uint32_t>(coin.nHeight * 2 + coin.fCoinBase); - ss << coin.out; - return ss; -} - -//! Warning: be very careful when changing this! assumeutxo and UTXO snapshot -//! validation commitments are reliant on the hash constructed by this -//! function. -//! -//! If the construction of this hash is changed, it will invalidate -//! existing UTXO snapshots. This will not result in any kind of consensus -//! failure, but it will force clients that were expecting to make use of -//! assumeutxo to do traditional IBD instead. -//! -//! It is also possible, though very unlikely, that a change in this -//! construction could cause a previously invalid (and potentially malicious) -//! UTXO snapshot to be considered valid. -static void ApplyHash(CHashWriter& ss, const uint256& hash, const std::map<uint32_t, Coin>& outputs) -{ - for (auto it = outputs.begin(); it != outputs.end(); ++it) { - if (it == outputs.begin()) { - ss << hash; - ss << VARINT(it->second.nHeight * 2 + it->second.fCoinBase ? 1u : 0u); - } - - ss << VARINT(it->first + 1); - ss << it->second.out.scriptPubKey; - ss << VARINT_MODE(it->second.out.nValue, VarIntMode::NONNEGATIVE_SIGNED); - - if (it == std::prev(outputs.end())) { - ss << VARINT(0u); - } - } -} - -static void ApplyHash(std::nullptr_t, const uint256& hash, const std::map<uint32_t, Coin>& outputs) {} - -static void ApplyHash(MuHash3072& muhash, const uint256& hash, const std::map<uint32_t, Coin>& outputs) -{ - for (auto it = outputs.begin(); it != outputs.end(); ++it) { - COutPoint outpoint = COutPoint(hash, it->first); - Coin coin = it->second; - muhash.Insert(MakeUCharSpan(TxOutSer(outpoint, coin))); - } -} - -static void ApplyStats(CCoinsStats& stats, const uint256& hash, const std::map<uint32_t, Coin>& outputs) -{ - assert(!outputs.empty()); - stats.nTransactions++; - for (auto it = outputs.begin(); it != outputs.end(); ++it) { - stats.nTransactionOutputs++; - if (stats.total_amount.has_value()) { - stats.total_amount = CheckedAdd(*stats.total_amount, it->second.out.nValue); - } - stats.nBogoSize += GetBogoSize(it->second.out.scriptPubKey); - } -} - -//! Calculate statistics about the unspent transaction output set -template <typename T> -static bool ComputeUTXOStats(CCoinsView* view, CCoinsStats& stats, T hash_obj, const std::function<void()>& interruption_point) -{ - std::unique_ptr<CCoinsViewCursor> pcursor(view->Cursor()); - assert(pcursor); - - PrepareHash(hash_obj, stats); - - uint256 prevkey; - std::map<uint32_t, Coin> outputs; - while (pcursor->Valid()) { - interruption_point(); - COutPoint key; - Coin coin; - if (pcursor->GetKey(key) && pcursor->GetValue(coin)) { - if (!outputs.empty() && key.hash != prevkey) { - ApplyStats(stats, prevkey, outputs); - ApplyHash(hash_obj, prevkey, outputs); - outputs.clear(); - } - prevkey = key.hash; - outputs[key.n] = std::move(coin); - stats.coins_count++; - } else { - return error("%s: unable to read value", __func__); - } - pcursor->Next(); - } - if (!outputs.empty()) { - ApplyStats(stats, prevkey, outputs); - ApplyHash(hash_obj, prevkey, outputs); - } - - FinalizeHash(hash_obj, stats); - - stats.nDiskSize = view->EstimateSize(); - - return true; -} - -std::optional<CCoinsStats> ComputeUTXOStats(CoinStatsHashType hash_type, CCoinsView* view, BlockManager& blockman, const std::function<void()>& interruption_point) -{ - CBlockIndex* pindex = WITH_LOCK(::cs_main, return blockman.LookupBlockIndex(view->GetBestBlock())); - CCoinsStats stats{Assert(pindex)->nHeight, pindex->GetBlockHash()}; - - bool success = [&]() -> bool { - switch (hash_type) { - case(CoinStatsHashType::HASH_SERIALIZED): { - CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); - return ComputeUTXOStats(view, stats, ss, interruption_point); - } - case(CoinStatsHashType::MUHASH): { - MuHash3072 muhash; - return ComputeUTXOStats(view, stats, muhash, interruption_point); - } - case(CoinStatsHashType::NONE): { - return ComputeUTXOStats(view, stats, nullptr, interruption_point); - } - } // no default case, so the compiler can warn about missing cases - assert(false); - }(); - - if (!success) { - return std::nullopt; - } - return stats; -} - -// The legacy hash serializes the hashBlock -static void PrepareHash(CHashWriter& ss, const CCoinsStats& stats) -{ - ss << stats.hashBlock; -} -// MuHash does not need the prepare step -static void PrepareHash(MuHash3072& muhash, CCoinsStats& stats) {} -static void PrepareHash(std::nullptr_t, CCoinsStats& stats) {} - -static void FinalizeHash(CHashWriter& ss, CCoinsStats& stats) -{ - stats.hashSerialized = ss.GetHash(); -} -static void FinalizeHash(MuHash3072& muhash, CCoinsStats& stats) -{ - uint256 out; - muhash.Finalize(out); - stats.hashSerialized = out; -} -static void FinalizeHash(std::nullptr_t, CCoinsStats& stats) {} - std::optional<CCoinsStats> GetUTXOStats(CCoinsView* view, BlockManager& blockman, CoinStatsHashType hash_type, const std::function<void()>& interruption_point, const CBlockIndex* pindex, bool index_requested) { // Use CoinStatsIndex if it is requested and available and a hash_type of Muhash or None was requested diff --git a/src/node/coinstats.h b/src/node/coinstats.h index d74d70deb2..8abf1e31e8 100644 --- a/src/node/coinstats.h +++ b/src/node/coinstats.h @@ -83,6 +83,8 @@ std::optional<CCoinsStats> GetUTXOStats(CCoinsView* view, node::BlockManager& bl uint64_t GetBogoSize(const CScript& script_pub_key); CDataStream TxOutSer(const COutPoint& outpoint, const Coin& coin); + +std::optional<CCoinsStats> ComputeUTXOStats(CoinStatsHashType hash_type, CCoinsView* view, BlockManager& blockman, const std::function<void()>& interruption_point = {}); } // namespace node #endif // BITCOIN_NODE_COINSTATS_H |