From a03239dd310972b7cdabe14accd8a98a74212019 Mon Sep 17 00:00:00 2001 From: Steven Roose Date: Wed, 20 Mar 2019 10:45:24 +0000 Subject: [PATCH] [BROKEN] Add raw tx RPCs for CA --- src/rpc/client.cpp | 7 + src/rpc/rawtransaction.cpp | 258 ++++++++++++++++++++++++++++++++----- src/rpc/rawtransaction.h | 2 +- 3 files changed, 234 insertions(+), 33 deletions(-) diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 4a0867c68bfb3..eebbf708b10aa 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -167,6 +167,13 @@ static const CRPCConvertParam vRPCConvertParams[] = { "combineblocksigs", 1, "signatures" }, { "sendtomainchain", 1, "amount" }, { "sendtomainchain", 2, "subtractfeefromamount" }, + { "rawblindrawtransaction", 1, "inputblinder" }, + { "rawblindrawtransaction", 2, "inputamount" }, + { "rawblindrawtransaction", 3, "inputasset" }, + { "rawblindrawtransaction", 4, "inputassetblinder" }, + { "rawblindrawtransaction", 6, "ignoreblindfail" }, + { "createrawtransaction", 4, "output_assets" }, + }; // clang-format on diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 573740a5557cf..f6e2e329a3886 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -28,7 +28,13 @@ #include #include #include +#include #include +#include +#include +#ifdef ENABLE_WALLET +#include +#endif #include #include @@ -343,7 +349,7 @@ static UniValue verifytxoutproof(const JSONRPCRequest& request) return res; } -CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, const UniValue& rbf) +CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, const UniValue& rbf, const UniValue& assets_in) { if (inputs_in.isNull() || outputs_in.isNull()) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, arguments 1 and 2 must be non-null"); @@ -363,6 +369,11 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal bool rbfOptIn = rbf.isTrue(); + UniValue assets; + if (!assets_in.isNull()) { + assets = assets_in.get_obj(); + } + for (unsigned int idx = 0; idx < inputs.size(); idx++) { const UniValue& input = inputs[idx]; const UniValue& o = input.get_obj(); @@ -417,11 +428,22 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal } outputs = std::move(outputs_dict); } + // Keep track of the fee output so we can add it in the very end of the transaction. + CTxOut fee_out; for (const std::string& name_ : outputs.getKeys()) { + // ELEMENTS: + // Asset defaults to policyAsset + CAsset asset(::policyAsset); + if (!assets.isNull()) { + if (!find_value(assets, name_).isNull()) { + asset = CAsset(ParseHashO(assets, name_)); + } + } + if (name_ == "data") { std::vector data = ParseHexV(outputs[name_].getValStr(), "Data"); - CTxOut out(0, CScript() << OP_RETURN << data); + CTxOut out(asset, 0, CScript() << OP_RETURN << data); rawTx.vout.push_back(out); } else if (name_ == "vdata") { // ELEMENTS: support multi-push OP_RETURN @@ -432,9 +454,12 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal datascript << data; } - //TODO(rebase) CA asset - CTxOut out(0, datascript); + CTxOut out(asset, 0, datascript); rawTx.vout.push_back(out); + } else if (name_ == "fee") { + // ELEMENTS: explicit fee outputs + CAmount nAmount = AmountFromValue(outputs[name_]); + fee_out = CTxOut(asset, nAmount, CScript()); } else { CTxDestination destination = DecodeDestination(name_); if (!IsValidDestination(destination)) { @@ -448,11 +473,20 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal CScript scriptPubKey = GetScriptForDestination(destination); CAmount nAmount = AmountFromValue(outputs[name_]); - CTxOut out(nAmount, scriptPubKey); + CTxOut out(asset, nAmount, scriptPubKey); + if (IsBlindDestination(destination)) { + CPubKey blind_pub = GetDestinationBlindingKey(destination); + out.nNonce.vchCommitment = std::vector(blind_pub.begin(), blind_pub.end()); + } rawTx.vout.push_back(out); } } + // Add fee output in the end. + if (!fee_out.nValue.IsNull() && fee_out.nValue.GetAmount() > 0) { + rawTx.vout.push_back(fee_out); + } + if (!rbf.isNull() && rawTx.vin.size() > 0 && rbfOptIn != SignalsOptInRBF(rawTx)) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter combination: Sequence number(s) contradict replaceable option"); } @@ -462,10 +496,10 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal static UniValue createrawtransaction(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) { + if (request.fHelp || request.params.size() < 2 || request.params.size() > 5) { throw std::runtime_error( // clang-format off - "createrawtransaction [{\"txid\":\"id\",\"vout\":n},...] [{\"address\":amount},{\"data\":\"hex\"},...] ( locktime ) ( replaceable )\n" + "createrawtransaction [{\"txid\":\"id\",\"vout\":n},...] [{\"address\":amount},{\"data\":\"hex\"},...] ( locktime ) ( replaceable ) ( {\"address\":\"asset\"} )\n" "\nCreate a transaction spending the given inputs and creating new outputs.\n" "Outputs can be addresses or data.\n" "Returns hex-encoded raw transaction.\n" @@ -490,6 +524,9 @@ static UniValue createrawtransaction(const JSONRPCRequest& request) " {\n" " \"data\": \"hex\" , (obj, optional) A key-value pair. The key must be \"data\", the value is hex encoded data\n" " \"vdata\": [\"hex\"] (string, optional) The key is \"vdata\", the value is an array of hex encoded data\n" + " },\n" + " {\n" + " \"fee\": x.xxx (numeric or string, optional) The key is \"fee\", the value the fee output you want to add.\n" " }\n" " ,... More key-value pairs of the above form. For compatibility reasons, a dictionary, which holds the key-value pairs directly, is also\n" " accepted as second parameter.\n" @@ -497,6 +534,12 @@ static UniValue createrawtransaction(const JSONRPCRequest& request) "3. locktime (numeric, optional, default=0) Raw locktime. Non-0 value also locktime-activates inputs\n" "4. replaceable (boolean, optional, default=false) Marks this transaction as BIP125 replaceable.\n" " Allows this transaction to be replaced by a transaction with higher fees. If provided, it is an error if explicit sequence numbers are incompatible.\n" + "5. \"output_assets\" (strings, optional, default=bitcoin) A json object of assets to addresses\n" + " {\n" + " \"address\": \"hex\" \n" + " \"fee\": \"hex\" \n" + " ...\n" + " }\n" "\nResult:\n" "\"transaction\" (string) hex string of the transaction\n" @@ -513,11 +556,12 @@ static UniValue createrawtransaction(const JSONRPCRequest& request) UniValue::VARR, UniValueType(), // ARR or OBJ, checked later UniValue::VNUM, - UniValue::VBOOL + UniValue::VBOOL, + UniValue::VOBJ }, true ); - CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3]); + CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3], request.params[4]); return EncodeHexTx(rawTx); } @@ -834,9 +878,13 @@ UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxsUnival } Coin newcoin; newcoin.out.scriptPubKey = scriptPubKey; - newcoin.out.nValue = MAX_MONEY; + newcoin.out.nValue = CConfidentialValue(MAX_MONEY); if (prevOut.exists("amount")) { - newcoin.out.nValue = AmountFromValue(find_value(prevOut, "amount")); + newcoin.out.nValue = CConfidentialValue(AmountFromValue(find_value(prevOut, "amount"))); + } else if (prevOut.exists("amountcommitment")) { + // Segwit sigs require the amount commitment to be sighashed + uint256 asset_commit = uint256S(prevOut["amountcommitment"].get_str()); + newcoin.out.nValue.vchCommitment = std::vector(asset_commit.begin(), asset_commit.end()); } newcoin.nHeight = 1; view.AddCoin(out, std::move(newcoin), true); @@ -896,9 +944,7 @@ UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxsUnival } const CScript& prevPubKey = txin.m_is_pegin ? GetPeginOutputFromWitness(txConst.witness.vtxinwit[i].m_pegin_witness).scriptPubKey : coin.out.scriptPubKey; - //TODO(rebase) CT - //const CConfidentialValue& amount = txin.m_is_pegin ? GetPeginOutputFromWitness(txConst.witness.vtxinwit[i].m_pegin_witness).nValue : coin.out.nValue; - const CAmount& amount = txin.m_is_pegin ? GetPeginOutputFromWitness(txConst.witness.vtxinwit[i].m_pegin_witness).nValue : coin.out.nValue; + const CConfidentialValue& amount = txin.m_is_pegin ? GetPeginOutputFromWitness(txConst.witness.vtxinwit[i].m_pegin_witness).nValue : coin.out.nValue; SignatureData sigdata = DataFromTransaction(mtx, i, coin.out); // Only sign SIGHASH_SINGLE if there's a corresponding output: @@ -909,7 +955,7 @@ UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxsUnival UpdateTransaction(mtx, i, sigdata); // amount must be specified for valid segwit signature - if (amount == MAX_MONEY && !inWitness.scriptWitness.IsNull()) { + if (amount.IsExplicit() && amount.GetAmount() == MAX_MONEY && !mtx.witness.vtxinwit[i].scriptWitness.IsNull()) { throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Missing amount for %s", coin.out.ToString())); } @@ -963,7 +1009,8 @@ static UniValue signrawtransactionwithkey(const JSONRPCRequest& request) " \"vout\":n, (numeric, required) The output number\n" " \"scriptPubKey\": \"hex\", (string, required) script key\n" " \"redeemScript\": \"hex\", (string, required for P2SH or P2WSH) redeem script\n" - " \"amount\": value (numeric, required) The amount spent\n" + " \"amount\": value (numeric, required if non-confidential segwit output) The amount spent\n" + " \"amountcommitment\": \"hex\", (string, required if confidential segiwt output) The amount commitment spent\n" " }\n" " ,...\n" " ]\n" @@ -1336,6 +1383,7 @@ UniValue decodepsbt(const JSONRPCRequest& request) UniValue tx_univ(UniValue::VOBJ); TxToUniv(CTransaction(*psbtx.tx), uint256(), tx_univ, false); result.pushKV("tx", tx_univ); + result.pushKV("fees", AmountMapToUniv(GetFeeMap(CTransaction(*psbtx.tx)), "")); // Unknown data UniValue unknowns(UniValue::VOBJ); @@ -1345,8 +1393,6 @@ UniValue decodepsbt(const JSONRPCRequest& request) result.pushKV("unknown", unknowns); // inputs - CAmount total_in = 0; - bool have_all_utxos = true; UniValue inputs(UniValue::VARR); for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) { const PSBTInput& input = psbtx.inputs[i]; @@ -1357,8 +1403,11 @@ UniValue decodepsbt(const JSONRPCRequest& request) UniValue out(UniValue::VOBJ); - out.pushKV("amount", ValueFromAmount(txout.nValue)); - total_in += txout.nValue; + if (txout.nValue.IsExplicit()) { + out.pushKV("amount", ValueFromAmount(txout.nValue.GetAmount())); + } else { + out.pushKV("amountcommitment", txout.nValue.GetHex()); + } UniValue o(UniValue::VOBJ); ScriptToUniv(txout.scriptPubKey, o, true); @@ -1368,9 +1417,6 @@ UniValue decodepsbt(const JSONRPCRequest& request) UniValue non_wit(UniValue::VOBJ); TxToUniv(*input.non_witness_utxo, uint256(), non_wit, false); in.pushKV("non_witness_utxo", non_wit); - total_in += input.non_witness_utxo->vout[psbtx.tx->vin[i].prevout.n].nValue; - } else { - have_all_utxos = false; } // Partial sigs @@ -1442,7 +1488,6 @@ UniValue decodepsbt(const JSONRPCRequest& request) result.pushKV("inputs", inputs); // outputs - CAmount output_value = 0; UniValue outputs(UniValue::VARR); for (unsigned int i = 0; i < psbtx.outputs.size(); ++i) { const PSBTOutput& output = psbtx.outputs[i]; @@ -1482,14 +1527,8 @@ UniValue decodepsbt(const JSONRPCRequest& request) } outputs.push_back(out); - - // Fee calculation - output_value += psbtx.tx->vout[i].nValue; } result.pushKV("outputs", outputs); - if (have_all_utxos) { - result.pushKV("fee", ValueFromAmount(total_in - output_value)); - } return result; } @@ -1642,6 +1681,12 @@ UniValue createpsbt(const JSONRPCRequest& request) "3. locktime (numeric, optional, default=0) Raw locktime. Non-0 value also locktime-activates inputs\n" "4. replaceable (boolean, optional, default=false) Marks this transaction as BIP125 replaceable.\n" " Allows this transaction to be replaced by a transaction with higher fees. If provided, it is an error if explicit sequence numbers are incompatible.\n" + "5. \"output_assets\" (strings, optional, default=bitcoin) A json object of assets to addresses\n" + " {\n" + " \"address\": \"hex\" \n" + " \"fee\": \"hex\" \n" + " ...\n" + " }\n" "\nResult:\n" " \"psbt\" (string) The resulting raw transaction (base64-encoded string)\n" "\nExamples:\n" @@ -1657,7 +1702,7 @@ UniValue createpsbt(const JSONRPCRequest& request) }, true ); - CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3]); + CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3], request.params[4]); // Make a blank psbt PartiallySignedTransaction psbtx; @@ -1745,12 +1790,160 @@ UniValue converttopsbt(const JSONRPCRequest& request) return EncodeBase64((unsigned char*)ssTx.data(), ssTx.size()); } +UniValue rawblindrawtransaction(const JSONRPCRequest& request) +{ + if (request.fHelp || (request.params.size() < 5 || request.params.size() > 7)) + throw std::runtime_error( + "rawblindrawtransaction \"hexstring\" [\"inputblinder\",...] [\"inputamount\",...] [\"inputasset\",...] [\"inputassetblinder\",...] ( totalblinder, ignoreblindfail )\n" + "\nConvert one or more outputs of a raw transaction into confidential ones.\n" + "Returns the hex-encoded raw transaction.\n" + "The input raw transaction cannot have already-blinded outputs.\n" + "The output keys used can be specified by using a confidential address in createrawtransaction.\n" + "If an additional blinded output is required to make a balanced blinding, a 0-value unspendable output will be added. Since there is no access to the wallet the blinding pubkey from the last output with blinding key will be repeated.\n" + "You can not blind issuances with this call.\n" + + "\nArguments:\n" + "1. \"hexstring\", (string, required) A hex-encoded raw transaction.\n" + "2. [ (array, required) An array with one entry per transaction input.\n" + " \"inputamountblinder\" (string, required) A hex-encoded blinding factor, one for each input.\n" + " Blinding factors can be found in the \"blinder\" output of listunspent.\n" + " ],\n" + "3. [ (array, required) An array with one entry per transaction input.\n" + " \"inputamount\" (numeric, required) An amount for each input.\n" + " ],\n" + "4. [ (array, required) An array with one entry per transaction input.\n" + " \"inputasset\" (string, required) A hex-encoded asset id, one for each input.\n" + " ],\n" + "5. [ (array, required) An array with one entry per transaction input.\n" + " \"inputassetblinder\" (string, required) A hex-encoded asset blinding factor, one for each input.\n" + " ],\n" + "6. \"totalblinder\" (string, optional) Ignored for now.\n" + "7. \"ignoreblindfail\"\" (bool, optional, default=true) Return a transaction even when a blinding attempt fails due to number of blinded inputs/outputs.\n" + + "\nResult:\n" + "\"transaction\" (string) hex string of the transaction\n" + ); + + std::vector txData(ParseHexV(request.params[0], "argument 1")); + CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION); + CMutableTransaction tx; + try { + ssData >> tx; + } catch (const std::exception &) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); + } + + UniValue inputBlinds = request.params[1].get_array(); + UniValue inputAmounts = request.params[2].get_array(); + UniValue inputAssets = request.params[3].get_array(); + UniValue inputAssetBlinds = request.params[4].get_array(); + + bool fIgnoreBlindFail = true; + if (!request.params[6].isNull()) { + fIgnoreBlindFail = request.params[6].get_bool(); + } + + int n_blinded_ins = 0; + + if (inputBlinds.size() != tx.vin.size()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, + "Invalid parameter: one (potentially empty) input blind for each input must be provided"); + } + if (inputAmounts.size() != tx.vin.size()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, + "Invalid parameter: one (potentially empty) input blind for each input must be provided"); + } + if (inputAssets.size() != tx.vin.size()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, + "Invalid parameter: one (potentially empty) input asset id for each input must be provided"); + } + if (inputAssetBlinds.size() != tx.vin.size()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, + "Invalid parameter: one (potentially empty) input asset blind for each input must be provided"); + } + + std::vector input_amounts; + std::vector input_blinds; + std::vector input_asset_blinds; + std::vector input_assets; + std::vector output_value_blinds; + std::vector output_asset_blinds; + std::vector output_assets; + std::vector output_pubkeys; + for (size_t nIn = 0; nIn < tx.vin.size(); nIn++) { + if (!inputBlinds[nIn].isStr()) + throw JSONRPCError(RPC_INVALID_PARAMETER, "input blinds must be an array of hex strings"); + if (!inputAssetBlinds[nIn].isStr()) + throw JSONRPCError(RPC_INVALID_PARAMETER, "input asset blinds must be an array of hex strings"); + if (!inputAssets[nIn].isStr()) + throw JSONRPCError(RPC_INVALID_PARAMETER, "input asset ids must be an array of hex strings"); + + std::string blind(inputBlinds[nIn].get_str()); + std::string assetblind(inputAssetBlinds[nIn].get_str()); + std::string asset(inputAssets[nIn].get_str()); + if (!IsHex(blind) || blind.length() != 32*2) + throw JSONRPCError(RPC_INVALID_PARAMETER, "input blinds must be an array of 32-byte hex-encoded strings"); + if (!IsHex(assetblind) || assetblind.length() != 32*2) + throw JSONRPCError(RPC_INVALID_PARAMETER, "input asset blinds must be an array of 32-byte hex-encoded strings"); + if (!IsHex(asset) || asset.length() != 32*2) + throw JSONRPCError(RPC_INVALID_PARAMETER, "input asset blinds must be an array of 32-byte hex-encoded strings"); + + input_blinds.push_back(uint256S(blind)); + input_asset_blinds.push_back(uint256S(assetblind)); + input_assets.push_back(CAsset(uint256S(asset))); + input_amounts.push_back(AmountFromValue(inputAmounts[nIn])); + + if (!input_blinds.back().IsNull()) { + n_blinded_ins++; + } + } + + RawFillBlinds(tx, output_value_blinds, output_asset_blinds, output_pubkeys); + + // How many are we trying to blind? + int num_pubkeys = 0; + unsigned int keyIndex = -1; + for (unsigned int i = 0; i < output_pubkeys.size(); i++) { + const CPubKey& key = output_pubkeys[i]; + if (key.IsValid()) { + num_pubkeys++; + keyIndex = i; + } + } + + if (num_pubkeys == 0 && n_blinded_ins == 0) { + // Vacuous, just return the transaction + return EncodeHexTx(tx); + } else if (n_blinded_ins > 0 && num_pubkeys == 0) { + // No notion of wallet, cannot complete this blinding without passed-in pubkey + throw JSONRPCError(RPC_INVALID_PARAMETER, "Unable to blind transaction: Add another output to blind in order to complete the blinding."); + } else if (n_blinded_ins == 0 && num_pubkeys == 1) { + if (fIgnoreBlindFail) { + // Just get rid of the ECDH key in the nonce field and return + tx.vout[keyIndex].nNonce.SetNull(); + return EncodeHexTx(tx); + } else { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Unable to blind transaction: Add another output to blind in order to complete the blinding."); + } + } + + int ret = BlindTransaction(input_blinds, input_asset_blinds, input_assets, input_amounts, output_value_blinds, output_asset_blinds, output_pubkeys, std::vector(), std::vector(), tx); + if (ret != num_pubkeys) { + // TODO Have more rich return values, communicating to user what has been blinded + // User may be ok not blinding something that for instance has no corresponding type on input + throw JSONRPCError(RPC_INVALID_PARAMETER, "Unable to blind transaction: Are you sure each asset type to blind is represented in the inputs?"); + } + + return EncodeHexTx(tx); +} + + // clang-format off static const CRPCCommand commands[] = { // category name actor (function) argNames // --------------------- ------------------------ ----------------------- ---------- { "rawtransactions", "getrawtransaction", &getrawtransaction, {"txid","verbose","blockhash"} }, - { "rawtransactions", "createrawtransaction", &createrawtransaction, {"inputs","outputs","locktime","replaceable"} }, + { "rawtransactions", "createrawtransaction", &createrawtransaction, {"inputs","outputs","locktime","replaceable", "output_assets"} }, { "rawtransactions", "decoderawtransaction", &decoderawtransaction, {"hexstring","iswitness"} }, { "rawtransactions", "decodescript", &decodescript, {"hexstring"} }, { "rawtransactions", "sendrawtransaction", &sendrawtransaction, {"hexstring","allowhighfees"} }, @@ -1763,6 +1956,7 @@ static const CRPCCommand commands[] = { "rawtransactions", "finalizepsbt", &finalizepsbt, {"psbt", "extract"} }, { "rawtransactions", "createpsbt", &createpsbt, {"inputs","outputs","locktime","replaceable"} }, { "rawtransactions", "converttopsbt", &converttopsbt, {"hexstring","permitsigdata","iswitness"} }, + { "rawtransactions", "rawblindrawtransaction", &rawblindrawtransaction, {"hexstring", "inputblinder", "inputamount", "inputasset", "inputassetblinder", "totalblinder", "ignoreblindfail"} }, { "blockchain", "gettxoutproof", &gettxoutproof, {"txids", "blockhash"} }, { "blockchain", "verifytxoutproof", &verifytxoutproof, {"proof"} }, diff --git a/src/rpc/rawtransaction.h b/src/rpc/rawtransaction.h index 924611ed5a538..7a433bb9351ac 100644 --- a/src/rpc/rawtransaction.h +++ b/src/rpc/rawtransaction.h @@ -13,6 +13,6 @@ class UniValue; UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxs, CBasicKeyStore *keystore, bool tempKeystore, const UniValue& hashType); /** Create a transaction from univalue parameters */ -CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, const UniValue& rbf); +CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, const UniValue& rbf, const UniValue& assets_in); #endif // BITCOIN_RPC_RAWTRANSACTION_H