Changeset View
Changeset View
Standalone View
Standalone View
src/rpc/rawtransaction.cpp
Show First 20 Lines • Show All 66 Lines • ▼ Show 20 Lines | if (!hashBlock.IsNull()) { | ||||
entry.pushKV("blocktime", pindex->GetBlockTime()); | entry.pushKV("blocktime", pindex->GetBlockTime()); | ||||
} else { | } else { | ||||
entry.pushKV("confirmations", 0); | entry.pushKV("confirmations", 0); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
static UniValue getrawtransaction(const Config &config, | static RPCHelpMan getrawtransaction() { | ||||
const JSONRPCRequest &request) { | return RPCHelpMan{ | ||||
RPCHelpMan{ | |||||
"getrawtransaction", | "getrawtransaction", | ||||
"By default this function only works for mempool transactions. When " | "By default this function only works for mempool transactions. When " | ||||
"called with a blockhash\n" | "called with a blockhash\n" | ||||
"argument, getrawtransaction will return the transaction if the " | "argument, getrawtransaction will return the transaction if the " | ||||
"specified block is available and\n" | "specified block is available and\n" | ||||
"the transaction is found in that block. When called without a " | "the transaction is found in that block. When called without a " | ||||
"blockhash argument, getrawtransaction\n" | "blockhash argument, getrawtransaction\n" | ||||
"will return the transaction if it is in the mempool, or if -txindex " | "will return the transaction if it is in the mempool, or if -txindex " | ||||
▲ Show 20 Lines • Show All 99 Lines • ▼ Show 20 Lines | return RPCHelpMan{ | ||||
}, | }, | ||||
RPCExamples{HelpExampleCli("getrawtransaction", "\"mytxid\"") + | RPCExamples{HelpExampleCli("getrawtransaction", "\"mytxid\"") + | ||||
HelpExampleCli("getrawtransaction", "\"mytxid\" true") + | HelpExampleCli("getrawtransaction", "\"mytxid\" true") + | ||||
HelpExampleRpc("getrawtransaction", "\"mytxid\", true") + | HelpExampleRpc("getrawtransaction", "\"mytxid\", true") + | ||||
HelpExampleCli("getrawtransaction", | HelpExampleCli("getrawtransaction", | ||||
"\"mytxid\" false \"myblockhash\"") + | "\"mytxid\" false \"myblockhash\"") + | ||||
HelpExampleCli("getrawtransaction", | HelpExampleCli("getrawtransaction", | ||||
"\"mytxid\" true \"myblockhash\"")}, | "\"mytxid\" true \"myblockhash\"")}, | ||||
} | [&](const RPCHelpMan &self, const Config &config, | ||||
.Check(request); | const JSONRPCRequest &request) -> UniValue { | ||||
const NodeContext &node = EnsureNodeContext(request.context); | const NodeContext &node = EnsureNodeContext(request.context); | ||||
bool in_active_chain = true; | bool in_active_chain = true; | ||||
TxId txid = TxId(ParseHashV(request.params[0], "parameter 1")); | TxId txid = TxId(ParseHashV(request.params[0], "parameter 1")); | ||||
CBlockIndex *blockindex = nullptr; | CBlockIndex *blockindex = nullptr; | ||||
const CChainParams ¶ms = config.GetChainParams(); | const CChainParams ¶ms = config.GetChainParams(); | ||||
if (txid == params.GenesisBlock().hashMerkleRoot) { | if (txid == params.GenesisBlock().hashMerkleRoot) { | ||||
// Special exception for the genesis block coinbase transaction | // Special exception for the genesis block coinbase transaction | ||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, | throw JSONRPCError( | ||||
RPC_INVALID_ADDRESS_OR_KEY, | |||||
"The genesis block coinbase is not considered an " | "The genesis block coinbase is not considered an " | ||||
"ordinary transaction and cannot be retrieved"); | "ordinary transaction and cannot be retrieved"); | ||||
} | } | ||||
// Accept either a bool (true) or a num (>=1) to indicate verbose output. | // Accept either a bool (true) or a num (>=1) to indicate verbose | ||||
// output. | |||||
bool fVerbose = false; | bool fVerbose = false; | ||||
if (!request.params[1].isNull()) { | if (!request.params[1].isNull()) { | ||||
fVerbose = request.params[1].isNum() | fVerbose = request.params[1].isNum() | ||||
? (request.params[1].get_int() != 0) | ? (request.params[1].get_int() != 0) | ||||
: request.params[1].get_bool(); | : request.params[1].get_bool(); | ||||
} | } | ||||
if (!request.params[2].isNull()) { | if (!request.params[2].isNull()) { | ||||
LOCK(cs_main); | LOCK(cs_main); | ||||
BlockHash blockhash(ParseHashV(request.params[2], "parameter 3")); | BlockHash blockhash( | ||||
ParseHashV(request.params[2], "parameter 3")); | |||||
blockindex = LookupBlockIndex(blockhash); | blockindex = LookupBlockIndex(blockhash); | ||||
if (!blockindex) { | if (!blockindex) { | ||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, | throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, | ||||
"Block hash not found"); | "Block hash not found"); | ||||
} | } | ||||
in_active_chain = ::ChainActive().Contains(blockindex); | in_active_chain = ::ChainActive().Contains(blockindex); | ||||
} | } | ||||
bool f_txindex_ready = false; | bool f_txindex_ready = false; | ||||
if (g_txindex && !blockindex) { | if (g_txindex && !blockindex) { | ||||
f_txindex_ready = g_txindex->BlockUntilSyncedToCurrentChain(); | f_txindex_ready = g_txindex->BlockUntilSyncedToCurrentChain(); | ||||
} | } | ||||
BlockHash hash_block; | BlockHash hash_block; | ||||
const CTransactionRef tx = | const CTransactionRef tx = | ||||
GetTransaction(blockindex, node.mempool.get(), txid, | GetTransaction(blockindex, node.mempool.get(), txid, | ||||
params.GetConsensus(), hash_block); | params.GetConsensus(), hash_block); | ||||
if (!tx) { | if (!tx) { | ||||
std::string errmsg; | std::string errmsg; | ||||
if (blockindex) { | if (blockindex) { | ||||
if (!blockindex->nStatus.hasData()) { | if (!blockindex->nStatus.hasData()) { | ||||
throw JSONRPCError(RPC_MISC_ERROR, "Block not available"); | throw JSONRPCError(RPC_MISC_ERROR, | ||||
"Block not available"); | |||||
} | } | ||||
errmsg = "No such transaction found in the provided block"; | errmsg = "No such transaction found in the provided block"; | ||||
} else if (!g_txindex) { | } else if (!g_txindex) { | ||||
errmsg = "No such mempool transaction. Use -txindex or provide a " | errmsg = | ||||
"block hash to enable blockchain transaction queries"; | "No such mempool transaction. Use -txindex or provide " | ||||
"a block hash to enable blockchain transaction queries"; | |||||
} else if (!f_txindex_ready) { | } else if (!f_txindex_ready) { | ||||
errmsg = "No such mempool transaction. Blockchain transactions are " | errmsg = "No such mempool transaction. Blockchain " | ||||
"still in the process of being indexed"; | "transactions are still in the process of being " | ||||
"indexed"; | |||||
} else { | } else { | ||||
errmsg = "No such mempool or blockchain transaction"; | errmsg = "No such mempool or blockchain transaction"; | ||||
} | } | ||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, | throw JSONRPCError( | ||||
errmsg + | RPC_INVALID_ADDRESS_OR_KEY, | ||||
". Use gettransaction for wallet transactions."); | errmsg + ". Use gettransaction for wallet transactions."); | ||||
} | } | ||||
if (!fVerbose) { | if (!fVerbose) { | ||||
return EncodeHexTx(*tx, RPCSerializationFlags()); | return EncodeHexTx(*tx, RPCSerializationFlags()); | ||||
} | } | ||||
UniValue result(UniValue::VOBJ); | UniValue result(UniValue::VOBJ); | ||||
if (blockindex) { | if (blockindex) { | ||||
result.pushKV("in_active_chain", in_active_chain); | result.pushKV("in_active_chain", in_active_chain); | ||||
} | } | ||||
TxToJSON(*tx, hash_block, result); | TxToJSON(*tx, hash_block, result); | ||||
return result; | return result; | ||||
}, | |||||
}; | |||||
} | } | ||||
static UniValue gettxoutproof(const Config &config, | static RPCHelpMan gettxoutproof() { | ||||
const JSONRPCRequest &request) { | return RPCHelpMan{ | ||||
RPCHelpMan{ | |||||
"gettxoutproof", | "gettxoutproof", | ||||
"Returns a hex-encoded proof that \"txid\" was included in a block.\n" | "Returns a hex-encoded proof that \"txid\" was included in a block.\n" | ||||
"\nNOTE: By default this function only works sometimes. " | "\nNOTE: By default this function only works sometimes. " | ||||
"This is when there is an\n" | "This is when there is an\n" | ||||
"unspent output in the utxo for this transaction. To make it always " | "unspent output in the utxo for this transaction. To make it always " | ||||
"work,\n" | "work,\n" | ||||
"you need to maintain a transaction index, using the -txindex command " | "you need to maintain a transaction index, using the -txindex command " | ||||
"line option or\n" | "line option or\n" | ||||
Show All 13 Lines | return RPCHelpMan{ | ||||
{"blockhash", RPCArg::Type::STR_HEX, | {"blockhash", RPCArg::Type::STR_HEX, | ||||
RPCArg::Optional::OMITTED_NAMED_ARG, | RPCArg::Optional::OMITTED_NAMED_ARG, | ||||
"If specified, looks for txid in the block with this hash"}, | "If specified, looks for txid in the block with this hash"}, | ||||
}, | }, | ||||
RPCResult{ | RPCResult{ | ||||
RPCResult::Type::STR, "data", | RPCResult::Type::STR, "data", | ||||
"A string that is a serialized, hex-encoded data for the proof."}, | "A string that is a serialized, hex-encoded data for the proof."}, | ||||
RPCExamples{""}, | RPCExamples{""}, | ||||
} | [&](const RPCHelpMan &self, const Config &config, | ||||
.Check(request); | const JSONRPCRequest &request) -> UniValue { | ||||
std::set<TxId> setTxIds; | std::set<TxId> setTxIds; | ||||
TxId oneTxId; | TxId oneTxId; | ||||
UniValue txids = request.params[0].get_array(); | UniValue txids = request.params[0].get_array(); | ||||
for (unsigned int idx = 0; idx < txids.size(); idx++) { | for (unsigned int idx = 0; idx < txids.size(); idx++) { | ||||
const UniValue &utxid = txids[idx]; | const UniValue &utxid = txids[idx]; | ||||
TxId txid(ParseHashV(utxid, "txid")); | TxId txid(ParseHashV(utxid, "txid")); | ||||
if (setTxIds.count(txid)) { | if (setTxIds.count(txid)) { | ||||
throw JSONRPCError( | throw JSONRPCError( | ||||
RPC_INVALID_PARAMETER, | RPC_INVALID_PARAMETER, | ||||
std::string("Invalid parameter, duplicated txid: ") + | std::string("Invalid parameter, duplicated txid: ") + | ||||
utxid.get_str()); | utxid.get_str()); | ||||
} | } | ||||
setTxIds.insert(txid); | setTxIds.insert(txid); | ||||
oneTxId = txid; | oneTxId = txid; | ||||
} | } | ||||
CBlockIndex *pblockindex = nullptr; | CBlockIndex *pblockindex = nullptr; | ||||
BlockHash hashBlock; | BlockHash hashBlock; | ||||
if (!request.params[1].isNull()) { | if (!request.params[1].isNull()) { | ||||
LOCK(cs_main); | LOCK(cs_main); | ||||
hashBlock = BlockHash(ParseHashV(request.params[1], "blockhash")); | hashBlock = | ||||
BlockHash(ParseHashV(request.params[1], "blockhash")); | |||||
pblockindex = LookupBlockIndex(hashBlock); | pblockindex = LookupBlockIndex(hashBlock); | ||||
if (!pblockindex) { | if (!pblockindex) { | ||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); | throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, | ||||
"Block not found"); | |||||
} | } | ||||
} else { | } else { | ||||
LOCK(cs_main); | LOCK(cs_main); | ||||
// Loop through txids and try to find which block they're in. Exit loop | // Loop through txids and try to find which block they're in. | ||||
// once a block is found. | // Exit loop once a block is found. | ||||
for (const auto &txid : setTxIds) { | for (const auto &txid : setTxIds) { | ||||
const Coin &coin = | const Coin &coin = | ||||
AccessByTxid(::ChainstateActive().CoinsTip(), txid); | AccessByTxid(::ChainstateActive().CoinsTip(), txid); | ||||
if (!coin.IsSpent()) { | if (!coin.IsSpent()) { | ||||
pblockindex = ::ChainActive()[coin.GetHeight()]; | pblockindex = ::ChainActive()[coin.GetHeight()]; | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
// Allow txindex to catch up if we need to query it and before we acquire | // Allow txindex to catch up if we need to query it and before we | ||||
// cs_main. | // acquire cs_main. | ||||
if (g_txindex && !pblockindex) { | if (g_txindex && !pblockindex) { | ||||
g_txindex->BlockUntilSyncedToCurrentChain(); | g_txindex->BlockUntilSyncedToCurrentChain(); | ||||
} | } | ||||
const Consensus::Params ¶ms = config.GetChainParams().GetConsensus(); | const Consensus::Params ¶ms = | ||||
config.GetChainParams().GetConsensus(); | |||||
LOCK(cs_main); | LOCK(cs_main); | ||||
if (pblockindex == nullptr) { | if (pblockindex == nullptr) { | ||||
const CTransactionRef tx = GetTransaction( | const CTransactionRef tx = GetTransaction( | ||||
/* block_index */ nullptr, | /* block_index */ nullptr, | ||||
/* mempool */ nullptr, oneTxId, Params().GetConsensus(), hashBlock); | /* mempool */ nullptr, oneTxId, Params().GetConsensus(), | ||||
hashBlock); | |||||
if (!tx || hashBlock.IsNull()) { | if (!tx || hashBlock.IsNull()) { | ||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, | throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, | ||||
"Transaction not yet in block"); | "Transaction not yet in block"); | ||||
} | } | ||||
pblockindex = LookupBlockIndex(hashBlock); | pblockindex = LookupBlockIndex(hashBlock); | ||||
if (!pblockindex) { | if (!pblockindex) { | ||||
throw JSONRPCError(RPC_INTERNAL_ERROR, "Transaction index corrupt"); | throw JSONRPCError(RPC_INTERNAL_ERROR, | ||||
"Transaction index corrupt"); | |||||
} | } | ||||
} | } | ||||
CBlock block; | CBlock block; | ||||
if (!ReadBlockFromDisk(block, pblockindex, params)) { | if (!ReadBlockFromDisk(block, pblockindex, params)) { | ||||
throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk"); | throw JSONRPCError(RPC_INTERNAL_ERROR, | ||||
"Can't read block from disk"); | |||||
} | } | ||||
unsigned int ntxFound = 0; | unsigned int ntxFound = 0; | ||||
for (const auto &tx : block.vtx) { | for (const auto &tx : block.vtx) { | ||||
if (setTxIds.count(tx->GetId())) { | if (setTxIds.count(tx->GetId())) { | ||||
ntxFound++; | ntxFound++; | ||||
} | } | ||||
} | } | ||||
if (ntxFound != setTxIds.size()) { | if (ntxFound != setTxIds.size()) { | ||||
throw JSONRPCError( | throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, | ||||
RPC_INVALID_ADDRESS_OR_KEY, | "Not all transactions found in specified or " | ||||
"Not all transactions found in specified or retrieved block"); | "retrieved block"); | ||||
} | } | ||||
CDataStream ssMB(SER_NETWORK, PROTOCOL_VERSION); | CDataStream ssMB(SER_NETWORK, PROTOCOL_VERSION); | ||||
CMerkleBlock mb(block, setTxIds); | CMerkleBlock mb(block, setTxIds); | ||||
ssMB << mb; | ssMB << mb; | ||||
std::string strHex = HexStr(ssMB); | std::string strHex = HexStr(ssMB); | ||||
return strHex; | return strHex; | ||||
}, | |||||
}; | |||||
} | } | ||||
static UniValue verifytxoutproof(const Config &config, | static RPCHelpMan verifytxoutproof() { | ||||
const JSONRPCRequest &request) { | return RPCHelpMan{ | ||||
RPCHelpMan{ | |||||
"verifytxoutproof", | "verifytxoutproof", | ||||
"Verifies that a proof points to a transaction in a block, returning " | "Verifies that a proof points to a transaction in a block, returning " | ||||
"the transaction it commits to\n" | "the transaction it commits to\n" | ||||
"and throwing an RPC error if the block is not in our best chain\n", | "and throwing an RPC error if the block is not in our best chain\n", | ||||
{ | { | ||||
{"proof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, | {"proof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, | ||||
"The hex-encoded proof generated by gettxoutproof"}, | "The hex-encoded proof generated by gettxoutproof"}, | ||||
}, | }, | ||||
RPCResult{RPCResult::Type::ARR, | RPCResult{RPCResult::Type::ARR, | ||||
"", | "", | ||||
"", | "", | ||||
{ | { | ||||
{RPCResult::Type::STR_HEX, "txid", | {RPCResult::Type::STR_HEX, "txid", | ||||
"The txid(s) which the proof commits to, or empty array " | "The txid(s) which the proof commits to, or empty array " | ||||
"if the proof can not be validated."}, | "if the proof can not be validated."}, | ||||
}}, | }}, | ||||
RPCExamples{""}, | RPCExamples{""}, | ||||
} | [&](const RPCHelpMan &self, const Config &config, | ||||
.Check(request); | const JSONRPCRequest &request) -> UniValue { | ||||
CDataStream ssMB(ParseHexV(request.params[0], "proof"), SER_NETWORK, | CDataStream ssMB(ParseHexV(request.params[0], "proof"), SER_NETWORK, | ||||
PROTOCOL_VERSION); | PROTOCOL_VERSION); | ||||
CMerkleBlock merkleBlock; | CMerkleBlock merkleBlock; | ||||
ssMB >> merkleBlock; | ssMB >> merkleBlock; | ||||
UniValue res(UniValue::VARR); | UniValue res(UniValue::VARR); | ||||
std::vector<uint256> vMatch; | std::vector<uint256> vMatch; | ||||
std::vector<size_t> vIndex; | std::vector<size_t> vIndex; | ||||
if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) != | if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) != | ||||
merkleBlock.header.hashMerkleRoot) { | merkleBlock.header.hashMerkleRoot) { | ||||
return res; | return res; | ||||
} | } | ||||
LOCK(cs_main); | LOCK(cs_main); | ||||
const CBlockIndex *pindex = LookupBlockIndex(merkleBlock.header.GetHash()); | const CBlockIndex *pindex = | ||||
if (!pindex || !::ChainActive().Contains(pindex) || pindex->nTx == 0) { | LookupBlockIndex(merkleBlock.header.GetHash()); | ||||
if (!pindex || !::ChainActive().Contains(pindex) || | |||||
pindex->nTx == 0) { | |||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, | throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, | ||||
"Block not found in chain"); | "Block not found in chain"); | ||||
} | } | ||||
// Check if proof is valid, only add results if so | // Check if proof is valid, only add results if so | ||||
if (pindex->nTx == merkleBlock.txn.GetNumTransactions()) { | if (pindex->nTx == merkleBlock.txn.GetNumTransactions()) { | ||||
for (const uint256 &hash : vMatch) { | for (const uint256 &hash : vMatch) { | ||||
res.push_back(hash.GetHex()); | res.push_back(hash.GetHex()); | ||||
} | } | ||||
} | } | ||||
return res; | return res; | ||||
}, | |||||
}; | |||||
} | } | ||||
static UniValue createrawtransaction(const Config &config, | static RPCHelpMan createrawtransaction() { | ||||
const JSONRPCRequest &request) { | return RPCHelpMan{ | ||||
RPCHelpMan{ | |||||
"createrawtransaction", | "createrawtransaction", | ||||
"Create a transaction spending the given inputs and creating new " | "Create a transaction spending the given inputs and creating new " | ||||
"outputs.\n" | "outputs.\n" | ||||
"Outputs can be addresses or data.\n" | "Outputs can be addresses or data.\n" | ||||
"Returns hex-encoded raw transaction.\n" | "Returns hex-encoded raw transaction.\n" | ||||
"Note that the transaction's inputs are not signed, and\n" | "Note that the transaction's inputs are not signed, and\n" | ||||
"it is not stored in the wallet or transmitted to the network.\n", | "it is not stored in the wallet or transmitted to the network.\n", | ||||
{ | { | ||||
▲ Show 20 Lines • Show All 73 Lines • ▼ Show 20 Lines | return RPCHelpMan{ | ||||
"\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]" | "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]" | ||||
"\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"") + | "\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"") + | ||||
HelpExampleRpc("createrawtransaction", | HelpExampleRpc("createrawtransaction", | ||||
"\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]" | "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]" | ||||
"\", \"[{\\\"address\\\":10000.00}]\"") + | "\", \"[{\\\"address\\\":10000.00}]\"") + | ||||
HelpExampleRpc("createrawtransaction", | HelpExampleRpc("createrawtransaction", | ||||
"\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]" | "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]" | ||||
"\", \"[{\\\"data\\\":\\\"00010203\\\"}]\"")}, | "\", \"[{\\\"data\\\":\\\"00010203\\\"}]\"")}, | ||||
} | [&](const RPCHelpMan &self, const Config &config, | ||||
.Check(request); | const JSONRPCRequest &request) -> UniValue { | ||||
RPCTypeCheck(request.params, | RPCTypeCheck(request.params, | ||||
{UniValue::VARR, | {UniValue::VARR, | ||||
UniValueType(), // ARR or OBJ, checked later | UniValueType(), // ARR or OBJ, checked later | ||||
UniValue::VNUM}, | UniValue::VNUM}, | ||||
true); | true); | ||||
CMutableTransaction rawTx = | CMutableTransaction rawTx = | ||||
ConstructTransaction(config.GetChainParams(), request.params[0], | ConstructTransaction(config.GetChainParams(), request.params[0], | ||||
request.params[1], request.params[2]); | request.params[1], request.params[2]); | ||||
return EncodeHexTx(CTransaction(rawTx)); | return EncodeHexTx(CTransaction(rawTx)); | ||||
}, | |||||
}; | |||||
} | } | ||||
static UniValue decoderawtransaction(const Config &config, | static RPCHelpMan decoderawtransaction() { | ||||
const JSONRPCRequest &request) { | return RPCHelpMan{ | ||||
RPCHelpMan{ | |||||
"decoderawtransaction", | "decoderawtransaction", | ||||
"Return a JSON object representing the serialized, hex-encoded " | "Return a JSON object representing the serialized, hex-encoded " | ||||
"transaction.\n", | "transaction.\n", | ||||
{ | { | ||||
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, | {"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, | ||||
"The transaction hex string"}, | "The transaction hex string"}, | ||||
}, | }, | ||||
RPCResult{ | RPCResult{ | ||||
▲ Show 20 Lines • Show All 57 Lines • ▼ Show 20 Lines | return RPCHelpMan{ | ||||
"bitcoin address"}, | "bitcoin address"}, | ||||
}}, | }}, | ||||
}}, | }}, | ||||
}}, | }}, | ||||
}}, | }}, | ||||
}}, | }}, | ||||
RPCExamples{HelpExampleCli("decoderawtransaction", "\"hexstring\"") + | RPCExamples{HelpExampleCli("decoderawtransaction", "\"hexstring\"") + | ||||
HelpExampleRpc("decoderawtransaction", "\"hexstring\"")}, | HelpExampleRpc("decoderawtransaction", "\"hexstring\"")}, | ||||
} | [&](const RPCHelpMan &self, const Config &config, | ||||
.Check(request); | const JSONRPCRequest &request) -> UniValue { | ||||
RPCTypeCheck(request.params, {UniValue::VSTR}); | RPCTypeCheck(request.params, {UniValue::VSTR}); | ||||
CMutableTransaction mtx; | CMutableTransaction mtx; | ||||
if (!DecodeHexTx(mtx, request.params[0].get_str())) { | if (!DecodeHexTx(mtx, request.params[0].get_str())) { | ||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); | throw JSONRPCError(RPC_DESERIALIZATION_ERROR, | ||||
"TX decode failed"); | |||||
} | } | ||||
UniValue result(UniValue::VOBJ); | UniValue result(UniValue::VOBJ); | ||||
TxToUniv(CTransaction(std::move(mtx)), uint256(), result, false); | TxToUniv(CTransaction(std::move(mtx)), uint256(), result, false); | ||||
return result; | return result; | ||||
}, | |||||
}; | |||||
} | } | ||||
static std::string GetAllOutputTypes() { | static std::string GetAllOutputTypes() { | ||||
std::vector<std::string> ret; | std::vector<std::string> ret; | ||||
using U = std::underlying_type<TxoutType>::type; | using U = std::underlying_type<TxoutType>::type; | ||||
for (U i = (U)TxoutType::NONSTANDARD; i <= (U)TxoutType::NULL_DATA; ++i) { | for (U i = (U)TxoutType::NONSTANDARD; i <= (U)TxoutType::NULL_DATA; ++i) { | ||||
ret.emplace_back(GetTxnOutputType(static_cast<TxoutType>(i))); | ret.emplace_back(GetTxnOutputType(static_cast<TxoutType>(i))); | ||||
} | } | ||||
return Join(ret, ", "); | return Join(ret, ", "); | ||||
} | } | ||||
static UniValue decodescript(const Config &config, | static RPCHelpMan decodescript() { | ||||
const JSONRPCRequest &request) { | return RPCHelpMan{ | ||||
RPCHelpMan{ | |||||
"decodescript", | "decodescript", | ||||
"Decode a hex-encoded script.\n", | "Decode a hex-encoded script.\n", | ||||
{ | { | ||||
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, | {"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, | ||||
"the hex-encoded script"}, | "the hex-encoded script"}, | ||||
}, | }, | ||||
RPCResult{ | RPCResult{ | ||||
RPCResult::Type::OBJ, | RPCResult::Type::OBJ, | ||||
Show All 11 Lines | return RPCHelpMan{ | ||||
{RPCResult::Type::STR, "address", "bitcoin address"}, | {RPCResult::Type::STR, "address", "bitcoin address"}, | ||||
}}, | }}, | ||||
{RPCResult::Type::STR, "p2sh", | {RPCResult::Type::STR, "p2sh", | ||||
"address of P2SH script wrapping this redeem script (not " | "address of P2SH script wrapping this redeem script (not " | ||||
"returned if the script is already a P2SH)"}, | "returned if the script is already a P2SH)"}, | ||||
}}, | }}, | ||||
RPCExamples{HelpExampleCli("decodescript", "\"hexstring\"") + | RPCExamples{HelpExampleCli("decodescript", "\"hexstring\"") + | ||||
HelpExampleRpc("decodescript", "\"hexstring\"")}, | HelpExampleRpc("decodescript", "\"hexstring\"")}, | ||||
} | [&](const RPCHelpMan &self, const Config &config, | ||||
.Check(request); | const JSONRPCRequest &request) -> UniValue { | ||||
RPCTypeCheck(request.params, {UniValue::VSTR}); | RPCTypeCheck(request.params, {UniValue::VSTR}); | ||||
UniValue r(UniValue::VOBJ); | UniValue r(UniValue::VOBJ); | ||||
CScript script; | CScript script; | ||||
if (request.params[0].get_str().size() > 0) { | if (request.params[0].get_str().size() > 0) { | ||||
std::vector<uint8_t> scriptData( | std::vector<uint8_t> scriptData( | ||||
ParseHexV(request.params[0], "argument")); | ParseHexV(request.params[0], "argument")); | ||||
script = CScript(scriptData.begin(), scriptData.end()); | script = CScript(scriptData.begin(), scriptData.end()); | ||||
} else { | } else { | ||||
// Empty scripts are valid. | // Empty scripts are valid. | ||||
} | } | ||||
ScriptPubKeyToUniv(script, r, /* fIncludeHex */ false); | ScriptPubKeyToUniv(script, r, /* fIncludeHex */ false); | ||||
UniValue type; | UniValue type; | ||||
type = find_value(r, "type"); | type = find_value(r, "type"); | ||||
if (type.isStr() && type.get_str() != "scripthash") { | if (type.isStr() && type.get_str() != "scripthash") { | ||||
// P2SH cannot be wrapped in a P2SH. If this script is already a P2SH, | // P2SH cannot be wrapped in a P2SH. If this script is already a | ||||
// don't return the address for a P2SH of the P2SH. | // P2SH, don't return the address for a P2SH of the P2SH. | ||||
r.pushKV("p2sh", EncodeDestination(ScriptHash(script), config)); | r.pushKV("p2sh", EncodeDestination(ScriptHash(script), config)); | ||||
} | } | ||||
return r; | return r; | ||||
}, | |||||
}; | |||||
} | } | ||||
static UniValue combinerawtransaction(const Config &config, | static RPCHelpMan combinerawtransaction() { | ||||
const JSONRPCRequest &request) { | return RPCHelpMan{ | ||||
RPCHelpMan{ | |||||
"combinerawtransaction", | "combinerawtransaction", | ||||
"Combine multiple partially signed transactions into one " | "Combine multiple partially signed transactions into one " | ||||
"transaction.\n" | "transaction.\n" | ||||
"The combined transaction may be another partially signed transaction " | "The combined transaction may be another partially signed transaction " | ||||
"or a \n" | "or a \n" | ||||
"fully signed transaction.", | "fully signed transaction.", | ||||
{ | { | ||||
{ | { | ||||
"txs", | "txs", | ||||
RPCArg::Type::ARR, | RPCArg::Type::ARR, | ||||
RPCArg::Optional::NO, | RPCArg::Optional::NO, | ||||
"The hex strings of partially signed " | "The hex strings of partially signed " | ||||
"transactions", | "transactions", | ||||
{ | { | ||||
{"hexstring", RPCArg::Type::STR_HEX, | {"hexstring", RPCArg::Type::STR_HEX, | ||||
RPCArg::Optional::OMITTED, | RPCArg::Optional::OMITTED, | ||||
"A hex-encoded raw transaction"}, | "A hex-encoded raw transaction"}, | ||||
}, | }, | ||||
}, | }, | ||||
}, | }, | ||||
RPCResult{RPCResult::Type::STR, "", | RPCResult{RPCResult::Type::STR, "", | ||||
"The hex-encoded raw transaction with signature(s)"}, | "The hex-encoded raw transaction with signature(s)"}, | ||||
RPCExamples{HelpExampleCli("combinerawtransaction", | RPCExamples{HelpExampleCli("combinerawtransaction", | ||||
R"('["myhex1", "myhex2", "myhex3"]')")}, | R"('["myhex1", "myhex2", "myhex3"]')")}, | ||||
} | [&](const RPCHelpMan &self, const Config &config, | ||||
.Check(request); | const JSONRPCRequest &request) -> UniValue { | ||||
UniValue txs = request.params[0].get_array(); | UniValue txs = request.params[0].get_array(); | ||||
std::vector<CMutableTransaction> txVariants(txs.size()); | std::vector<CMutableTransaction> 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())) { | if (!DecodeHexTx(txVariants[idx], txs[idx].get_str())) { | ||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, | throw JSONRPCError( | ||||
RPC_DESERIALIZATION_ERROR, | |||||
strprintf("TX decode failed for tx %d", idx)); | strprintf("TX decode failed for tx %d", idx)); | ||||
} | } | ||||
} | } | ||||
if (txVariants.empty()) { | if (txVariants.empty()) { | ||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Missing transactions"); | throw JSONRPCError(RPC_DESERIALIZATION_ERROR, | ||||
"Missing transactions"); | |||||
} | } | ||||
// mergedTx will end up with all the signatures; it | // mergedTx will end up with all the signatures; it | ||||
// starts as a clone of the rawtx: | // starts as a clone of the rawtx: | ||||
CMutableTransaction mergedTx(txVariants[0]); | CMutableTransaction mergedTx(txVariants[0]); | ||||
// Fetch previous transactions (inputs): | // Fetch previous transactions (inputs): | ||||
CCoinsView viewDummy; | CCoinsView viewDummy; | ||||
CCoinsViewCache view(&viewDummy); | CCoinsViewCache view(&viewDummy); | ||||
{ | { | ||||
const CTxMemPool &mempool = EnsureMemPool(request.context); | const CTxMemPool &mempool = EnsureMemPool(request.context); | ||||
LOCK(cs_main); | LOCK(cs_main); | ||||
LOCK(mempool.cs); | LOCK(mempool.cs); | ||||
CCoinsViewCache &viewChain = ::ChainstateActive().CoinsTip(); | CCoinsViewCache &viewChain = ::ChainstateActive().CoinsTip(); | ||||
CCoinsViewMemPool viewMempool(&viewChain, mempool); | CCoinsViewMemPool viewMempool(&viewChain, mempool); | ||||
// temporarily switch cache backend to db+mempool view | // temporarily switch cache backend to db+mempool view | ||||
view.SetBackend(viewMempool); | view.SetBackend(viewMempool); | ||||
for (const CTxIn &txin : mergedTx.vin) { | for (const CTxIn &txin : mergedTx.vin) { | ||||
// Load entries from viewChain into view; can fail. | // Load entries from viewChain into view; can fail. | ||||
view.AccessCoin(txin.prevout); | view.AccessCoin(txin.prevout); | ||||
} | } | ||||
// switch back to avoid locking mempool for too long | // switch back to avoid locking mempool for too long | ||||
view.SetBackend(viewDummy); | view.SetBackend(viewDummy); | ||||
} | } | ||||
// Use CTransaction for the constant parts of the | // Use CTransaction for the constant parts of the | ||||
// transaction to avoid rehashing. | // transaction to avoid rehashing. | ||||
const CTransaction txConst(mergedTx); | const CTransaction txConst(mergedTx); | ||||
// Sign what we can: | // Sign what we can: | ||||
for (size_t i = 0; i < mergedTx.vin.size(); i++) { | for (size_t i = 0; i < mergedTx.vin.size(); i++) { | ||||
CTxIn &txin = mergedTx.vin[i]; | CTxIn &txin = mergedTx.vin[i]; | ||||
const Coin &coin = view.AccessCoin(txin.prevout); | const Coin &coin = view.AccessCoin(txin.prevout); | ||||
if (coin.IsSpent()) { | if (coin.IsSpent()) { | ||||
throw JSONRPCError(RPC_VERIFY_ERROR, | throw JSONRPCError(RPC_VERIFY_ERROR, | ||||
"Input not found or already spent"); | "Input not found or already spent"); | ||||
} | } | ||||
SignatureData sigdata; | SignatureData sigdata; | ||||
const CTxOut &txout = coin.GetTxOut(); | const CTxOut &txout = coin.GetTxOut(); | ||||
// ... and merge in other signatures: | // ... and merge in other signatures: | ||||
for (const CMutableTransaction &txv : txVariants) { | for (const CMutableTransaction &txv : txVariants) { | ||||
if (txv.vin.size() > i) { | if (txv.vin.size() > i) { | ||||
sigdata.MergeSignatureData(DataFromTransaction(txv, i, txout)); | sigdata.MergeSignatureData( | ||||
DataFromTransaction(txv, i, txout)); | |||||
} | } | ||||
} | } | ||||
ProduceSignature( | ProduceSignature(DUMMY_SIGNING_PROVIDER, | ||||
DUMMY_SIGNING_PROVIDER, | MutableTransactionSignatureCreator( | ||||
MutableTransactionSignatureCreator(&mergedTx, i, txout.nValue), | &mergedTx, i, txout.nValue), | ||||
txout.scriptPubKey, sigdata); | txout.scriptPubKey, sigdata); | ||||
UpdateInput(txin, sigdata); | UpdateInput(txin, sigdata); | ||||
} | } | ||||
return EncodeHexTx(CTransaction(mergedTx)); | return EncodeHexTx(CTransaction(mergedTx)); | ||||
}, | |||||
}; | |||||
} | } | ||||
static UniValue signrawtransactionwithkey(const Config &config, | static RPCHelpMan signrawtransactionwithkey() { | ||||
const JSONRPCRequest &request) { | return RPCHelpMan{ | ||||
RPCHelpMan{ | |||||
"signrawtransactionwithkey", | "signrawtransactionwithkey", | ||||
"Sign inputs for raw transaction (serialized, hex-encoded).\n" | "Sign inputs for raw transaction (serialized, hex-encoded).\n" | ||||
"The second argument is an array of base58-encoded private\n" | "The second argument is an array of base58-encoded private\n" | ||||
"keys that will be the only keys used to sign the transaction.\n" | "keys that will be the only keys used to sign the transaction.\n" | ||||
"The third optional argument (may be null) is an array of previous " | "The third optional argument (may be null) is an array of previous " | ||||
"transaction outputs that\n" | "transaction outputs that\n" | ||||
"this transaction depends on but may not yet be in the block chain.\n", | "this transaction depends on but may not yet be in the block chain.\n", | ||||
{ | { | ||||
▲ Show 20 Lines • Show All 78 Lines • ▼ Show 20 Lines | return RPCHelpMan{ | ||||
}}, | }}, | ||||
}}, | }}, | ||||
}}, | }}, | ||||
RPCExamples{ | RPCExamples{ | ||||
HelpExampleCli("signrawtransactionwithkey", | HelpExampleCli("signrawtransactionwithkey", | ||||
"\"myhex\" \"[\\\"key1\\\",\\\"key2\\\"]\"") + | "\"myhex\" \"[\\\"key1\\\",\\\"key2\\\"]\"") + | ||||
HelpExampleRpc("signrawtransactionwithkey", | HelpExampleRpc("signrawtransactionwithkey", | ||||
"\"myhex\", \"[\\\"key1\\\",\\\"key2\\\"]\"")}, | "\"myhex\", \"[\\\"key1\\\",\\\"key2\\\"]\"")}, | ||||
} | [&](const RPCHelpMan &self, const Config &config, | ||||
.Check(request); | const JSONRPCRequest &request) -> UniValue { | ||||
RPCTypeCheck(request.params, | |||||
RPCTypeCheck( | {UniValue::VSTR, UniValue::VARR, UniValue::VARR, | ||||
request.params, | UniValue::VSTR}, | ||||
{UniValue::VSTR, UniValue::VARR, UniValue::VARR, UniValue::VSTR}, true); | true); | ||||
CMutableTransaction mtx; | CMutableTransaction mtx; | ||||
if (!DecodeHexTx(mtx, request.params[0].get_str())) { | if (!DecodeHexTx(mtx, request.params[0].get_str())) { | ||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); | throw JSONRPCError(RPC_DESERIALIZATION_ERROR, | ||||
"TX decode failed"); | |||||
} | } | ||||
FillableSigningProvider keystore; | FillableSigningProvider keystore; | ||||
const UniValue &keys = request.params[1].get_array(); | const UniValue &keys = request.params[1].get_array(); | ||||
for (size_t idx = 0; idx < keys.size(); ++idx) { | for (size_t idx = 0; idx < keys.size(); ++idx) { | ||||
UniValue k = keys[idx]; | UniValue k = keys[idx]; | ||||
CKey key = DecodeSecret(k.get_str()); | CKey key = DecodeSecret(k.get_str()); | ||||
if (!key.IsValid()) { | if (!key.IsValid()) { | ||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, | throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, | ||||
"Invalid private key"); | "Invalid private key"); | ||||
} | } | ||||
keystore.AddKey(key); | keystore.AddKey(key); | ||||
} | } | ||||
// Fetch previous transactions (inputs): | // Fetch previous transactions (inputs): | ||||
std::map<COutPoint, Coin> coins; | std::map<COutPoint, Coin> coins; | ||||
for (const CTxIn &txin : mtx.vin) { | for (const CTxIn &txin : mtx.vin) { | ||||
// Create empty map entry keyed by prevout. | // Create empty map entry keyed by prevout. | ||||
coins[txin.prevout]; | coins[txin.prevout]; | ||||
} | } | ||||
NodeContext &node = EnsureNodeContext(request.context); | NodeContext &node = EnsureNodeContext(request.context); | ||||
FindCoins(node, coins); | FindCoins(node, coins); | ||||
// Parse the prevtxs array | // Parse the prevtxs array | ||||
ParsePrevouts(request.params[2], &keystore, coins); | ParsePrevouts(request.params[2], &keystore, coins); | ||||
UniValue result(UniValue::VOBJ); | UniValue result(UniValue::VOBJ); | ||||
SignTransaction(mtx, &keystore, coins, request.params[3], result); | SignTransaction(mtx, &keystore, coins, request.params[3], result); | ||||
return result; | return result; | ||||
}, | |||||
}; | |||||
} | } | ||||
static UniValue sendrawtransaction(const Config &config, | static RPCHelpMan sendrawtransaction() { | ||||
const JSONRPCRequest &request) { | return RPCHelpMan{ | ||||
RPCHelpMan{ | |||||
"sendrawtransaction", | "sendrawtransaction", | ||||
"Submits raw transaction (serialized, hex-encoded) to local node and " | "Submits raw transaction (serialized, hex-encoded) to local node and " | ||||
"network.\n" | "network.\n" | ||||
"\nAlso see createrawtransaction and " | "\nAlso see createrawtransaction and " | ||||
"signrawtransactionwithkey calls.\n", | "signrawtransactionwithkey calls.\n", | ||||
{ | { | ||||
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, | {"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, | ||||
"The hex string of the raw transaction"}, | "The hex string of the raw transaction"}, | ||||
Show All 13 Lines | return RPCHelpMan{ | ||||
"\"[{\\\"txid\\\" : \\\"mytxid\\\",\\\"vout\\\":0}]\" " | "\"[{\\\"txid\\\" : \\\"mytxid\\\",\\\"vout\\\":0}]\" " | ||||
"\"{\\\"myaddress\\\":10000}\"") + | "\"{\\\"myaddress\\\":10000}\"") + | ||||
"Sign the transaction, and get back the hex\n" + | "Sign the transaction, and get back the hex\n" + | ||||
HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") + | HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") + | ||||
"\nSend the transaction (signed hex)\n" + | "\nSend the transaction (signed hex)\n" + | ||||
HelpExampleCli("sendrawtransaction", "\"signedhex\"") + | HelpExampleCli("sendrawtransaction", "\"signedhex\"") + | ||||
"\nAs a JSON-RPC call\n" + | "\nAs a JSON-RPC call\n" + | ||||
HelpExampleRpc("sendrawtransaction", "\"signedhex\"")}, | HelpExampleRpc("sendrawtransaction", "\"signedhex\"")}, | ||||
} | [&](const RPCHelpMan &self, const Config &config, | ||||
.Check(request); | const JSONRPCRequest &request) -> UniValue { | ||||
RPCTypeCheck(request.params, | RPCTypeCheck(request.params, | ||||
{ | { | ||||
UniValue::VSTR, | UniValue::VSTR, | ||||
// VNUM or VSTR, checked inside AmountFromValue() | // VNUM or VSTR, checked inside AmountFromValue() | ||||
UniValueType(), | UniValueType(), | ||||
}); | }); | ||||
// parse hex string from parameter | // parse hex string from parameter | ||||
CMutableTransaction mtx; | CMutableTransaction mtx; | ||||
if (!DecodeHexTx(mtx, request.params[0].get_str())) { | if (!DecodeHexTx(mtx, request.params[0].get_str())) { | ||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); | throw JSONRPCError(RPC_DESERIALIZATION_ERROR, | ||||
"TX decode failed"); | |||||
} | } | ||||
CTransactionRef tx(MakeTransactionRef(std::move(mtx))); | CTransactionRef tx(MakeTransactionRef(std::move(mtx))); | ||||
const CFeeRate max_raw_tx_fee_rate = | const CFeeRate max_raw_tx_fee_rate = | ||||
request.params[1].isNull() | request.params[1].isNull() | ||||
? DEFAULT_MAX_RAW_TX_FEE_RATE | ? DEFAULT_MAX_RAW_TX_FEE_RATE | ||||
: CFeeRate(AmountFromValue(request.params[1])); | : CFeeRate(AmountFromValue(request.params[1])); | ||||
int64_t virtual_size = GetVirtualTransactionSize(*tx); | int64_t virtual_size = GetVirtualTransactionSize(*tx); | ||||
Amount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size); | Amount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size); | ||||
std::string err_string; | std::string err_string; | ||||
AssertLockNotHeld(cs_main); | AssertLockNotHeld(cs_main); | ||||
NodeContext &node = EnsureNodeContext(request.context); | NodeContext &node = EnsureNodeContext(request.context); | ||||
const TransactionError err = BroadcastTransaction( | const TransactionError err = BroadcastTransaction( | ||||
node, config, tx, err_string, max_raw_tx_fee, /*relay*/ true, | node, config, tx, err_string, max_raw_tx_fee, /*relay*/ true, | ||||
/*wait_callback*/ true); | /*wait_callback*/ true); | ||||
if (err != TransactionError::OK) { | if (err != TransactionError::OK) { | ||||
throw JSONRPCTransactionError(err, err_string); | throw JSONRPCTransactionError(err, err_string); | ||||
} | } | ||||
return tx->GetHash().GetHex(); | return tx->GetHash().GetHex(); | ||||
}, | |||||
}; | |||||
} | } | ||||
static UniValue testmempoolaccept(const Config &config, | static RPCHelpMan testmempoolaccept() { | ||||
const JSONRPCRequest &request) { | return RPCHelpMan{ | ||||
RPCHelpMan{ | |||||
"testmempoolaccept", | "testmempoolaccept", | ||||
"Returns result of mempool acceptance tests indicating if raw" | "Returns result of mempool acceptance tests indicating if raw" | ||||
" transaction (serialized, hex-encoded) would be accepted" | " transaction (serialized, hex-encoded) would be accepted" | ||||
" by mempool.\n" | " by mempool.\n" | ||||
"\nThis checks if the transaction violates the consensus or policy " | "\nThis checks if the transaction violates the consensus or policy " | ||||
"rules.\n" | "rules.\n" | ||||
"\nSee sendrawtransaction call.\n", | "\nSee sendrawtransaction call.\n", | ||||
{ | { | ||||
▲ Show 20 Lines • Show All 50 Lines • ▼ Show 20 Lines | return RPCHelpMan{ | ||||
"\"[{\\\"txid\\\" : \\\"mytxid\\\",\\\"vout\\\":0}]\" " | "\"[{\\\"txid\\\" : \\\"mytxid\\\",\\\"vout\\\":0}]\" " | ||||
"\"{\\\"myaddress\\\":10000}\"") + | "\"{\\\"myaddress\\\":10000}\"") + | ||||
"Sign the transaction, and get back the hex\n" + | "Sign the transaction, and get back the hex\n" + | ||||
HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") + | HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") + | ||||
"\nTest acceptance of the transaction (signed hex)\n" + | "\nTest acceptance of the transaction (signed hex)\n" + | ||||
HelpExampleCli("testmempoolaccept", R"('["signedhex"]')") + | HelpExampleCli("testmempoolaccept", R"('["signedhex"]')") + | ||||
"\nAs a JSON-RPC call\n" + | "\nAs a JSON-RPC call\n" + | ||||
HelpExampleRpc("testmempoolaccept", "[\"signedhex\"]")}, | HelpExampleRpc("testmempoolaccept", "[\"signedhex\"]")}, | ||||
} | [&](const RPCHelpMan &self, const Config &config, | ||||
.Check(request); | const JSONRPCRequest &request) -> UniValue { | ||||
RPCTypeCheck(request.params, | RPCTypeCheck(request.params, | ||||
{ | { | ||||
UniValue::VARR, | UniValue::VARR, | ||||
// VNUM or VSTR, checked inside AmountFromValue() | // VNUM or VSTR, checked inside AmountFromValue() | ||||
UniValueType(), | UniValueType(), | ||||
}); | }); | ||||
if (request.params[0].get_array().size() != 1) { | if (request.params[0].get_array().size() != 1) { | ||||
throw JSONRPCError( | throw JSONRPCError( | ||||
RPC_INVALID_PARAMETER, | RPC_INVALID_PARAMETER, | ||||
"Array must contain exactly one raw transaction for now"); | "Array must contain exactly one raw transaction for now"); | ||||
} | } | ||||
CMutableTransaction mtx; | CMutableTransaction mtx; | ||||
if (!DecodeHexTx(mtx, request.params[0].get_array()[0].get_str())) { | if (!DecodeHexTx(mtx, request.params[0].get_array()[0].get_str())) { | ||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); | throw JSONRPCError(RPC_DESERIALIZATION_ERROR, | ||||
"TX decode failed"); | |||||
} | } | ||||
CTransactionRef tx(MakeTransactionRef(std::move(mtx))); | CTransactionRef tx(MakeTransactionRef(std::move(mtx))); | ||||
const TxId &txid = tx->GetId(); | const TxId &txid = tx->GetId(); | ||||
const CFeeRate max_raw_tx_fee_rate = | const CFeeRate max_raw_tx_fee_rate = | ||||
request.params[1].isNull() | request.params[1].isNull() | ||||
? DEFAULT_MAX_RAW_TX_FEE_RATE | ? DEFAULT_MAX_RAW_TX_FEE_RATE | ||||
: CFeeRate(AmountFromValue(request.params[1])); | : CFeeRate(AmountFromValue(request.params[1])); | ||||
CTxMemPool &mempool = EnsureMemPool(request.context); | CTxMemPool &mempool = EnsureMemPool(request.context); | ||||
int64_t virtual_size = GetVirtualTransactionSize(*tx); | int64_t virtual_size = GetVirtualTransactionSize(*tx); | ||||
Amount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size); | Amount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size); | ||||
UniValue result(UniValue::VARR); | UniValue result(UniValue::VARR); | ||||
UniValue result_0(UniValue::VOBJ); | UniValue result_0(UniValue::VOBJ); | ||||
result_0.pushKV("txid", txid.GetHex()); | result_0.pushKV("txid", txid.GetHex()); | ||||
TxValidationState state; | TxValidationState state; | ||||
bool test_accept_res; | bool test_accept_res; | ||||
Amount fee; | Amount fee; | ||||
{ | { | ||||
LOCK(cs_main); | LOCK(cs_main); | ||||
test_accept_res = AcceptToMemoryPool( | test_accept_res = AcceptToMemoryPool( | ||||
config, mempool, state, std::move(tx), false /* bypass_limits */, | config, mempool, state, std::move(tx), | ||||
max_raw_tx_fee, true /* test_accept */, &fee); | false /* bypass_limits */, max_raw_tx_fee, | ||||
true /* test_accept */, &fee); | |||||
} | } | ||||
result_0.pushKV("allowed", test_accept_res); | result_0.pushKV("allowed", test_accept_res); | ||||
// Only return the fee and size if the transaction would pass ATMP. | // Only return the fee and size if the transaction would pass ATMP. | ||||
// These can be used to calculate the feerate. | // These can be used to calculate the feerate. | ||||
if (test_accept_res) { | if (test_accept_res) { | ||||
result_0.pushKV("size", virtual_size); | result_0.pushKV("size", virtual_size); | ||||
UniValue fees(UniValue::VOBJ); | UniValue fees(UniValue::VOBJ); | ||||
fees.pushKV("base", fee); | fees.pushKV("base", fee); | ||||
result_0.pushKV("fees", fees); | result_0.pushKV("fees", fees); | ||||
} else { | } else { | ||||
if (state.IsInvalid()) { | if (state.IsInvalid()) { | ||||
if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS) { | if (state.GetResult() == | ||||
TxValidationResult::TX_MISSING_INPUTS) { | |||||
result_0.pushKV("reject-reason", "missing-inputs"); | result_0.pushKV("reject-reason", "missing-inputs"); | ||||
} else { | } else { | ||||
result_0.pushKV("reject-reason", | result_0.pushKV( | ||||
"reject-reason", | |||||
strprintf("%s", state.GetRejectReason())); | strprintf("%s", state.GetRejectReason())); | ||||
} | } | ||||
} else { | } else { | ||||
result_0.pushKV("reject-reason", state.GetRejectReason()); | result_0.pushKV("reject-reason", state.GetRejectReason()); | ||||
} | } | ||||
} | } | ||||
result.push_back(std::move(result_0)); | result.push_back(std::move(result_0)); | ||||
return result; | return result; | ||||
}, | |||||
}; | |||||
} | } | ||||
static UniValue decodepsbt(const Config &config, | static RPCHelpMan decodepsbt() { | ||||
const JSONRPCRequest &request) { | return RPCHelpMan{ | ||||
RPCHelpMan{ | |||||
"decodepsbt", | "decodepsbt", | ||||
"Return a JSON object representing the serialized, base64-encoded " | "Return a JSON object representing the serialized, base64-encoded " | ||||
"partially signed Bitcoin transaction.\n", | "partially signed Bitcoin transaction.\n", | ||||
{ | { | ||||
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, | {"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, | ||||
"The PSBT base64 string"}, | "The PSBT base64 string"}, | ||||
}, | }, | ||||
RPCResult{ | RPCResult{ | ||||
▲ Show 20 Lines • Show All 141 Lines • ▼ Show 20 Lines | return RPCHelpMan{ | ||||
}}, | }}, | ||||
}}, | }}, | ||||
}}, | }}, | ||||
{RPCResult::Type::STR_AMOUNT, "fee", /* optional */ true, | {RPCResult::Type::STR_AMOUNT, "fee", /* optional */ true, | ||||
"The transaction fee paid if all UTXOs slots in the PSBT have " | "The transaction fee paid if all UTXOs slots in the PSBT have " | ||||
"been filled."}, | "been filled."}, | ||||
}}, | }}, | ||||
RPCExamples{HelpExampleCli("decodepsbt", "\"psbt\"")}, | RPCExamples{HelpExampleCli("decodepsbt", "\"psbt\"")}, | ||||
} | [&](const RPCHelpMan &self, const Config &config, | ||||
.Check(request); | const JSONRPCRequest &request) -> UniValue { | ||||
RPCTypeCheck(request.params, {UniValue::VSTR}); | RPCTypeCheck(request.params, {UniValue::VSTR}); | ||||
// Unserialize the transactions | // Unserialize the transactions | ||||
PartiallySignedTransaction psbtx; | PartiallySignedTransaction psbtx; | ||||
std::string error; | std::string error; | ||||
if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) { | if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) { | ||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, | throw JSONRPCError(RPC_DESERIALIZATION_ERROR, | ||||
strprintf("TX decode failed %s", error)); | strprintf("TX decode failed %s", error)); | ||||
} | } | ||||
UniValue result(UniValue::VOBJ); | UniValue result(UniValue::VOBJ); | ||||
// Add the decoded tx | // Add the decoded tx | ||||
UniValue tx_univ(UniValue::VOBJ); | UniValue tx_univ(UniValue::VOBJ); | ||||
TxToUniv(CTransaction(*psbtx.tx), uint256(), tx_univ, false); | TxToUniv(CTransaction(*psbtx.tx), uint256(), tx_univ, false); | ||||
result.pushKV("tx", tx_univ); | result.pushKV("tx", tx_univ); | ||||
// Unknown data | // Unknown data | ||||
if (psbtx.unknown.size() > 0) { | if (psbtx.unknown.size() > 0) { | ||||
UniValue unknowns(UniValue::VOBJ); | UniValue unknowns(UniValue::VOBJ); | ||||
for (auto entry : psbtx.unknown) { | for (auto entry : psbtx.unknown) { | ||||
unknowns.pushKV(HexStr(entry.first), HexStr(entry.second)); | unknowns.pushKV(HexStr(entry.first), HexStr(entry.second)); | ||||
} | } | ||||
result.pushKV("unknown", unknowns); | result.pushKV("unknown", unknowns); | ||||
} | } | ||||
// inputs | // inputs | ||||
Amount total_in = Amount::zero(); | Amount total_in = Amount::zero(); | ||||
bool have_all_utxos = true; | bool have_all_utxos = true; | ||||
UniValue inputs(UniValue::VARR); | UniValue inputs(UniValue::VARR); | ||||
for (size_t i = 0; i < psbtx.inputs.size(); ++i) { | for (size_t i = 0; i < psbtx.inputs.size(); ++i) { | ||||
const PSBTInput &input = psbtx.inputs[i]; | const PSBTInput &input = psbtx.inputs[i]; | ||||
UniValue in(UniValue::VOBJ); | UniValue in(UniValue::VOBJ); | ||||
// UTXOs | // UTXOs | ||||
if (!input.utxo.IsNull()) { | if (!input.utxo.IsNull()) { | ||||
const CTxOut &txout = input.utxo; | const CTxOut &txout = input.utxo; | ||||
UniValue out(UniValue::VOBJ); | UniValue out(UniValue::VOBJ); | ||||
out.pushKV("amount", txout.nValue); | out.pushKV("amount", txout.nValue); | ||||
if (MoneyRange(txout.nValue) && | if (MoneyRange(txout.nValue) && | ||||
MoneyRange(total_in + txout.nValue)) { | MoneyRange(total_in + txout.nValue)) { | ||||
total_in += txout.nValue; | total_in += txout.nValue; | ||||
} else { | } else { | ||||
// Hack to just not show fee later | // Hack to just not show fee later | ||||
have_all_utxos = false; | have_all_utxos = false; | ||||
} | } | ||||
UniValue o(UniValue::VOBJ); | UniValue o(UniValue::VOBJ); | ||||
ScriptToUniv(txout.scriptPubKey, o, true); | ScriptToUniv(txout.scriptPubKey, o, true); | ||||
out.pushKV("scriptPubKey", o); | out.pushKV("scriptPubKey", o); | ||||
in.pushKV("utxo", out); | in.pushKV("utxo", out); | ||||
} else { | } else { | ||||
have_all_utxos = false; | have_all_utxos = false; | ||||
} | } | ||||
// Partial sigs | // Partial sigs | ||||
if (!input.partial_sigs.empty()) { | if (!input.partial_sigs.empty()) { | ||||
UniValue partial_sigs(UniValue::VOBJ); | UniValue partial_sigs(UniValue::VOBJ); | ||||
for (const auto &sig : input.partial_sigs) { | for (const auto &sig : input.partial_sigs) { | ||||
partial_sigs.pushKV(HexStr(sig.second.first), | partial_sigs.pushKV(HexStr(sig.second.first), | ||||
HexStr(sig.second.second)); | HexStr(sig.second.second)); | ||||
} | } | ||||
in.pushKV("partial_signatures", partial_sigs); | in.pushKV("partial_signatures", partial_sigs); | ||||
} | } | ||||
// Sighash | // Sighash | ||||
uint8_t sighashbyte = input.sighash_type.getRawSigHashType() & 0xff; | uint8_t sighashbyte = | ||||
input.sighash_type.getRawSigHashType() & 0xff; | |||||
if (sighashbyte > 0) { | if (sighashbyte > 0) { | ||||
in.pushKV("sighash", SighashToStr(sighashbyte)); | in.pushKV("sighash", SighashToStr(sighashbyte)); | ||||
} | } | ||||
// Redeem script | // Redeem script | ||||
if (!input.redeem_script.empty()) { | if (!input.redeem_script.empty()) { | ||||
UniValue r(UniValue::VOBJ); | UniValue r(UniValue::VOBJ); | ||||
ScriptToUniv(input.redeem_script, r, false); | ScriptToUniv(input.redeem_script, r, false); | ||||
in.pushKV("redeem_script", r); | in.pushKV("redeem_script", r); | ||||
} | } | ||||
// keypaths | // keypaths | ||||
if (!input.hd_keypaths.empty()) { | if (!input.hd_keypaths.empty()) { | ||||
UniValue keypaths(UniValue::VARR); | UniValue keypaths(UniValue::VARR); | ||||
for (auto entry : input.hd_keypaths) { | for (auto entry : input.hd_keypaths) { | ||||
UniValue keypath(UniValue::VOBJ); | UniValue keypath(UniValue::VOBJ); | ||||
keypath.pushKV("pubkey", HexStr(entry.first)); | keypath.pushKV("pubkey", HexStr(entry.first)); | ||||
keypath.pushKV( | keypath.pushKV( | ||||
"master_fingerprint", | "master_fingerprint", | ||||
strprintf("%08x", ReadBE32(entry.second.fingerprint))); | strprintf("%08x", | ||||
keypath.pushKV("path", WriteHDKeypath(entry.second.path)); | ReadBE32(entry.second.fingerprint))); | ||||
keypath.pushKV("path", | |||||
WriteHDKeypath(entry.second.path)); | |||||
keypaths.push_back(keypath); | keypaths.push_back(keypath); | ||||
} | } | ||||
in.pushKV("bip32_derivs", keypaths); | in.pushKV("bip32_derivs", keypaths); | ||||
} | } | ||||
// Final scriptSig | // Final scriptSig | ||||
if (!input.final_script_sig.empty()) { | if (!input.final_script_sig.empty()) { | ||||
UniValue scriptsig(UniValue::VOBJ); | UniValue scriptsig(UniValue::VOBJ); | ||||
scriptsig.pushKV("asm", | scriptsig.pushKV( | ||||
ScriptToAsmStr(input.final_script_sig, true)); | "asm", ScriptToAsmStr(input.final_script_sig, true)); | ||||
scriptsig.pushKV("hex", HexStr(input.final_script_sig)); | scriptsig.pushKV("hex", HexStr(input.final_script_sig)); | ||||
in.pushKV("final_scriptSig", scriptsig); | in.pushKV("final_scriptSig", scriptsig); | ||||
} | } | ||||
// Unknown data | // Unknown data | ||||
if (input.unknown.size() > 0) { | if (input.unknown.size() > 0) { | ||||
UniValue unknowns(UniValue::VOBJ); | UniValue unknowns(UniValue::VOBJ); | ||||
for (auto entry : input.unknown) { | for (auto entry : input.unknown) { | ||||
unknowns.pushKV(HexStr(entry.first), HexStr(entry.second)); | unknowns.pushKV(HexStr(entry.first), | ||||
HexStr(entry.second)); | |||||
} | } | ||||
in.pushKV("unknown", unknowns); | in.pushKV("unknown", unknowns); | ||||
} | } | ||||
inputs.push_back(in); | inputs.push_back(in); | ||||
} | } | ||||
result.pushKV("inputs", inputs); | result.pushKV("inputs", inputs); | ||||
// outputs | // outputs | ||||
Amount output_value = Amount::zero(); | Amount output_value = Amount::zero(); | ||||
UniValue outputs(UniValue::VARR); | UniValue outputs(UniValue::VARR); | ||||
for (size_t i = 0; i < psbtx.outputs.size(); ++i) { | for (size_t i = 0; i < psbtx.outputs.size(); ++i) { | ||||
const PSBTOutput &output = psbtx.outputs[i]; | const PSBTOutput &output = psbtx.outputs[i]; | ||||
UniValue out(UniValue::VOBJ); | UniValue out(UniValue::VOBJ); | ||||
// Redeem script | // Redeem script | ||||
if (!output.redeem_script.empty()) { | if (!output.redeem_script.empty()) { | ||||
UniValue r(UniValue::VOBJ); | UniValue r(UniValue::VOBJ); | ||||
ScriptToUniv(output.redeem_script, r, false); | ScriptToUniv(output.redeem_script, r, false); | ||||
out.pushKV("redeem_script", r); | out.pushKV("redeem_script", r); | ||||
} | } | ||||
// keypaths | // keypaths | ||||
if (!output.hd_keypaths.empty()) { | if (!output.hd_keypaths.empty()) { | ||||
UniValue keypaths(UniValue::VARR); | UniValue keypaths(UniValue::VARR); | ||||
for (auto entry : output.hd_keypaths) { | for (auto entry : output.hd_keypaths) { | ||||
UniValue keypath(UniValue::VOBJ); | UniValue keypath(UniValue::VOBJ); | ||||
keypath.pushKV("pubkey", HexStr(entry.first)); | keypath.pushKV("pubkey", HexStr(entry.first)); | ||||
keypath.pushKV( | keypath.pushKV( | ||||
"master_fingerprint", | "master_fingerprint", | ||||
strprintf("%08x", ReadBE32(entry.second.fingerprint))); | strprintf("%08x", | ||||
keypath.pushKV("path", WriteHDKeypath(entry.second.path)); | ReadBE32(entry.second.fingerprint))); | ||||
keypath.pushKV("path", | |||||
WriteHDKeypath(entry.second.path)); | |||||
keypaths.push_back(keypath); | keypaths.push_back(keypath); | ||||
} | } | ||||
out.pushKV("bip32_derivs", keypaths); | out.pushKV("bip32_derivs", keypaths); | ||||
} | } | ||||
// Unknown data | // Unknown data | ||||
if (output.unknown.size() > 0) { | if (output.unknown.size() > 0) { | ||||
UniValue unknowns(UniValue::VOBJ); | UniValue unknowns(UniValue::VOBJ); | ||||
for (auto entry : output.unknown) { | for (auto entry : output.unknown) { | ||||
unknowns.pushKV(HexStr(entry.first), HexStr(entry.second)); | unknowns.pushKV(HexStr(entry.first), | ||||
HexStr(entry.second)); | |||||
} | } | ||||
out.pushKV("unknown", unknowns); | out.pushKV("unknown", unknowns); | ||||
} | } | ||||
outputs.push_back(out); | outputs.push_back(out); | ||||
// Fee calculation | // Fee calculation | ||||
if (MoneyRange(psbtx.tx->vout[i].nValue) && | if (MoneyRange(psbtx.tx->vout[i].nValue) && | ||||
MoneyRange(output_value + psbtx.tx->vout[i].nValue)) { | MoneyRange(output_value + psbtx.tx->vout[i].nValue)) { | ||||
output_value += psbtx.tx->vout[i].nValue; | output_value += psbtx.tx->vout[i].nValue; | ||||
} else { | } else { | ||||
// Hack to just not show fee later | // Hack to just not show fee later | ||||
have_all_utxos = false; | have_all_utxos = false; | ||||
} | } | ||||
} | } | ||||
result.pushKV("outputs", outputs); | result.pushKV("outputs", outputs); | ||||
if (have_all_utxos) { | if (have_all_utxos) { | ||||
result.pushKV("fee", total_in - output_value); | result.pushKV("fee", total_in - output_value); | ||||
} | } | ||||
return result; | return result; | ||||
}, | |||||
}; | |||||
} | } | ||||
static UniValue combinepsbt(const Config &config, | static RPCHelpMan combinepsbt() { | ||||
const JSONRPCRequest &request) { | return RPCHelpMan{ | ||||
RPCHelpMan{ | |||||
"combinepsbt", | "combinepsbt", | ||||
"Combine multiple partially signed Bitcoin transactions into one " | "Combine multiple partially signed Bitcoin transactions into one " | ||||
"transaction.\n" | "transaction.\n" | ||||
"Implements the Combiner role.\n", | "Implements the Combiner role.\n", | ||||
{ | { | ||||
{ | { | ||||
"txs", | "txs", | ||||
RPCArg::Type::ARR, | RPCArg::Type::ARR, | ||||
RPCArg::Optional::NO, | RPCArg::Optional::NO, | ||||
"The base64 strings of partially signed transactions", | "The base64 strings of partially signed transactions", | ||||
{ | { | ||||
{"psbt", RPCArg::Type::STR, RPCArg::Optional::OMITTED, | {"psbt", RPCArg::Type::STR, RPCArg::Optional::OMITTED, | ||||
"A base64 string of a PSBT"}, | "A base64 string of a PSBT"}, | ||||
}, | }, | ||||
}, | }, | ||||
}, | }, | ||||
RPCResult{RPCResult::Type::STR, "", | RPCResult{RPCResult::Type::STR, "", | ||||
"The base64-encoded partially signed transaction"}, | "The base64-encoded partially signed transaction"}, | ||||
RPCExamples{HelpExampleCli( | RPCExamples{HelpExampleCli( | ||||
"combinepsbt", R"('["mybase64_1", "mybase64_2", "mybase64_3"]')")}, | "combinepsbt", R"('["mybase64_1", "mybase64_2", "mybase64_3"]')")}, | ||||
} | [&](const RPCHelpMan &self, const Config &config, | ||||
.Check(request); | const JSONRPCRequest &request) -> UniValue { | ||||
RPCTypeCheck(request.params, {UniValue::VARR}, true); | RPCTypeCheck(request.params, {UniValue::VARR}, true); | ||||
// Unserialize the transactions | // Unserialize the transactions | ||||
std::vector<PartiallySignedTransaction> psbtxs; | std::vector<PartiallySignedTransaction> psbtxs; | ||||
UniValue txs = request.params[0].get_array(); | UniValue txs = request.params[0].get_array(); | ||||
if (txs.empty()) { | if (txs.empty()) { | ||||
throw JSONRPCError(RPC_INVALID_PARAMETER, | throw JSONRPCError(RPC_INVALID_PARAMETER, | ||||
"Parameter 'txs' cannot be empty"); | "Parameter 'txs' cannot be empty"); | ||||
} | } | ||||
for (size_t i = 0; i < txs.size(); ++i) { | for (size_t i = 0; i < txs.size(); ++i) { | ||||
PartiallySignedTransaction psbtx; | PartiallySignedTransaction psbtx; | ||||
std::string error; | std::string error; | ||||
if (!DecodeBase64PSBT(psbtx, txs[i].get_str(), error)) { | if (!DecodeBase64PSBT(psbtx, txs[i].get_str(), error)) { | ||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, | throw JSONRPCError(RPC_DESERIALIZATION_ERROR, | ||||
strprintf("TX decode failed %s", error)); | strprintf("TX decode failed %s", error)); | ||||
} | } | ||||
psbtxs.push_back(psbtx); | psbtxs.push_back(psbtx); | ||||
} | } | ||||
PartiallySignedTransaction merged_psbt; | PartiallySignedTransaction merged_psbt; | ||||
const TransactionError error = CombinePSBTs(merged_psbt, psbtxs); | const TransactionError error = CombinePSBTs(merged_psbt, psbtxs); | ||||
if (error != TransactionError::OK) { | if (error != TransactionError::OK) { | ||||
throw JSONRPCTransactionError(error); | throw JSONRPCTransactionError(error); | ||||
} | } | ||||
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); | CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); | ||||
ssTx << merged_psbt; | ssTx << merged_psbt; | ||||
return EncodeBase64(MakeUCharSpan(ssTx)); | return EncodeBase64(MakeUCharSpan(ssTx)); | ||||
}, | |||||
}; | |||||
} | } | ||||
static UniValue finalizepsbt(const Config &config, | static RPCHelpMan finalizepsbt() { | ||||
const JSONRPCRequest &request) { | return RPCHelpMan{ | ||||
RPCHelpMan{ | |||||
"finalizepsbt", | "finalizepsbt", | ||||
"Finalize the inputs of a PSBT. If the transaction is fully signed, it " | "Finalize the inputs of a PSBT. If the transaction is fully signed, it " | ||||
"will produce a\n" | "will produce a\n" | ||||
"network serialized transaction which can be broadcast with " | "network serialized transaction which can be broadcast with " | ||||
"sendrawtransaction. Otherwise a PSBT will be\n" | "sendrawtransaction. Otherwise a PSBT will be\n" | ||||
"created which has the final_scriptSigfields filled for inputs that " | "created which has the final_scriptSigfields filled for inputs that " | ||||
"are complete.\n" | "are complete.\n" | ||||
"Implements the Finalizer and Extractor roles.\n", | "Implements the Finalizer and Extractor roles.\n", | ||||
Show All 14 Lines | return RPCHelpMan{ | ||||
"The base64-encoded partially signed transaction if not " | "The base64-encoded partially signed transaction if not " | ||||
"extracted"}, | "extracted"}, | ||||
{RPCResult::Type::STR_HEX, "hex", | {RPCResult::Type::STR_HEX, "hex", | ||||
"The hex-encoded network transaction if extracted"}, | "The hex-encoded network transaction if extracted"}, | ||||
{RPCResult::Type::BOOL, "complete", | {RPCResult::Type::BOOL, "complete", | ||||
"If the transaction has a complete set of signatures"}, | "If the transaction has a complete set of signatures"}, | ||||
}}, | }}, | ||||
RPCExamples{HelpExampleCli("finalizepsbt", "\"psbt\"")}, | RPCExamples{HelpExampleCli("finalizepsbt", "\"psbt\"")}, | ||||
} | [&](const RPCHelpMan &self, const Config &config, | ||||
.Check(request); | const JSONRPCRequest &request) -> UniValue { | ||||
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL}, | |||||
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL}, true); | true); | ||||
// Unserialize the transactions | // Unserialize the transactions | ||||
PartiallySignedTransaction psbtx; | PartiallySignedTransaction psbtx; | ||||
std::string error; | std::string error; | ||||
if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) { | if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) { | ||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, | throw JSONRPCError(RPC_DESERIALIZATION_ERROR, | ||||
strprintf("TX decode failed %s", error)); | strprintf("TX decode failed %s", error)); | ||||
} | } | ||||
bool extract = request.params[1].isNull() || (!request.params[1].isNull() && | bool extract = | ||||
request.params[1].get_bool()); | request.params[1].isNull() || | ||||
(!request.params[1].isNull() && request.params[1].get_bool()); | |||||
CMutableTransaction mtx; | CMutableTransaction mtx; | ||||
bool complete = FinalizeAndExtractPSBT(psbtx, mtx); | bool complete = FinalizeAndExtractPSBT(psbtx, mtx); | ||||
UniValue result(UniValue::VOBJ); | UniValue result(UniValue::VOBJ); | ||||
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); | CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); | ||||
std::string result_str; | std::string result_str; | ||||
if (complete && extract) { | if (complete && extract) { | ||||
ssTx << mtx; | ssTx << mtx; | ||||
result_str = HexStr(ssTx); | result_str = HexStr(ssTx); | ||||
result.pushKV("hex", result_str); | result.pushKV("hex", result_str); | ||||
} else { | } else { | ||||
ssTx << psbtx; | ssTx << psbtx; | ||||
result_str = EncodeBase64(ssTx.str()); | result_str = EncodeBase64(ssTx.str()); | ||||
result.pushKV("psbt", result_str); | result.pushKV("psbt", result_str); | ||||
} | } | ||||
result.pushKV("complete", complete); | result.pushKV("complete", complete); | ||||
return result; | return result; | ||||
}, | |||||
}; | |||||
} | } | ||||
static UniValue createpsbt(const Config &config, | static RPCHelpMan createpsbt() { | ||||
const JSONRPCRequest &request) { | return RPCHelpMan{ | ||||
RPCHelpMan{ | |||||
"createpsbt", | "createpsbt", | ||||
"Creates a transaction in the Partially Signed Transaction format.\n" | "Creates a transaction in the Partially Signed Transaction format.\n" | ||||
"Implements the Creator role.\n", | "Implements the Creator role.\n", | ||||
{ | { | ||||
{ | { | ||||
"inputs", | "inputs", | ||||
RPCArg::Type::ARR, | RPCArg::Type::ARR, | ||||
RPCArg::Optional::NO, | RPCArg::Optional::NO, | ||||
▲ Show 20 Lines • Show All 59 Lines • ▼ Show 20 Lines | return RPCHelpMan{ | ||||
{"locktime", RPCArg::Type::NUM, /* default */ "0", | {"locktime", RPCArg::Type::NUM, /* default */ "0", | ||||
"Raw locktime. Non-0 value also locktime-activates inputs"}, | "Raw locktime. Non-0 value also locktime-activates inputs"}, | ||||
}, | }, | ||||
RPCResult{RPCResult::Type::STR, "", | RPCResult{RPCResult::Type::STR, "", | ||||
"The resulting raw transaction (base64-encoded string)"}, | "The resulting raw transaction (base64-encoded string)"}, | ||||
RPCExamples{HelpExampleCli( | RPCExamples{HelpExampleCli( | ||||
"createpsbt", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]" | "createpsbt", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]" | ||||
"\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"")}, | "\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"")}, | ||||
} | [&](const RPCHelpMan &self, const Config &config, | ||||
.Check(request); | const JSONRPCRequest &request) -> UniValue { | ||||
RPCTypeCheck(request.params, | RPCTypeCheck(request.params, | ||||
{ | { | ||||
UniValue::VARR, | UniValue::VARR, | ||||
UniValueType(), // ARR or OBJ, checked later | UniValueType(), // ARR or OBJ, checked later | ||||
UniValue::VNUM, | UniValue::VNUM, | ||||
}, | }, | ||||
true); | true); | ||||
CMutableTransaction rawTx = | CMutableTransaction rawTx = | ||||
ConstructTransaction(config.GetChainParams(), request.params[0], | ConstructTransaction(config.GetChainParams(), request.params[0], | ||||
request.params[1], request.params[2]); | request.params[1], request.params[2]); | ||||
// Make a blank psbt | // Make a blank psbt | ||||
PartiallySignedTransaction psbtx; | PartiallySignedTransaction psbtx; | ||||
psbtx.tx = rawTx; | psbtx.tx = rawTx; | ||||
for (size_t i = 0; i < rawTx.vin.size(); ++i) { | for (size_t i = 0; i < rawTx.vin.size(); ++i) { | ||||
psbtx.inputs.push_back(PSBTInput()); | psbtx.inputs.push_back(PSBTInput()); | ||||
} | } | ||||
for (size_t i = 0; i < rawTx.vout.size(); ++i) { | for (size_t i = 0; i < rawTx.vout.size(); ++i) { | ||||
psbtx.outputs.push_back(PSBTOutput()); | psbtx.outputs.push_back(PSBTOutput()); | ||||
} | } | ||||
// Serialize the PSBT | // Serialize the PSBT | ||||
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); | CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); | ||||
ssTx << psbtx; | ssTx << psbtx; | ||||
return EncodeBase64(MakeUCharSpan(ssTx)); | return EncodeBase64(MakeUCharSpan(ssTx)); | ||||
}, | |||||
}; | |||||
} | } | ||||
static UniValue converttopsbt(const Config &config, | static RPCHelpMan converttopsbt() { | ||||
const JSONRPCRequest &request) { | return RPCHelpMan{ | ||||
RPCHelpMan{ | |||||
"converttopsbt", | "converttopsbt", | ||||
"Converts a network serialized transaction to a PSBT. " | "Converts a network serialized transaction to a PSBT. " | ||||
"This should be used only with createrawtransaction and " | "This should be used only with createrawtransaction and " | ||||
"fundrawtransaction\n" | "fundrawtransaction\n" | ||||
"createpsbt and walletcreatefundedpsbt should be used for new " | "createpsbt and walletcreatefundedpsbt should be used for new " | ||||
"applications.\n", | "applications.\n", | ||||
{ | { | ||||
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, | {"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, | ||||
"The hex string of a raw transaction"}, | "The hex string of a raw transaction"}, | ||||
{"permitsigdata", RPCArg::Type::BOOL, /* default */ "false", | {"permitsigdata", RPCArg::Type::BOOL, /* default */ "false", | ||||
"If true, any signatures in the input will be discarded and " | "If true, any signatures in the input will be discarded and " | ||||
"conversion.\n" | "conversion.\n" | ||||
" will continue. If false, RPC will " | " will continue. If false, RPC will " | ||||
"fail if any signatures are present."}, | "fail if any signatures are present."}, | ||||
}, | }, | ||||
RPCResult{RPCResult::Type::STR, "", | RPCResult{RPCResult::Type::STR, "", | ||||
"The resulting raw transaction (base64-encoded string)"}, | "The resulting raw transaction (base64-encoded string)"}, | ||||
RPCExamples{ | RPCExamples{ | ||||
"\nCreate a transaction\n" + | "\nCreate a transaction\n" + | ||||
HelpExampleCli("createrawtransaction", | HelpExampleCli("createrawtransaction", | ||||
"\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]" | "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]" | ||||
"\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"") + | "\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"") + | ||||
"\nConvert the transaction to a PSBT\n" + | "\nConvert the transaction to a PSBT\n" + | ||||
HelpExampleCli("converttopsbt", "\"rawtransaction\"")}, | HelpExampleCli("converttopsbt", "\"rawtransaction\"")}, | ||||
} | [&](const RPCHelpMan &self, const Config &config, | ||||
.Check(request); | const JSONRPCRequest &request) -> UniValue { | ||||
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL}, | |||||
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL}, true); | true); | ||||
// parse hex string from parameter | // parse hex string from parameter | ||||
CMutableTransaction tx; | CMutableTransaction tx; | ||||
bool permitsigdata = | bool permitsigdata = request.params[1].isNull() | ||||
request.params[1].isNull() ? false : request.params[1].get_bool(); | ? false | ||||
: request.params[1].get_bool(); | |||||
if (!DecodeHexTx(tx, request.params[0].get_str())) { | if (!DecodeHexTx(tx, request.params[0].get_str())) { | ||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); | throw JSONRPCError(RPC_DESERIALIZATION_ERROR, | ||||
"TX decode failed"); | |||||
} | } | ||||
// Remove all scriptSigs from inputs | // Remove all scriptSigs from inputs | ||||
for (CTxIn &input : tx.vin) { | for (CTxIn &input : tx.vin) { | ||||
if (!input.scriptSig.empty() && !permitsigdata) { | if (!input.scriptSig.empty() && !permitsigdata) { | ||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, | throw JSONRPCError(RPC_DESERIALIZATION_ERROR, | ||||
"Inputs must not have scriptSigs"); | "Inputs must not have scriptSigs"); | ||||
} | } | ||||
input.scriptSig.clear(); | input.scriptSig.clear(); | ||||
} | } | ||||
// Make a blank psbt | // Make a blank psbt | ||||
PartiallySignedTransaction psbtx; | PartiallySignedTransaction psbtx; | ||||
psbtx.tx = tx; | psbtx.tx = tx; | ||||
for (size_t i = 0; i < tx.vin.size(); ++i) { | for (size_t i = 0; i < tx.vin.size(); ++i) { | ||||
psbtx.inputs.push_back(PSBTInput()); | psbtx.inputs.push_back(PSBTInput()); | ||||
} | } | ||||
for (size_t i = 0; i < tx.vout.size(); ++i) { | for (size_t i = 0; i < tx.vout.size(); ++i) { | ||||
psbtx.outputs.push_back(PSBTOutput()); | psbtx.outputs.push_back(PSBTOutput()); | ||||
} | } | ||||
// Serialize the PSBT | // Serialize the PSBT | ||||
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); | CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); | ||||
ssTx << psbtx; | ssTx << psbtx; | ||||
return EncodeBase64(MakeUCharSpan(ssTx)); | return EncodeBase64(MakeUCharSpan(ssTx)); | ||||
}, | |||||
}; | |||||
} | } | ||||
UniValue utxoupdatepsbt(const Config &config, const JSONRPCRequest &request) { | RPCHelpMan utxoupdatepsbt() { | ||||
RPCHelpMan{ | return RPCHelpMan{ | ||||
"utxoupdatepsbt", | "utxoupdatepsbt", | ||||
"Updates all inputs and outputs in a PSBT with data from output " | "Updates all inputs and outputs in a PSBT with data from output " | ||||
"descriptors, the UTXO set or the mempool.\n", | "descriptors, the UTXO set or the mempool.\n", | ||||
{ | { | ||||
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, | {"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, | ||||
"A base64 string of a PSBT"}, | "A base64 string of a PSBT"}, | ||||
{"descriptors", | {"descriptors", | ||||
RPCArg::Type::ARR, | RPCArg::Type::ARR, | ||||
Show All 13 Lines | return RPCHelpMan{ | ||||
"Up to what index HD chains should be explored (either " | "Up to what index HD chains should be explored (either " | ||||
"end or [begin,end])"}, | "end or [begin,end])"}, | ||||
}}, | }}, | ||||
}}, | }}, | ||||
}, | }, | ||||
RPCResult{RPCResult::Type::STR, "", | RPCResult{RPCResult::Type::STR, "", | ||||
"The base64-encoded partially signed transaction with inputs " | "The base64-encoded partially signed transaction with inputs " | ||||
"updated"}, | "updated"}, | ||||
RPCExamples{HelpExampleCli("utxoupdatepsbt", "\"psbt\"")}} | RPCExamples{HelpExampleCli("utxoupdatepsbt", "\"psbt\"")}, | ||||
.Check(request); | [&](const RPCHelpMan &self, const Config &config, | ||||
const JSONRPCRequest &request) -> UniValue { | |||||
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR}, true); | RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR}, | ||||
true); | |||||
// Unserialize the transactions | // Unserialize the transactions | ||||
PartiallySignedTransaction psbtx; | PartiallySignedTransaction psbtx; | ||||
std::string error; | std::string error; | ||||
if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) { | if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) { | ||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, | throw JSONRPCError(RPC_DESERIALIZATION_ERROR, | ||||
strprintf("TX decode failed %s", error)); | strprintf("TX decode failed %s", error)); | ||||
} | } | ||||
// Parse descriptors, if any. | // Parse descriptors, if any. | ||||
FlatSigningProvider provider; | FlatSigningProvider provider; | ||||
if (!request.params[1].isNull()) { | if (!request.params[1].isNull()) { | ||||
auto descs = request.params[1].get_array(); | auto descs = request.params[1].get_array(); | ||||
for (size_t i = 0; i < descs.size(); ++i) { | for (size_t i = 0; i < descs.size(); ++i) { | ||||
EvalDescriptorStringOrObject(descs[i], provider); | EvalDescriptorStringOrObject(descs[i], provider); | ||||
} | } | ||||
} | } | ||||
// We don't actually need private keys further on; hide them as a | // We don't actually need private keys further on; hide them as a | ||||
// precaution. | // precaution. | ||||
HidingSigningProvider public_provider(&provider, /* nosign */ true, | HidingSigningProvider public_provider(&provider, /* nosign */ true, | ||||
/* nobip32derivs */ false); | /* nobip32derivs */ false); | ||||
// Fetch previous transactions (inputs): | // Fetch previous transactions (inputs): | ||||
CCoinsView viewDummy; | CCoinsView viewDummy; | ||||
CCoinsViewCache view(&viewDummy); | CCoinsViewCache view(&viewDummy); | ||||
{ | { | ||||
const CTxMemPool &mempool = EnsureMemPool(request.context); | const CTxMemPool &mempool = EnsureMemPool(request.context); | ||||
LOCK2(cs_main, mempool.cs); | LOCK2(cs_main, mempool.cs); | ||||
CCoinsViewCache &viewChain = ::ChainstateActive().CoinsTip(); | CCoinsViewCache &viewChain = ::ChainstateActive().CoinsTip(); | ||||
CCoinsViewMemPool viewMempool(&viewChain, mempool); | CCoinsViewMemPool viewMempool(&viewChain, mempool); | ||||
// temporarily switch cache backend to db+mempool view | // temporarily switch cache backend to db+mempool view | ||||
view.SetBackend(viewMempool); | view.SetBackend(viewMempool); | ||||
for (const CTxIn &txin : psbtx.tx->vin) { | for (const CTxIn &txin : psbtx.tx->vin) { | ||||
// Load entries from viewChain into view; can fail. | // Load entries from viewChain into view; can fail. | ||||
view.AccessCoin(txin.prevout); | view.AccessCoin(txin.prevout); | ||||
} | } | ||||
// switch back to avoid locking mempool for too long | // switch back to avoid locking mempool for too long | ||||
view.SetBackend(viewDummy); | view.SetBackend(viewDummy); | ||||
} | } | ||||
// Fill the inputs | // Fill the inputs | ||||
for (size_t i = 0; i < psbtx.tx->vin.size(); ++i) { | for (size_t i = 0; i < psbtx.tx->vin.size(); ++i) { | ||||
PSBTInput &input = psbtx.inputs.at(i); | PSBTInput &input = psbtx.inputs.at(i); | ||||
if (!input.utxo.IsNull()) { | if (!input.utxo.IsNull()) { | ||||
continue; | continue; | ||||
} | } | ||||
// Update script/keypath information using descriptor data. | // Update script/keypath information using descriptor data. | ||||
// Note that SignPSBTInput does a lot more than just constructing ECDSA | // Note that SignPSBTInput does a lot more than just | ||||
// signatures we don't actually care about those here, in fact. | // constructing ECDSA signatures we don't actually care about | ||||
// those here, in fact. | |||||
SignPSBTInput(public_provider, psbtx, i, | SignPSBTInput(public_provider, psbtx, i, | ||||
/* sighash_type */ SigHashType().withForkId()); | /* sighash_type */ SigHashType().withForkId()); | ||||
} | } | ||||
// Update script/keypath information using descriptor data. | // Update script/keypath information using descriptor data. | ||||
for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) { | for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) { | ||||
UpdatePSBTOutput(public_provider, psbtx, i); | UpdatePSBTOutput(public_provider, psbtx, i); | ||||
} | } | ||||
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); | CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); | ||||
ssTx << psbtx; | ssTx << psbtx; | ||||
return EncodeBase64(MakeUCharSpan(ssTx)); | return EncodeBase64(MakeUCharSpan(ssTx)); | ||||
}, | |||||
}; | |||||
} | } | ||||
UniValue joinpsbts(const Config &config, const JSONRPCRequest &request) { | RPCHelpMan joinpsbts() { | ||||
RPCHelpMan{ | return RPCHelpMan{ | ||||
"joinpsbts", | "joinpsbts", | ||||
"Joins multiple distinct PSBTs with different inputs and outputs " | "Joins multiple distinct PSBTs with different inputs and outputs " | ||||
"into one PSBT with inputs and outputs from all of the PSBTs\n" | "into one PSBT with inputs and outputs from all of the PSBTs\n" | ||||
"No input in any of the PSBTs can be in more than one of the PSBTs.\n", | "No input in any of the PSBTs can be in more than one of the PSBTs.\n", | ||||
{{"txs", | {{"txs", | ||||
RPCArg::Type::ARR, | RPCArg::Type::ARR, | ||||
RPCArg::Optional::NO, | RPCArg::Optional::NO, | ||||
"The base64 strings of partially signed transactions", | "The base64 strings of partially signed transactions", | ||||
{{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, | {{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, | ||||
"A base64 string of a PSBT"}}}}, | "A base64 string of a PSBT"}}}}, | ||||
RPCResult{RPCResult::Type::STR, "", | RPCResult{RPCResult::Type::STR, "", | ||||
"The base64-encoded partially signed transaction"}, | "The base64-encoded partially signed transaction"}, | ||||
RPCExamples{HelpExampleCli("joinpsbts", "\"psbt\"")}} | RPCExamples{HelpExampleCli("joinpsbts", "\"psbt\"")}, | ||||
.Check(request); | [&](const RPCHelpMan &self, const Config &config, | ||||
const JSONRPCRequest &request) -> UniValue { | |||||
RPCTypeCheck(request.params, {UniValue::VARR}, true); | RPCTypeCheck(request.params, {UniValue::VARR}, true); | ||||
// Unserialize the transactions | // Unserialize the transactions | ||||
std::vector<PartiallySignedTransaction> psbtxs; | std::vector<PartiallySignedTransaction> psbtxs; | ||||
UniValue txs = request.params[0].get_array(); | UniValue txs = request.params[0].get_array(); | ||||
if (txs.size() <= 1) { | if (txs.size() <= 1) { | ||||
throw JSONRPCError(RPC_INVALID_PARAMETER, | throw JSONRPCError( | ||||
RPC_INVALID_PARAMETER, | |||||
"At least two PSBTs are required to join PSBTs."); | "At least two PSBTs are required to join PSBTs."); | ||||
} | } | ||||
uint32_t best_version = 1; | uint32_t best_version = 1; | ||||
uint32_t best_locktime = 0xffffffff; | uint32_t best_locktime = 0xffffffff; | ||||
for (size_t i = 0; i < txs.size(); ++i) { | for (size_t i = 0; i < txs.size(); ++i) { | ||||
PartiallySignedTransaction psbtx; | PartiallySignedTransaction psbtx; | ||||
std::string error; | std::string error; | ||||
if (!DecodeBase64PSBT(psbtx, txs[i].get_str(), error)) { | if (!DecodeBase64PSBT(psbtx, txs[i].get_str(), error)) { | ||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, | throw JSONRPCError(RPC_DESERIALIZATION_ERROR, | ||||
strprintf("TX decode failed %s", error)); | strprintf("TX decode failed %s", error)); | ||||
} | } | ||||
psbtxs.push_back(psbtx); | psbtxs.push_back(psbtx); | ||||
// Choose the highest version number | // Choose the highest version number | ||||
if (static_cast<uint32_t>(psbtx.tx->nVersion) > best_version) { | if (static_cast<uint32_t>(psbtx.tx->nVersion) > best_version) { | ||||
best_version = static_cast<uint32_t>(psbtx.tx->nVersion); | best_version = static_cast<uint32_t>(psbtx.tx->nVersion); | ||||
} | } | ||||
// Choose the lowest lock time | // Choose the lowest lock time | ||||
if (psbtx.tx->nLockTime < best_locktime) { | if (psbtx.tx->nLockTime < best_locktime) { | ||||
best_locktime = psbtx.tx->nLockTime; | best_locktime = psbtx.tx->nLockTime; | ||||
} | } | ||||
} | } | ||||
// Create a blank psbt where everything will be added | // Create a blank psbt where everything will be added | ||||
PartiallySignedTransaction merged_psbt; | PartiallySignedTransaction merged_psbt; | ||||
merged_psbt.tx = CMutableTransaction(); | merged_psbt.tx = CMutableTransaction(); | ||||
merged_psbt.tx->nVersion = static_cast<int32_t>(best_version); | merged_psbt.tx->nVersion = static_cast<int32_t>(best_version); | ||||
merged_psbt.tx->nLockTime = best_locktime; | merged_psbt.tx->nLockTime = best_locktime; | ||||
// Merge | // Merge | ||||
for (auto &psbt : psbtxs) { | for (auto &psbt : psbtxs) { | ||||
for (size_t i = 0; i < psbt.tx->vin.size(); ++i) { | for (size_t i = 0; i < psbt.tx->vin.size(); ++i) { | ||||
if (!merged_psbt.AddInput(psbt.tx->vin[i], psbt.inputs[i])) { | if (!merged_psbt.AddInput(psbt.tx->vin[i], | ||||
psbt.inputs[i])) { | |||||
throw JSONRPCError( | throw JSONRPCError( | ||||
RPC_INVALID_PARAMETER, | RPC_INVALID_PARAMETER, | ||||
strprintf( | strprintf("Input %s:%d exists in multiple PSBTs", | ||||
"Input %s:%d exists in multiple PSBTs", | psbt.tx->vin[i] | ||||
psbt.tx->vin[i].prevout.GetTxId().ToString().c_str(), | .prevout.GetTxId() | ||||
.ToString() | |||||
.c_str(), | |||||
psbt.tx->vin[i].prevout.GetN())); | psbt.tx->vin[i].prevout.GetN())); | ||||
} | } | ||||
} | } | ||||
for (size_t i = 0; i < psbt.tx->vout.size(); ++i) { | for (size_t i = 0; i < psbt.tx->vout.size(); ++i) { | ||||
merged_psbt.AddOutput(psbt.tx->vout[i], psbt.outputs[i]); | merged_psbt.AddOutput(psbt.tx->vout[i], psbt.outputs[i]); | ||||
} | } | ||||
merged_psbt.unknown.insert(psbt.unknown.begin(), psbt.unknown.end()); | merged_psbt.unknown.insert(psbt.unknown.begin(), | ||||
psbt.unknown.end()); | |||||
} | } | ||||
// Generate list of shuffled indices for shuffling inputs and outputs of the | // Generate list of shuffled indices for shuffling inputs and | ||||
// merged PSBT | // outputs of the merged PSBT | ||||
std::vector<int> input_indices(merged_psbt.inputs.size()); | std::vector<int> input_indices(merged_psbt.inputs.size()); | ||||
std::iota(input_indices.begin(), input_indices.end(), 0); | std::iota(input_indices.begin(), input_indices.end(), 0); | ||||
std::vector<int> output_indices(merged_psbt.outputs.size()); | std::vector<int> output_indices(merged_psbt.outputs.size()); | ||||
std::iota(output_indices.begin(), output_indices.end(), 0); | std::iota(output_indices.begin(), output_indices.end(), 0); | ||||
// Shuffle input and output indices lists | // Shuffle input and output indices lists | ||||
Shuffle(input_indices.begin(), input_indices.end(), FastRandomContext()); | Shuffle(input_indices.begin(), input_indices.end(), | ||||
Shuffle(output_indices.begin(), output_indices.end(), FastRandomContext()); | FastRandomContext()); | ||||
Shuffle(output_indices.begin(), output_indices.end(), | |||||
FastRandomContext()); | |||||
PartiallySignedTransaction shuffled_psbt; | PartiallySignedTransaction shuffled_psbt; | ||||
shuffled_psbt.tx = CMutableTransaction(); | shuffled_psbt.tx = CMutableTransaction(); | ||||
shuffled_psbt.tx->nVersion = merged_psbt.tx->nVersion; | shuffled_psbt.tx->nVersion = merged_psbt.tx->nVersion; | ||||
shuffled_psbt.tx->nLockTime = merged_psbt.tx->nLockTime; | shuffled_psbt.tx->nLockTime = merged_psbt.tx->nLockTime; | ||||
for (int i : input_indices) { | for (int i : input_indices) { | ||||
shuffled_psbt.AddInput(merged_psbt.tx->vin[i], merged_psbt.inputs[i]); | shuffled_psbt.AddInput(merged_psbt.tx->vin[i], | ||||
merged_psbt.inputs[i]); | |||||
} | } | ||||
for (int i : output_indices) { | for (int i : output_indices) { | ||||
shuffled_psbt.AddOutput(merged_psbt.tx->vout[i], | shuffled_psbt.AddOutput(merged_psbt.tx->vout[i], | ||||
merged_psbt.outputs[i]); | merged_psbt.outputs[i]); | ||||
} | } | ||||
shuffled_psbt.unknown.insert(merged_psbt.unknown.begin(), | shuffled_psbt.unknown.insert(merged_psbt.unknown.begin(), | ||||
merged_psbt.unknown.end()); | merged_psbt.unknown.end()); | ||||
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); | CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); | ||||
ssTx << shuffled_psbt; | ssTx << shuffled_psbt; | ||||
return EncodeBase64(MakeUCharSpan(ssTx)); | return EncodeBase64(MakeUCharSpan(ssTx)); | ||||
}, | |||||
}; | |||||
} | } | ||||
UniValue analyzepsbt(const Config &config, const JSONRPCRequest &request) { | RPCHelpMan analyzepsbt() { | ||||
RPCHelpMan{ | return RPCHelpMan{ | ||||
"analyzepsbt", | "analyzepsbt", | ||||
"Analyzes and provides information about the current status of a " | "Analyzes and provides information about the current status of a " | ||||
"PSBT and its inputs\n", | "PSBT and its inputs\n", | ||||
{{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, | {{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, | ||||
"A base64 string of a PSBT"}}, | "A base64 string of a PSBT"}}, | ||||
RPCResult{ | RPCResult{ | ||||
RPCResult::Type::OBJ, | RPCResult::Type::OBJ, | ||||
"", | "", | ||||
▲ Show 20 Lines • Show All 57 Lines • ▼ Show 20 Lines | return RPCHelpMan{ | ||||
{RPCResult::Type::STR_AMOUNT, "fee", /* optional */ true, | {RPCResult::Type::STR_AMOUNT, "fee", /* optional */ true, | ||||
"The transaction fee paid. Shown only if all UTXO slots in " | "The transaction fee paid. Shown only if all UTXO slots in " | ||||
"the PSBT have been filled"}, | "the PSBT have been filled"}, | ||||
{RPCResult::Type::STR, "next", | {RPCResult::Type::STR, "next", | ||||
"Role of the next person that this psbt needs to go to"}, | "Role of the next person that this psbt needs to go to"}, | ||||
{RPCResult::Type::STR, "error", /* optional */ true, | {RPCResult::Type::STR, "error", /* optional */ true, | ||||
"Error message (if there is one)"}, | "Error message (if there is one)"}, | ||||
}}, | }}, | ||||
RPCExamples{HelpExampleCli("analyzepsbt", "\"psbt\"")}} | RPCExamples{HelpExampleCli("analyzepsbt", "\"psbt\"")}, | ||||
.Check(request); | [&](const RPCHelpMan &self, const Config &config, | ||||
const JSONRPCRequest &request) -> UniValue { | |||||
RPCTypeCheck(request.params, {UniValue::VSTR}); | RPCTypeCheck(request.params, {UniValue::VSTR}); | ||||
// Unserialize the transaction | // Unserialize the transaction | ||||
PartiallySignedTransaction psbtx; | PartiallySignedTransaction psbtx; | ||||
std::string error; | std::string error; | ||||
if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) { | if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) { | ||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, | throw JSONRPCError(RPC_DESERIALIZATION_ERROR, | ||||
strprintf("TX decode failed %s", error)); | strprintf("TX decode failed %s", error)); | ||||
} | } | ||||
PSBTAnalysis psbta = AnalyzePSBT(psbtx); | PSBTAnalysis psbta = AnalyzePSBT(psbtx); | ||||
UniValue result(UniValue::VOBJ); | UniValue result(UniValue::VOBJ); | ||||
UniValue inputs_result(UniValue::VARR); | UniValue inputs_result(UniValue::VARR); | ||||
for (const auto &input : psbta.inputs) { | for (const auto &input : psbta.inputs) { | ||||
UniValue input_univ(UniValue::VOBJ); | UniValue input_univ(UniValue::VOBJ); | ||||
UniValue missing(UniValue::VOBJ); | UniValue missing(UniValue::VOBJ); | ||||
input_univ.pushKV("has_utxo", input.has_utxo); | input_univ.pushKV("has_utxo", input.has_utxo); | ||||
input_univ.pushKV("is_final", input.is_final); | input_univ.pushKV("is_final", input.is_final); | ||||
input_univ.pushKV("next", PSBTRoleName(input.next)); | input_univ.pushKV("next", PSBTRoleName(input.next)); | ||||
if (!input.missing_pubkeys.empty()) { | if (!input.missing_pubkeys.empty()) { | ||||
UniValue missing_pubkeys_univ(UniValue::VARR); | UniValue missing_pubkeys_univ(UniValue::VARR); | ||||
for (const CKeyID &pubkey : input.missing_pubkeys) { | for (const CKeyID &pubkey : input.missing_pubkeys) { | ||||
missing_pubkeys_univ.push_back(HexStr(pubkey)); | missing_pubkeys_univ.push_back(HexStr(pubkey)); | ||||
} | } | ||||
missing.pushKV("pubkeys", missing_pubkeys_univ); | missing.pushKV("pubkeys", missing_pubkeys_univ); | ||||
} | } | ||||
if (!input.missing_redeem_script.IsNull()) { | if (!input.missing_redeem_script.IsNull()) { | ||||
missing.pushKV("redeemscript", HexStr(input.missing_redeem_script)); | missing.pushKV("redeemscript", | ||||
HexStr(input.missing_redeem_script)); | |||||
} | } | ||||
if (!input.missing_sigs.empty()) { | if (!input.missing_sigs.empty()) { | ||||
UniValue missing_sigs_univ(UniValue::VARR); | UniValue missing_sigs_univ(UniValue::VARR); | ||||
for (const CKeyID &pubkey : input.missing_sigs) { | for (const CKeyID &pubkey : input.missing_sigs) { | ||||
missing_sigs_univ.push_back(HexStr(pubkey)); | missing_sigs_univ.push_back(HexStr(pubkey)); | ||||
} | } | ||||
missing.pushKV("signatures", missing_sigs_univ); | missing.pushKV("signatures", missing_sigs_univ); | ||||
} | } | ||||
if (!missing.getKeys().empty()) { | if (!missing.getKeys().empty()) { | ||||
input_univ.pushKV("missing", missing); | input_univ.pushKV("missing", missing); | ||||
} | } | ||||
inputs_result.push_back(input_univ); | inputs_result.push_back(input_univ); | ||||
} | } | ||||
if (!inputs_result.empty()) { | if (!inputs_result.empty()) { | ||||
result.pushKV("inputs", inputs_result); | result.pushKV("inputs", inputs_result); | ||||
} | } | ||||
if (psbta.estimated_vsize != std::nullopt) { | if (psbta.estimated_vsize != std::nullopt) { | ||||
result.pushKV("estimated_vsize", (int)*psbta.estimated_vsize); | result.pushKV("estimated_vsize", (int)*psbta.estimated_vsize); | ||||
} | } | ||||
if (psbta.estimated_feerate != std::nullopt) { | if (psbta.estimated_feerate != std::nullopt) { | ||||
result.pushKV("estimated_feerate", | result.pushKV("estimated_feerate", | ||||
psbta.estimated_feerate->GetFeePerK()); | psbta.estimated_feerate->GetFeePerK()); | ||||
} | } | ||||
if (psbta.fee != std::nullopt) { | if (psbta.fee != std::nullopt) { | ||||
result.pushKV("fee", *psbta.fee); | result.pushKV("fee", *psbta.fee); | ||||
} | } | ||||
result.pushKV("next", PSBTRoleName(psbta.next)); | result.pushKV("next", PSBTRoleName(psbta.next)); | ||||
if (!psbta.error.empty()) { | if (!psbta.error.empty()) { | ||||
result.pushKV("error", psbta.error); | result.pushKV("error", psbta.error); | ||||
} | } | ||||
return result; | return result; | ||||
}, | |||||
}; | |||||
} | } | ||||
void RegisterRawTransactionRPCCommands(CRPCTable &t) { | void RegisterRawTransactionRPCCommands(CRPCTable &t) { | ||||
// clang-format off | // clang-format off | ||||
static const CRPCCommand commands[] = { | static const CRPCCommand commands[] = { | ||||
// category name actor (function) argNames | // category name actor (function) argNames | ||||
// ------------------- ------------------------ ---------------------- ---------- | // ------------------- ------------------------ ---------------------- ---------- | ||||
{ "rawtransactions", "getrawtransaction", getrawtransaction, {"txid","verbose","blockhash"} }, | { "rawtransactions", "getrawtransaction", getrawtransaction, {"txid","verbose","blockhash"} }, | ||||
Show All 23 Lines |