From 2d39db7aa128a948b6ad11242591ef26a342f5b1 Mon Sep 17 00:00:00 2001 From: Andrew Chow Date: Wed, 1 Jun 2022 17:39:58 -0400 Subject: [PATCH] wallet: Explicitly preserve scriptSig and scriptWitness in CreateTransaction When creating a transaction with preset inputs, also preserve the scriptSig and scriptWitness for those preset inputs if they are provided (e.g. in fundrawtransaction). --- src/wallet/coincontrol.cpp | 41 +++++++++++++++++++++++++++++++++++++- src/wallet/coincontrol.h | 37 ++++++++++++++++++++++++++++++++++ src/wallet/spend.cpp | 31 +++++++++++++++++++++++++++- 3 files changed, 107 insertions(+), 2 deletions(-) diff --git a/src/wallet/coincontrol.cpp b/src/wallet/coincontrol.cpp index f13b7073bee..873c5ab3837 100644 --- a/src/wallet/coincontrol.cpp +++ b/src/wallet/coincontrol.cpp @@ -39,7 +39,10 @@ std::optional CCoinControl::GetExternalOutput(const COutPoint& outpoint) PreselectedInput& CCoinControl::Select(const COutPoint& outpoint) { - return m_selected[outpoint]; + auto& input = m_selected[outpoint]; + input.SetPosition(m_selection_pos); + ++m_selection_pos; + return input; } void CCoinControl::UnSelect(const COutPoint& outpoint) { @@ -78,6 +81,12 @@ std::optional CCoinControl::GetSequence(const COutPoint& outpoint) con return it != m_selected.end() ? it->second.GetSequence() : std::nullopt; } +std::pair, std::optional> CCoinControl::GetScripts(const COutPoint& outpoint) const +{ + const auto it = m_selected.find(outpoint); + return it != m_selected.end() ? m_selected.at(outpoint).GetScripts() : std::make_pair(std::nullopt, std::nullopt); +} + void PreselectedInput::SetTxOut(const CTxOut& txout) { m_txout = txout; @@ -113,4 +122,34 @@ std::optional PreselectedInput::GetSequence() const { return m_sequence; } + +void PreselectedInput::SetScriptSig(const CScript& script) +{ + m_script_sig = script; +} + +void PreselectedInput::SetScriptWitness(const CScriptWitness& script_wit) +{ + m_script_witness = script_wit; +} + +bool PreselectedInput::HasScripts() const +{ + return m_script_sig.has_value() || m_script_witness.has_value(); +} + +std::pair, std::optional> PreselectedInput::GetScripts() const +{ + return {m_script_sig, m_script_witness}; +} + +void PreselectedInput::SetPosition(unsigned int pos) +{ + m_pos = pos; +} + +std::optional PreselectedInput::GetPosition() const +{ + return m_pos; +} } // namespace wallet diff --git a/src/wallet/coincontrol.h b/src/wallet/coincontrol.h index 66f9dcb53c0..b2f813383dc 100644 --- a/src/wallet/coincontrol.h +++ b/src/wallet/coincontrol.h @@ -33,6 +33,12 @@ private: std::optional m_weight; //! The sequence number for this input std::optional m_sequence; + //! The scriptSig for this input + std::optional m_script_sig; + //! The scriptWitness for this input + std::optional m_script_witness; + //! The position in the inputs vector for this input + std::optional m_pos; public: /** @@ -54,6 +60,20 @@ public: void SetSequence(uint32_t sequence); /** Retrieve the sequence for this input. */ std::optional GetSequence() const; + + /** Set the scriptSig for this input. */ + void SetScriptSig(const CScript& script); + /** Set the scriptWitness for this input. */ + void SetScriptWitness(const CScriptWitness& script_wit); + /** Return whether either the scriptSig or scriptWitness are set for this input. */ + bool HasScripts() const; + /** Retrieve both the scriptSig and the scriptWitness. */ + std::pair, std::optional> GetScripts() const; + + /** Store the position of this input. */ + void SetPosition(unsigned int pos); + /** Retrieve the position of this input. */ + std::optional GetPosition() const; }; /** Coin Control Features. */ @@ -141,10 +161,27 @@ public: std::optional GetInputWeight(const COutPoint& outpoint) const; /** Retrieve the sequence for an input */ std::optional GetSequence(const COutPoint& outpoint) const; + /** Retrieves the scriptSig and scriptWitness for an input. */ + std::pair, std::optional> GetScripts(const COutPoint& outpoint) const; + + bool HasSelectedOrder() const + { + return m_selection_pos > 0; + } + + std::optional GetSelectionPos(const COutPoint& outpoint) const + { + const auto it = m_selected.find(outpoint); + if (it == m_selected.end()) { + return std::nullopt; + } + return it->second.GetPosition(); + } private: //! Selected inputs (inputs that will be used, regardless of whether they're optimal or not) std::map m_selected; + unsigned int m_selection_pos{0}; }; } // namespace wallet diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp index c560c711373..72ec719f226 100644 --- a/src/wallet/spend.cpp +++ b/src/wallet/spend.cpp @@ -1146,6 +1146,25 @@ static util::Result CreateTransactionInternal( // Shuffle selected coins and fill in final vin std::vector> selected_coins = result.GetShuffledInputVector(); + if (coin_control.HasSelected() && coin_control.HasSelectedOrder()) { + // When there are preselected inputs, we need to move them to be the first UTXOs + // and have them be in the order selected. We can use stable_sort for this, where we + // compare with the positions stored in coin_control. The COutputs that have positions + // will be placed before those that don't, and those positions will be in order. + std::stable_sort(selected_coins.begin(), selected_coins.end(), + [&coin_control](const std::shared_ptr& a, const std::shared_ptr& b) { + auto a_pos = coin_control.GetSelectionPos(a->outpoint); + auto b_pos = coin_control.GetSelectionPos(b->outpoint); + if (a_pos.has_value() && b_pos.has_value()) { + return a_pos.value() < b_pos.value(); + } else if (a_pos.has_value() && !b_pos.has_value()) { + return true; + } else { + return false; + } + }); + } + // The sequence number is set to non-maxint so that DiscourageFeeSniping // works. // @@ -1162,7 +1181,15 @@ static util::Result CreateTransactionInternal( // If an input has a preset sequence, we can't do anti-fee-sniping use_anti_fee_sniping = false; } - txNew.vin.emplace_back(coin->outpoint, CScript(), sequence.value_or(default_sequence)); + txNew.vin.emplace_back(coin->outpoint, CScript{}, sequence.value_or(default_sequence)); + + auto scripts = coin_control.GetScripts(coin->outpoint); + if (scripts.first) { + txNew.vin.back().scriptSig = *scripts.first; + } + if (scripts.second) { + txNew.vin.back().scriptWitness = *scripts.second; + } } if (coin_control.m_locktime) { txNew.nLockTime = coin_control.m_locktime.value(); @@ -1381,6 +1408,8 @@ bool FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& nFeeRet, preset_txin.SetTxOut(coins[outPoint].out); } preset_txin.SetSequence(txin.nSequence); + preset_txin.SetScriptSig(txin.scriptSig); + preset_txin.SetScriptWitness(txin.scriptWitness); } auto res = CreateTransaction(wallet, vecSend, nChangePosInOut, coinControl, false);