e987ae5a554c9952812746c29f2766bacea4b727 test: Add test for deterministic UTXO set hash results (Fabian Jahr) 6ccc8fc067bf516cda7bc5d7d721945be5ac2003 test: Add test for gettxoutsetinfo RPC with MuHash (Fabian Jahr) 0d3b2f643d7da3202c0a0e757539208c4aa7c450 rpc: Add hash_type MUHASH to gettxoutsetinfo (Fabian Jahr) 2474645f3b15687e7f196b89eb935d6e6a98a9da refactor: Separate hash and stats calculation in coinstats (Fabian Jahr) a1fcceac69097a8e6540a6fd8121a5d53022528f refactor: Improve encapsulation between MuHash3072 and Num3072 (Fabian Jahr) Pull request description: This is another Pr in the series PRs for Coinstatsindex (see overview in #18000). This PR adds the `hash_type` option `muhash` to `gettxoutsetinfo` through which the user can calculate the serialized muhash of the utxo set. This PR does not use the index yet. ACKs for top commit: Sjors: tACK e987ae5 achow101: ACK e987ae5a554c9952812746c29f2766bacea4b727 jonatack: Tested re-ACK e987ae5a554c9952812746c29f2766bacea4b727 per `git diff 3506d90 e987ae5`, reviewed diff, debug built, ran gettxoutsetinfo -signet and help on this branch vs master, at height 23127 both returned `hash_serialized_2` of `2b72d65f3b6efb2311f58374ea2b939abf49684d44f4bafda45faa3b5452a454` and this branch returned `muhash` of `c9f1ff12d345ccf9939c6bbf087e6f7399b6115adee1569287e9c5c43dbb475c` ryanofsky: Code review ACK e987ae5a554c9952812746c29f2766bacea4b727. Looks very good. I left one suggestion to simplify code, but feel free to ignore it here and maybe consider it for later since PR has already had a lot of review. Tree-SHA512: 9a739ce375e73749fa69a467262b60d3e5314ef384e2d7150b3bbc8e4125cd9fd1db95306623bb9a632fcbaf5d9d2bf2f5cc43bf717d4ff5e2c9c4b52dd9296c
155 lines
4.9 KiB
C++
155 lines
4.9 KiB
C++
// Copyright (c) 2010 Satoshi Nakamoto
|
|
// Copyright (c) 2009-2020 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/system.h>
|
|
#include <validation.h>
|
|
|
|
#include <map>
|
|
|
|
static uint64_t GetBogoSize(const CScript& scriptPubKey)
|
|
{
|
|
return 32 /* txid */ +
|
|
4 /* vout index */ +
|
|
4 /* height + coinbase */ +
|
|
8 /* amount */ +
|
|
2 /* scriptPubKey len */ +
|
|
scriptPubKey.size() /* scriptPubKey */;
|
|
}
|
|
|
|
static void ApplyHash(CCoinsStats& stats, CHashWriter& ss, const uint256& hash, const std::map<uint32_t, Coin>& outputs, std::map<uint32_t, Coin>::const_iterator 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(CCoinsStats& stats, std::nullptr_t, const uint256& hash, const std::map<uint32_t, Coin>& outputs, std::map<uint32_t, Coin>::const_iterator it) {}
|
|
|
|
static void ApplyHash(CCoinsStats& stats, MuHash3072& muhash, const uint256& hash, const std::map<uint32_t, Coin>& outputs, std::map<uint32_t, Coin>::const_iterator it)
|
|
{
|
|
COutPoint outpoint = COutPoint(hash, it->first);
|
|
Coin coin = it->second;
|
|
|
|
CDataStream ss(SER_DISK, PROTOCOL_VERSION);
|
|
ss << outpoint;
|
|
ss << static_cast<uint32_t>(coin.nHeight * 2 + coin.fCoinBase);
|
|
ss << coin.out;
|
|
muhash.Insert(MakeUCharSpan(ss));
|
|
}
|
|
|
|
template <typename T>
|
|
static void ApplyStats(CCoinsStats& stats, T& hash_obj, const uint256& hash, const std::map<uint32_t, Coin>& outputs)
|
|
{
|
|
assert(!outputs.empty());
|
|
stats.nTransactions++;
|
|
for (auto it = outputs.begin(); it != outputs.end(); ++it) {
|
|
ApplyHash(stats, hash_obj, hash, outputs, it);
|
|
|
|
stats.nTransactionOutputs++;
|
|
stats.nTotalAmount += it->second.out.nValue;
|
|
stats.nBogoSize += GetBogoSize(it->second.out.scriptPubKey);
|
|
}
|
|
}
|
|
|
|
//! Calculate statistics about the unspent transaction output set
|
|
template <typename T>
|
|
static bool GetUTXOStats(CCoinsView* view, CCoinsStats& stats, T hash_obj, const std::function<void()>& interruption_point)
|
|
{
|
|
stats = CCoinsStats();
|
|
std::unique_ptr<CCoinsViewCursor> pcursor(view->Cursor());
|
|
assert(pcursor);
|
|
|
|
stats.hashBlock = pcursor->GetBestBlock();
|
|
{
|
|
LOCK(cs_main);
|
|
stats.nHeight = g_chainman.m_blockman.LookupBlockIndex(stats.hashBlock)->nHeight;
|
|
}
|
|
|
|
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, 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, hash_obj, prevkey, outputs);
|
|
}
|
|
|
|
FinalizeHash(hash_obj, stats);
|
|
|
|
stats.nDiskSize = view->EstimateSize();
|
|
return true;
|
|
}
|
|
|
|
bool GetUTXOStats(CCoinsView* view, CCoinsStats& stats, CoinStatsHashType hash_type, const std::function<void()>& interruption_point)
|
|
{
|
|
switch (hash_type) {
|
|
case(CoinStatsHashType::HASH_SERIALIZED): {
|
|
CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION);
|
|
return GetUTXOStats(view, stats, ss, interruption_point);
|
|
}
|
|
case(CoinStatsHashType::MUHASH): {
|
|
MuHash3072 muhash;
|
|
return GetUTXOStats(view, stats, muhash, interruption_point);
|
|
}
|
|
case(CoinStatsHashType::NONE): {
|
|
return GetUTXOStats(view, stats, nullptr, interruption_point);
|
|
}
|
|
} // no default case, so the compiler can warn about missing cases
|
|
assert(false);
|
|
}
|
|
|
|
// 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) {}
|