Skip to content

Commit 765d0f0

Browse files
sipafjahr
authored andcommitted
muhash in gettxoutsetinfo
1 parent d55a51d commit 765d0f0

File tree

3 files changed

+212
-17
lines changed

3 files changed

+212
-17
lines changed

src/hash.h

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <crypto/common.h>
1010
#include <crypto/ripemd160.h>
1111
#include <crypto/sha256.h>
12+
#include <crypto/sha512.h>
1213
#include <prevector.h>
1314
#include <serialize.h>
1415
#include <uint256.h>
@@ -191,6 +192,41 @@ class CHashVerifier : public CHashWriter
191192
}
192193
};
193194

195+
/** A writer stream that computes a 256-bit truncated SHA512. */
196+
class TruncatedSHA512Writer
197+
{
198+
private:
199+
CSHA512 ctx;
200+
201+
const int nType;
202+
const int nVersion;
203+
public:
204+
205+
TruncatedSHA512Writer(int nTypeIn, int nVersionIn) : nType(nTypeIn), nVersion(nVersionIn) {}
206+
207+
int GetType() const { return nType; }
208+
int GetVersion() const { return nVersion; }
209+
210+
void write(const char *pch, size_t size) {
211+
ctx.Write((const unsigned char*)pch, size);
212+
}
213+
214+
uint256 GetHash() {
215+
unsigned char out[64];
216+
ctx.Finalize(out);
217+
uint256 result;
218+
memcpy((unsigned char*)&result, out, 32);
219+
return result;
220+
}
221+
222+
template<typename T>
223+
TruncatedSHA512Writer& operator<<(const T& obj) {
224+
// Serialize to this stream
225+
::Serialize(*this, obj);
226+
return (*this);
227+
}
228+
};
229+
194230
/** Compute the 256-bit hash of an object's serialization. */
195231
template<typename T>
196232
uint256 SerializeHash(const T& obj, int nType=SER_GETHASH, int nVersion=PROTOCOL_VERSION)

src/rpc/blockchain.cpp

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <chainparams.h>
1212
#include <coins.h>
1313
#include <consensus/validation.h>
14+
#include <crypto/muhash.h>
1415
#include <core_io.h>
1516
#include <hash.h>
1617
#include <index/blockfilterindex.h>
@@ -35,6 +36,7 @@
3536
#include <warnings.h>
3637

3738
#include <assert.h>
39+
3840
#include <stdint.h>
3941

4042
#include <univalue.h>
@@ -916,29 +918,28 @@ struct CCoinsStats
916918
uint64_t nTransactions;
917919
uint64_t nTransactionOutputs;
918920
uint64_t nBogoSize;
919-
uint256 hashSerialized;
921+
uint256 muhash;
920922
uint64_t nDiskSize;
921923
CAmount nTotalAmount;
922924

923925
CCoinsStats() : nHeight(0), nTransactions(0), nTransactionOutputs(0), nBogoSize(0), nDiskSize(0), nTotalAmount(0) {}
924926
};
925927

926-
static void ApplyStats(CCoinsStats &stats, CHashWriter& ss, const uint256& hash, const std::map<uint32_t, Coin>& outputs)
928+
static void ApplyStats(CCoinsStats &stats, MuHash3072& acc, const uint256& hash, const std::map<uint32_t, Coin>& outputs)
927929
{
928930
assert(!outputs.empty());
929-
ss << hash;
930-
ss << VARINT(outputs.begin()->second.nHeight * 2 + outputs.begin()->second.fCoinBase ? 1u : 0u);
931931
stats.nTransactions++;
932-
for (const auto& output : outputs) {
933-
ss << VARINT(output.first + 1);
934-
ss << output.second.out.scriptPubKey;
935-
ss << VARINT(output.second.out.nValue, VarIntMode::NONNEGATIVE_SIGNED);
932+
for (const auto output : outputs) {
933+
TruncatedSHA512Writer ss(SER_DISK, 0);
934+
ss << COutPoint(hash, output.first);
935+
ss << (uint32_t)(output.second.nHeight * 2 + output.second.fCoinBase);
936+
ss << output.second.out;
937+
acc *= MuHash3072(ss.GetHash().begin());
936938
stats.nTransactionOutputs++;
937939
stats.nTotalAmount += output.second.out.nValue;
938940
stats.nBogoSize += 32 /* txid */ + 4 /* vout index */ + 4 /* height + coinbase */ + 8 /* amount */ +
939941
2 /* scriptPubKey len */ + output.second.out.scriptPubKey.size() /* scriptPubKey */;
940942
}
941-
ss << VARINT(0u);
942943
}
943944

944945
//! Calculate statistics about the unspent transaction output set
@@ -947,22 +948,21 @@ static bool GetUTXOStats(CCoinsView *view, CCoinsStats &stats)
947948
std::unique_ptr<CCoinsViewCursor> pcursor(view->Cursor());
948949
assert(pcursor);
949950

950-
CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION);
951951
stats.hashBlock = pcursor->GetBestBlock();
952952
{
953953
LOCK(cs_main);
954954
stats.nHeight = LookupBlockIndex(stats.hashBlock)->nHeight;
955955
}
956-
ss << stats.hashBlock;
957-
uint256 prevkey;
956+
MuHash3072 acc;
958957
std::map<uint32_t, Coin> outputs;
958+
uint256 prevkey;
959959
while (pcursor->Valid()) {
960960
boost::this_thread::interruption_point();
961961
COutPoint key;
962962
Coin coin;
963963
if (pcursor->GetKey(key) && pcursor->GetValue(coin)) {
964964
if (!outputs.empty() && key.hash != prevkey) {
965-
ApplyStats(stats, ss, prevkey, outputs);
965+
ApplyStats(stats, acc, prevkey, outputs);
966966
outputs.clear();
967967
}
968968
prevkey = key.hash;
@@ -973,9 +973,13 @@ static bool GetUTXOStats(CCoinsView *view, CCoinsStats &stats)
973973
pcursor->Next();
974974
}
975975
if (!outputs.empty()) {
976-
ApplyStats(stats, ss, prevkey, outputs);
976+
ApplyStats(stats, acc, prevkey, outputs);
977977
}
978-
stats.hashSerialized = ss.GetHash();
978+
unsigned char data[384];
979+
acc.Finalize(data);
980+
TruncatedSHA512Writer ss(SER_DISK, 0);
981+
ss << FLATDATA(data);
982+
stats.muhash = ss.GetHash();
979983
stats.nDiskSize = view->EstimateSize();
980984
return true;
981985
}
@@ -1049,7 +1053,7 @@ static UniValue gettxoutsetinfo(const JSONRPCRequest& request)
10491053
" \"transactions\": n, (numeric) The number of transactions with unspent outputs\n"
10501054
" \"txouts\": n, (numeric) The number of unspent transaction outputs\n"
10511055
" \"bogosize\": n, (numeric) A meaningless metric for UTXO set size\n"
1052-
" \"hash_serialized_2\": \"hash\", (string) The serialized hash\n"
1056+
" \"muhash\": \"hash\", (string) Rolling UTXO set hash\n"
10531057
" \"disk_size\": n, (numeric) The estimated size of the chainstate on disk\n"
10541058
" \"total_amount\": x.xxx (numeric) The total amount\n"
10551059
"}\n"
@@ -1072,7 +1076,7 @@ static UniValue gettxoutsetinfo(const JSONRPCRequest& request)
10721076
ret.pushKV("transactions", (int64_t)stats.nTransactions);
10731077
ret.pushKV("txouts", (int64_t)stats.nTransactionOutputs);
10741078
ret.pushKV("bogosize", (int64_t)stats.nBogoSize);
1075-
ret.pushKV("hash_serialized_2", stats.hashSerialized.GetHex());
1079+
ret.pushKV("muhash", stats.muhash.GetHex());
10761080
ret.pushKV("disk_size", stats.nDiskSize);
10771081
ret.pushKV("total_amount", ValueFromAmount(stats.nTotalAmount));
10781082
} else {

test/functional/blockchain.py

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2014-2016 The Bitcoin Core developers
3+
# Distributed under the MIT software license, see the accompanying
4+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5+
"""Test RPCs related to blockchainstate.
6+
7+
Test the following RPCs:
8+
- gettxoutsetinfo
9+
- getdifficulty
10+
- getbestblockhash
11+
- getblockhash
12+
- getblockheader
13+
- getchaintxstats
14+
- getnetworkhashps
15+
- verifychain
16+
17+
Tests correspond to code in rpc/blockchain.cpp.
18+
"""
19+
20+
from decimal import Decimal
21+
import http.client
22+
import subprocess
23+
24+
from test_framework.test_framework import (BitcoinTestFramework, BITCOIND_PROC_WAIT_TIMEOUT)
25+
from test_framework.util import (
26+
assert_equal,
27+
assert_raises,
28+
assert_raises_jsonrpc,
29+
assert_is_hex_string,
30+
assert_is_hash_string,
31+
)
32+
33+
34+
class BlockchainTest(BitcoinTestFramework):
35+
36+
def __init__(self):
37+
super().__init__()
38+
self.setup_clean_chain = False
39+
self.num_nodes = 1
40+
self.extra_args = [['-stopatheight=207']]
41+
42+
def run_test(self):
43+
self._test_getchaintxstats()
44+
self._test_gettxoutsetinfo()
45+
self._test_getblockheader()
46+
self._test_getdifficulty()
47+
self._test_getnetworkhashps()
48+
self._test_stopatheight()
49+
assert self.nodes[0].verifychain(4, 0)
50+
51+
def _test_getchaintxstats(self):
52+
chaintxstats = self.nodes[0].getchaintxstats(1)
53+
# 200 txs plus genesis tx
54+
assert_equal(chaintxstats['txcount'], 201)
55+
# tx rate should be 1 per 10 minutes, or 1/600
56+
# we have to round because of binary math
57+
assert_equal(round(chaintxstats['txrate'] * 600, 10), Decimal(1))
58+
59+
def _test_gettxoutsetinfo(self):
60+
node = self.nodes[0]
61+
res = node.gettxoutsetinfo()
62+
63+
assert_equal(res['total_amount'], Decimal('8725.00000000'))
64+
assert_equal(res['transactions'], 200)
65+
assert_equal(res['height'], 200)
66+
assert_equal(res['txouts'], 200)
67+
assert_equal(res['bogosize'], 17000),
68+
assert_equal(res['bestblock'], node.getblockhash(200))
69+
size = res['disk_size']
70+
assert size > 6400
71+
assert size < 64000
72+
assert_equal(len(res['bestblock']), 64)
73+
assert_equal(len(res['muhash']), 64)
74+
75+
self.log.info("Test that gettxoutsetinfo() works for blockchain with just the genesis block")
76+
b1hash = node.getblockhash(1)
77+
node.invalidateblock(b1hash)
78+
79+
res2 = node.gettxoutsetinfo()
80+
assert_equal(res2['transactions'], 0)
81+
assert_equal(res2['total_amount'], Decimal('0'))
82+
assert_equal(res2['height'], 0)
83+
assert_equal(res2['txouts'], 0)
84+
assert_equal(res2['bogosize'], 0),
85+
assert_equal(res2['bestblock'], node.getblockhash(0))
86+
assert_equal(res2['muhash'], '1ab6089a655235d9609a687448dc4cc429302704eded8b0615f93bfe03ec658f')
87+
assert res2['muhash'] != res['muhash']
88+
89+
self.log.info("Test that gettxoutsetinfo() returns the same result after invalidate/reconsider block")
90+
node.reconsiderblock(b1hash)
91+
92+
res3 = node.gettxoutsetinfo()
93+
assert_equal(res['total_amount'], res3['total_amount'])
94+
assert_equal(res['transactions'], res3['transactions'])
95+
assert_equal(res['height'], res3['height'])
96+
assert_equal(res['txouts'], res3['txouts'])
97+
assert_equal(res['bogosize'], res3['bogosize'])
98+
assert_equal(res['bestblock'], res3['bestblock'])
99+
assert_equal(res['muhash'], res3['muhash'])
100+
101+
def _test_getblockheader(self):
102+
node = self.nodes[0]
103+
104+
assert_raises_jsonrpc(-5, "Block not found",
105+
node.getblockheader, "nonsense")
106+
107+
besthash = node.getbestblockhash()
108+
secondbesthash = node.getblockhash(199)
109+
header = node.getblockheader(besthash)
110+
111+
assert_equal(header['hash'], besthash)
112+
assert_equal(header['height'], 200)
113+
assert_equal(header['confirmations'], 1)
114+
assert_equal(header['previousblockhash'], secondbesthash)
115+
assert_is_hex_string(header['chainwork'])
116+
assert_is_hash_string(header['hash'])
117+
assert_is_hash_string(header['previousblockhash'])
118+
assert_is_hash_string(header['merkleroot'])
119+
assert_is_hash_string(header['bits'], length=None)
120+
assert isinstance(header['time'], int)
121+
assert isinstance(header['mediantime'], int)
122+
assert isinstance(header['nonce'], int)
123+
assert isinstance(header['version'], int)
124+
assert isinstance(int(header['versionHex'], 16), int)
125+
assert isinstance(header['difficulty'], Decimal)
126+
127+
def _test_getdifficulty(self):
128+
difficulty = self.nodes[0].getdifficulty()
129+
# 1 hash in 2 should be valid, so difficulty should be 1/2**31
130+
# binary => decimal => binary math is why we do this check
131+
assert abs(difficulty * 2**31 - 1) < 0.0001
132+
133+
def _test_getnetworkhashps(self):
134+
hashes_per_second = self.nodes[0].getnetworkhashps()
135+
# This should be 2 hashes every 10 minutes or 1/300
136+
assert abs(hashes_per_second * 300 - 1) < 0.0001
137+
138+
def _test_stopatheight(self):
139+
assert_equal(self.nodes[0].getblockcount(), 200)
140+
self.nodes[0].generate(6)
141+
assert_equal(self.nodes[0].getblockcount(), 206)
142+
self.log.debug('Node should not stop at this height')
143+
assert_raises(subprocess.TimeoutExpired, lambda: self.bitcoind_processes[0].wait(timeout=3))
144+
try:
145+
self.nodes[0].generate(1)
146+
except (ConnectionError, http.client.BadStatusLine):
147+
pass # The node already shut down before response
148+
self.log.debug('Node should stop at this height...')
149+
self.bitcoind_processes[0].wait(timeout=BITCOIND_PROC_WAIT_TIMEOUT)
150+
self.nodes[0] = self.start_node(0, self.options.tmpdir)
151+
assert_equal(self.nodes[0].getblockcount(), 207)
152+
153+
154+
if __name__ == '__main__':
155+
BlockchainTest().main()

0 commit comments

Comments
 (0)