v31.1-roots.1 · Commit 6: [PATCH 06/17] feat(wallet): preserve Roots signing and spend controls on v31
src/wallet/rpc/spend.cpp
Before: src/wallet/rpc/spend.cpp · After: src/wallet/rpc/spend.cpp
File metadata
index 2991a27594ed1379ced89dd3da5e9c6c51a22920..442dacafccd5a31d8096e30279a5ded00e33df7f 100644
Download original patch · Release notes
Diff rows 1–101 of 101. Line numbers refer to the original before/after files.
+ Added− RemovedUnmarked lines are context.
Scroll the code area horizontally to read long lines. Before is on the left; after is on the right. Unified view fits narrow screens more easily.
src/wallet/rpc/spend.cpp
CHANGED
|
@@ -5,6 +5,7 @@
|
|
| 5 |
#include <common/messages.h>
|
| 6 |
#include <consensus/validation.h>
|
| 7 |
#include <core_io.h>
|
|
|
|
| 8 |
#include <key_io.h>
|
| 9 |
#include <node/types.h>
|
| 10 |
#include <policy/policy.h>
|
|
@@ -32,6 +33,94 @@ using common::TransactionErrorString;
|
|
| 32 |
using node::TransactionError;
|
| 33 |
|
| 34 |
namespace wallet {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
std::vector<CRecipient> CreateRecipients(const std::vector<std::pair<CTxDestination, CAmount>>& outputs, const std::set<int>& subtract_fee_outputs)
|
| 36 |
{
|
| 37 |
std::vector<CRecipient> recipients;
|
|
|
|
| 5 |
#include <common/messages.h>
|
| 6 |
#include <consensus/validation.h>
|
| 7 |
#include <core_io.h>
|
| 8 |
+
#include <interfaces/wallet.h>
|
| 9 |
#include <key_io.h>
|
| 10 |
#include <node/types.h>
|
| 11 |
#include <policy/policy.h>
|
|
|
|
| 33 |
using node::TransactionError;
|
| 34 |
|
| 35 |
namespace wallet {
|
| 36 |
+
//! Sweep coins controlled by transient keys. The keys are deliberately kept out
|
| 37 |
+
//! of the wallet and are only used by the local signing provider below.
|
| 38 |
+
RPCHelpMan sweepprivkeys()
|
| 39 |
+
{
|
| 40 |
+
return RPCHelpMan{"sweepprivkeys",
|
| 41 |
+
"Sweep confirmed UTXOs from supplied Base58 WIF private keys to a spendable address in this wallet.\n"
|
| 42 |
+
"All valid WIF keys support P2PKH. Compressed keys also support native P2WPKH and wrapped P2SH-P2WPKH. "
|
| 43 |
+
"Taproot and other script forms are not scanned.\n"
|
| 44 |
+
"The destination must be controlled and spendable by this selected wallet; watch-only and private-key-disabled wallets are rejected. "
|
| 45 |
+
"Supplied keys are never imported or saved. Keep an independent backup until the transaction confirms.\n"
|
| 46 |
+
"With broadcast=false (the default), the result is a signed preview and no transaction is submitted. The fee reflects current wallet fee settings and chain state. "
|
| 47 |
+
"Set broadcast=true only after reviewing a fresh preview; broadcast can fail if inputs, fee policy, or chain state changed.\n",
|
| 48 |
+
{
|
| 49 |
+
{"privkeys", RPCArg::Type::ARR, RPCArg::Optional::NO, "Valid Base58 WIF private keys", {{"privatekey", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "private key"}}},
|
| 50 |
+
{"destination", RPCArg::Type::STR, RPCArg::Optional::NO, "A spendable address controlled by this selected wallet"},
|
| 51 |
+
{"broadcast", RPCArg::Type::BOOL, RPCArg::Default{false}, "Submit the signed transaction after a preview has been reviewed"},
|
| 52 |
+
},
|
| 53 |
+
RPCResult{RPCResult::Type::OBJ, "", "", {
|
| 54 |
+
{RPCResult::Type::STR_HEX, "hex", "Signed transaction"},
|
| 55 |
+
{RPCResult::Type::STR_HEX, "txid", "Transaction id"},
|
| 56 |
+
{RPCResult::Type::STR_AMOUNT, "amount", "Total value of swept inputs"},
|
| 57 |
+
{RPCResult::Type::STR_AMOUNT, "fee", "Transaction fee"},
|
| 58 |
+
{RPCResult::Type::NUM, "inputs", "Number of swept inputs"},
|
| 59 |
+
{RPCResult::Type::BOOL, "broadcast", "Whether the transaction was submitted for broadcast"},
|
| 60 |
+
}},
|
| 61 |
+
RPCExamples{HelpExampleCli("sweepprivkeys", "'[\"cV...\"]' bcrt1... false")},
|
| 62 |
+
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue {
|
| 63 |
+
const std::shared_ptr<CWallet> pwallet = GetWalletForJSONRPCRequest(request);
|
| 64 |
+
if (!pwallet) return UniValue::VNULL;
|
| 65 |
+
|
| 66 |
+
FlatSigningProvider provider;
|
| 67 |
+
std::set<CKeyID> seen_keys;
|
| 68 |
+
std::set<CScript> scan_objects;
|
| 69 |
+
for (const UniValue& value : request.params[0].get_array().getValues()) {
|
| 70 |
+
CKey key{DecodeSecret(value.get_str())};
|
| 71 |
+
if (!key.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key or wrong network");
|
| 72 |
+
const CPubKey pubkey{key.GetPubKey()};
|
| 73 |
+
if (!seen_keys.insert(pubkey.GetID()).second) throw JSONRPCError(RPC_INVALID_PARAMETER, "Duplicate private key");
|
| 74 |
+
provider.keys.emplace(pubkey.GetID(), key);
|
| 75 |
+
provider.pubkeys.emplace(pubkey.GetID(), pubkey);
|
| 76 |
+
scan_objects.insert(GetScriptForDestination(PKHash(pubkey)));
|
| 77 |
+
if (pubkey.IsCompressed()) {
|
| 78 |
+
const CScript witness{GetScriptForDestination(WitnessV0KeyHash(pubkey))};
|
| 79 |
+
scan_objects.insert(witness);
|
| 80 |
+
provider.scripts.emplace(CScriptID(witness), witness);
|
| 81 |
+
scan_objects.insert(GetScriptForDestination(ScriptHash(witness)));
|
| 82 |
+
}
|
| 83 |
+
}
|
| 84 |
+
if (seen_keys.empty()) throw JSONRPCError(RPC_INVALID_PARAMETER, "At least one private key is required");
|
| 85 |
+
|
| 86 |
+
const CTxDestination destination{DecodeDestination(request.params[1].get_str())};
|
| 87 |
+
if (!IsValidDestination(destination)) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid destination");
|
| 88 |
+
const CScript destination_script{GetScriptForDestination(destination)};
|
| 89 |
+
{
|
| 90 |
+
LOCK(pwallet->cs_wallet);
|
| 91 |
+
if (!CanSweepTo(*pwallet, destination_script)) {
|
| 92 |
+
throw JSONRPCError(RPC_WALLET_ERROR, "Destination is not controlled by this wallet");
|
| 93 |
+
}
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
std::map<COutPoint, Coin> coins;
|
| 97 |
+
switch (pwallet->chain().findScriptPubKeys(scan_objects, coins)) {
|
| 98 |
+
case interfaces::ScanResult::SUCCESS: break;
|
| 99 |
+
case interfaces::ScanResult::BUSY: throw JSONRPCError(RPC_MISC_ERROR, "UTXO scan already in progress");
|
| 100 |
+
case interfaces::ScanResult::ABORTED: throw JSONRPCError(RPC_MISC_ERROR, "UTXO scan aborted");
|
| 101 |
+
case interfaces::ScanResult::UNAVAILABLE: throw JSONRPCError(RPC_MISC_ERROR, "UTXO scan unavailable");
|
| 102 |
+
}
|
| 103 |
+
const bool broadcast{!request.params[2].isNull() && request.params[2].get_bool()};
|
| 104 |
+
interfaces::SweepAuthorization authorization;
|
| 105 |
+
auto swept{CreateSweepTransaction(*pwallet, provider, coins, destination_script, broadcast ? SweepBroadcastFn{[&](const CTransactionRef& tx, std::string& error) {
|
| 106 |
+
return pwallet->chain().broadcastTransaction(tx, pwallet->m_default_max_tx_fee, node::TxBroadcast::MEMPOOL_AND_BROADCAST_TO_ALL, error);
|
| 107 |
+
}} : SweepBroadcastFn{}, authorization)};
|
| 108 |
+
if (!swept) {
|
| 109 |
+
const std::string error{util::ErrorString(swept).original};
|
| 110 |
+
const int code{error == "No spendable UTXOs found" || error == "Fee exceeds swept value" ? RPC_WALLET_INSUFFICIENT_FUNDS :
|
| 111 |
+
error == "Unable to sign all swept inputs" ? RPC_VERIFY_ERROR : RPC_WALLET_ERROR};
|
| 112 |
+
throw JSONRPCError(code, error);
|
| 113 |
+
}
|
| 114 |
+
UniValue result{UniValue::VOBJ};
|
| 115 |
+
result.pushKV("hex", EncodeHexTx(*swept->tx));
|
| 116 |
+
result.pushKV("txid", swept->tx->GetHash().GetHex());
|
| 117 |
+
result.pushKV("amount", ValueFromAmount(swept->amount));
|
| 118 |
+
result.pushKV("fee", ValueFromAmount(swept->fee));
|
| 119 |
+
result.pushKV("inputs", coins.size());
|
| 120 |
+
result.pushKV("broadcast", broadcast);
|
| 121 |
+
return result;
|
| 122 |
+
}};
|
| 123 |
+
}
|
| 124 |
std::vector<CRecipient> CreateRecipients(const std::vector<std::pair<CTxDestination, CAmount>>& outputs, const std::set<int>& subtract_fee_outputs)
|
| 125 |
{
|
| 126 |
std::vector<CRecipient> recipients;
|