v29.4-roots.4 · Commit 23: [PATCH 23/37] feat(wallet): add transient private-key sweep RPC
src/wallet/rpc/spend.cpp
Before: src/wallet/rpc/spend.cpp · After: src/wallet/rpc/spend.cpp
File metadata
index 53692f85114844ad70a03f435c8ef28e7139b2b3..ea29d8fd2b11d5ac79e7f4ffdf6ef4a39a88d6c4 100644
Download original patch · Release notes
Diff rows 1–88 of 88. 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
|
@@ -31,6 +31,88 @@ using common::TransactionErrorString;
|
|
| 31 |
using node::TransactionError;
|
| 32 |
|
| 33 |
namespace wallet {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
std::vector<CRecipient> CreateRecipients(const std::vector<std::pair<CTxDestination, CAmount>>& outputs, const std::set<int>& subtract_fee_outputs)
|
| 35 |
{
|
| 36 |
std::vector<CRecipient> recipients;
|
|
|
|
| 31 |
using node::TransactionError;
|
| 32 |
|
| 33 |
namespace wallet {
|
| 34 |
+
//! Sweep coins controlled by transient keys. The keys are deliberately kept out
|
| 35 |
+
//! of the wallet and are only used by the local signing provider below.
|
| 36 |
+
RPCHelpMan sweepprivkeys()
|
| 37 |
+
{
|
| 38 |
+
return RPCHelpMan{"sweepprivkeys",
|
| 39 |
+
"Sweep confirmed UTXOs for supplied private keys to an address already controlled by this wallet.\n"
|
| 40 |
+
"Supplied keys are never imported or saved. Set broadcast to true only after reviewing a preview.\n",
|
| 41 |
+
{
|
| 42 |
+
{"privkeys", RPCArg::Type::ARR, RPCArg::Optional::NO, "Base58-encoded private keys", {{"privatekey", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "private key"}}},
|
| 43 |
+
{"destination", RPCArg::Type::STR, RPCArg::Optional::NO, "An address controlled by this wallet"},
|
| 44 |
+
{"broadcast", RPCArg::Type::BOOL, RPCArg::Default{false}, "Broadcast the signed transaction instead of returning a preview"},
|
| 45 |
+
},
|
| 46 |
+
RPCResult{RPCResult::Type::OBJ, "", "", {
|
| 47 |
+
{RPCResult::Type::STR_HEX, "hex", "Signed transaction"},
|
| 48 |
+
{RPCResult::Type::STR_HEX, "txid", "Transaction id"},
|
| 49 |
+
{RPCResult::Type::STR_AMOUNT, "amount", "Total value of swept inputs"},
|
| 50 |
+
{RPCResult::Type::STR_AMOUNT, "fee", "Transaction fee"},
|
| 51 |
+
{RPCResult::Type::NUM, "inputs", "Number of swept inputs"},
|
| 52 |
+
{RPCResult::Type::BOOL, "broadcast", "Whether the transaction was broadcast"},
|
| 53 |
+
}},
|
| 54 |
+
RPCExamples{HelpExampleCli("sweepprivkeys", "'[\"cV...\"]' bcrt1... false")},
|
| 55 |
+
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue {
|
| 56 |
+
const std::shared_ptr<CWallet> pwallet = GetWalletForJSONRPCRequest(request);
|
| 57 |
+
if (!pwallet) return UniValue::VNULL;
|
| 58 |
+
|
| 59 |
+
FlatSigningProvider provider;
|
| 60 |
+
std::set<CKeyID> seen_keys;
|
| 61 |
+
std::set<CScript> scan_objects;
|
| 62 |
+
for (const UniValue& value : request.params[0].get_array().getValues()) {
|
| 63 |
+
CKey key{DecodeSecret(value.get_str())};
|
| 64 |
+
if (!key.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key or wrong network");
|
| 65 |
+
const CPubKey pubkey{key.GetPubKey()};
|
| 66 |
+
if (!seen_keys.insert(pubkey.GetID()).second) throw JSONRPCError(RPC_INVALID_PARAMETER, "Duplicate private key");
|
| 67 |
+
provider.keys.emplace(pubkey.GetID(), key);
|
| 68 |
+
provider.pubkeys.emplace(pubkey.GetID(), pubkey);
|
| 69 |
+
scan_objects.insert(GetScriptForDestination(PKHash(pubkey)));
|
| 70 |
+
if (pubkey.IsCompressed()) {
|
| 71 |
+
const CScript witness{GetScriptForDestination(WitnessV0KeyHash(pubkey))};
|
| 72 |
+
scan_objects.insert(witness);
|
| 73 |
+
provider.scripts.emplace(CScriptID(witness), witness);
|
| 74 |
+
scan_objects.insert(GetScriptForDestination(ScriptHash(witness)));
|
| 75 |
+
}
|
| 76 |
+
}
|
| 77 |
+
if (seen_keys.empty()) throw JSONRPCError(RPC_INVALID_PARAMETER, "At least one private key is required");
|
| 78 |
+
|
| 79 |
+
const CTxDestination destination{DecodeDestination(request.params[1].get_str())};
|
| 80 |
+
if (!IsValidDestination(destination)) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid destination");
|
| 81 |
+
const CScript destination_script{GetScriptForDestination(destination)};
|
| 82 |
+
{
|
| 83 |
+
LOCK(pwallet->cs_wallet);
|
| 84 |
+
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) || !(pwallet->IsMine(destination_script) & ISMINE_SPENDABLE)) {
|
| 85 |
+
throw JSONRPCError(RPC_WALLET_ERROR, "Destination is not controlled by this wallet");
|
| 86 |
+
}
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
std::map<COutPoint, Coin> coins;
|
| 90 |
+
switch (pwallet->chain().findScriptPubKeys(scan_objects, coins)) {
|
| 91 |
+
case interfaces::ScanResult::SUCCESS: break;
|
| 92 |
+
case interfaces::ScanResult::BUSY: throw JSONRPCError(RPC_MISC_ERROR, "UTXO scan already in progress");
|
| 93 |
+
case interfaces::ScanResult::ABORTED: throw JSONRPCError(RPC_MISC_ERROR, "UTXO scan aborted");
|
| 94 |
+
case interfaces::ScanResult::UNAVAILABLE: throw JSONRPCError(RPC_MISC_ERROR, "UTXO scan unavailable");
|
| 95 |
+
}
|
| 96 |
+
const bool broadcast{!request.params[2].isNull() && request.params[2].get_bool()};
|
| 97 |
+
auto swept{CreateSweepTransaction(*pwallet, provider, coins, destination_script, broadcast ? SweepBroadcastFn{[&](const CTransactionRef& tx, std::string& error) {
|
| 98 |
+
return pwallet->chain().broadcastTransaction(tx, pwallet->m_default_max_tx_fee, /*relay=*/true, error);
|
| 99 |
+
}} : SweepBroadcastFn{})};
|
| 100 |
+
if (!swept) {
|
| 101 |
+
const std::string error{util::ErrorString(swept).original};
|
| 102 |
+
const int code{error == "No spendable UTXOs found" || error == "Fee exceeds swept value" ? RPC_WALLET_INSUFFICIENT_FUNDS :
|
| 103 |
+
error == "Unable to sign all swept inputs" ? RPC_VERIFY_ERROR : RPC_WALLET_ERROR};
|
| 104 |
+
throw JSONRPCError(code, error);
|
| 105 |
+
}
|
| 106 |
+
UniValue result{UniValue::VOBJ};
|
| 107 |
+
result.pushKV("hex", EncodeHexTx(*swept->tx));
|
| 108 |
+
result.pushKV("txid", swept->tx->GetHash().GetHex());
|
| 109 |
+
result.pushKV("amount", ValueFromAmount(swept->amount));
|
| 110 |
+
result.pushKV("fee", ValueFromAmount(swept->fee));
|
| 111 |
+
result.pushKV("inputs", coins.size());
|
| 112 |
+
result.pushKV("broadcast", broadcast);
|
| 113 |
+
return result;
|
| 114 |
+
}};
|
| 115 |
+
}
|
| 116 |
std::vector<CRecipient> CreateRecipients(const std::vector<std::pair<CTxDestination, CAmount>>& outputs, const std::set<int>& subtract_fee_outputs)
|
| 117 |
{
|
| 118 |
std::vector<CRecipient> recipients;
|