diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 421656152cb..a71a69a02f0 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -639,16 +639,37 @@ static RPCHelpMan combinerawtransaction() { UniValue txs = request.params[0].get_array(); + + // Can't merge < 2 items + if (txs.size() < 2) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Missing transactions"); + } + std::vector txVariants(txs.size()); - for (unsigned int idx = 0; idx < txs.size(); idx++) { + for (unsigned int idx{0}; idx < txs.size(); ++idx) { if (!DecodeHexTx(txVariants[idx], txs[idx].get_str())) { throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed for tx %d. Make sure the tx has at least one input.", idx)); } } - if (txVariants.empty()) { - throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Missing transactions"); + { // Test Tx relation for mergeability. This involves converting each tx to PSBT format (stripping sigscripts/witnesses) and ensuring txid is consistent. + std::vector txVariantsCopy(txVariants); + Txid txid{}; + for (unsigned int k{0}; k < txVariantsCopy.size(); ++k) { + // Remove all scriptSigs and scriptWitnesses from inputs + for (CTxIn& input : txVariantsCopy[k].vin) { + input.scriptSig.clear(); + input.scriptWitness.SetNull(); + } + if (k == 0) { + txid = txVariantsCopy[k].GetHash(); + } else { + if (txid != txVariantsCopy[k].GetHash()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Transaction %d not compatible (different transactions)", k)); + } + } + } } // mergedTx will end up with all the signatures; it @@ -678,7 +699,7 @@ static RPCHelpMan combinerawtransaction() // transaction to avoid rehashing. const CTransaction txConst(mergedTx); // Sign what we can: - for (unsigned int i = 0; i < mergedTx.vin.size(); i++) { + for (unsigned int i{0}; i < mergedTx.vin.size(); ++i) { CTxIn& txin = mergedTx.vin[i]; const Coin& coin = view.AccessCoin(txin.prevout); if (coin.IsSpent()) { diff --git a/test/functional/rpc_createmultisig.py b/test/functional/rpc_createmultisig.py index 0692a1203df..09868eccc4e 100755 --- a/test/functional/rpc_createmultisig.py +++ b/test/functional/rpc_createmultisig.py @@ -199,8 +199,18 @@ class RpcCreateMultiSigTest(BitcoinTestFramework): assert_equal(rawtx3["complete"], False) assert_raises_rpc_error(-22, "TX decode failed", node2.combinerawtransaction, [rawtx2['hex'], rawtx3['hex'] + "00"]) assert_raises_rpc_error(-22, "Missing transactions", node2.combinerawtransaction, []) + assert_raises_rpc_error(-22, "Missing transactions", node2.combinerawtransaction, [rawtx2['hex']]) combined_rawtx = node2.combinerawtransaction([rawtx2["hex"], rawtx3["hex"]]) + # transactions are not related + UNRELATED_TXID = "1d1d4e24ed99057e84c3f80fd8fbec79ed9e1acee37da269356ecea000000000" + unrelatedTx = self.nodes[2].createrawtransaction(inputs=[{'txid': UNRELATED_TXID, 'vout': 9}], outputs=[{out_addr: 99}]) + assert_raises_rpc_error(-8, "Transaction 1 not compatible (different transactions)", self.nodes[0].combinerawtransaction, [rawtx2['hex'], unrelatedTx]) + + # Accept duplicate transactions in combinerawtransaction + dupeMergedTx = self.nodes[0].combinerawtransaction([rawtx2['hex'], rawtx2['hex']]) + assert_equal(rawtx2['hex'], dupeMergedTx) + tx = node0.sendrawtransaction(combined_rawtx, 0) blk = self.generate(node0, 1)[0] assert tx in node0.getblock(blk)["tx"]