diff options
author | Ava Chow <github@achow101.com> | 2024-06-27 13:59:46 -0400 |
---|---|---|
committer | Ava Chow <github@achow101.com> | 2024-06-27 13:59:46 -0400 |
commit | f0745d028e42486fb346015cfc481923739c479e (patch) | |
tree | 5a51bee47a72b386b7edb7c24dcd2b95f814405b | |
parent | b27afb7fb774809c9c075d56e44657e0b607a00c (diff) | |
parent | a9c7300135f0188daa5bce5491e2daf2dd8da8ae (diff) |
Merge bitcoin/bitcoin#30050: refactor, wallet: get serialized size of `CRecipient`s directly
a9c7300135f0188daa5bce5491e2daf2dd8da8ae move-only: refactor CreateTransactionInternal (josibake)
adc6ab25bba42ce9e7ed6bd7599aabb6dead6987 wallet: use CRecipient instead of CTxOut (josibake)
Pull request description:
Broken out from #28201
---
In order to estimate fees properly, we need to know what the final serialized transaction size will be. This PR refactors `CreateTransactionInternal` to:
* Get the serialized size directly from the `CRecipient`: this sets us up in a future PR to calculate the serialized size of silent payment `CTxDestinations` (see https://github.com/bitcoin/bitcoin/pull/28201/commits/797e21c8c1bf393e668eeef8bb7ee9cae1e5e41e)
* Use the new `GetSerializeSizeForRecipient` to move the serialize size calculation to *before* coin selection and the output creation to *after* coin selection: this also sets us up for silent payments sending in a future PR in that silent payments outputs cannot be created until after the inputs to the transaction have been selected
Aside from the silent payments use case, I think this structure logically makes more sense. As a reminder, move-only commits are best reviewed with something like `git diff -w --color-moved=dimmed-zebra`
ACKs for top commit:
S3RK:
reACK a9c7300135f0188daa5bce5491e2daf2dd8da8ae
achow101:
ACK a9c7300135f0188daa5bce5491e2daf2dd8da8ae
rkrux:
tACK [a9c7300](https://github.com/bitcoin/bitcoin/pull/30050/commits/a9c7300135f0188daa5bce5491e2daf2dd8da8ae)
Tree-SHA512: 412e1764b98f7428c8530c3a68f55e32063d6b66ab2ff613e1c7e12d49b049807cb60055cfe7f7e8ffe7ac7f0f9931427cbfd3efe7d4f97a5a0f6d1bf1aaac58
-rw-r--r-- | src/wallet/spend.cpp | 40 |
1 files changed, 23 insertions, 17 deletions
diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp index c16b8a9d4f..0a59353052 100644 --- a/src/wallet/spend.cpp +++ b/src/wallet/spend.cpp @@ -983,6 +983,16 @@ static void DiscourageFeeSniping(CMutableTransaction& tx, FastRandomContext& rng } } +size_t GetSerializeSizeForRecipient(const CRecipient& recipient) +{ + return ::GetSerializeSize(CTxOut(recipient.nAmount, GetScriptForDestination(recipient.dest))); +} + +bool IsDust(const CRecipient& recipient, const CFeeRate& dustRelayFee) +{ + return ::IsDust(CTxOut(recipient.nAmount, GetScriptForDestination(recipient.dest)), dustRelayFee); +} + static util::Result<CreatedTransactionResult> CreateTransactionInternal( CWallet& wallet, const std::vector<CRecipient>& vecSend, @@ -1005,12 +1015,20 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal( // Set the long term feerate estimate to the wallet's consolidate feerate coin_selection_params.m_long_term_feerate = wallet.m_consolidate_feerate; + // Static vsize overhead + outputs vsize. 4 nVersion, 4 nLocktime, 1 input count, 1 witness overhead (dummy, flag, stack size) + coin_selection_params.tx_noinputs_size = 10 + GetSizeOfCompactSize(vecSend.size()); // bytes for output count CAmount recipients_sum = 0; const OutputType change_type = wallet.TransactionChangeType(coin_control.m_change_type ? *coin_control.m_change_type : wallet.m_default_change_type, vecSend); ReserveDestination reservedest(&wallet, change_type); unsigned int outputs_to_subtract_fee_from = 0; // The number of outputs which we are subtracting the fee from for (const auto& recipient : vecSend) { + if (IsDust(recipient, wallet.chain().relayDustFee())) { + return util::Error{_("Transaction amount too small")}; + } + + // Include the fee cost for outputs. + coin_selection_params.tx_noinputs_size += GetSerializeSizeForRecipient(recipient); recipients_sum += recipient.nAmount; if (recipient.fSubtractFeeFromAmount) { @@ -1095,23 +1113,6 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal( const auto change_spend_fee = coin_selection_params.m_discard_feerate.GetFee(coin_selection_params.change_spend_size); coin_selection_params.min_viable_change = std::max(change_spend_fee + 1, dust); - // Static vsize overhead + outputs vsize. 4 version, 4 nLocktime, 1 input count, 1 witness overhead (dummy, flag, stack size) - coin_selection_params.tx_noinputs_size = 10 + GetSizeOfCompactSize(vecSend.size()); // bytes for output count - - // vouts to the payees - for (const auto& recipient : vecSend) - { - CTxOut txout(recipient.nAmount, GetScriptForDestination(recipient.dest)); - - // Include the fee cost for outputs. - coin_selection_params.tx_noinputs_size += ::GetSerializeSize(txout); - - if (IsDust(txout, wallet.chain().relayDustFee())) { - return util::Error{_("Transaction amount too small")}; - } - txNew.vout.push_back(txout); - } - // Include the fees for things that aren't inputs, excluding the change output const CAmount not_input_fees = coin_selection_params.m_effective_feerate.GetFee(coin_selection_params.m_subtract_fee_outputs ? 0 : coin_selection_params.tx_noinputs_size); CAmount selection_target = recipients_sum + not_input_fees; @@ -1152,6 +1153,11 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal( result.GetWaste(), result.GetSelectedValue()); + // vouts to the payees + for (const auto& recipient : vecSend) + { + txNew.vout.emplace_back(recipient.nAmount, GetScriptForDestination(recipient.dest)); + } const CAmount change_amount = result.GetChange(coin_selection_params.min_viable_change, coin_selection_params.m_change_fee); if (change_amount > 0) { CTxOut newTxOut(change_amount, scriptChange); |