mirror of
https://github.com/dashpay/dash.git
synced 2024-12-26 04:22:55 +01:00
merge bitcoin#19145: Add hash_type MUHASH for gettxoutsetinfo
This commit is contained in:
parent
c7eb44a911
commit
ef18d0858d
@ -17,7 +17,6 @@ namespace {
|
|||||||
using limb_t = Num3072::limb_t;
|
using limb_t = Num3072::limb_t;
|
||||||
using double_limb_t = Num3072::double_limb_t;
|
using double_limb_t = Num3072::double_limb_t;
|
||||||
constexpr int LIMB_SIZE = Num3072::LIMB_SIZE;
|
constexpr int LIMB_SIZE = Num3072::LIMB_SIZE;
|
||||||
constexpr int LIMBS = Num3072::LIMBS;
|
|
||||||
/** 2^3072 - 1103717, the largest 3072-bit safe prime number, is used as the modulus. */
|
/** 2^3072 - 1103717, the largest 3072-bit safe prime number, is used as the modulus. */
|
||||||
constexpr limb_t MAX_PRIME_DIFF = 1103717;
|
constexpr limb_t MAX_PRIME_DIFF = 1103717;
|
||||||
|
|
||||||
@ -123,7 +122,7 @@ inline void square_n_mul(Num3072& in_out, const int sq, const Num3072& mul)
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
/** Indicates wether d is larger than the modulus. */
|
/** Indicates whether d is larger than the modulus. */
|
||||||
bool Num3072::IsOverflow() const
|
bool Num3072::IsOverflow() const
|
||||||
{
|
{
|
||||||
if (this->limbs[0] <= std::numeric_limits<limb_t>::max() - MAX_PRIME_DIFF) return false;
|
if (this->limbs[0] <= std::numeric_limits<limb_t>::max() - MAX_PRIME_DIFF) return false;
|
||||||
@ -276,18 +275,33 @@ void Num3072::Divide(const Num3072& a)
|
|||||||
if (this->IsOverflow()) this->FullReduce();
|
if (this->IsOverflow()) this->FullReduce();
|
||||||
}
|
}
|
||||||
|
|
||||||
Num3072 MuHash3072::ToNum3072(Span<const unsigned char> in) {
|
Num3072::Num3072(const unsigned char (&data)[BYTE_SIZE]) {
|
||||||
Num3072 out{};
|
|
||||||
uint256 hashed_in = (CHashWriter(SER_DISK, 0) << in).GetSHA256();
|
|
||||||
unsigned char tmp[BYTE_SIZE];
|
|
||||||
ChaCha20(hashed_in.data(), hashed_in.size()).Keystream(tmp, BYTE_SIZE);
|
|
||||||
for (int i = 0; i < LIMBS; ++i) {
|
for (int i = 0; i < LIMBS; ++i) {
|
||||||
if (sizeof(limb_t) == 4) {
|
if (sizeof(limb_t) == 4) {
|
||||||
out.limbs[i] = ReadLE32(tmp + 4 * i);
|
this->limbs[i] = ReadLE32(data + 4 * i);
|
||||||
} else if (sizeof(limb_t) == 8) {
|
} else if (sizeof(limb_t) == 8) {
|
||||||
out.limbs[i] = ReadLE64(tmp + 8 * i);
|
this->limbs[i] = ReadLE64(data + 8 * i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Num3072::ToBytes(unsigned char (&out)[BYTE_SIZE]) {
|
||||||
|
for (int i = 0; i < LIMBS; ++i) {
|
||||||
|
if (sizeof(limb_t) == 4) {
|
||||||
|
WriteLE32(out + i * 4, this->limbs[i]);
|
||||||
|
} else if (sizeof(limb_t) == 8) {
|
||||||
|
WriteLE64(out + i * 8, this->limbs[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Num3072 MuHash3072::ToNum3072(Span<const unsigned char> in) {
|
||||||
|
unsigned char tmp[Num3072::BYTE_SIZE];
|
||||||
|
|
||||||
|
uint256 hashed_in = (CHashWriter(SER_DISK, 0) << in).GetSHA256();
|
||||||
|
ChaCha20(hashed_in.data(), hashed_in.size()).Keystream(tmp, Num3072::BYTE_SIZE);
|
||||||
|
Num3072 out{tmp};
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -301,14 +315,8 @@ void MuHash3072::Finalize(uint256& out) noexcept
|
|||||||
m_numerator.Divide(m_denominator);
|
m_numerator.Divide(m_denominator);
|
||||||
m_denominator.SetToOne(); // Needed to keep the MuHash object valid
|
m_denominator.SetToOne(); // Needed to keep the MuHash object valid
|
||||||
|
|
||||||
unsigned char data[384];
|
unsigned char data[Num3072::BYTE_SIZE];
|
||||||
for (int i = 0; i < LIMBS; ++i) {
|
m_numerator.ToBytes(data);
|
||||||
if (sizeof(limb_t) == 4) {
|
|
||||||
WriteLE32(data + i * 4, m_numerator.limbs[i]);
|
|
||||||
} else if (sizeof(limb_t) == 8) {
|
|
||||||
WriteLE64(data + i * 8, m_numerator.limbs[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
out = (CHashWriter(SER_DISK, 0) << data).GetSHA256();
|
out = (CHashWriter(SER_DISK, 0) << data).GetSHA256();
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ private:
|
|||||||
Num3072 GetInverse() const;
|
Num3072 GetInverse() const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
static constexpr size_t BYTE_SIZE = 384;
|
||||||
|
|
||||||
#ifdef HAVE___INT128
|
#ifdef HAVE___INT128
|
||||||
typedef unsigned __int128 double_limb_t;
|
typedef unsigned __int128 double_limb_t;
|
||||||
@ -48,8 +49,10 @@ public:
|
|||||||
void Divide(const Num3072& a);
|
void Divide(const Num3072& a);
|
||||||
void SetToOne();
|
void SetToOne();
|
||||||
void Square();
|
void Square();
|
||||||
|
void ToBytes(unsigned char (&out)[BYTE_SIZE]);
|
||||||
|
|
||||||
Num3072() { this->SetToOne(); };
|
Num3072() { this->SetToOne(); };
|
||||||
|
Num3072(const unsigned char (&data)[BYTE_SIZE]);
|
||||||
|
|
||||||
SERIALIZE_METHODS(Num3072, obj)
|
SERIALIZE_METHODS(Num3072, obj)
|
||||||
{
|
{
|
||||||
@ -78,7 +81,7 @@ public:
|
|||||||
* arbitrary subset of the update operations, allowing them to be
|
* arbitrary subset of the update operations, allowing them to be
|
||||||
* efficiently combined later.
|
* efficiently combined later.
|
||||||
*
|
*
|
||||||
* Muhash does not support checking if an element is already part of the
|
* MuHash does not support checking if an element is already part of the
|
||||||
* set. That is why this class does not enforce the use of a set as the
|
* set. That is why this class does not enforce the use of a set as the
|
||||||
* data it represents because there is no efficient way to do so.
|
* data it represents because there is no efficient way to do so.
|
||||||
* It is possible to add elements more than once and also to remove
|
* It is possible to add elements more than once and also to remove
|
||||||
@ -91,8 +94,6 @@ public:
|
|||||||
class MuHash3072
|
class MuHash3072
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
static constexpr size_t BYTE_SIZE = 384;
|
|
||||||
|
|
||||||
Num3072 m_numerator;
|
Num3072 m_numerator;
|
||||||
Num3072 m_denominator;
|
Num3072 m_denominator;
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
#include <node/coinstats.h>
|
#include <node/coinstats.h>
|
||||||
|
|
||||||
#include <coins.h>
|
#include <coins.h>
|
||||||
|
#include <crypto/muhash.h>
|
||||||
#include <hash.h>
|
#include <hash.h>
|
||||||
#include <serialize.h>
|
#include <serialize.h>
|
||||||
#include <uint256.h>
|
#include <uint256.h>
|
||||||
@ -27,21 +28,48 @@ static uint64_t GetBogoSize(const CScript& scriptPubKey)
|
|||||||
scriptPubKey.size() /* scriptPubKey */;
|
scriptPubKey.size() /* scriptPubKey */;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ApplyStats(CCoinsStats& stats, CHashWriter& ss, const uint256& hash, const std::map<uint32_t, Coin>& outputs)
|
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(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());
|
assert(!outputs.empty());
|
||||||
ss << hash;
|
|
||||||
ss << VARINT(outputs.begin()->second.nHeight * 2 + outputs.begin()->second.fCoinBase ? 1u : 0u);
|
|
||||||
stats.nTransactions++;
|
stats.nTransactions++;
|
||||||
for (const auto& output : outputs) {
|
for (auto it = outputs.begin(); it != outputs.end(); ++it) {
|
||||||
ss << VARINT(output.first + 1);
|
ApplyHash(stats, hash_obj, hash, outputs, it);
|
||||||
ss << output.second.out.scriptPubKey;
|
|
||||||
ss << VARINT(output.second.out.nValue, VarIntMode::NONNEGATIVE_SIGNED);
|
|
||||||
stats.nTransactionOutputs++;
|
stats.nTransactionOutputs++;
|
||||||
stats.nTotalAmount += output.second.out.nValue;
|
stats.nTotalAmount += it->second.out.nValue;
|
||||||
stats.nBogoSize += GetBogoSize(output.second.out.scriptPubKey);
|
stats.nBogoSize += GetBogoSize(it->second.out.scriptPubKey);
|
||||||
}
|
}
|
||||||
ss << VARINT(0u);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ApplyStats(CCoinsStats& stats, std::nullptr_t, const uint256& hash, const std::map<uint32_t, Coin>& outputs)
|
static void ApplyStats(CCoinsStats& stats, std::nullptr_t, const uint256& hash, const std::map<uint32_t, Coin>& outputs)
|
||||||
@ -105,6 +133,10 @@ bool GetUTXOStats(CCoinsView* view, CCoinsStats& stats, CoinStatsHashType hash_t
|
|||||||
CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION);
|
CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION);
|
||||||
return GetUTXOStats(view, stats, ss);
|
return GetUTXOStats(view, stats, ss);
|
||||||
}
|
}
|
||||||
|
case(CoinStatsHashType::MUHASH): {
|
||||||
|
MuHash3072 muhash;
|
||||||
|
return GetUTXOStats(view, stats, muhash);
|
||||||
|
}
|
||||||
case(CoinStatsHashType::NONE): {
|
case(CoinStatsHashType::NONE): {
|
||||||
return GetUTXOStats(view, stats, nullptr);
|
return GetUTXOStats(view, stats, nullptr);
|
||||||
}
|
}
|
||||||
@ -117,10 +149,18 @@ static void PrepareHash(CHashWriter& ss, CCoinsStats& stats)
|
|||||||
{
|
{
|
||||||
ss << stats.hashBlock;
|
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 PrepareHash(std::nullptr_t, CCoinsStats& stats) {}
|
||||||
|
|
||||||
static void FinalizeHash(CHashWriter& ss, CCoinsStats& stats)
|
static void FinalizeHash(CHashWriter& ss, CCoinsStats& stats)
|
||||||
{
|
{
|
||||||
stats.hashSerialized = ss.GetHash();
|
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) {}
|
static void FinalizeHash(std::nullptr_t, CCoinsStats& stats) {}
|
||||||
|
@ -15,6 +15,7 @@ class CCoinsView;
|
|||||||
|
|
||||||
enum class CoinStatsHashType {
|
enum class CoinStatsHashType {
|
||||||
HASH_SERIALIZED,
|
HASH_SERIALIZED,
|
||||||
|
MUHASH,
|
||||||
NONE,
|
NONE,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1236,15 +1236,28 @@ static UniValue pruneblockchain(const JSONRPCRequest& request)
|
|||||||
return uint64_t(block->nHeight);
|
return uint64_t(block->nHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CoinStatsHashType ParseHashType(const std::string& hash_type_input)
|
||||||
|
{
|
||||||
|
if (hash_type_input == "hash_serialized_2") {
|
||||||
|
return CoinStatsHashType::HASH_SERIALIZED;
|
||||||
|
} else if (hash_type_input == "muhash") {
|
||||||
|
return CoinStatsHashType::MUHASH;
|
||||||
|
} else if (hash_type_input == "none") {
|
||||||
|
return CoinStatsHashType::NONE;
|
||||||
|
} else {
|
||||||
|
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("%s is not a valid hash_type", hash_type_input));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static UniValue gettxoutsetinfo(const JSONRPCRequest& request)
|
static UniValue gettxoutsetinfo(const JSONRPCRequest& request)
|
||||||
{
|
{
|
||||||
if (request.fHelp || request.params.size() != 0)
|
if (request.fHelp || request.params.size() > 1)
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
RPCHelpMan{"gettxoutsetinfo",
|
RPCHelpMan{"gettxoutsetinfo",
|
||||||
"\nReturns statistics about the unspent transaction output set.\n"
|
"\nReturns statistics about the unspent transaction output set.\n"
|
||||||
"Note this call may take some time.\n",
|
"Note this call may take some time.\n",
|
||||||
{
|
{
|
||||||
{"hash_type", RPCArg::Type::STR, /* default */ "hash_serialized_2", "Which UTXO set hash should be calculated. Options: 'hash_serialized_2' (the legacy algorithm), 'none'."},
|
{"hash_type", RPCArg::Type::STR, /* default */ "hash_serialized_2", "Which UTXO set hash should be calculated. Options: 'hash_serialized_2' (the legacy algorithm), 'muhash', 'none'."},
|
||||||
},
|
},
|
||||||
RPCResult{
|
RPCResult{
|
||||||
RPCResult::Type::OBJ, "", "",
|
RPCResult::Type::OBJ, "", "",
|
||||||
@ -1255,6 +1268,7 @@ static UniValue gettxoutsetinfo(const JSONRPCRequest& request)
|
|||||||
{RPCResult::Type::NUM, "txouts", "The number of unspent transaction outputs"},
|
{RPCResult::Type::NUM, "txouts", "The number of unspent transaction outputs"},
|
||||||
{RPCResult::Type::NUM, "bogosize", "A meaningless metric for UTXO set size"},
|
{RPCResult::Type::NUM, "bogosize", "A meaningless metric for UTXO set size"},
|
||||||
{RPCResult::Type::STR_HEX, "hash_serialized_2", "The serialized hash (only present if 'hash_serialized_2' hash_type is chosen)"},
|
{RPCResult::Type::STR_HEX, "hash_serialized_2", "The serialized hash (only present if 'hash_serialized_2' hash_type is chosen)"},
|
||||||
|
{RPCResult::Type::STR_HEX, "muhash", "The serialized hash (only present if 'muhash' hash_type is chosen)"},
|
||||||
{RPCResult::Type::NUM, "disk_size", "The estimated size of the chainstate on disk"},
|
{RPCResult::Type::NUM, "disk_size", "The estimated size of the chainstate on disk"},
|
||||||
{RPCResult::Type::STR_AMOUNT, "total_amount", "The total amount"},
|
{RPCResult::Type::STR_AMOUNT, "total_amount", "The total amount"},
|
||||||
}},
|
}},
|
||||||
@ -1269,7 +1283,7 @@ static UniValue gettxoutsetinfo(const JSONRPCRequest& request)
|
|||||||
CCoinsStats stats;
|
CCoinsStats stats;
|
||||||
::ChainstateActive().ForceFlushStateToDisk();
|
::ChainstateActive().ForceFlushStateToDisk();
|
||||||
|
|
||||||
const CoinStatsHashType hash_type = ParseHashType(request.params[0], CoinStatsHashType::HASH_SERIALIZED);
|
const CoinStatsHashType hash_type{request.params[0].isNull() ? CoinStatsHashType::HASH_SERIALIZED : ParseHashType(request.params[0].get_str())};
|
||||||
|
|
||||||
CCoinsView* coins_view = WITH_LOCK(cs_main, return &ChainstateActive().CoinsDB());
|
CCoinsView* coins_view = WITH_LOCK(cs_main, return &ChainstateActive().CoinsDB());
|
||||||
if (GetUTXOStats(coins_view, stats, hash_type)) {
|
if (GetUTXOStats(coins_view, stats, hash_type)) {
|
||||||
@ -1281,6 +1295,9 @@ static UniValue gettxoutsetinfo(const JSONRPCRequest& request)
|
|||||||
if (hash_type == CoinStatsHashType::HASH_SERIALIZED) {
|
if (hash_type == CoinStatsHashType::HASH_SERIALIZED) {
|
||||||
ret.pushKV("hash_serialized_2", stats.hashSerialized.GetHex());
|
ret.pushKV("hash_serialized_2", stats.hashSerialized.GetHex());
|
||||||
}
|
}
|
||||||
|
if (hash_type == CoinStatsHashType::MUHASH) {
|
||||||
|
ret.pushKV("muhash", stats.hashSerialized.GetHex());
|
||||||
|
}
|
||||||
ret.pushKV("disk_size", stats.nDiskSize);
|
ret.pushKV("disk_size", stats.nDiskSize);
|
||||||
ret.pushKV("total_amount", ValueFromAmount(stats.nTotalAmount));
|
ret.pushKV("total_amount", ValueFromAmount(stats.nTotalAmount));
|
||||||
} else {
|
} else {
|
||||||
|
@ -161,23 +161,6 @@ bool ParseBoolV(const UniValue& v, const std::string &strName)
|
|||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strName+" must be true, false, yes, no, 1 or 0 (not '"+strBool+"')");
|
throw JSONRPCError(RPC_INVALID_PARAMETER, strName+" must be true, false, yes, no, 1 or 0 (not '"+strBool+"')");
|
||||||
}
|
}
|
||||||
|
|
||||||
CoinStatsHashType ParseHashType(const UniValue& param, const CoinStatsHashType default_type)
|
|
||||||
{
|
|
||||||
if (param.isNull()) {
|
|
||||||
return default_type;
|
|
||||||
} else {
|
|
||||||
std::string hash_type_input = param.get_str();
|
|
||||||
|
|
||||||
if (hash_type_input == "hash_serialized_2") {
|
|
||||||
return CoinStatsHashType::HASH_SERIALIZED;
|
|
||||||
} else if (hash_type_input == "none") {
|
|
||||||
return CoinStatsHashType::NONE;
|
|
||||||
} else {
|
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("%d is not a valid hash_type", hash_type_input));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string HelpExampleCli(const std::string& methodname, const std::string& args)
|
std::string HelpExampleCli(const std::string& methodname, const std::string& args)
|
||||||
{
|
{
|
||||||
return "> dash-cli " + methodname + " " + args + "\n";
|
return "> dash-cli " + methodname + " " + args + "\n";
|
||||||
|
@ -69,8 +69,6 @@ extern int64_t ParseInt64V(const UniValue& v, const std::string &strName);
|
|||||||
extern double ParseDoubleV(const UniValue& v, const std::string &strName);
|
extern double ParseDoubleV(const UniValue& v, const std::string &strName);
|
||||||
extern bool ParseBoolV(const UniValue& v, const std::string &strName);
|
extern bool ParseBoolV(const UniValue& v, const std::string &strName);
|
||||||
|
|
||||||
CoinStatsHashType ParseHashType(const UniValue& param, const CoinStatsHashType default_type);
|
|
||||||
|
|
||||||
extern CAmount AmountFromValue(const UniValue& value);
|
extern CAmount AmountFromValue(const UniValue& value);
|
||||||
extern std::string HelpExampleCli(const std::string& methodname, const std::string& args);
|
extern std::string HelpExampleCli(const std::string& methodname, const std::string& args);
|
||||||
extern std::string HelpExampleRpc(const std::string& methodname, const std::string& args);
|
extern std::string HelpExampleRpc(const std::string& methodname, const std::string& args);
|
||||||
|
86
test/functional/feature_utxo_set_hash.py
Executable file
86
test/functional/feature_utxo_set_hash.py
Executable file
@ -0,0 +1,86 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright (c) 2020-2021 The Bitcoin Core developers
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
"""Test UTXO set hash value calculation in gettxoutsetinfo."""
|
||||||
|
|
||||||
|
import struct
|
||||||
|
|
||||||
|
from test_framework.blocktools import create_transaction
|
||||||
|
from test_framework.messages import (
|
||||||
|
CBlock,
|
||||||
|
COutPoint,
|
||||||
|
FromHex,
|
||||||
|
)
|
||||||
|
from test_framework.muhash import MuHash3072
|
||||||
|
from test_framework.test_framework import BitcoinTestFramework
|
||||||
|
from test_framework.util import assert_equal
|
||||||
|
|
||||||
|
class UTXOSetHashTest(BitcoinTestFramework):
|
||||||
|
def set_test_params(self):
|
||||||
|
self.num_nodes = 1
|
||||||
|
self.setup_clean_chain = True
|
||||||
|
|
||||||
|
def skip_test_if_missing_module(self):
|
||||||
|
self.skip_if_no_wallet()
|
||||||
|
|
||||||
|
def test_deterministic_hash_results(self):
|
||||||
|
self.log.info("Test deterministic UTXO set hash results")
|
||||||
|
|
||||||
|
# These depend on the setup_clean_chain option, the chain loaded from the cache
|
||||||
|
assert_equal(self.nodes[0].gettxoutsetinfo()['hash_serialized_2'], "b61ee2cb582d2f4f94493f3d480e9a59d064706e98a12be0f335a3eeadd5678a")
|
||||||
|
assert_equal(self.nodes[0].gettxoutsetinfo("muhash")['muhash'], "dd5ad2a105c2d29495f577245c357409002329b9f4d6182c0af3dc2f462555c8")
|
||||||
|
|
||||||
|
def test_muhash_implementation(self):
|
||||||
|
self.log.info("Test MuHash implementation consistency")
|
||||||
|
|
||||||
|
node = self.nodes[0]
|
||||||
|
|
||||||
|
# Generate 100 blocks and remove the first since we plan to spend its
|
||||||
|
# coinbase
|
||||||
|
block_hashes = node.generate(100)
|
||||||
|
blocks = list(map(lambda block: FromHex(CBlock(), node.getblock(block, False)), block_hashes))
|
||||||
|
spending = blocks.pop(0)
|
||||||
|
|
||||||
|
# Create a spending transaction and mine a block which includes it
|
||||||
|
tx = create_transaction(node, spending.vtx[0].rehash(), node.getnewaddress(), amount=49)
|
||||||
|
txid = node.sendrawtransaction(hexstring=tx.serialize().hex(), maxfeerate=0)
|
||||||
|
|
||||||
|
tx_block = node.generateblock(node.getnewaddress(), [txid])['hash']
|
||||||
|
blocks.append(FromHex(CBlock(), node.getblock(tx_block, False)))
|
||||||
|
|
||||||
|
# Serialize the outputs that should be in the UTXO set and add them to
|
||||||
|
# a MuHash object
|
||||||
|
muhash = MuHash3072()
|
||||||
|
|
||||||
|
for height, block in enumerate(blocks):
|
||||||
|
# The Genesis block coinbase is not part of the UTXO set and we
|
||||||
|
# spent the first mined block
|
||||||
|
height += 2
|
||||||
|
|
||||||
|
for tx in block.vtx:
|
||||||
|
for n, tx_out in enumerate(tx.vout):
|
||||||
|
coinbase = 1 if not tx.vin[0].prevout.hash else 0
|
||||||
|
|
||||||
|
# Skip witness commitment
|
||||||
|
if (coinbase and n > 0):
|
||||||
|
continue
|
||||||
|
|
||||||
|
data = COutPoint(int(tx.rehash(), 16), n).serialize()
|
||||||
|
data += struct.pack("<i", height * 2 + coinbase)
|
||||||
|
data += tx_out.serialize()
|
||||||
|
|
||||||
|
muhash.insert(data)
|
||||||
|
|
||||||
|
finalized = muhash.digest()
|
||||||
|
node_muhash = node.gettxoutsetinfo("muhash")['muhash']
|
||||||
|
|
||||||
|
assert_equal(finalized[::-1].hex(), node_muhash)
|
||||||
|
|
||||||
|
def run_test(self):
|
||||||
|
self.test_deterministic_hash_results()
|
||||||
|
self.test_muhash_implementation()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
UTXOSetHashTest().main()
|
@ -215,6 +215,18 @@ class BlockchainTest(BitcoinTestFramework):
|
|||||||
res5 = node.gettxoutsetinfo(hash_type='none')
|
res5 = node.gettxoutsetinfo(hash_type='none')
|
||||||
assert 'hash_serialized_2' not in res5
|
assert 'hash_serialized_2' not in res5
|
||||||
|
|
||||||
|
# hash_type muhash should return a different UTXO set hash.
|
||||||
|
res6 = node.gettxoutsetinfo(hash_type='muhash')
|
||||||
|
assert 'muhash' in res6
|
||||||
|
assert(res['hash_serialized_2'] != res6['muhash'])
|
||||||
|
|
||||||
|
# muhash should not be included in gettxoutset unless requested.
|
||||||
|
for r in [res, res2, res3, res4, res5]:
|
||||||
|
assert 'muhash' not in r
|
||||||
|
|
||||||
|
# Unknown hash_type raises an error
|
||||||
|
assert_raises_rpc_error(-8, "foohash is not a valid hash_type", node.gettxoutsetinfo, "foohash")
|
||||||
|
|
||||||
def _test_getblockheader(self):
|
def _test_getblockheader(self):
|
||||||
node = self.nodes[0]
|
node = self.nodes[0]
|
||||||
|
|
||||||
|
@ -183,6 +183,7 @@ BASE_SCRIPTS = [
|
|||||||
'rpc_getblockfilter.py',
|
'rpc_getblockfilter.py',
|
||||||
'rpc_invalidateblock.py',
|
'rpc_invalidateblock.py',
|
||||||
'feature_txindex.py',
|
'feature_txindex.py',
|
||||||
|
'feature_utxo_set_hash.py',
|
||||||
'mempool_packages.py',
|
'mempool_packages.py',
|
||||||
'mempool_package_onemore.py',
|
'mempool_package_onemore.py',
|
||||||
'feature_versionbits_warning.py',
|
'feature_versionbits_warning.py',
|
||||||
|
Loading…
Reference in New Issue
Block a user