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. Before is on the left; after is on the right. Unified view fits narrow screens more easily.
src/wallet/interfaces.cpp
CHANGED
|
@@ -29,6 +29,10 @@
|
|
| 29 |
#include <wallet/spend.h>
|
| 30 |
#include <wallet/wallet.h>
|
| 31 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
#include <memory>
|
| 33 |
#include <string>
|
| 34 |
#include <utility>
|
|
@@ -45,6 +49,7 @@ using interfaces::WalletBalances;
|
|
| 45 |
using interfaces::WalletLoader;
|
| 46 |
using interfaces::WalletMigrationResult;
|
| 47 |
using interfaces::WalletOrderForm;
|
|
|
|
| 48 |
using interfaces::WalletTx;
|
| 49 |
using interfaces::WalletTxOut;
|
| 50 |
using interfaces::WalletTxStatus;
|
|
@@ -113,10 +118,23 @@ WalletTxOut MakeWalletTxOut(const CWallet& wallet,
|
|
| 113 |
int depth) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
|
| 114 |
{
|
| 115 |
WalletTxOut result;
|
|
|
|
| 116 |
result.txout = wtx.tx->vout[n];
|
| 117 |
result.time = wtx.GetTxTime();
|
| 118 |
result.depth_in_main_chain = depth;
|
| 119 |
-
result.is_spent = wallet.IsSpent(COutPoint(wtx.GetHash(), n));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
return result;
|
| 121 |
}
|
| 122 |
|
|
@@ -124,10 +142,37 @@ WalletTxOut MakeWalletTxOut(const CWallet& wallet,
|
|
| 124 |
const COutput& output) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
|
| 125 |
{
|
| 126 |
WalletTxOut result;
|
|
|
|
| 127 |
result.txout = output.txout;
|
| 128 |
result.time = output.time;
|
| 129 |
result.depth_in_main_chain = output.depth;
|
| 130 |
result.is_spent = wallet.IsSpent(output.outpoint);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
return result;
|
| 132 |
}
|
| 133 |
|
|
@@ -174,6 +219,50 @@ public:
|
|
| 174 |
LOCK(m_wallet->cs_wallet);
|
| 175 |
return m_wallet->IsMine(dest);
|
| 176 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 177 |
bool setAddressBook(const CTxDestination& dest, const std::string& name, const std::optional<AddressPurpose>& purpose) override
|
| 178 |
{
|
| 179 |
return m_wallet->SetAddressBook(dest, name, purpose);
|
|
@@ -447,13 +536,32 @@ public:
|
|
| 447 |
}
|
| 448 |
return result;
|
| 449 |
}
|
| 450 |
-
std::vector<WalletTxOut> getCoins(const std::vector<COutPoint>& outputs) override
|
| 451 |
{
|
| 452 |
LOCK(m_wallet->cs_wallet);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 453 |
std::vector<WalletTxOut> result;
|
| 454 |
result.reserve(outputs.size());
|
| 455 |
for (const auto& output : outputs) {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 456 |
result.emplace_back();
|
|
|
|
| 457 |
auto it = m_wallet->mapWallet.find(output.hash);
|
| 458 |
if (it != m_wallet->mapWallet.end()) {
|
| 459 |
int depth = m_wallet->GetTxDepthInMainChain(it->second);
|
|
@@ -478,6 +586,7 @@ public:
|
|
| 478 |
return result;
|
| 479 |
}
|
| 480 |
unsigned int getConfirmTarget() override { return m_wallet->m_confirm_target; }
|
|
|
|
| 481 |
bool hdEnabled() override { return m_wallet->IsHDEnabled(); }
|
| 482 |
bool canGetAddresses() override { return m_wallet->CanGetAddresses(); }
|
| 483 |
bool hasExternalSigner() override { return m_wallet->IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER); }
|
|
|
|
| 29 |
#include <wallet/spend.h>
|
| 30 |
#include <wallet/wallet.h>
|
| 31 |
|
| 32 |
+
#include <key_io.h>
|
| 33 |
+
#include <script/solver.h>
|
| 34 |
+
#include <policy/policy.h>
|
| 35 |
+
|
| 36 |
#include <memory>
|
| 37 |
#include <string>
|
| 38 |
#include <utility>
|
|
|
|
| 49 |
using interfaces::WalletLoader;
|
| 50 |
using interfaces::WalletMigrationResult;
|
| 51 |
using interfaces::WalletOrderForm;
|
| 52 |
+
using interfaces::WalletSweepResult;
|
| 53 |
using interfaces::WalletTx;
|
| 54 |
using interfaces::WalletTxOut;
|
| 55 |
using interfaces::WalletTxStatus;
|
|
|
|
| 118 |
int depth) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
|
| 119 |
{
|
| 120 |
WalletTxOut result;
|
| 121 |
+
result.outpoint = COutPoint{wtx.GetHash(), static_cast<uint32_t>(n)};
|
| 122 |
result.txout = wtx.tx->vout[n];
|
| 123 |
result.time = wtx.GetTxTime();
|
| 124 |
result.depth_in_main_chain = depth;
|
| 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 |
+
}
|
| 138 |
return result;
|
| 139 |
}
|
| 140 |
|
|
|
|
| 142 |
const COutput& output) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
|
| 143 |
{
|
| 144 |
WalletTxOut result;
|
| 145 |
+
result.outpoint = output.outpoint;
|
| 146 |
result.txout = output.txout;
|
| 147 |
result.time = output.time;
|
| 148 |
result.depth_in_main_chain = output.depth;
|
| 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 |
+
}
|
| 176 |
return result;
|
| 177 |
}
|
| 178 |
|
|
|
|
| 219 |
LOCK(m_wallet->cs_wallet);
|
| 220 |
return m_wallet->IsMine(dest);
|
| 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 |
+
}
|
| 266 |
bool setAddressBook(const CTxDestination& dest, const std::string& name, const std::optional<AddressPurpose>& purpose) override
|
| 267 |
{
|
| 268 |
return m_wallet->SetAddressBook(dest, name, purpose);
|
|
|
|
| 536 |
}
|
| 537 |
return result;
|
| 538 |
}
|
| 539 |
+
std::vector<WalletTxOut> getCoins(const std::vector<COutPoint>& outputs, const std::optional<CFeeRate>& feerate) override
|
| 540 |
{
|
| 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 |
+
|
| 556 |
std::vector<WalletTxOut> result;
|
| 557 |
result.reserve(outputs.size());
|
| 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 |
+
}
|
| 563 |
result.emplace_back();
|
| 564 |
+
result.back().outpoint = output;
|
| 565 |
auto it = m_wallet->mapWallet.find(output.hash);
|
| 566 |
if (it != m_wallet->mapWallet.end()) {
|
| 567 |
int depth = m_wallet->GetTxDepthInMainChain(it->second);
|
|
|
|
| 586 |
return result;
|
| 587 |
}
|
| 588 |
unsigned int getConfirmTarget() override { return m_wallet->m_confirm_target; }
|
| 589 |
+
bool getDefaultRbf() override { return m_wallet->m_signal_rbf; }
|
| 590 |
bool hdEnabled() override { return m_wallet->IsHDEnabled(); }
|
| 591 |
bool canGetAddresses() override { return m_wallet->CanGetAddresses(); }
|
| 592 |
bool hasExternalSigner() override { return m_wallet->IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER); }
|