Skip to content

Commit 85eb502

Browse files
committed
2WP: Verify number of transactions in SPV proof
Use the getblockheader RPC from Bitcoin Core to verify if the number of transactions mentioned in the SPV proof matches the number for the block known by the Core daemon. Inspired by bitcoin/bitcoin@6b9dc8c
1 parent 621b52f commit 85eb502

File tree

6 files changed

+42
-6
lines changed

6 files changed

+42
-6
lines changed

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/validation.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2531,6 +2531,7 @@ bool IsValidPeginWitness(const CScriptWitness& pegin_witness, const COutPoint& p
25312531

25322532
uint256 block_hash;
25332533
uint256 tx_hash;
2534+
int num_txs;
25342535
// Get txout proof
25352536
if (Params().GetConsensus().ParentChainHasPow()) {
25362537

@@ -2546,6 +2547,8 @@ bool IsValidPeginWitness(const CScriptWitness& pegin_witness, const COutPoint& p
25462547
if (!CheckPeginTx(stack[4], pegtx, prevout, value, claim_script)) {
25472548
return false;
25482549
}
2550+
2551+
num_txs = merkle_block_pow.txn.GetNumTransactions();
25492552
} else {
25502553

25512554
CMerkleBlock merkle_block;
@@ -2561,6 +2564,8 @@ bool IsValidPeginWitness(const CScriptWitness& pegin_witness, const COutPoint& p
25612564
if (!CheckPeginTx(stack[4], pegtx, prevout, value, claim_script)) {
25622565
return false;
25632566
}
2567+
2568+
num_txs = merkle_block.txn.GetNumTransactions();
25642569
}
25652570

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

25812586
// Finally, validate peg-in via rpc call
25822587
if (check_depth && GetBoolArg("-validatepegin", DEFAULT_VALIDATE_PEGIN)) {
2583-
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+
}
25842591
}
25852592
return true;
25862593
}

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)