Skip to content

Commit

Permalink
coinstats: Re-introduce hash_serialized_2
Browse files Browse the repository at this point in the history
  • Loading branch information
fjahr committed Oct 20, 2023
1 parent 942f058 commit 47ae7d8
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 8 deletions.
50 changes: 46 additions & 4 deletions src/kernel/coinstats.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,35 @@ void RemoveCoinHash(MuHash3072& muhash, const COutPoint& outpoint, const Coin& c

static void ApplyCoinHash(std::nullptr_t, const COutPoint& outpoint, const Coin& coin) {}

// Support for deprecated hash_serialized_2
static void ApplyHashV2(HashWriter& 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);
}
}
}

// These should never be called, but are needed to satisfy the template
static void ApplyHashV2(MuHash3072& muhash, const uint256& hash, const std::map<uint32_t, Coin>& outputs)
{
assert(false);
}
static void ApplyHashV2(std::nullptr_t, const uint256& hash, const std::map<uint32_t, Coin>& outputs)
{
assert(false);
}

//! Warning: be very careful when changing this! assumeutxo and UTXO snapshot
//! validation commitments are reliant on the hash constructed by this
//! function.
Expand Down Expand Up @@ -114,7 +143,7 @@ static void ApplyStats(CCoinsStats& stats, const uint256& hash, const std::map<u

//! 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)
static bool ComputeUTXOStats(CCoinsView* view, CCoinsStats& stats, T hash_obj, const std::function<void()>& interruption_point, const bool hash_v2 = false)
{
std::unique_ptr<CCoinsViewCursor> pcursor(view->Cursor());
assert(pcursor);
Expand All @@ -128,7 +157,11 @@ static bool ComputeUTXOStats(CCoinsView* view, CCoinsStats& stats, T hash_obj, c
if (pcursor->GetKey(key) && pcursor->GetValue(coin)) {
if (!outputs.empty() && key.hash != prevkey) {
ApplyStats(stats, prevkey, outputs);
ApplyHash(hash_obj, prevkey, outputs);
if (hash_v2) {
ApplyHashV2(hash_obj, prevkey, outputs);
} else {
ApplyHash(hash_obj, prevkey, outputs);
}
outputs.clear();
}
prevkey = key.hash;
Expand All @@ -141,7 +174,11 @@ static bool ComputeUTXOStats(CCoinsView* view, CCoinsStats& stats, T hash_obj, c
}
if (!outputs.empty()) {
ApplyStats(stats, prevkey, outputs);
ApplyHash(hash_obj, prevkey, outputs);
if (hash_v2) {
ApplyHashV2(hash_obj, prevkey, outputs);
} else {
ApplyHash(hash_obj, prevkey, outputs);
}
}

FinalizeHash(hash_obj, stats);
Expand All @@ -160,7 +197,12 @@ std::optional<CCoinsStats> ComputeUTXOStats(CoinStatsHashType hash_type, CCoinsV
switch (hash_type) {
case(CoinStatsHashType::HASH_SERIALIZED): {
HashWriter ss{};
return ComputeUTXOStats(view, stats, ss, interruption_point);
return ComputeUTXOStats(view, stats, ss, interruption_point, false);
}
case(CoinStatsHashType::LEGACY_HASH_SERIALIZED): {
HashWriter ss{};
ss << stats.hashBlock;
return ComputeUTXOStats(view, stats, ss, interruption_point, true);
}
case(CoinStatsHashType::MUHASH): {
MuHash3072 muhash;
Expand Down
1 change: 1 addition & 0 deletions src/kernel/coinstats.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class BlockManager;
namespace kernel {
enum class CoinStatsHashType {
HASH_SERIALIZED,
LEGACY_HASH_SERIALIZED,
MUHASH,
NONE,
};
Expand Down
14 changes: 11 additions & 3 deletions src/rpc/blockchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -822,6 +822,8 @@ CoinStatsHashType ParseHashType(const std::string& hash_type_input)
{
if (hash_type_input == "hash_serialized_3") {
return CoinStatsHashType::HASH_SERIALIZED;
} else if (hash_type_input == "hash_serialized_2") {
return CoinStatsHashType::LEGACY_HASH_SERIALIZED;
} else if (hash_type_input == "muhash") {
return CoinStatsHashType::MUHASH;
} else if (hash_type_input == "none") {
Expand Down Expand Up @@ -867,7 +869,7 @@ static RPCHelpMan gettxoutsetinfo()
"\nReturns statistics about the unspent transaction output set.\n"
"Note this call may take some time if you are not using coinstatsindex.\n",
{
{"hash_type", RPCArg::Type::STR, RPCArg::Default{"hash_serialized_3"}, "Which UTXO set hash should be calculated. Options: 'hash_serialized_3' (the legacy algorithm), 'muhash', 'none'."},
{"hash_type", RPCArg::Type::STR, RPCArg::Default{"hash_serialized_3"}, "Which UTXO set hash should be calculated. Options: 'hash_serialized_3', 'hash_serialized_2' (broken legacy algorithm), 'muhash', 'none'."},
{"hash_or_height", RPCArg::Type::NUM, RPCArg::DefaultHint{"the current best block"}, "The block hash or height of the target height (only available with coinstatsindex).",
RPCArgOptions{
.skip_type_check = true,
Expand All @@ -883,6 +885,7 @@ static RPCHelpMan gettxoutsetinfo()
{RPCResult::Type::NUM, "txouts", "The number of unspent transaction outputs"},
{RPCResult::Type::NUM, "bogosize", "Database-independent, meaningless metric indicating the UTXO set size"},
{RPCResult::Type::STR_HEX, "hash_serialized_3", /*optional=*/true, "The serialized hash (only present if 'hash_serialized_3' hash_type is chosen)"},
{RPCResult::Type::STR_HEX, "hash_serialized_2", /*optional=*/true, "The broken legacy serialized hash (only present if 'hash_serialized_2' hash_type is chosen)"},
{RPCResult::Type::STR_HEX, "muhash", /*optional=*/true, "The serialized hash (only present if 'muhash' hash_type is chosen)"},
{RPCResult::Type::NUM, "transactions", /*optional=*/true, "The number of transactions with unspent outputs (not available when coinstatsindex is used)"},
{RPCResult::Type::NUM, "disk_size", /*optional=*/true, "The estimated size of the chainstate on disk (not available when coinstatsindex is used)"},
Expand Down Expand Up @@ -919,7 +922,9 @@ static RPCHelpMan gettxoutsetinfo()
UniValue ret(UniValue::VOBJ);

const CBlockIndex* pindex{nullptr};
const CoinStatsHashType hash_type{request.params[0].isNull() ? CoinStatsHashType::HASH_SERIALIZED : ParseHashType(request.params[0].get_str())};
const bool deprecation_enabled{IsDeprecatedRPCEnabled("gettxoutsetinfo")};
const CoinStatsHashType default_hash_type{deprecation_enabled ? CoinStatsHashType::LEGACY_HASH_SERIALIZED : CoinStatsHashType::HASH_SERIALIZED};
const CoinStatsHashType hash_type{request.params[0].isNull() ? default_hash_type : ParseHashType(request.params[0].get_str())};
bool index_requested = request.params[2].isNull() || request.params[2].get_bool();

NodeContext& node = EnsureAnyNodeContext(request.context);
Expand All @@ -942,7 +947,7 @@ static RPCHelpMan gettxoutsetinfo()
}

if (hash_type == CoinStatsHashType::HASH_SERIALIZED) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "hash_serialized_3 hash type cannot be queried for a specific block");
throw JSONRPCError(RPC_INVALID_PARAMETER, "hash_serialized_3/hash_serialized_2 hash types cannot be queried for a specific block");
}

if (!index_requested) {
Expand Down Expand Up @@ -973,6 +978,9 @@ static RPCHelpMan gettxoutsetinfo()
if (hash_type == CoinStatsHashType::HASH_SERIALIZED) {
ret.pushKV("hash_serialized_3", stats.hashSerialized.GetHex());
}
if (hash_type == CoinStatsHashType::LEGACY_HASH_SERIALIZED) {
ret.pushKV("hash_serialized_2", stats.hashSerialized.GetHex());
}
if (hash_type == CoinStatsHashType::MUHASH) {
ret.pushKV("muhash", stats.hashSerialized.GetHex());
}
Expand Down
2 changes: 1 addition & 1 deletion test/functional/feature_coinstatsindex.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ def _test_reorg_index(self):
def _test_index_rejects_hash_serialized(self):
self.log.info("Test that the rpc raises if the legacy hash is passed with the index")

msg = "hash_serialized_3 hash type cannot be queried for a specific block"
msg = "hash_serialized_3/hash_serialized_2 hash types cannot be queried for a specific block"
assert_raises_rpc_error(-8, msg, self.nodes[1].gettxoutsetinfo, hash_type='hash_serialized_3', hash_or_height=111)

for use_index in {True, False, None}:
Expand Down

0 comments on commit 47ae7d8

Please sign in to comment.