v31.1-roots.1 · Commit 6: [PATCH 06/17] feat(wallet): preserve Roots signing and spend controls on v31
src/wallet/spend.cpp
Before: src/wallet/spend.cpp · After: src/wallet/spend.cpp
File metadata
index 613375385d37e55c33c7e1c1fdbf0dbe0809cc31..30d0b1574b6816f133652d1fe7402b3229cc3e00 100644
Download original patch · Release notes
Diff rows 1–161 of 161. 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/spend.cpp
CHANGED
|
@@ -9,12 +9,14 @@
|
|
| 9 |
#include <consensus/amount.h>
|
| 10 |
#include <consensus/validation.h>
|
| 11 |
#include <interfaces/chain.h>
|
|
|
|
| 12 |
#include <node/types.h>
|
| 13 |
#include <numeric>
|
| 14 |
#include <policy/policy.h>
|
| 15 |
#include <policy/truc_policy.h>
|
| 16 |
#include <primitives/transaction.h>
|
| 17 |
#include <primitives/transaction_identifier.h>
|
|
|
|
| 18 |
#include <script/script.h>
|
| 19 |
#include <script/signingprovider.h>
|
| 20 |
#include <script/solver.h>
|
|
@@ -26,6 +28,7 @@
|
|
| 26 |
#include <wallet/coincontrol.h>
|
| 27 |
#include <wallet/fees.h>
|
| 28 |
#include <wallet/receive.h>
|
|
|
|
| 29 |
#include <wallet/spend.h>
|
| 30 |
#include <wallet/transaction.h>
|
| 31 |
#include <wallet/wallet.h>
|
|
@@ -43,6 +46,107 @@ TRACEPOINT_SEMAPHORE(coin_selection, attempting_aps_create_tx);
|
|
| 43 |
TRACEPOINT_SEMAPHORE(coin_selection, aps_create_tx_internal);
|
| 44 |
|
| 45 |
namespace wallet {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
static constexpr size_t OUTPUT_GROUP_MAX_ENTRIES{100};
|
| 47 |
|
| 48 |
/** Whether the descriptor represents, directly or not, a witness program. */
|
|
@@ -994,6 +1098,16 @@ static bool IsCurrentForAntiFeeSniping(interfaces::Chain& chain, const uint256&
|
|
| 994 |
return true;
|
| 995 |
}
|
| 996 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 997 |
void DiscourageFeeSniping(CMutableTransaction& tx, FastRandomContext& rng_fast,
|
| 998 |
interfaces::Chain& chain, const uint256& block_hash, int block_height)
|
| 999 |
{
|
|
@@ -1020,15 +1134,8 @@ void DiscourageFeeSniping(CMutableTransaction& tx, FastRandomContext& rng_fast,
|
|
| 1020 |
// now we ensure code won't be written that makes assumptions about
|
| 1021 |
// nLockTime that preclude a fix later.
|
| 1022 |
if (IsCurrentForAntiFeeSniping(chain, block_hash)) {
|
| 1023 |
-
tx.nLockTime = block_height;
|
| 1024 |
-
|
| 1025 |
-
// Secondly occasionally randomly pick a nLockTime even further back, so
|
| 1026 |
-
// that transactions that are delayed after signing for whatever reason,
|
| 1027 |
-
// e.g. high-latency mix networks and some CoinJoin implementations, have
|
| 1028 |
-
// better privacy.
|
| 1029 |
-
if (rng_fast.randrange(10) == 0) {
|
| 1030 |
-
tx.nLockTime = std::max(0, int(tx.nLockTime) - int(rng_fast.randrange(100)));
|
| 1031 |
-
}
|
| 1032 |
} else {
|
| 1033 |
// If our chain is lagging behind, we can't discourage fee sniping nor help
|
| 1034 |
// the privacy of high-latency transactions. To avoid leaking a potentially
|
|
|
|
| 9 |
#include <consensus/amount.h>
|
| 10 |
#include <consensus/validation.h>
|
| 11 |
#include <interfaces/chain.h>
|
| 12 |
+
#include <interfaces/wallet.h>
|
| 13 |
#include <node/types.h>
|
| 14 |
#include <numeric>
|
| 15 |
#include <policy/policy.h>
|
| 16 |
#include <policy/truc_policy.h>
|
| 17 |
#include <primitives/transaction.h>
|
| 18 |
#include <primitives/transaction_identifier.h>
|
| 19 |
+
#include <rpc/rawtransaction_util.h>
|
| 20 |
#include <script/script.h>
|
| 21 |
#include <script/signingprovider.h>
|
| 22 |
#include <script/solver.h>
|
|
|
|
| 28 |
#include <wallet/coincontrol.h>
|
| 29 |
#include <wallet/fees.h>
|
| 30 |
#include <wallet/receive.h>
|
| 31 |
+
#include <wallet/scriptpubkeyman.h>
|
| 32 |
#include <wallet/spend.h>
|
| 33 |
#include <wallet/transaction.h>
|
| 34 |
#include <wallet/wallet.h>
|
|
|
|
| 46 |
TRACEPOINT_SEMAPHORE(coin_selection, aps_create_tx_internal);
|
| 47 |
|
| 48 |
namespace wallet {
|
| 49 |
+
namespace {
|
| 50 |
+
// Test native script satisfaction without creating signatures or accessing secrets.
|
| 51 |
+
// Unlike the sizing dummy, absent keys must not produce successful signatures.
|
| 52 |
+
class AvailableSignatureCreator final : public BaseSignatureCreator {
|
| 53 |
+
const std::set<CKeyID>& m_keys;
|
| 54 |
+
|
| 55 |
+
bool HaveKey(const XOnlyPubKey& key) const
|
| 56 |
+
{
|
| 57 |
+
for (const auto& pubkey : key.GetCPubKeys()) {
|
| 58 |
+
if (m_keys.contains(pubkey.GetID())) return true;
|
| 59 |
+
}
|
| 60 |
+
return false;
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
public:
|
| 64 |
+
explicit AvailableSignatureCreator(const std::set<CKeyID>& keys) : m_keys(keys) {}
|
| 65 |
+
const BaseSignatureChecker& Checker() const override { return DUMMY_CHECKER; }
|
| 66 |
+
bool CreateSig(const SigningProvider& provider, std::vector<unsigned char>& sig, const CKeyID& keyid, const CScript& script, SigVersion version) const override
|
| 67 |
+
{
|
| 68 |
+
return m_keys.contains(keyid) && DUMMY_SIGNATURE_CREATOR.CreateSig(provider, sig, keyid, script, version);
|
| 69 |
+
}
|
| 70 |
+
bool CreateSchnorrSig(const SigningProvider& provider, std::vector<unsigned char>& sig, const XOnlyPubKey& key, const uint256* leaf, const uint256* root, SigVersion version) const override
|
| 71 |
+
{
|
| 72 |
+
return HaveKey(key) && DUMMY_SIGNATURE_CREATOR.CreateSchnorrSig(provider, sig, key, leaf, root, version);
|
| 73 |
+
}
|
| 74 |
+
std::vector<uint8_t> CreateMuSig2Nonce(const SigningProvider&, const CPubKey&, const CPubKey&, const CPubKey&, const uint256*, const uint256*, SigVersion, const SignatureData&) const override { return {}; }
|
| 75 |
+
bool CreateMuSig2PartialSig(const SigningProvider&, uint256&, const CPubKey&, const CPubKey&, const CPubKey&, const uint256*, const std::vector<std::pair<uint256, bool>>&, SigVersion, const SignatureData&) const override { return false; }
|
| 76 |
+
bool CreateMuSig2AggregateSig(const std::vector<CPubKey>& participants, std::vector<uint8_t>& sig, const CPubKey& aggregate, const CPubKey& script, const uint256* leaf, const std::vector<std::pair<uint256, bool>>& tweaks, SigVersion version, const SignatureData& data) const override
|
| 77 |
+
{
|
| 78 |
+
if (participants.empty() || !std::all_of(participants.begin(), participants.end(), [&](const CPubKey& key) { return m_keys.contains(key.GetID()); })) return false;
|
| 79 |
+
return DUMMY_SIGNATURE_CREATOR.CreateMuSig2AggregateSig(participants, sig, aggregate, script, leaf, tweaks, version, data);
|
| 80 |
+
}
|
| 81 |
+
};
|
| 82 |
+
} // namespace
|
| 83 |
+
|
| 84 |
+
bool CanSweepTo(const CWallet& wallet, const CScript& destination)
|
| 85 |
+
{
|
| 86 |
+
AssertLockHeld(wallet.cs_wallet);
|
| 87 |
+
if (!wallet.IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS) ||
|
| 88 |
+
wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) ||
|
| 89 |
+
wallet.IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER) || !wallet.IsMine(destination)) return false;
|
| 90 |
+
std::set<CKeyID> available_keys;
|
| 91 |
+
FlatSigningProvider provider;
|
| 92 |
+
for (const auto* manager : wallet.GetScriptPubKeyMans(destination)) {
|
| 93 |
+
if (const auto* descriptor_manager{dynamic_cast<const DescriptorScriptPubKeyMan*>(manager)}) {
|
| 94 |
+
const auto keys{descriptor_manager->GetAvailableKeys(destination)};
|
| 95 |
+
available_keys.insert(keys.begin(), keys.end());
|
| 96 |
+
// Combine public solving data just as native wallet signing combines
|
| 97 |
+
// providers, so one descriptor need not supply every usable path.
|
| 98 |
+
const auto solving{descriptor_manager->GetSolvingProvider(destination)};
|
| 99 |
+
if (const auto* public_data{dynamic_cast<const FlatSigningProvider*>(solving.get())}) provider.Merge(FlatSigningProvider{*public_data});
|
| 100 |
+
}
|
| 101 |
+
}
|
| 102 |
+
SignatureData satisfaction;
|
| 103 |
+
return ProduceSignature(provider, AvailableSignatureCreator{available_keys}, destination, satisfaction);
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
util::Result<SweepTransactionResult> CreateSweepTransaction(CWallet& wallet, const SigningProvider& provider,
|
| 107 |
+
const std::map<COutPoint, Coin>& coins,
|
| 108 |
+
const CScript& destination, const SweepBroadcastFn& broadcast,
|
| 109 |
+
interfaces::SweepAuthorization& authorization)
|
| 110 |
+
{
|
| 111 |
+
if (authorization.IsCancelled()) return util::Error{Untranslated("Sweep cancelled")};
|
| 112 |
+
CAmount amount{0};
|
| 113 |
+
CMutableTransaction tx;
|
| 114 |
+
for (const auto& [outpoint, coin] : coins) {
|
| 115 |
+
amount += coin.out.nValue;
|
| 116 |
+
tx.vin.emplace_back(outpoint);
|
| 117 |
+
}
|
| 118 |
+
if (coins.empty()) return util::Error{Untranslated("No spendable UTXOs found")};
|
| 119 |
+
tx.vout.emplace_back(amount, destination);
|
| 120 |
+
std::map<int, bilingual_str> errors;
|
| 121 |
+
if (!::SignTransaction(tx, &provider, coins, SIGHASH_DEFAULT, errors)) return util::Error{Untranslated("Unable to sign all swept inputs")};
|
| 122 |
+
// Fee settings can change while the UTXO scan runs. Take one synchronized
|
| 123 |
+
// snapshot for every signing iteration without holding the wallet lock
|
| 124 |
+
// during signing or broadcast.
|
| 125 |
+
const CFeeRate fee_rate{WITH_LOCK(wallet.cs_wallet, return GetMinimumFeeRate(wallet, CCoinControl{}, nullptr))};
|
| 126 |
+
CAmount fee{0};
|
| 127 |
+
for (int attempts{0}; attempts < 10; ++attempts) {
|
| 128 |
+
fee = fee_rate.GetFee(GetVirtualTransactionSize(CTransaction{tx}));
|
| 129 |
+
if (fee >= amount) return util::Error{Untranslated("Fee exceeds swept value")};
|
| 130 |
+
tx.vout[0].nValue = amount - fee;
|
| 131 |
+
errors.clear();
|
| 132 |
+
if (!::SignTransaction(tx, &provider, coins, SIGHASH_DEFAULT, errors)) return util::Error{Untranslated("Unable to sign all swept inputs")};
|
| 133 |
+
if (fee >= fee_rate.GetFee(GetVirtualTransactionSize(CTransaction{tx}))) break;
|
| 134 |
+
}
|
| 135 |
+
if (fee < fee_rate.GetFee(GetVirtualTransactionSize(CTransaction{tx}))) {
|
| 136 |
+
return util::Error{Untranslated("Unable to calculate a stable sweep fee")};
|
| 137 |
+
}
|
| 138 |
+
if (IsDust(tx.vout[0], wallet.chain().relayMinFee())) return util::Error{Untranslated("Swept output would be dust")};
|
| 139 |
+
if (authorization.IsCancelled()) return util::Error{Untranslated("Sweep cancelled")};
|
| 140 |
+
CTransactionRef final_tx{MakeTransactionRef(tx)};
|
| 141 |
+
if (broadcast) {
|
| 142 |
+
// This is the last safe cancellation point. BeginBroadcast and Cancel
|
| 143 |
+
// use one atomic state transition, so only one of them can succeed.
|
| 144 |
+
if (!authorization.BeginBroadcast()) return util::Error{Untranslated("Sweep cancelled")};
|
| 145 |
+
std::string error;
|
| 146 |
+
if (!broadcast(final_tx, error)) return util::Error{Untranslated("Sweep transaction broadcast failed: " + error)};
|
| 147 |
+
}
|
| 148 |
+
return SweepTransactionResult{final_tx, amount, fee};
|
| 149 |
+
}
|
| 150 |
static constexpr size_t OUTPUT_GROUP_MAX_ENTRIES{100};
|
| 151 |
|
| 152 |
/** Whether the descriptor represents, directly or not, a witness program. */
|
|
|
|
| 1098 |
return true;
|
| 1099 |
}
|
| 1100 |
|
| 1101 |
+
uint32_t GetAntiFeeSnipingLockTime(int block_height, FastRandomContext& rng_fast)
|
| 1102 |
+
{
|
| 1103 |
+
uint32_t lock_time{static_cast<uint32_t>(block_height)};
|
| 1104 |
+
if (rng_fast.randrange(10) == 0) {
|
| 1105 |
+
lock_time = std::max(0, int(lock_time) - int(rng_fast.randrange(100)));
|
| 1106 |
+
}
|
| 1107 |
+
if (lock_time == PARASITE_CAT21_LOCKTIME) --lock_time;
|
| 1108 |
+
return lock_time;
|
| 1109 |
+
}
|
| 1110 |
+
|
| 1111 |
void DiscourageFeeSniping(CMutableTransaction& tx, FastRandomContext& rng_fast,
|
| 1112 |
interfaces::Chain& chain, const uint256& block_hash, int block_height)
|
| 1113 |
{
|
|
|
|
| 1134 |
// now we ensure code won't be written that makes assumptions about
|
| 1135 |
// nLockTime that preclude a fix later.
|
| 1136 |
if (IsCurrentForAntiFeeSniping(chain, block_hash)) {
|
| 1137 |
+
// Avoid locktime values reserved by local policy.
|
| 1138 |
+
tx.nLockTime = GetAntiFeeSnipingLockTime(block_height, rng_fast);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1139 |
} else {
|
| 1140 |
// If our chain is lagging behind, we can't discourage fee sniping nor help
|
| 1141 |
// the privacy of high-latency transactions. To avoid leaking a potentially
|