Skip to content

Commit 6364408

Browse files
committed
Merge pull request #5199
1ec900a Remove broken+useless lock/unlock log prints (Matt Corallo) 352ed22 Add merkle blocks test (Matt Corallo) 59ed61b Add RPC call to generate and verify merkle blocks (Matt Corallo) 30da90d Add CMerkleBlock constructor for tx set + block and an empty one (Matt Corallo)
2 parents f9645ba + 1ec900a commit 6364408

File tree

9 files changed

+238
-5
lines changed

9 files changed

+238
-5
lines changed

qa/pull-tester/rpc-tests.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ testScripts=(
2828
'httpbasics.py'
2929
'zapwallettxes.py'
3030
'proxy_test.py'
31+
'merkle_blocks.py'
3132
# 'forknotify.py'
3233
);
3334
if [ "x${ENABLE_BITCOIND}${ENABLE_UTILS}${ENABLE_WALLET}" = "x111" ]; then

qa/rpc-tests/merkle_blocks.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
#!/usr/bin/env python2
2+
# Copyright (c) 2014 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+
6+
#
7+
# Test merkleblock fetch/validation
8+
#
9+
10+
from test_framework import BitcoinTestFramework
11+
from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException
12+
from util import *
13+
import os
14+
import shutil
15+
16+
class MerkleBlockTest(BitcoinTestFramework):
17+
18+
def setup_chain(self):
19+
print("Initializing test directory "+self.options.tmpdir)
20+
initialize_chain_clean(self.options.tmpdir, 4)
21+
22+
def setup_network(self):
23+
self.nodes = []
24+
# Nodes 0/1 are "wallet" nodes
25+
self.nodes.append(start_node(0, self.options.tmpdir, ["-debug"]))
26+
self.nodes.append(start_node(1, self.options.tmpdir, ["-debug"]))
27+
# Nodes 2/3 are used for testing
28+
self.nodes.append(start_node(2, self.options.tmpdir, ["-debug"]))
29+
self.nodes.append(start_node(3, self.options.tmpdir, ["-debug", "-txindex"]))
30+
connect_nodes(self.nodes[0], 1)
31+
connect_nodes(self.nodes[0], 2)
32+
connect_nodes(self.nodes[0], 3)
33+
34+
self.is_network_split = False
35+
self.sync_all()
36+
37+
def run_test(self):
38+
print "Mining blocks..."
39+
self.nodes[0].generate(105)
40+
self.sync_all()
41+
42+
chain_height = self.nodes[1].getblockcount()
43+
assert_equal(chain_height, 105)
44+
assert_equal(self.nodes[1].getbalance(), 0)
45+
assert_equal(self.nodes[2].getbalance(), 0)
46+
47+
node0utxos = self.nodes[0].listunspent(1)
48+
tx1 = self.nodes[0].createrawtransaction([node0utxos.pop()], {self.nodes[1].getnewaddress(): 50})
49+
txid1 = self.nodes[0].sendrawtransaction(self.nodes[0].signrawtransaction(tx1)["hex"])
50+
tx2 = self.nodes[0].createrawtransaction([node0utxos.pop()], {self.nodes[1].getnewaddress(): 50})
51+
txid2 = self.nodes[0].sendrawtransaction(self.nodes[0].signrawtransaction(tx2)["hex"])
52+
assert_raises(JSONRPCException, self.nodes[0].gettxoutproof, [txid1])
53+
54+
self.nodes[0].generate(1)
55+
blockhash = self.nodes[0].getblockhash(chain_height + 1)
56+
self.sync_all()
57+
58+
txlist = []
59+
blocktxn = self.nodes[0].getblock(blockhash, True)["tx"]
60+
txlist.append(blocktxn[1])
61+
txlist.append(blocktxn[2])
62+
63+
assert_equal(self.nodes[2].verifytxoutproof(self.nodes[2].gettxoutproof([txid1])), [txid1])
64+
assert_equal(self.nodes[2].verifytxoutproof(self.nodes[2].gettxoutproof([txid1, txid2])), txlist)
65+
assert_equal(self.nodes[2].verifytxoutproof(self.nodes[2].gettxoutproof([txid1, txid2], blockhash)), txlist)
66+
67+
txin_spent = self.nodes[1].listunspent(1).pop()
68+
tx3 = self.nodes[1].createrawtransaction([txin_spent], {self.nodes[0].getnewaddress(): 50})
69+
self.nodes[0].sendrawtransaction(self.nodes[1].signrawtransaction(tx3)["hex"])
70+
self.nodes[0].generate(1)
71+
self.sync_all()
72+
73+
txid_spent = txin_spent["txid"]
74+
txid_unspent = txid1 if txin_spent["txid"] != txid1 else txid2
75+
76+
# We cant find the block from a fully-spent tx
77+
assert_raises(JSONRPCException, self.nodes[2].gettxoutproof, [txid_spent])
78+
# ...but we can if we specify the block
79+
assert_equal(self.nodes[2].verifytxoutproof(self.nodes[2].gettxoutproof([txid_spent], blockhash)), [txid_spent])
80+
# ...or if the first tx is not fully-spent
81+
assert_equal(self.nodes[2].verifytxoutproof(self.nodes[2].gettxoutproof([txid_unspent])), [txid_unspent])
82+
try:
83+
assert_equal(self.nodes[2].verifytxoutproof(self.nodes[2].gettxoutproof([txid1, txid2])), txlist)
84+
except JSONRPCException:
85+
assert_equal(self.nodes[2].verifytxoutproof(self.nodes[2].gettxoutproof([txid2, txid1])), txlist)
86+
# ...or if we have a -txindex
87+
assert_equal(self.nodes[2].verifytxoutproof(self.nodes[3].gettxoutproof([txid_spent])), [txid_spent])
88+
89+
if __name__ == '__main__':
90+
MerkleBlockTest().main()

src/merkleblock.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,29 @@ CMerkleBlock::CMerkleBlock(const CBlock& block, CBloomFilter& filter)
3737
txn = CPartialMerkleTree(vHashes, vMatch);
3838
}
3939

40+
CMerkleBlock::CMerkleBlock(const CBlock& block, const std::set<uint256>& txids)
41+
{
42+
header = block.GetBlockHeader();
43+
44+
vector<bool> vMatch;
45+
vector<uint256> vHashes;
46+
47+
vMatch.reserve(block.vtx.size());
48+
vHashes.reserve(block.vtx.size());
49+
50+
for (unsigned int i = 0; i < block.vtx.size(); i++)
51+
{
52+
const uint256& hash = block.vtx[i].GetHash();
53+
if (txids.count(hash))
54+
vMatch.push_back(true);
55+
else
56+
vMatch.push_back(false);
57+
vHashes.push_back(hash);
58+
}
59+
60+
txn = CPartialMerkleTree(vHashes, vMatch);
61+
}
62+
4063
uint256 CPartialMerkleTree::CalcHash(int height, unsigned int pos, const std::vector<uint256> &vTxid) {
4164
if (height == 0) {
4265
// hash at height 0 is the txids themself

src/merkleblock.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,11 @@ class CMerkleBlock
139139
*/
140140
CMerkleBlock(const CBlock& block, CBloomFilter& filter);
141141

142+
// Create from a CBlock, matching the txids in the set
143+
CMerkleBlock(const CBlock& block, const std::set<uint256>& txids);
144+
145+
CMerkleBlock() {}
146+
142147
ADD_SERIALIZE_METHODS;
143148

144149
template <typename Stream, typename Operation>

src/rpcclient.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
7979
{ "sendrawtransaction", 1 },
8080
{ "gettxout", 1 },
8181
{ "gettxout", 2 },
82+
{ "gettxoutproof", 0 },
8283
{ "lockunspent", 0 },
8384
{ "lockunspent", 1 },
8485
{ "importprivkey", 2 },

src/rpcrawtransaction.cpp

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "init.h"
1010
#include "keystore.h"
1111
#include "main.h"
12+
#include "merkleblock.h"
1213
#include "net.h"
1314
#include "rpcserver.h"
1415
#include "script/script.h"
@@ -193,6 +194,119 @@ Value getrawtransaction(const Array& params, bool fHelp)
193194
return result;
194195
}
195196

197+
Value gettxoutproof(const Array& params, bool fHelp)
198+
{
199+
if (fHelp || (params.size() != 1 && params.size() != 2))
200+
throw runtime_error(
201+
"gettxoutproof [\"txid\",...] ( blockhash )\n"
202+
"\nReturns a hex-encoded proof that \"txid\" was included in a block.\n"
203+
"\nNOTE: By default this function only works sometimes. This is when there is an\n"
204+
"unspent output in the utxo for this transaction. To make it always work,\n"
205+
"you need to maintain a transaction index, using the -txindex command line option or\n"
206+
"specify the block in which the transaction is included in manually (by blockhash).\n"
207+
"\nReturn the raw transaction data.\n"
208+
"\nArguments:\n"
209+
"1. \"txids\" (string) A json array of txids to filter\n"
210+
" [\n"
211+
" \"txid\" (string) A transaction hash\n"
212+
" ,...\n"
213+
" ]\n"
214+
"2. \"block hash\" (string, optional) If specified, looks for txid in the block with this hash\n"
215+
"\nResult:\n"
216+
"\"data\" (string) A string that is a serialized, hex-encoded data for the proof.\n"
217+
);
218+
219+
set<uint256> setTxids;
220+
uint256 oneTxid;
221+
Array txids = params[0].get_array();
222+
BOOST_FOREACH(Value& txid, txids) {
223+
if (txid.get_str().length() != 64 || !IsHex(txid.get_str()))
224+
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid txid ")+txid.get_str());
225+
uint256 hash(uint256S(txid.get_str()));
226+
if (setTxids.count(hash))
227+
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, duplicated txid: ")+txid.get_str());
228+
setTxids.insert(hash);
229+
oneTxid = hash;
230+
}
231+
232+
LOCK(cs_main);
233+
234+
CBlockIndex* pblockindex = NULL;
235+
236+
uint256 hashBlock;
237+
if (params.size() > 1)
238+
{
239+
hashBlock = uint256S(params[1].get_str());
240+
if (!mapBlockIndex.count(hashBlock))
241+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
242+
pblockindex = mapBlockIndex[hashBlock];
243+
} else {
244+
CCoins coins;
245+
if (pcoinsTip->GetCoins(oneTxid, coins) && coins.nHeight > 0 && coins.nHeight <= chainActive.Height())
246+
pblockindex = chainActive[coins.nHeight];
247+
}
248+
249+
if (pblockindex == NULL)
250+
{
251+
CTransaction tx;
252+
if (!GetTransaction(oneTxid, tx, hashBlock, false) || hashBlock.IsNull())
253+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not yet in block");
254+
if (!mapBlockIndex.count(hashBlock))
255+
throw JSONRPCError(RPC_INTERNAL_ERROR, "Transaction index corrupt");
256+
pblockindex = mapBlockIndex[hashBlock];
257+
}
258+
259+
CBlock block;
260+
if(!ReadBlockFromDisk(block, pblockindex))
261+
throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk");
262+
263+
unsigned int ntxFound = 0;
264+
BOOST_FOREACH(const CTransaction&tx, block.vtx)
265+
if (setTxids.count(tx.GetHash()))
266+
ntxFound++;
267+
if (ntxFound != setTxids.size())
268+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "(Not all) transactions not found in specified block");
269+
270+
CDataStream ssMB(SER_NETWORK, PROTOCOL_VERSION);
271+
CMerkleBlock mb(block, setTxids);
272+
ssMB << mb;
273+
std::string strHex = HexStr(ssMB.begin(), ssMB.end());
274+
return strHex;
275+
}
276+
277+
Value verifytxoutproof(const Array& params, bool fHelp)
278+
{
279+
if (fHelp || params.size() != 1)
280+
throw runtime_error(
281+
"verifytxoutproof \"proof\"\n"
282+
"\nVerifies that a proof points to a transaction in a block, returning the transaction it commits to\n"
283+
"and throwing an RPC error if the block is not in our best chain\n"
284+
"\nArguments:\n"
285+
"1. \"proof\" (string, required) The hex-encoded proof generated by gettxoutproof\n"
286+
"\nResult:\n"
287+
"[\"txid\"] (array, strings) The txid(s) which the proof commits to, or empty array if the proof is invalid\n"
288+
);
289+
290+
CDataStream ssMB(ParseHexV(params[0], "proof"), SER_NETWORK, PROTOCOL_VERSION);
291+
CMerkleBlock merkleBlock;
292+
ssMB >> merkleBlock;
293+
294+
Array res;
295+
296+
vector<uint256> vMatch;
297+
if (merkleBlock.txn.ExtractMatches(vMatch) != merkleBlock.header.hashMerkleRoot)
298+
return res;
299+
300+
LOCK(cs_main);
301+
302+
if (!mapBlockIndex.count(merkleBlock.header.GetHash()) || !chainActive.Contains(mapBlockIndex[merkleBlock.header.GetHash()]))
303+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain");
304+
305+
BOOST_FOREACH(const uint256& hash, vMatch)
306+
res.push_back(hash.GetHex());
307+
return res;
308+
}
309+
196310
Value createrawtransaction(const Array& params, bool fHelp)
197311
{
198312
if (fHelp || params.size() != 2)

src/rpcserver.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,8 @@ static const CRPCCommand vRPCCommands[] =
293293
{ "blockchain", "getmempoolinfo", &getmempoolinfo, true },
294294
{ "blockchain", "getrawmempool", &getrawmempool, true },
295295
{ "blockchain", "gettxout", &gettxout, true },
296+
{ "blockchain", "gettxoutproof", &gettxoutproof, true },
297+
{ "blockchain", "verifytxoutproof", &verifytxoutproof, true },
296298
{ "blockchain", "gettxoutsetinfo", &gettxoutsetinfo, true },
297299
{ "blockchain", "verifychain", &verifychain, true },
298300

src/rpcserver.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,8 @@ extern json_spirit::Value decoderawtransaction(const json_spirit::Array& params,
218218
extern json_spirit::Value decodescript(const json_spirit::Array& params, bool fHelp);
219219
extern json_spirit::Value signrawtransaction(const json_spirit::Array& params, bool fHelp);
220220
extern json_spirit::Value sendrawtransaction(const json_spirit::Array& params, bool fHelp);
221+
extern json_spirit::Value gettxoutproof(const json_spirit::Array& params, bool fHelp);
222+
extern json_spirit::Value verifytxoutproof(const json_spirit::Array& params, bool fHelp);
221223

222224
extern json_spirit::Value getblockcount(const json_spirit::Array& params, bool fHelp); // in rpcblockchain.cpp
223225
extern json_spirit::Value getbestblockhash(const json_spirit::Array& params, bool fHelp);

src/sync.cpp

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,6 @@ static void push_lock(void* c, const CLockLocation& locklocation, bool fTry)
8686
if (lockstack.get() == NULL)
8787
lockstack.reset(new LockStack);
8888

89-
LogPrint("lock", "Locking: %s\n", locklocation.ToString());
9089
dd_mutex.lock();
9190

9291
(*lockstack).push_back(std::make_pair(c, locklocation));
@@ -113,10 +112,6 @@ static void push_lock(void* c, const CLockLocation& locklocation, bool fTry)
113112

114113
static void pop_lock()
115114
{
116-
if (fDebug) {
117-
const CLockLocation& locklocation = (*lockstack).rbegin()->second;
118-
LogPrint("lock", "Unlocked: %s\n", locklocation.ToString());
119-
}
120115
dd_mutex.lock();
121116
(*lockstack).pop_back();
122117
dd_mutex.unlock();

0 commit comments

Comments
 (0)