v31.1-roots.1 · Commit 6: [PATCH 06/17] feat(wallet): preserve Roots signing and spend controls on v31
src/wallet/interfaces.cpp
Before: src/wallet/interfaces.cpp · After: src/wallet/interfaces.cpp
File metadata
index 3ffac043b95ebe922f2bfc173452606a9935c2e7..14b0a44fe499cc91d783511895ae87289f923bd3 100644
Download original patch · Release notes
Diff rows 1–168 of 168. 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/interfaces.cpp
CHANGED
|
@@ -29,6 +29,10 @@
|
|
|
29
29
|
#include <wallet/spend.h>
|
|
30
30
|
#include <wallet/wallet.h>
|
|
31
31
|
|
|
32
|
+
#include <key_io.h>
|
|
33
|
+
#include <script/solver.h>
|
|
34
|
+
#include <policy/policy.h>
|
|
35
|
+
|
|
32
36
|
#include <memory>
|
|
33
37
|
#include <string>
|
|
34
38
|
#include <utility>
|
|
@@ -45,6 +49,7 @@ using interfaces::WalletBalances;
|
|
|
45
49
|
using interfaces::WalletLoader;
|
|
46
50
|
using interfaces::WalletMigrationResult;
|
|
47
51
|
using interfaces::WalletOrderForm;
|
|
52
|
+
using interfaces::WalletSweepResult;
|
|
48
53
|
using interfaces::WalletTx;
|
|
49
54
|
using interfaces::WalletTxOut;
|
|
50
55
|
using interfaces::WalletTxStatus;
|
|
@@ -113,10 +118,23 @@ WalletTxOut MakeWalletTxOut(const CWallet& wallet,
|
|
|
113
118
|
int depth) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
|
|
114
119
|
{
|
|
115
120
|
WalletTxOut result;
|
|
121
|
+
result.outpoint = COutPoint{wtx.GetHash(), static_cast<uint32_t>(n)};
|
|
116
122
|
result.txout = wtx.tx->vout[n];
|
|
117
123
|
result.time = wtx.GetTxTime();
|
|
118
124
|
result.depth_in_main_chain = depth;
|
|
119
|
-
result.is_spent = wallet.IsSpent(COutPoint(wtx.GetHash(), n));
|
|
125
|
+
result.is_spent = wallet.IsSpent(result.outpoint);
|
|
126
|
+
result.is_locked = wallet.IsLockedCoin(result.outpoint);
|
|
127
|
+
result.is_reused = wallet.IsSpentKey(result.txout.scriptPubKey);
|
|
128
|
+
result.is_immature = wallet.IsTxImmatureCoinBase(wtx);
|
|
129
|
+
result.is_change = OutputIsChange(wallet, result.txout);
|
|
130
|
+
ExtractDestination(result.txout.scriptPubKey, result.address);
|
|
131
|
+
if (const auto* entry = wallet.FindAddressBookEntry(result.address)) {
|
|
132
|
+
result.label = entry->GetLabel();
|
|
133
|
+
}
|
|
134
|
+
CTxDestination grouping_address;
|
|
135
|
+
if (ExtractDestination(FindNonChangeParentOutput(wallet, result.outpoint).scriptPubKey, grouping_address)) {
|
|
136
|
+
result.grouping_address = grouping_address;
|
|
137
|
+
}
|
|
120
138
|
return result;
|
|
121
139
|
}
|
|
122
140
|
|
|
@@ -124,10 +142,37 @@ WalletTxOut MakeWalletTxOut(const CWallet& wallet,
|
|
|
124
142
|
const COutput& output) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
|
|
125
143
|
{
|
|
126
144
|
WalletTxOut result;
|
|
145
|
+
result.outpoint = output.outpoint;
|
|
127
146
|
result.txout = output.txout;
|
|
128
147
|
result.time = output.time;
|
|
129
148
|
result.depth_in_main_chain = output.depth;
|
|
130
149
|
result.is_spent = wallet.IsSpent(output.outpoint);
|
|
150
|
+
result.input_bytes = output.input_bytes;
|
|
151
|
+
if (output.HasEffectiveValue()) result.effective_value = output.GetEffectiveValue();
|
|
152
|
+
result.ancestor_bump_fees = output.ancestor_bump_fees;
|
|
153
|
+
result.is_spendable = wallet.IsMine(output.txout);
|
|
154
|
+
// Core treats descriptor-owned outputs as selectable even without keys.
|
|
155
|
+
// Report missing signing material separately so PSBT selection remains usable.
|
|
156
|
+
result.is_watch_only = !wallet.IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER);
|
|
157
|
+
for (const auto* spkm : wallet.GetScriptPubKeyMans(output.txout.scriptPubKey)) {
|
|
158
|
+
if (spkm->HavePrivateKeys()) result.is_watch_only = false;
|
|
159
|
+
}
|
|
160
|
+
result.is_solvable = output.solvable;
|
|
161
|
+
result.is_safe = output.safe;
|
|
162
|
+
result.is_locked = wallet.IsLockedCoin(output.outpoint);
|
|
163
|
+
result.is_reused = wallet.IsSpentKey(output.txout.scriptPubKey);
|
|
164
|
+
if (const CWalletTx* wtx = wallet.GetWalletTx(output.outpoint.hash)) {
|
|
165
|
+
result.is_immature = wallet.IsTxImmatureCoinBase(*wtx);
|
|
166
|
+
}
|
|
167
|
+
result.is_change = OutputIsChange(wallet, output.txout);
|
|
168
|
+
ExtractDestination(output.txout.scriptPubKey, result.address);
|
|
169
|
+
if (const auto* entry = wallet.FindAddressBookEntry(result.address)) {
|
|
170
|
+
result.label = entry->GetLabel();
|
|
171
|
+
}
|
|
172
|
+
CTxDestination grouping_address;
|
|
173
|
+
if (ExtractDestination(FindNonChangeParentOutput(wallet, output.outpoint).scriptPubKey, grouping_address)) {
|
|
174
|
+
result.grouping_address = grouping_address;
|
|
175
|
+
}
|
|
131
176
|
return result;
|
|
132
177
|
}
|
|
133
178
|
|
|
@@ -174,6 +219,50 @@ public:
|
|
|
174
219
|
LOCK(m_wallet->cs_wallet);
|
|
175
220
|
return m_wallet->IsMine(dest);
|
|
176
221
|
}
|
|
222
|
+
util::Result<WalletSweepResult> sweepPrivateKey(const SecureString& private_key,
|
|
223
|
+
const CTxDestination& destination,
|
|
224
|
+
bool broadcast,
|
|
225
|
+
const std::optional<Txid>& expected_txid,
|
|
226
|
+
const std::shared_ptr<interfaces::SweepAuthorization>& authorization) override
|
|
227
|
+
{
|
|
228
|
+
if (!authorization || authorization->IsCancelled()) return util::Error{Untranslated("Sweep cancelled")};
|
|
229
|
+
CKey key{DecodeSecret(private_key)};
|
|
230
|
+
if (!key.IsValid()) return util::Error{Untranslated("Invalid private key or wrong network")};
|
|
231
|
+
const CPubKey pubkey{key.GetPubKey()};
|
|
232
|
+
FlatSigningProvider provider;
|
|
233
|
+
provider.keys.emplace(pubkey.GetID(), key);
|
|
234
|
+
provider.pubkeys.emplace(pubkey.GetID(), pubkey);
|
|
235
|
+
std::set<CScript> scan_objects{GetScriptForDestination(PKHash(pubkey))};
|
|
236
|
+
if (pubkey.IsCompressed()) {
|
|
237
|
+
const CScript witness{GetScriptForDestination(WitnessV0KeyHash(pubkey))};
|
|
238
|
+
scan_objects.insert(witness);
|
|
239
|
+
provider.scripts.emplace(CScriptID(witness), witness);
|
|
240
|
+
scan_objects.insert(GetScriptForDestination(ScriptHash(witness)));
|
|
241
|
+
}
|
|
242
|
+
const CScript destination_script{GetScriptForDestination(destination)};
|
|
243
|
+
{
|
|
244
|
+
LOCK(m_wallet->cs_wallet);
|
|
245
|
+
if (!CanSweepTo(*m_wallet, destination_script)) {
|
|
246
|
+
return util::Error{Untranslated("Destination is not controlled by this wallet")};
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
std::map<COutPoint, Coin> coins;
|
|
250
|
+
switch (m_wallet->chain().findScriptPubKeys(scan_objects, coins, [&] { return authorization->IsCancelled(); })) {
|
|
251
|
+
case interfaces::ScanResult::SUCCESS: break;
|
|
252
|
+
case interfaces::ScanResult::BUSY: return util::Error{Untranslated("UTXO scan already in progress")};
|
|
253
|
+
case interfaces::ScanResult::ABORTED: return util::Error{Untranslated("UTXO scan aborted")};
|
|
254
|
+
case interfaces::ScanResult::UNAVAILABLE: return util::Error{Untranslated("UTXO scan unavailable")};
|
|
255
|
+
}
|
|
256
|
+
auto swept{CreateSweepTransaction(*m_wallet, provider, coins, destination_script, broadcast ? SweepBroadcastFn{[&, expected_txid](const CTransactionRef& tx, std::string& error) {
|
|
257
|
+
if (expected_txid && tx->GetHash() != *expected_txid) {
|
|
258
|
+
error = "Sweep preview is stale; create a fresh preview";
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
return m_wallet->chain().broadcastTransaction(tx, m_wallet->m_default_max_tx_fee, node::TxBroadcast::MEMPOOL_AND_BROADCAST_TO_ALL, error);
|
|
262
|
+
}} : SweepBroadcastFn{}, *authorization)};
|
|
263
|
+
if (!swept) return util::Error{util::ErrorString(swept)};
|
|
264
|
+
return WalletSweepResult{swept->tx, swept->amount, swept->fee, coins.size(), GetVirtualTransactionSize(*swept->tx), broadcast};
|
|
265
|
+
}
|
|
177
266
|
bool setAddressBook(const CTxDestination& dest, const std::string& name, const std::optional<AddressPurpose>& purpose) override
|
|
178
267
|
{
|
|
179
268
|
return m_wallet->SetAddressBook(dest, name, purpose);
|
|
@@ -447,13 +536,32 @@ public:
|
|
|
447
536
|
}
|
|
448
537
|
return result;
|
|
449
538
|
}
|
|
450
|
-
std::vector<WalletTxOut> getCoins(const std::vector<COutPoint>& outputs) override
|
|
539
|
+
std::vector<WalletTxOut> getCoins(const std::vector<COutPoint>& outputs, const std::optional<CFeeRate>& feerate) override
|
|
451
540
|
{
|
|
452
541
|
LOCK(m_wallet->cs_wallet);
|
|
542
|
+
CCoinControl coin_control;
|
|
543
|
+
// Expose factual state rather than applying a caller's transaction
|
|
544
|
+
// policy. In particular, dirty, locked, unsafe, watch-only, and
|
|
545
|
+
// immature coins need to be inspectable even when not selectable.
|
|
546
|
+
coin_control.m_avoid_address_reuse = false;
|
|
547
|
+
CoinFilterParams params;
|
|
548
|
+
params.check_version_trucness = false;
|
|
549
|
+
params.include_immature_coinbase = true;
|
|
550
|
+
params.skip_locked = false;
|
|
551
|
+
std::map<COutPoint, COutput> available_coins;
|
|
552
|
+
for (const COutput& coin : AvailableCoins(*m_wallet, &coin_control, feerate, params).All()) {
|
|
553
|
+
available_coins.emplace(coin.outpoint, coin);
|
|
554
|
+
}
|
|
555
|
+
|
|
453
556
|
std::vector<WalletTxOut> result;
|
|
454
557
|
result.reserve(outputs.size());
|
|
455
558
|
for (const auto& output : outputs) {
|
|
559
|
+
if (const auto coin = available_coins.find(output); coin != available_coins.end()) {
|
|
560
|
+
result.emplace_back(MakeWalletTxOut(*m_wallet, coin->second));
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
456
563
|
result.emplace_back();
|
|
564
|
+
result.back().outpoint = output;
|
|
457
565
|
auto it = m_wallet->mapWallet.find(output.hash);
|
|
458
566
|
if (it != m_wallet->mapWallet.end()) {
|
|
459
567
|
int depth = m_wallet->GetTxDepthInMainChain(it->second);
|
|
@@ -478,6 +586,7 @@ public:
|
|
|
478
586
|
return result;
|
|
479
587
|
}
|
|
480
588
|
unsigned int getConfirmTarget() override { return m_wallet->m_confirm_target; }
|
|
589
|
+
bool getDefaultRbf() override { return m_wallet->m_signal_rbf; }
|
|
481
590
|
bool hdEnabled() override { return m_wallet->IsHDEnabled(); }
|
|
482
591
|
bool canGetAddresses() override { return m_wallet->CanGetAddresses(); }
|
|
483
592
|
bool hasExternalSigner() override { return m_wallet->IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER); }
|