Skip to content

Commit 7927e87

Browse files
committed
Merge #400: 2WP: Verify number of transactions in SPV proof
85eb502 2WP: Verify number of transactions in SPV proof (Steven Roose) 621b52f Check parent RPC version on startup (Steven Roose) 7bfb100 expose CBlockIndex::nTx in getblock(header) (Gregory Sanders)
2 parents d644293 + 85eb502 commit 7927e87

File tree

9 files changed

+80
-15
lines changed

9 files changed

+80
-15
lines changed

qa/rpc-tests/pruning.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,10 +269,17 @@ def has_block(index):
269269
# should not prune because chain tip of node 3 (995) < PruneAfterHeight (1000)
270270
assert_raises_message(JSONRPCException, "Blockchain is too short for pruning", node.pruneblockchain, height(500))
271271

272+
# Save block transaction count before pruning, assert value
273+
block1_details = node.getblock(node.getblockhash(1))
274+
assert_equal(block1_details["nTx"], len(block1_details["tx"]))
275+
272276
# mine 6 blocks so we are at height 1001 (i.e., above PruneAfterHeight)
273277
node.generate(6)
274278
assert_equal(node.getblockchaininfo()["blocks"], 1001)
275279

280+
# Pruned block should still know the number of transactions
281+
assert_equal(node.getblockheader(node.getblockhash(1))["nTx"], block1_details["nTx"])
282+
276283
# negative heights should raise an exception
277284
assert_raises_message(JSONRPCException, "Negative", node.pruneblockchain, -10)
278285

src/callrpc.cpp

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ UniValue CallRPC(const std::string& strMethod, const UniValue& params, bool conn
178178
return reply;
179179
}
180180

181-
bool IsConfirmedBitcoinBlock(const uint256& hash, int nMinConfirmationDepth)
181+
bool IsConfirmedBitcoinBlock(const uint256& hash, const int nMinConfirmationDepth, const int nbTxs)
182182
{
183183
try {
184184
UniValue params(UniValue::VARR);
@@ -189,8 +189,21 @@ bool IsConfirmedBitcoinBlock(const uint256& hash, int nMinConfirmationDepth)
189189
UniValue result = find_value(reply, "result");
190190
if (!result.isObject())
191191
return false;
192-
result = find_value(result.get_obj(), "confirmations");
193-
return result.isNum() && result.get_int64() >= nMinConfirmationDepth;
192+
193+
UniValue confirmations = find_value(result.get_obj(), "confirmations");
194+
if (!confirmations.isNum() || confirmations.get_int64() < nMinConfirmationDepth) {
195+
return false;
196+
}
197+
198+
// Only perform extra test if nbTxs has been provided (non-zero).
199+
if (nbTxs != 0) {
200+
UniValue nTx = find_value(result.get_obj(), "nTx");
201+
if (!nTx.isNum() || nTx.get_int64() != nbTxs) {
202+
LogPrintf("ERROR: Invalid number of transactions in merkle block for %s",
203+
hash.GetHex());
204+
return false;
205+
}
206+
}
194207
} catch (CConnectionFailed& e) {
195208
LogPrintf("ERROR: Lost connection to bitcoind RPC, you will want to restart after fixing this!\n");
196209
return false;

src/callrpc.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ class CConnectionFailed : public std::runtime_error
3535
};
3636

3737
UniValue CallRPC(const std::string& strMethod, const UniValue& params, bool connectToMainchain=false);
38-
bool IsConfirmedBitcoinBlock(const uint256& hash, int nMinConfirmationDepth);
38+
39+
// Verify if the block with given hash has at least the specified minimum number
40+
// of confirmations.
41+
// For validating merkle blocks, you can provide the nbTxs parameter to verify if
42+
// it equals the number of transactions in the block.
43+
bool IsConfirmedBitcoinBlock(const uint256& hash, int nMinConfirmationDepth, int nbTxs);
3944

4045
#endif // BITCOIN_CALLRPC_H

src/merkleblock.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,11 @@ class CPartialMerkleTree
115115
* returns the merkle root, or 0 in case of failure
116116
*/
117117
uint256 ExtractMatches(std::vector<uint256> &vMatch, std::vector<unsigned int> &vnIndex);
118+
119+
/** Get number of transactions the merkle proof is indicating for cross-reference with
120+
* local blockchain knowledge.
121+
*/
122+
unsigned int GetNumTransactions() const { return nTransactions; };
118123
};
119124

120125

src/primitives/bitcoin/merkleblock.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,11 @@ class CPartialMerkleTree
120120
* returns the merkle root, or 0 in case of failure
121121
*/
122122
uint256 ExtractMatches(std::vector<uint256> &vMatch, std::vector<unsigned int> &vnIndex);
123+
124+
/** Get number of transactions the merkle proof is indicating for cross-reference with
125+
* local blockchain knowledge.
126+
*/
127+
unsigned int GetNumTransactions() const { return nTransactions; };
123128
};
124129

125130

src/rpc/blockchain.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ UniValue blockheaderToJSON(const CBlockIndex* blockindex)
6262
result.push_back(Pair("merkleroot", blockindex->hashMerkleRoot.GetHex()));
6363
result.push_back(Pair("time", (int64_t)blockindex->nTime));
6464
result.push_back(Pair("mediantime", (int64_t)blockindex->GetMedianTimePast()));
65+
result.push_back(Pair("nTx", (uint64_t)blockindex->nTx));
6566
result.push_back(Pair("signblock_witness_asm", ScriptToAsmStr(blockindex->proof.solution)));
6667
result.push_back(Pair("signblock_witness_hex", HexStr(blockindex->proof.solution)));
6768

@@ -104,6 +105,7 @@ UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool tx
104105
result.push_back(Pair("tx", txs));
105106
result.push_back(Pair("time", block.GetBlockTime()));
106107
result.push_back(Pair("mediantime", (int64_t)blockindex->GetMedianTimePast()));
108+
result.push_back(Pair("nTx", (int64_t)blockindex->nTx));
107109
result.push_back(Pair("signblock_witness_asm", ScriptToAsmStr(blockindex->proof.solution)));
108110
result.push_back(Pair("signblock_witness_hex", HexStr(blockindex->proof.solution)));
109111

@@ -605,8 +607,7 @@ UniValue getblockheader(const JSONRPCRequest& request)
605607
" \"merkleroot\" : \"xxxx\", (string) The merkle root\n"
606608
" \"time\" : ttt, (numeric) The block time in seconds since epoch (Jan 1 1970 GMT)\n"
607609
" \"mediantime\" : ttt, (numeric) The median block time in seconds since epoch (Jan 1 1970 GMT)\n"
608-
" \"signblock_witness_asm\":\"asm\", (string) scriptSig for block signing (asm)'\n"
609-
" \"signblock_witness_hex\":\"hex\", (string) scriptSig for block signing (hex)'\n"
610+
" \"nTx\" : n, (numeric) The number of transactions in the block.\n"
610611
" \"previousblockhash\" : \"hash\", (string) The hash of the previous block\n"
611612
" \"nextblockhash\" : \"hash\", (string) The hash of the next block\n"
612613
"}\n"
@@ -691,8 +692,7 @@ static UniValue getblock(const JSONRPCRequest& request)
691692
" ],\n"
692693
" \"time\" : ttt, (numeric) The block time in seconds since epoch (Jan 1 1970 GMT)\n"
693694
" \"mediantime\" : ttt, (numeric) The median block time in seconds since epoch (Jan 1 1970 GMT)\n"
694-
" \"signblock_witness_asm\":\"asm\", (string) scriptSig for block signing (asm)'\n"
695-
" \"signblock_witness_hex\":\"hex\", (string) scriptSig for block signing (hex)'\n"
695+
" \"nTx\" : n, (numeric) The number of transactions in the block.\n"
696696
" \"previousblockhash\" : \"hash\", (string) The hash of the previous block\n"
697697
" \"nextblockhash\" : \"hash\" (string) The hash of the next block\n"
698698
"}\n"

src/validation.cpp

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2229,16 +2229,18 @@ bool BitcoindRPCCheck(const bool init)
22292229
vblocksToReconsiderAgain.clear();
22302230
pblocktree->WriteInvalidBlockQueue(vblocksToReconsider);
22312231

2232-
//Next, check for working rpc
2232+
// Next, check for working and valid rpc
22332233
if (GetBoolArg("-validatepegin", DEFAULT_VALIDATE_PEGIN)) {
22342234
// During init try until a non-RPC_IN_WARMUP result
22352235
while (true) {
22362236
try {
2237+
// The first thing we have to check is the version of the node.
22372238
UniValue params(UniValue::VARR);
2238-
params.push_back(UniValue(0));
2239-
UniValue reply = CallRPC("getblockhash", params, true);
2240-
UniValue error = find_value(reply, "error");
2239+
UniValue reply = CallRPC("getnetworkinfo", params, true);
2240+
UniValue error = reply["error"];
22412241
if (!error.isNull()) {
2242+
// On the first call, it's possible to node is still in
2243+
// warmup; in that case, just wait and retry.
22422244
if (error["code"].get_int() == RPC_IN_WARMUP) {
22432245
MilliSleep(1000);
22442246
continue;
@@ -2249,6 +2251,22 @@ bool BitcoindRPCCheck(const bool init)
22492251
}
22502252
}
22512253
UniValue result = reply["result"];
2254+
if (!result.isObject() || !result.get_obj()["version"].isNum() ||
2255+
result.get_obj()["version"].get_int() < MIN_PARENT_NODE_VERSION) {
2256+
LogPrintf("ERROR: Parent chain daemon too old; "
2257+
"need Bitcoin Core version 0.16.2 or newer.\n");
2258+
return false;
2259+
}
2260+
2261+
// Then check the genesis block to correspond to parent chain.
2262+
params.push_back(UniValue(0));
2263+
reply = CallRPC("getblockhash", params, true);
2264+
error = reply["error"];
2265+
if (!error.isNull()) {
2266+
LogPrintf("ERROR: Bitcoind RPC check returned 'error' response.\n");
2267+
return false;
2268+
}
2269+
result = reply["result"];
22522270
if (!result.isStr() || result.get_str() != Params().ParentGenesisBlockHash().GetHex()) {
22532271
LogPrintf("ERROR: Invalid parent genesis block hash response via RPC. Contacting wrong parent daemon?\n");
22542272
return false;
@@ -2304,7 +2322,7 @@ bool BitcoindRPCCheck(const bool init)
23042322

23052323
//Write back remaining blocks
23062324
pblocktree->WriteInvalidBlockQueue(vblocksToReconsiderAgain);
2307-
}
2325+
}
23082326
return true;
23092327
}
23102328

@@ -2513,6 +2531,7 @@ bool IsValidPeginWitness(const CScriptWitness& pegin_witness, const COutPoint& p
25132531

25142532
uint256 block_hash;
25152533
uint256 tx_hash;
2534+
int num_txs;
25162535
// Get txout proof
25172536
if (Params().GetConsensus().ParentChainHasPow()) {
25182537

@@ -2528,6 +2547,8 @@ bool IsValidPeginWitness(const CScriptWitness& pegin_witness, const COutPoint& p
25282547
if (!CheckPeginTx(stack[4], pegtx, prevout, value, claim_script)) {
25292548
return false;
25302549
}
2550+
2551+
num_txs = merkle_block_pow.txn.GetNumTransactions();
25312552
} else {
25322553

25332554
CMerkleBlock merkle_block;
@@ -2543,6 +2564,8 @@ bool IsValidPeginWitness(const CScriptWitness& pegin_witness, const COutPoint& p
25432564
if (!CheckPeginTx(stack[4], pegtx, prevout, value, claim_script)) {
25442565
return false;
25452566
}
2567+
2568+
num_txs = merkle_block.txn.GetNumTransactions();
25462569
}
25472570

25482571
// Check that the merkle proof corresponds to the txid
@@ -2562,7 +2585,9 @@ bool IsValidPeginWitness(const CScriptWitness& pegin_witness, const COutPoint& p
25622585

25632586
// Finally, validate peg-in via rpc call
25642587
if (check_depth && GetBoolArg("-validatepegin", DEFAULT_VALIDATE_PEGIN)) {
2565-
return IsConfirmedBitcoinBlock(block_hash, Params().GetConsensus().pegin_min_depth);
2588+
if (!IsConfirmedBitcoinBlock(block_hash, Params().GetConsensus().pegin_min_depth, num_txs)) {
2589+
return false;
2590+
}
25662591
}
25672592
return true;
25682593
}

src/validation.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,10 @@ static const int MAX_UNCONNECTING_HEADERS = 10;
149149

150150
static const bool DEFAULT_PEERBLOOMFILTERS = false;
151151

152+
/** The minimum version for the parent chain node.
153+
* We need v0.16.2 to get the nTx field in getblockheader. */
154+
static const int MIN_PARENT_NODE_VERSION = 160200; // 0.16.2
155+
152156
struct BlockHasher
153157
{
154158
size_t operator()(const uint256& hash) const { return hash.GetCheapHash(); }

src/wallet/rpcwallet.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3702,7 +3702,8 @@ static UniValue createrawpegin(const JSONRPCRequest& request, T_tx_ref& txBTCRef
37023702

37033703
// Additional block lee-way to avoid bitcoin block races
37043704
if (GetBoolArg("-validatepegin", DEFAULT_VALIDATE_PEGIN)) {
3705-
ret.push_back(Pair("mature", IsConfirmedBitcoinBlock(merkleBlock.header.GetHash(), Params().GetConsensus().pegin_min_depth+2)));
3705+
ret.push_back(Pair("mature", IsConfirmedBitcoinBlock(merkleBlock.header.GetHash(),
3706+
Params().GetConsensus().pegin_min_depth+2, merkleBlock.txn.GetNumTransactions())));
37063707
}
37073708

37083709
return ret;

0 commit comments

Comments
 (0)