Skip to content

Commit 2d39db7

Browse files
committed
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).
1 parent 14e5074 commit 2d39db7

File tree

3 files changed

+107
-2
lines changed

3 files changed

+107
-2
lines changed

src/wallet/coincontrol.cpp

+40-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@ std::optional<CTxOut> CCoinControl::GetExternalOutput(const COutPoint& outpoint)
3939

4040
PreselectedInput& CCoinControl::Select(const COutPoint& outpoint)
4141
{
42-
return m_selected[outpoint];
42+
auto& input = m_selected[outpoint];
43+
input.SetPosition(m_selection_pos);
44+
++m_selection_pos;
45+
return input;
4346
}
4447
void CCoinControl::UnSelect(const COutPoint& outpoint)
4548
{
@@ -78,6 +81,12 @@ std::optional<uint32_t> CCoinControl::GetSequence(const COutPoint& outpoint) con
7881
return it != m_selected.end() ? it->second.GetSequence() : std::nullopt;
7982
}
8083

84+
std::pair<std::optional<CScript>, std::optional<CScriptWitness>> CCoinControl::GetScripts(const COutPoint& outpoint) const
85+
{
86+
const auto it = m_selected.find(outpoint);
87+
return it != m_selected.end() ? m_selected.at(outpoint).GetScripts() : std::make_pair(std::nullopt, std::nullopt);
88+
}
89+
8190
void PreselectedInput::SetTxOut(const CTxOut& txout)
8291
{
8392
m_txout = txout;
@@ -113,4 +122,34 @@ std::optional<uint32_t> PreselectedInput::GetSequence() const
113122
{
114123
return m_sequence;
115124
}
125+
126+
void PreselectedInput::SetScriptSig(const CScript& script)
127+
{
128+
m_script_sig = script;
129+
}
130+
131+
void PreselectedInput::SetScriptWitness(const CScriptWitness& script_wit)
132+
{
133+
m_script_witness = script_wit;
134+
}
135+
136+
bool PreselectedInput::HasScripts() const
137+
{
138+
return m_script_sig.has_value() || m_script_witness.has_value();
139+
}
140+
141+
std::pair<std::optional<CScript>, std::optional<CScriptWitness>> PreselectedInput::GetScripts() const
142+
{
143+
return {m_script_sig, m_script_witness};
144+
}
145+
146+
void PreselectedInput::SetPosition(unsigned int pos)
147+
{
148+
m_pos = pos;
149+
}
150+
151+
std::optional<unsigned int> PreselectedInput::GetPosition() const
152+
{
153+
return m_pos;
154+
}
116155
} // namespace wallet

src/wallet/coincontrol.h

+37
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ class PreselectedInput
3333
std::optional<int64_t> m_weight;
3434
//! The sequence number for this input
3535
std::optional<uint32_t> m_sequence;
36+
//! The scriptSig for this input
37+
std::optional<CScript> m_script_sig;
38+
//! The scriptWitness for this input
39+
std::optional<CScriptWitness> m_script_witness;
40+
//! The position in the inputs vector for this input
41+
std::optional<unsigned int> m_pos;
3642

3743
public:
3844
/**
@@ -54,6 +60,20 @@ class PreselectedInput
5460
void SetSequence(uint32_t sequence);
5561
/** Retrieve the sequence for this input. */
5662
std::optional<uint32_t> GetSequence() const;
63+
64+
/** Set the scriptSig for this input. */
65+
void SetScriptSig(const CScript& script);
66+
/** Set the scriptWitness for this input. */
67+
void SetScriptWitness(const CScriptWitness& script_wit);
68+
/** Return whether either the scriptSig or scriptWitness are set for this input. */
69+
bool HasScripts() const;
70+
/** Retrieve both the scriptSig and the scriptWitness. */
71+
std::pair<std::optional<CScript>, std::optional<CScriptWitness>> GetScripts() const;
72+
73+
/** Store the position of this input. */
74+
void SetPosition(unsigned int pos);
75+
/** Retrieve the position of this input. */
76+
std::optional<unsigned int> GetPosition() const;
5777
};
5878

5979
/** Coin Control Features. */
@@ -141,10 +161,27 @@ class CCoinControl
141161
std::optional<int64_t> GetInputWeight(const COutPoint& outpoint) const;
142162
/** Retrieve the sequence for an input */
143163
std::optional<uint32_t> GetSequence(const COutPoint& outpoint) const;
164+
/** Retrieves the scriptSig and scriptWitness for an input. */
165+
std::pair<std::optional<CScript>, std::optional<CScriptWitness>> GetScripts(const COutPoint& outpoint) const;
166+
167+
bool HasSelectedOrder() const
168+
{
169+
return m_selection_pos > 0;
170+
}
171+
172+
std::optional<unsigned int> GetSelectionPos(const COutPoint& outpoint) const
173+
{
174+
const auto it = m_selected.find(outpoint);
175+
if (it == m_selected.end()) {
176+
return std::nullopt;
177+
}
178+
return it->second.GetPosition();
179+
}
144180

145181
private:
146182
//! Selected inputs (inputs that will be used, regardless of whether they're optimal or not)
147183
std::map<COutPoint, PreselectedInput> m_selected;
184+
unsigned int m_selection_pos{0};
148185
};
149186
} // namespace wallet
150187

src/wallet/spend.cpp

+30-1
Original file line numberDiff line numberDiff line change
@@ -1146,6 +1146,25 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
11461146
// Shuffle selected coins and fill in final vin
11471147
std::vector<std::shared_ptr<COutput>> selected_coins = result.GetShuffledInputVector();
11481148

1149+
if (coin_control.HasSelected() && coin_control.HasSelectedOrder()) {
1150+
// When there are preselected inputs, we need to move them to be the first UTXOs
1151+
// and have them be in the order selected. We can use stable_sort for this, where we
1152+
// compare with the positions stored in coin_control. The COutputs that have positions
1153+
// will be placed before those that don't, and those positions will be in order.
1154+
std::stable_sort(selected_coins.begin(), selected_coins.end(),
1155+
[&coin_control](const std::shared_ptr<COutput>& a, const std::shared_ptr<COutput>& b) {
1156+
auto a_pos = coin_control.GetSelectionPos(a->outpoint);
1157+
auto b_pos = coin_control.GetSelectionPos(b->outpoint);
1158+
if (a_pos.has_value() && b_pos.has_value()) {
1159+
return a_pos.value() < b_pos.value();
1160+
} else if (a_pos.has_value() && !b_pos.has_value()) {
1161+
return true;
1162+
} else {
1163+
return false;
1164+
}
1165+
});
1166+
}
1167+
11491168
// The sequence number is set to non-maxint so that DiscourageFeeSniping
11501169
// works.
11511170
//
@@ -1162,7 +1181,15 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
11621181
// If an input has a preset sequence, we can't do anti-fee-sniping
11631182
use_anti_fee_sniping = false;
11641183
}
1165-
txNew.vin.emplace_back(coin->outpoint, CScript(), sequence.value_or(default_sequence));
1184+
txNew.vin.emplace_back(coin->outpoint, CScript{}, sequence.value_or(default_sequence));
1185+
1186+
auto scripts = coin_control.GetScripts(coin->outpoint);
1187+
if (scripts.first) {
1188+
txNew.vin.back().scriptSig = *scripts.first;
1189+
}
1190+
if (scripts.second) {
1191+
txNew.vin.back().scriptWitness = *scripts.second;
1192+
}
11661193
}
11671194
if (coin_control.m_locktime) {
11681195
txNew.nLockTime = coin_control.m_locktime.value();
@@ -1381,6 +1408,8 @@ bool FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& nFeeRet,
13811408
preset_txin.SetTxOut(coins[outPoint].out);
13821409
}
13831410
preset_txin.SetSequence(txin.nSequence);
1411+
preset_txin.SetScriptSig(txin.scriptSig);
1412+
preset_txin.SetScriptWitness(txin.scriptWitness);
13841413
}
13851414

13861415
auto res = CreateTransaction(wallet, vecSend, nChangePosInOut, coinControl, false);

0 commit comments

Comments
 (0)