Skip to content
Bitcoin Roots

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. Before is on the left; after is on the right. Unified view fits narrow screens more easily.

src/wallet/rpc/spend.cpp CHANGED
@@ -32,6 +32,88 @@ 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;
 
32
  using node::TransactionError;
33
 
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
+ }
117
  std::vector<CRecipient> CreateRecipients(const std::vector<std::pair<CTxDestination, CAmount>>& outputs, const std::set<int>& subtract_fee_outputs)
118
  {
119
  std::vector<CRecipient> recipients;