Skip to content

Commit d6e9ff0

Browse files
committed
Move transaction combining from signrawtransaction to new RPC
Proper DecodeHexTxParams
1 parent e1e376e commit d6e9ff0

File tree

3 files changed

+107
-42
lines changed

3 files changed

+107
-42
lines changed

src/rpc/client.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ static const CRPCConvertParam vRPCConvertParams[] = {
3030
{ "addmultisigaddress", 1, "keys" },
3131
{ "addpeeraddress", 1, "port" },
3232
{ "cleanbudget", 0, "try_sync" },
33+
{ "combinerawtransaction", 0, "txs" },
3334
{ "createmultisig", 0, "nrequired" },
3435
{ "createmultisig", 1, "keys" },
3536
{ "createrawtransaction", 0, "inputs" },

src/rpc/rawtransaction.cpp

Lines changed: 101 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,92 @@ static void TxInErrorToJSON(const CTxIn& txin, UniValue& vErrorsRet, const std::
502502
vErrorsRet.push_back(entry);
503503
}
504504

505+
UniValue combinerawtransaction(const JSONRPCRequest& request)
506+
{
507+
if (request.fHelp || request.params.size() != 1)
508+
throw std::runtime_error(
509+
"combinerawtransaction [\"hexstring\",...]\n"
510+
"\nCombine multiple partially signed transactions into one transaction.\n"
511+
"The combined transaction may be another partially signed transaction or a \n"
512+
"fully signed transaction."
513+
514+
"\nArguments:\n"
515+
"1. \"txs\" (string) A json array of hex strings of partially signed transactions\n"
516+
" [\n"
517+
" \"hexstring\" (string) A transaction hash\n"
518+
" ,...\n"
519+
" ]\n"
520+
521+
"\nResult:\n"
522+
"\"hex\" : \"value\", (string) The hex-encoded raw transaction with signature(s)\n"
523+
524+
"\nExamples:\n"
525+
+ HelpExampleCli("combinerawtransaction", "[\"myhex1\", \"myhex2\", \"myhex3\"]")
526+
);
527+
528+
529+
UniValue txs = request.params[0].get_array();
530+
std::vector<CMutableTransaction> txVariants(txs.size());
531+
532+
for (unsigned int idx = 0; idx < txs.size(); idx++) {
533+
if (!DecodeHexTx(txVariants[idx], txs[idx].get_str())) {
534+
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed for tx %d", idx));
535+
}
536+
}
537+
538+
if (txVariants.empty()) {
539+
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Missing transactions");
540+
}
541+
542+
// mergedTx will end up with all the signatures; it
543+
// starts as a clone of the rawtx:
544+
CMutableTransaction mergedTx(txVariants[0]);
545+
546+
// Fetch previous transactions (inputs):
547+
CCoinsView viewDummy;
548+
CCoinsViewCache view(&viewDummy);
549+
{
550+
LOCK(cs_main);
551+
LOCK(mempool.cs);
552+
CCoinsViewCache &viewChain = *pcoinsTip;
553+
CCoinsViewMemPool viewMempool(&viewChain, mempool);
554+
view.SetBackend(viewMempool); // temporarily switch cache backend to db+mempool view
555+
556+
for (const CTxIn& txin : mergedTx.vin) {
557+
view.AccessCoin(txin.prevout); // Load entries from viewChain into view; can fail.
558+
}
559+
560+
view.SetBackend(viewDummy); // switch back to avoid locking mempool for too long
561+
}
562+
563+
// Use CTransaction for the constant parts of the
564+
// transaction to avoid rehashing.
565+
const CTransaction txConst(mergedTx);
566+
// Sign what we can:
567+
for (unsigned int i = 0; i < mergedTx.vin.size(); i++) {
568+
CTxIn& txin = mergedTx.vin[i];
569+
const Coin& coin = view.AccessCoin(txin.prevout);
570+
if (coin.IsSpent()) {
571+
throw JSONRPCError(RPC_VERIFY_ERROR, "Input not found or already spent");
572+
}
573+
const CScript& prevPubKey = coin.out.scriptPubKey;
574+
const CAmount& amount = coin.out.nValue;
575+
576+
SignatureData sigdata;
577+
578+
// ... and merge in other signatures:
579+
for (const CMutableTransaction& txv : txVariants) {
580+
if (txv.vin.size() > i) {
581+
sigdata = CombineSignatures(prevPubKey, TransactionSignatureChecker(&txConst, i, amount), sigdata, DataFromTransaction(txv, i));
582+
}
583+
}
584+
585+
UpdateTransaction(mergedTx, i, sigdata);
586+
}
587+
588+
return EncodeHexTx(mergedTx);
589+
}
590+
505591
UniValue signrawtransaction(const JSONRPCRequest& request)
506592
{
507593
CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
@@ -570,30 +656,14 @@ UniValue signrawtransaction(const JSONRPCRequest& request)
570656
#endif
571657
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR, UniValue::VARR, UniValue::VSTR}, true);
572658

573-
std::vector<unsigned char> txData(ParseHexV(request.params[0], "argument 1"));
574-
CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION);
575-
std::vector<CMutableTransaction> txVariants;
576-
while (!ssData.empty()) {
577-
try {
578-
CMutableTransaction tx;
579-
ssData >> tx;
580-
txVariants.push_back(tx);
581-
} catch (const std::exception&) {
582-
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
583-
}
584-
}
585-
586-
if (txVariants.empty())
587-
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Missing transaction");
588-
589-
// mergedTx will end up with all the signatures; it
590-
// starts as a clone of the rawtx:
591-
CMutableTransaction mergedTx(txVariants[0]);
659+
CMutableTransaction mtx;
660+
if (!DecodeHexTx(mtx, request.params[0].get_str()))
661+
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
592662

593663
// Fetch previous transactions (inputs):
594664
std::map<COutPoint, std::pair<CScript, CAmount>> mapPrevOut; // todo: check why do we have this for regtest..
595665
if (Params().IsRegTestNet()) {
596-
for (const CTxIn &txbase : mergedTx.vin)
666+
for (const CTxIn &txbase : mtx.vin)
597667
{
598668
CTransactionRef tempTx;
599669
uint256 hashBlock;
@@ -611,7 +681,7 @@ UniValue signrawtransaction(const JSONRPCRequest& request)
611681
CCoinsViewMemPool viewMempool(&viewChain, mempool);
612682
view.SetBackend(viewMempool); // temporarily switch cache backend to db+mempool view
613683

614-
for (const CTxIn& txin : mergedTx.vin) {
684+
for (const CTxIn& txin : mtx.vin) {
615685
view.AccessCoin(txin.prevout); // Load entries from viewChain into view; can fail.
616686
}
617687

@@ -732,10 +802,10 @@ UniValue signrawtransaction(const JSONRPCRequest& request)
732802

733803
// Use CTransaction for the constant parts of the
734804
// transaction to avoid rehashing.
735-
const CTransaction txConst(mergedTx);
805+
const CTransaction txConst(mtx);
736806
// Sign what we can:
737-
for (unsigned int i = 0; i < mergedTx.vin.size(); i++) {
738-
CTxIn& txin = mergedTx.vin[i];
807+
for (unsigned int i = 0; i < mtx.vin.size(); i++) {
808+
CTxIn& txin = mtx.vin[i];
739809
const Coin& coin = view.AccessCoin(txin.prevout);
740810
if (Params().IsRegTestNet()) {
741811
if (mapPrevOut.count(txin.prevout) == 0 && coin.IsSpent())
@@ -763,18 +833,13 @@ UniValue signrawtransaction(const JSONRPCRequest& request)
763833
}
764834

765835
SignatureData sigdata;
766-
SigVersion sigversion = mergedTx.GetRequiredSigVersion();
836+
SigVersion sigversion = mtx.GetRequiredSigVersion();
767837
// Only sign SIGHASH_SINGLE if there's a corresponding output:
768-
if (!fHashSingle || (i < mergedTx.vout.size()))
769-
ProduceSignature(MutableTransactionSignatureCreator(&keystore, &mergedTx, i, amount, nHashType),
770-
prevPubKey, sigdata, sigversion, fColdStake);
838+
if (!fHashSingle || (i < mtx.vout.size()))
839+
ProduceSignature(MutableTransactionSignatureCreator(&keystore, &mtx, i, amount, nHashType), prevPubKey, sigdata, sigversion, fColdStake);
840+
sigdata = CombineSignatures(prevPubKey, TransactionSignatureChecker(&txConst, i, amount), sigdata, DataFromTransaction(mtx, i));
771841

772-
// ... and merge in other signatures:
773-
for (const CMutableTransaction& txv : txVariants) {
774-
sigdata = CombineSignatures(prevPubKey, TransactionSignatureChecker(&txConst, i, amount), sigdata, DataFromTransaction(txv, i));
775-
}
776-
777-
UpdateTransaction(mergedTx, i, sigdata);
842+
UpdateTransaction(mtx, i, sigdata);
778843

779844
ScriptError serror = SCRIPT_ERR_OK;
780845
if (!VerifyScript(txin.scriptSig, prevPubKey, STANDARD_SCRIPT_VERIFY_FLAGS,
@@ -785,7 +850,7 @@ UniValue signrawtransaction(const JSONRPCRequest& request)
785850
bool fComplete = vErrors.empty();
786851

787852
UniValue result(UniValue::VOBJ);
788-
result.pushKV("hex", EncodeHexTx(mergedTx));
853+
result.pushKV("hex", EncodeHexTx(mtx));
789854
result.pushKV("complete", fComplete);
790855
if (!vErrors.empty()) {
791856
result.pushKV("errors", vErrors);
@@ -895,6 +960,7 @@ UniValue sendrawtransaction(const JSONRPCRequest& request)
895960
static const CRPCCommand commands[] =
896961
{ // category name actor (function) okSafe argNames
897962
// --------------------- ------------------------ ----------------------- ------ --------
963+
{ "rawtransactions", "combinerawtransaction", &combinerawtransaction, true, {"txs"} },
898964
{ "rawtransactions", "createrawtransaction", &createrawtransaction, true, {"inputs","outputs","locktime"} },
899965
{ "rawtransactions", "decoderawtransaction", &decoderawtransaction, true, {"hexstring"} },
900966
{ "rawtransactions", "decodescript", &decodescript, true, {"hexstring"} },

test/functional/rpc_rawtransaction.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ def run_test(self):
284284
break
285285

286286
bal = self.nodes[0].getbalance()
287-
inputs = [{ "txid" : txId, "vout" : vout['n'], "scriptPubKey" : vout['scriptPubKey']['hex'], "redeemScript" : mSigObjValid['hex'], "amount" : vout['value']}]
287+
inputs = [{ "txid" : txId, "vout" : vout['n'], "scriptPubKey" : vout['scriptPubKey']['hex'], "redeemScript" : mSigObjValid['hex']}]
288288
outputs = { self.nodes[0].getnewaddress() : 2.19 }
289289
rawTx2 = self.nodes[2].createrawtransaction(inputs, outputs)
290290
rawTxPartialSigned1 = self.nodes[1].signrawtransaction(rawTx2, inputs)
@@ -294,12 +294,10 @@ def run_test(self):
294294
rawTxPartialSigned2 = self.nodes[2].signrawtransaction(rawTx2, inputs)
295295
self.log.info(rawTxPartialSigned2)
296296
assert_equal(rawTxPartialSigned2['complete'], False) #node2 only has one key, can't comp. sign the tx
297-
298-
rawTxSignedComplete = self.nodes[2].signrawtransaction(rawTxPartialSigned1['hex'], inputs)
299-
self.log.info(rawTxSignedComplete)
300-
assert_equal(rawTxSignedComplete['complete'], True)
301-
self.nodes[2].sendrawtransaction(rawTxSignedComplete['hex'])
302-
rawTx2 = self.nodes[0].decoderawtransaction(rawTxSignedComplete['hex'])
297+
rawTxComb = self.nodes[2].combinerawtransaction([rawTxPartialSigned1['hex'], rawTxPartialSigned2['hex']])
298+
self.log.info(rawTxComb)
299+
self.nodes[2].sendrawtransaction(rawTxComb)
300+
rawTx2 = self.nodes[0].decoderawtransaction(rawTxComb)
303301
self.sync_all()
304302
self.nodes[0].generate(1)
305303
self.sync_all()

0 commit comments

Comments
 (0)