Skip to content

Commit dabd704

Browse files
committed
Merge bitcoin#25273: wallet: Pass through transaction locktime and preset input sequences and scripts to CreateTransaction
0295b44 wallet: return CreatedTransactionResult from FundTransaction (Andrew Chow) 758501b wallet: use optional for change position as an optional in CreateTransaction (Andrew Chow) 2d39db7 wallet: Explicitly preserve scriptSig and scriptWitness in CreateTransaction (Andrew Chow) 14e5074 wallet: Explicitly preserve transaction version in CreateTransaction (Andrew Chow) 0fefcbb wallet: Explicitly preserve transaction locktime in CreateTransaction (Andrew Chow) 4d335bb wallet: Set preset input sequence through coin control (Andrew Chow) 596642c wallet: Replace SelectExternal with SetTxOut (Andrew Chow) 5321786 coincontrol: Replace HasInputWeight with returning optional from Get (Andrew Chow) e1abfb5 wallet: Introduce and use PreselectedInput class in CCoinControl (Andrew Chow) Pull request description: Currently `FundTransaction` handles transaction locktime and preset input data by extracting the selected inputs and change output from `CreateTransaction`'s results. This means that `CreateTransaction` is actually unaware of any user desired locktime or sequence numbers. This can have an effect on whether and how anti-fee-sniping works. This PR makes `CreateTransaction` aware of the locktime and preset input data by providing them to `CCoinControl`. `CreateTransasction` will then set the sequences, scriptSigs, scriptWItnesses, and locktime as appropriate if they are specified. This allows `FundTransaction` to actually use `CreateTransaction`'s result directly instead of having to extract the parts of it that it wants. Additionally `FundTransaction` will return a `CreateTransactionResult` as `CreateTransaction` does instead of having several output parameters. Lastly, instead of using `-1` as a magic number for the change output position, the change position is changed to be an optional with no value set indicating no desired change output position (when provided as an input parameter) or no change output present (in the result). ACKs for top commit: josibake: ACK bitcoin@0295b44 S3RK: Code review ACK 0295b44 Tree-SHA512: 016be4d41cbf97e1938506e70959bb5335b87006162a1c1c62fa0adb637cbe7aefb76d342b8efad5f37dc693f270c8d0a0839e239fd1ac32c6941a8172f1a710
2 parents 255004f + 0295b44 commit dabd704

File tree

12 files changed

+315
-167
lines changed

12 files changed

+315
-167
lines changed

src/wallet/coincontrol.cpp

Lines changed: 100 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -14,69 +14,142 @@ CCoinControl::CCoinControl()
1414

1515
bool CCoinControl::HasSelected() const
1616
{
17-
return !m_selected_inputs.empty();
17+
return !m_selected.empty();
1818
}
1919

20-
bool CCoinControl::IsSelected(const COutPoint& output) const
20+
bool CCoinControl::IsSelected(const COutPoint& outpoint) const
2121
{
22-
return m_selected_inputs.count(output) > 0;
22+
return m_selected.count(outpoint) > 0;
2323
}
2424

25-
bool CCoinControl::IsExternalSelected(const COutPoint& output) const
25+
bool CCoinControl::IsExternalSelected(const COutPoint& outpoint) const
2626
{
27-
return m_external_txouts.count(output) > 0;
27+
const auto it = m_selected.find(outpoint);
28+
return it != m_selected.end() && it->second.HasTxOut();
2829
}
2930

3031
std::optional<CTxOut> CCoinControl::GetExternalOutput(const COutPoint& outpoint) const
3132
{
32-
const auto ext_it = m_external_txouts.find(outpoint);
33-
if (ext_it == m_external_txouts.end()) {
33+
const auto it = m_selected.find(outpoint);
34+
if (it == m_selected.end() || !it->second.HasTxOut()) {
3435
return std::nullopt;
3536
}
37+
return it->second.GetTxOut();
38+
}
3639

37-
return std::make_optional(ext_it->second);
40+
PreselectedInput& CCoinControl::Select(const COutPoint& outpoint)
41+
{
42+
auto& input = m_selected[outpoint];
43+
input.SetPosition(m_selection_pos);
44+
++m_selection_pos;
45+
return input;
46+
}
47+
void CCoinControl::UnSelect(const COutPoint& outpoint)
48+
{
49+
m_selected.erase(outpoint);
3850
}
3951

40-
void CCoinControl::Select(const COutPoint& output)
52+
void CCoinControl::UnSelectAll()
4153
{
42-
m_selected_inputs.insert(output);
54+
m_selected.clear();
4355
}
4456

45-
void CCoinControl::SelectExternal(const COutPoint& outpoint, const CTxOut& txout)
57+
std::vector<COutPoint> CCoinControl::ListSelected() const
4658
{
47-
m_selected_inputs.insert(outpoint);
48-
m_external_txouts.emplace(outpoint, txout);
59+
std::vector<COutPoint> outpoints;
60+
std::transform(m_selected.begin(), m_selected.end(), std::back_inserter(outpoints),
61+
[](const std::map<COutPoint, PreselectedInput>::value_type& pair) {
62+
return pair.first;
63+
});
64+
return outpoints;
4965
}
5066

51-
void CCoinControl::UnSelect(const COutPoint& output)
67+
void CCoinControl::SetInputWeight(const COutPoint& outpoint, int64_t weight)
5268
{
53-
m_selected_inputs.erase(output);
69+
m_selected[outpoint].SetInputWeight(weight);
5470
}
5571

56-
void CCoinControl::UnSelectAll()
72+
std::optional<int64_t> CCoinControl::GetInputWeight(const COutPoint& outpoint) const
5773
{
58-
m_selected_inputs.clear();
74+
const auto it = m_selected.find(outpoint);
75+
return it != m_selected.end() ? it->second.GetInputWeight() : std::nullopt;
5976
}
6077

61-
std::vector<COutPoint> CCoinControl::ListSelected() const
78+
std::optional<uint32_t> CCoinControl::GetSequence(const COutPoint& outpoint) const
6279
{
63-
return {m_selected_inputs.begin(), m_selected_inputs.end()};
80+
const auto it = m_selected.find(outpoint);
81+
return it != m_selected.end() ? it->second.GetSequence() : std::nullopt;
6482
}
6583

66-
void CCoinControl::SetInputWeight(const COutPoint& outpoint, int64_t weight)
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+
90+
void PreselectedInput::SetTxOut(const CTxOut& txout)
91+
{
92+
m_txout = txout;
93+
}
94+
95+
CTxOut PreselectedInput::GetTxOut() const
96+
{
97+
assert(m_txout.has_value());
98+
return m_txout.value();
99+
}
100+
101+
bool PreselectedInput::HasTxOut() const
102+
{
103+
return m_txout.has_value();
104+
}
105+
106+
void PreselectedInput::SetInputWeight(int64_t weight)
107+
{
108+
m_weight = weight;
109+
}
110+
111+
std::optional<int64_t> PreselectedInput::GetInputWeight() const
112+
{
113+
return m_weight;
114+
}
115+
116+
void PreselectedInput::SetSequence(uint32_t sequence)
117+
{
118+
m_sequence = sequence;
119+
}
120+
121+
std::optional<uint32_t> PreselectedInput::GetSequence() const
122+
{
123+
return m_sequence;
124+
}
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
67142
{
68-
m_input_weights[outpoint] = weight;
143+
return {m_script_sig, m_script_witness};
69144
}
70145

71-
bool CCoinControl::HasInputWeight(const COutPoint& outpoint) const
146+
void PreselectedInput::SetPosition(unsigned int pos)
72147
{
73-
return m_input_weights.count(outpoint) > 0;
148+
m_pos = pos;
74149
}
75150

76-
int64_t CCoinControl::GetInputWeight(const COutPoint& outpoint) const
151+
std::optional<unsigned int> PreselectedInput::GetPosition() const
77152
{
78-
auto it = m_input_weights.find(outpoint);
79-
assert(it != m_input_weights.end());
80-
return it->second;
153+
return m_pos;
81154
}
82155
} // namespace wallet

src/wallet/coincontrol.h

Lines changed: 81 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,58 @@ const int DEFAULT_MAX_DEPTH = 9999999;
2424
//! Default for -avoidpartialspends
2525
static constexpr bool DEFAULT_AVOIDPARTIALSPENDS = false;
2626

27+
class PreselectedInput
28+
{
29+
private:
30+
//! The previous output being spent by this input
31+
std::optional<CTxOut> m_txout;
32+
//! The input weight for spending this input
33+
std::optional<int64_t> m_weight;
34+
//! The sequence number for this input
35+
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;
42+
43+
public:
44+
/**
45+
* Set the previous output for this input.
46+
* Only necessary if the input is expected to be an external input.
47+
*/
48+
void SetTxOut(const CTxOut& txout);
49+
/** Retrieve the previous output for this input. */
50+
CTxOut GetTxOut() const;
51+
/** Return whether the previous output is set for this input. */
52+
bool HasTxOut() const;
53+
54+
/** Set the weight for this input. */
55+
void SetInputWeight(int64_t weight);
56+
/** Retrieve the input weight for this input. */
57+
std::optional<int64_t> GetInputWeight() const;
58+
59+
/** Set the sequence for this input. */
60+
void SetSequence(uint32_t sequence);
61+
/** Retrieve the sequence for this input. */
62+
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;
77+
};
78+
2779
/** Coin Control Features. */
2880
class CCoinControl
2981
{
@@ -59,6 +111,10 @@ class CCoinControl
59111
int m_max_depth = DEFAULT_MAX_DEPTH;
60112
//! SigningProvider that has pubkeys and scripts to do spend size estimation for external inputs
61113
FlatSigningProvider m_external_provider;
114+
//! Locktime
115+
std::optional<uint32_t> m_locktime;
116+
//! Version
117+
std::optional<uint32_t> m_version;
62118

63119
CCoinControl();
64120

@@ -69,11 +125,11 @@ class CCoinControl
69125
/**
70126
* Returns true if the given output is pre-selected.
71127
*/
72-
bool IsSelected(const COutPoint& output) const;
128+
bool IsSelected(const COutPoint& outpoint) const;
73129
/**
74130
* Returns true if the given output is selected as an external input.
75131
*/
76-
bool IsExternalSelected(const COutPoint& output) const;
132+
bool IsExternalSelected(const COutPoint& outpoint) const;
77133
/**
78134
* Returns the external output for the given outpoint if it exists.
79135
*/
@@ -82,16 +138,11 @@ class CCoinControl
82138
* Lock-in the given output for spending.
83139
* The output will be included in the transaction even if it's not the most optimal choice.
84140
*/
85-
void Select(const COutPoint& output);
86-
/**
87-
* Lock-in the given output as an external input for spending because it is not in the wallet.
88-
* The output will be included in the transaction even if it's not the most optimal choice.
89-
*/
90-
void SelectExternal(const COutPoint& outpoint, const CTxOut& txout);
141+
PreselectedInput& Select(const COutPoint& outpoint);
91142
/**
92143
* Unselects the given output.
93144
*/
94-
void UnSelect(const COutPoint& output);
145+
void UnSelect(const COutPoint& outpoint);
95146
/**
96147
* Unselects all outputs.
97148
*/
@@ -104,23 +155,33 @@ class CCoinControl
104155
* Set an input's weight.
105156
*/
106157
void SetInputWeight(const COutPoint& outpoint, int64_t weight);
107-
/**
108-
* Returns true if the input weight is set.
109-
*/
110-
bool HasInputWeight(const COutPoint& outpoint) const;
111158
/**
112159
* Returns the input weight.
113160
*/
114-
int64_t GetInputWeight(const COutPoint& outpoint) const;
161+
std::optional<int64_t> GetInputWeight(const COutPoint& outpoint) const;
162+
/** Retrieve the sequence for an input */
163+
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+
}
115180

116181
private:
117182
//! Selected inputs (inputs that will be used, regardless of whether they're optimal or not)
118-
std::set<COutPoint> m_selected_inputs;
119-
//! Map of external inputs to include in the transaction
120-
//! These are not in the wallet, so we need to track them separately
121-
std::map<COutPoint, CTxOut> m_external_txouts;
122-
//! Map of COutPoints to the maximum weight for that input
123-
std::map<COutPoint, int64_t> m_input_weights;
183+
std::map<COutPoint, PreselectedInput> m_selected;
184+
unsigned int m_selection_pos{0};
124185
};
125186
} // namespace wallet
126187

src/wallet/feebumper.cpp

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -203,10 +203,9 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
203203
errors.push_back(Untranslated(strprintf("%s:%u is already spent", txin.prevout.hash.GetHex(), txin.prevout.n)));
204204
return Result::MISC_ERROR;
205205
}
206-
if (wallet.IsMine(txin.prevout)) {
207-
new_coin_control.Select(txin.prevout);
208-
} else {
209-
new_coin_control.SelectExternal(txin.prevout, coin.out);
206+
PreselectedInput& preset_txin = new_coin_control.Select(txin.prevout);
207+
if (!wallet.IsMine(txin.prevout)) {
208+
preset_txin.SetTxOut(coin.out);
210209
}
211210
input_value += coin.out.nValue;
212211
spent_outputs.push_back(coin.out);
@@ -317,8 +316,7 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
317316
// We cannot source new unconfirmed inputs(bip125 rule 2)
318317
new_coin_control.m_min_depth = 1;
319318

320-
constexpr int RANDOM_CHANGE_POSITION = -1;
321-
auto res = CreateTransaction(wallet, recipients, RANDOM_CHANGE_POSITION, new_coin_control, false);
319+
auto res = CreateTransaction(wallet, recipients, std::nullopt, new_coin_control, false);
322320
if (!res) {
323321
errors.push_back(Untranslated("Unable to create transaction.") + Untranslated(" ") + util::ErrorString(res));
324322
return Result::WALLET_ERROR;

src/wallet/interfaces.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -281,12 +281,12 @@ class WalletImpl : public Wallet
281281
CAmount& fee) override
282282
{
283283
LOCK(m_wallet->cs_wallet);
284-
auto res = CreateTransaction(*m_wallet, recipients, change_pos,
284+
auto res = CreateTransaction(*m_wallet, recipients, change_pos == -1 ? std::nullopt : std::make_optional(change_pos),
285285
coin_control, sign);
286286
if (!res) return util::Error{util::ErrorString(res)};
287287
const auto& txr = *res;
288288
fee = txr.fee;
289-
change_pos = txr.change_pos;
289+
change_pos = txr.change_pos ? *txr.change_pos : -1;
290290

291291
return txr.tx;
292292
}

0 commit comments

Comments
 (0)