Skip to content
Bitcoin Roots

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); }