From 47ae7d8c3c1a10b796d0b5e9cd08da61d7dfedf9 Mon Sep 17 00:00:00 2001 From: Fabian Jahr Date: Fri, 20 Oct 2023 20:18:38 +0200 Subject: [PATCH] coinstats: Re-introduce hash_serialized_2 --- src/kernel/coinstats.cpp | 50 +++++++++++++++++++++-- src/kernel/coinstats.h | 1 + src/rpc/blockchain.cpp | 14 +++++-- test/functional/feature_coinstatsindex.py | 2 +- 4 files changed, 59 insertions(+), 8 deletions(-) diff --git a/src/kernel/coinstats.cpp b/src/kernel/coinstats.cpp index 9bd755ed27120..b2f1c539d835e 100644 --- a/src/kernel/coinstats.cpp +++ b/src/kernel/coinstats.cpp @@ -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& 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& outputs) +{ + assert(false); +} +static void ApplyHashV2(std::nullptr_t, const uint256& hash, const std::map& 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. @@ -114,7 +143,7 @@ static void ApplyStats(CCoinsStats& stats, const uint256& hash, const std::map -static bool ComputeUTXOStats(CCoinsView* view, CCoinsStats& stats, T hash_obj, const std::function& interruption_point) +static bool ComputeUTXOStats(CCoinsView* view, CCoinsStats& stats, T hash_obj, const std::function& interruption_point, const bool hash_v2 = false) { std::unique_ptr pcursor(view->Cursor()); assert(pcursor); @@ -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; @@ -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); @@ -160,7 +197,12 @@ std::optional 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; diff --git a/src/kernel/coinstats.h b/src/kernel/coinstats.h index c0c363a842838..fecba37837ed2 100644 --- a/src/kernel/coinstats.h +++ b/src/kernel/coinstats.h @@ -25,6 +25,7 @@ class BlockManager; namespace kernel { enum class CoinStatsHashType { HASH_SERIALIZED, + LEGACY_HASH_SERIALIZED, MUHASH, NONE, }; diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 7b84747a3fd8b..26f068505cea2 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -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") { @@ -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, @@ -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)"}, @@ -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); @@ -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) { @@ -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()); } diff --git a/test/functional/feature_coinstatsindex.py b/test/functional/feature_coinstatsindex.py index d6c1567e64bca..37161c97c8d89 100755 --- a/test/functional/feature_coinstatsindex.py +++ b/test/functional/feature_coinstatsindex.py @@ -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}: