Changeset View
Changeset View
Standalone View
Standalone View
src/rpc/rawtransaction.cpp
Show First 20 Lines • Show All 718 Lines • ▼ Show 20 Lines | static void TxInErrorToJSON(const CTxIn &txin, UniValue &vErrorsRet, | ||||
entry.pushKV("vout", uint64_t(txin.prevout.GetN())); | entry.pushKV("vout", uint64_t(txin.prevout.GetN())); | ||||
entry.push_back(Pair("scriptSig", | entry.push_back(Pair("scriptSig", | ||||
HexStr(txin.scriptSig.begin(), txin.scriptSig.end()))); | HexStr(txin.scriptSig.begin(), txin.scriptSig.end()))); | ||||
entry.pushKV("sequence", uint64_t(txin.nSequence)); | entry.pushKV("sequence", uint64_t(txin.nSequence)); | ||||
entry.pushKV("error", strMessage); | entry.pushKV("error", strMessage); | ||||
vErrorsRet.push_back(entry); | vErrorsRet.push_back(entry); | ||||
} | } | ||||
UniValue combinerawtransaction(const Config &config, | |||||
const JSONRPCRequest &request) { | |||||
if (request.fHelp || request.params.size() != 1) { | |||||
throw std::runtime_error( | |||||
"combinerawtransaction [\"hexstring\",...]\n" | |||||
"\nCombine multiple partially signed transactions into one " | |||||
"transaction.\n" | |||||
"The combined transaction may be another partially signed " | |||||
"transaction or a \n" | |||||
"fully signed transaction." | |||||
"\nArguments:\n" | |||||
"1. \"txs\" (string) A json array of hex strings of " | |||||
"partially signed transactions\n" | |||||
" [\n" | |||||
" \"hexstring\" (string) A transaction hash\n" | |||||
" ,...\n" | |||||
" ]\n" | |||||
"\nResult:\n" | |||||
"\"hex\" : \"value\", (string) The hex-encoded raw " | |||||
"transaction with signature(s)\n" | |||||
"\nExamples:\n" + | |||||
HelpExampleCli("combinerawtransaction", | |||||
"[\"myhex1\", \"myhex2\", \"myhex3\"]")); | |||||
} | |||||
UniValue txs = request.params[0].get_array(); | |||||
std::vector<CMutableTransaction> txVariants(txs.size()); | |||||
for (unsigned int idx = 0; idx < txs.size(); idx++) { | |||||
if (!DecodeHexTx(txVariants[idx], txs[idx].get_str())) { | |||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, | |||||
strprintf("TX decode failed for tx %d", idx)); | |||||
} | |||||
} | |||||
if (txVariants.empty()) { | |||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Missing transactions"); | |||||
} | |||||
// mergedTx will end up with all the signatures; it | |||||
// starts as a clone of the rawtx: | |||||
CMutableTransaction mergedTx(txVariants[0]); | |||||
// Fetch previous transactions (inputs): | |||||
CCoinsView viewDummy; | |||||
CCoinsViewCache view(&viewDummy); | |||||
{ | |||||
LOCK(cs_main); | |||||
LOCK(mempool.cs); | |||||
CCoinsViewCache &viewChain = *pcoinsTip; | |||||
CCoinsViewMemPool viewMempool(&viewChain, mempool); | |||||
// temporarily switch cache backend to db+mempool view | |||||
view.SetBackend(viewMempool); | |||||
for (const CTxIn &txin : mergedTx.vin) { | |||||
// Load entries from viewChain into view; can fail. | |||||
view.AccessCoin(txin.prevout); | |||||
} | |||||
// switch back to avoid locking mempool for too long | |||||
view.SetBackend(viewDummy); | |||||
} | |||||
// Use CTransaction for the constant parts of the | |||||
// transaction to avoid rehashing. | |||||
const CTransaction txConst(mergedTx); | |||||
// Sign what we can: | |||||
for (unsigned int i = 0; i < mergedTx.vin.size(); i++) { | |||||
deadalnix: size_t | |||||
CTxIn &txin = mergedTx.vin[i]; | |||||
const Coin &coin = view.AccessCoin(txin.prevout); | |||||
if (coin.IsSpent()) { | |||||
throw JSONRPCError(RPC_VERIFY_ERROR, | |||||
"Input not found or already spent"); | |||||
} | |||||
const CScript &prevPubKey = coin.GetTxOut().scriptPubKey; | |||||
const Amount &amount = coin.GetTxOut().nValue; | |||||
SignatureData sigdata; | |||||
// ... and merge in other signatures: | |||||
for (const CMutableTransaction &txv : txVariants) { | |||||
if (txv.vin.size() > i) { | |||||
sigdata = CombineSignatures( | |||||
prevPubKey, | |||||
TransactionSignatureChecker(&txConst, i, amount), sigdata, | |||||
DataFromTransaction(txv, i)); | |||||
} | |||||
} | |||||
UpdateTransaction(mergedTx, i, sigdata); | |||||
} | |||||
return EncodeHexTx(CTransaction(mergedTx)); | |||||
} | |||||
static UniValue signrawtransaction(const Config &config, | static UniValue signrawtransaction(const Config &config, | ||||
const JSONRPCRequest &request) { | const JSONRPCRequest &request) { | ||||
#ifdef ENABLE_WALLET | #ifdef ENABLE_WALLET | ||||
CWallet *const pwallet = GetWalletForJSONRPCRequest(request); | CWallet *const pwallet = GetWalletForJSONRPCRequest(request); | ||||
#endif | #endif | ||||
if (request.fHelp || request.params.size() < 1 || | if (request.fHelp || request.params.size() < 1 || | ||||
request.params.size() > 4) { | request.params.size() > 4) { | ||||
▲ Show 20 Lines • Show All 93 Lines • ▼ Show 20 Lines | #ifdef ENABLE_WALLET | ||||
LOCK2(cs_main, pwallet ? &pwallet->cs_wallet : nullptr); | LOCK2(cs_main, pwallet ? &pwallet->cs_wallet : nullptr); | ||||
#else | #else | ||||
LOCK(cs_main); | LOCK(cs_main); | ||||
#endif | #endif | ||||
RPCTypeCheck( | RPCTypeCheck( | ||||
request.params, | request.params, | ||||
{UniValue::VSTR, UniValue::VARR, UniValue::VARR, UniValue::VSTR}, true); | {UniValue::VSTR, UniValue::VARR, UniValue::VARR, UniValue::VSTR}, true); | ||||
std::vector<uint8_t> txData(ParseHexV(request.params[0], "argument 1")); | CMutableTransaction mtx; | ||||
CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION); | if (!DecodeHexTx(mtx, request.params[0].get_str())) { | ||||
std::vector<CMutableTransaction> txVariants; | |||||
while (!ssData.empty()) { | |||||
try { | |||||
CMutableTransaction tx; | |||||
ssData >> tx; | |||||
txVariants.push_back(tx); | |||||
} catch (const std::exception &) { | |||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); | throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); | ||||
} | } | ||||
} | |||||
if (txVariants.empty()) { | |||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Missing transaction"); | |||||
} | |||||
// mergedTx will end up with all the signatures; it starts as a clone of the | |||||
// rawtx: | |||||
CMutableTransaction mergedTx(txVariants[0]); | |||||
// Fetch previous transactions (inputs): | // Fetch previous transactions (inputs): | ||||
CCoinsView viewDummy; | CCoinsView viewDummy; | ||||
CCoinsViewCache view(&viewDummy); | CCoinsViewCache view(&viewDummy); | ||||
{ | { | ||||
LOCK(mempool.cs); | LOCK(mempool.cs); | ||||
CCoinsViewCache &viewChain = *pcoinsTip; | CCoinsViewCache &viewChain = *pcoinsTip; | ||||
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 : mtx.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); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 154 Lines • ▼ Show 20 Lines | if (request.params.size() > 3 && !request.params[3].isNull()) { | ||||
} | } | ||||
} | } | ||||
// Script verification errors. | // Script verification errors. | ||||
UniValue vErrors(UniValue::VARR); | UniValue vErrors(UniValue::VARR); | ||||
// Use CTransaction for the constant parts of the transaction to avoid | // Use CTransaction for the constant parts of the transaction to avoid | ||||
// rehashing. | // rehashing. | ||||
const CTransaction txConst(mergedTx); | const CTransaction txConst(mtx); | ||||
// Sign what we can: | // Sign what we can: | ||||
for (size_t i = 0; i < mergedTx.vin.size(); i++) { | for (size_t i = 0; i < mtx.vin.size(); i++) { | ||||
CTxIn &txin = mergedTx.vin[i]; | CTxIn &txin = mtx.vin[i]; | ||||
const Coin &coin = view.AccessCoin(txin.prevout); | const Coin &coin = view.AccessCoin(txin.prevout); | ||||
if (coin.IsSpent()) { | if (coin.IsSpent()) { | ||||
TxInErrorToJSON(txin, vErrors, "Input not found or already spent"); | TxInErrorToJSON(txin, vErrors, "Input not found or already spent"); | ||||
continue; | continue; | ||||
} | } | ||||
const CScript &prevPubKey = coin.GetTxOut().scriptPubKey; | const CScript &prevPubKey = coin.GetTxOut().scriptPubKey; | ||||
const Amount amount = coin.GetTxOut().nValue; | const Amount amount = coin.GetTxOut().nValue; | ||||
SignatureData sigdata; | SignatureData sigdata; | ||||
// Only sign SIGHASH_SINGLE if there's a corresponding output: | // Only sign SIGHASH_SINGLE if there's a corresponding output: | ||||
if ((sigHashType.getBaseType() != BaseSigHashType::SINGLE) || | if ((sigHashType.getBaseType() != BaseSigHashType::SINGLE) || | ||||
(i < mergedTx.vout.size())) { | (i < mtx.vout.size())) { | ||||
ProduceSignature(MutableTransactionSignatureCreator( | ProduceSignature(MutableTransactionSignatureCreator( | ||||
&keystore, &mergedTx, i, amount, sigHashType), | &keystore, &mtx, i, amount, sigHashType), | ||||
jasonbcoxUnsubmitted Not Done Inline ActionsJust a note for other reviews, because I almost missed this: This line corresponds to if (!fHashSingle || (i < mtx.vout.size())) ProduceSignature(MutableTransactionSignatureCreator(&keystore, &mtx, i, amount, nHashType), prevPubKey, sigdata); in the original PR: https://github.com/bitcoin/bitcoin/pull/10571/files It threw me off because we had some refactors on this line that made it easy to miss while scanning. jasonbcox: Just a note for other reviews, because I almost missed this: This line corresponds to `if (! | |||||
prevPubKey, sigdata); | prevPubKey, sigdata); | ||||
} | } | ||||
// ... and merge in other signatures: | |||||
for (const CMutableTransaction &txv : txVariants) { | |||||
if (txv.vin.size() > i) { | |||||
sigdata = CombineSignatures( | sigdata = CombineSignatures( | ||||
prevPubKey, | prevPubKey, TransactionSignatureChecker(&txConst, i, amount), | ||||
TransactionSignatureChecker(&txConst, i, amount), sigdata, | sigdata, DataFromTransaction(mtx, i)); | ||||
DataFromTransaction(txv, i)); | |||||
} | |||||
} | |||||
UpdateTransaction(mergedTx, i, sigdata); | UpdateTransaction(mtx, i, sigdata); | ||||
ScriptError serror = SCRIPT_ERR_OK; | ScriptError serror = SCRIPT_ERR_OK; | ||||
if (!VerifyScript( | if (!VerifyScript( | ||||
txin.scriptSig, prevPubKey, STANDARD_SCRIPT_VERIFY_FLAGS, | txin.scriptSig, prevPubKey, STANDARD_SCRIPT_VERIFY_FLAGS, | ||||
TransactionSignatureChecker(&txConst, i, amount), &serror)) { | TransactionSignatureChecker(&txConst, i, amount), &serror)) { | ||||
TxInErrorToJSON(txin, vErrors, ScriptErrorString(serror)); | TxInErrorToJSON(txin, vErrors, ScriptErrorString(serror)); | ||||
} | } | ||||
} | } | ||||
bool fComplete = vErrors.empty(); | bool fComplete = vErrors.empty(); | ||||
UniValue result(UniValue::VOBJ); | UniValue result(UniValue::VOBJ); | ||||
result.pushKV("hex", EncodeHexTx(CTransaction(mergedTx))); | result.pushKV("hex", EncodeHexTx(CTransaction(mtx))); | ||||
result.pushKV("complete", fComplete); | result.pushKV("complete", fComplete); | ||||
if (!vErrors.empty()) { | if (!vErrors.empty()) { | ||||
result.pushKV("errors", vErrors); | result.pushKV("errors", vErrors); | ||||
} | } | ||||
return result; | return result; | ||||
} | } | ||||
▲ Show 20 Lines • Show All 94 Lines • ▼ Show 20 Lines | |||||
static const ContextFreeRPCCommand commands[] = { | static const ContextFreeRPCCommand commands[] = { | ||||
// category name actor (function) argNames | // category name actor (function) argNames | ||||
// ------------------- ------------------------ ---------------------- ---------- | // ------------------- ------------------------ ---------------------- ---------- | ||||
{ "rawtransactions", "getrawtransaction", getrawtransaction, {"txid","verbose"} }, | { "rawtransactions", "getrawtransaction", getrawtransaction, {"txid","verbose"} }, | ||||
{ "rawtransactions", "createrawtransaction", createrawtransaction, {"inputs","outputs","locktime"} }, | { "rawtransactions", "createrawtransaction", createrawtransaction, {"inputs","outputs","locktime"} }, | ||||
{ "rawtransactions", "decoderawtransaction", decoderawtransaction, {"hexstring"} }, | { "rawtransactions", "decoderawtransaction", decoderawtransaction, {"hexstring"} }, | ||||
{ "rawtransactions", "decodescript", decodescript, {"hexstring"} }, | { "rawtransactions", "decodescript", decodescript, {"hexstring"} }, | ||||
{ "rawtransactions", "sendrawtransaction", sendrawtransaction, {"hexstring","allowhighfees"} }, | { "rawtransactions", "sendrawtransaction", sendrawtransaction, {"hexstring","allowhighfees"} }, | ||||
{ "rawtransactions", "combinerawtransaction", combinerawtransaction, {"txs"} }, | |||||
{ "rawtransactions", "signrawtransaction", signrawtransaction, {"hexstring","prevtxs","privkeys","sighashtype"} }, /* uses wallet if enabled */ | { "rawtransactions", "signrawtransaction", signrawtransaction, {"hexstring","prevtxs","privkeys","sighashtype"} }, /* uses wallet if enabled */ | ||||
{ "blockchain", "gettxoutproof", gettxoutproof, {"txids", "blockhash"} }, | { "blockchain", "gettxoutproof", gettxoutproof, {"txids", "blockhash"} }, | ||||
{ "blockchain", "verifytxoutproof", verifytxoutproof, {"proof"} }, | { "blockchain", "verifytxoutproof", verifytxoutproof, {"proof"} }, | ||||
}; | }; | ||||
// clang-format on | // clang-format on | ||||
void RegisterRawTransactionRPCCommands(CRPCTable &t) { | void RegisterRawTransactionRPCCommands(CRPCTable &t) { | ||||
for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++) { | for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++) { | ||||
t.appendCommand(commands[vcidx].name, &commands[vcidx]); | t.appendCommand(commands[vcidx].name, &commands[vcidx]); | ||||
} | } | ||||
} | } |
size_t