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