Bump unconfirmed ancestor transactions to target feerate #26152

pull murchandamus wants to merge 8 commits into bitcoin:master from murchandamus:2022-09-ancestor-aware-funding changing 14 files +1094 −325
  1. murchandamus commented at 8:52 pm on September 21, 2022: contributor

    Includes some commits to address follow-ups from #27021: #27021 (comment)

    Reduces the effective value of unconfirmed UTXOs by the fees necessary to bump their ancestor transactions to the same feerate.

    While the individual UTXOs always account for their full ancestry before coin-selection, we can correct potential overestimates with a second pass where we establish the ancestry and bump fee for the whole input set collectively.

    Fixes #9645 Fixes #9864 Fixes #15553

  2. murchandamus force-pushed on Sep 21, 2022
  3. DrahtBot commented at 11:24 pm on September 21, 2022: contributor

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    Reviews

    See the guideline for information on the review process.

    If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #28391 (refactor: Simplify CTxMempool/BlockAssembler fields, remove some external mapTx access by TheCharlatan)
    • #28335 (RFC: Remove boost usage from kernel api / headers by TheCharlatan)
    • #27865 (wallet: Track no-longer-spendable TXOs separately by achow101)
    • #27601 (wallet: don’t duplicate change output if already exist by furszy)
    • #27286 (wallet: Keep track of the wallet’s own transaction outputs in memory by achow101)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

  4. in src/node/mini_miner.cpp:75 in 54fd46be52 outdated
    70+
    71+    // Remove the to-be-replaced transactions and build the descendant_set_by_txid cache.
    72+    for (const auto& txiter : cluster) {
    73+        const auto& txid = txiter->GetTx().GetHash();
    74+        // Cache descendants for future use. Unlike the real mempool, a descendant MockMempoolEntry
    75+        // will not exist wihout its ancestor MockMempoolEntry, so these sets won't be invalidated.
    


    fanquake commented at 8:30 am on September 22, 2022:
    0        // will not exist without its ancestor MockMempoolEntry, so these sets won't be invalidated.
    
  5. in test/functional/wallet_spend_unconfirmed.py:1 in 54fd46be52 outdated
    0@@ -0,0 +1,297 @@
    1+#!/usr/bin/env python3
    


    fanquake commented at 8:32 am on September 22, 2022:
    0File "test/functional/wallet_spend_unconfirmed.py" contains a shebang line, but has the file permission 644 instead of the expected executable permission 755. Do "chmod 755 test/functional/wallet_spend_unconfirmed.py" (or remove the shebang line).
    1ERROR: There were 1 failed tests in the lint-files.py lint test. Please resolve the above errors.
    

    This also needs to be added to the list of tests in test_runner.py. Which should deal with:

    0[1mWARNING!�[0m The following scripts are not being run: ['wallet_spend_unconfirmed.py']. Check the test lists in test_runner.py.
    
  6. fanquake commented at 8:33 am on September 22, 2022: member
    Concept ACK
  7. fanquake requested review from instagibbs on Sep 22, 2022
  8. fanquake requested review from t-bast on Sep 22, 2022
  9. murchandamus force-pushed on Sep 22, 2022
  10. murchandamus commented at 7:56 pm on September 22, 2022: contributor

    Thanks @fanquake, I fixed the two issues.

    I also added a test for a transaction using subtractfeefromamount

  11. glozow commented at 8:09 pm on September 22, 2022: member

    HUGE Concept ACK obviously 🥳 🥳

    ~This needs to obey -maxtxfee, so I’d suggest that coin selection keeps track of what ancestors the tx has and how much of the fees is ancestors vs this transaction. And we’d want a test where the target feerate is just below maxtxfee and there are low-feerate ancestors to bump, and so total fees paid / size of this tx is higher than maxtxfee, but actually you’re just paying to bump.~

    ~Also, given that there is a (small) chance of overpayment, it would be good to check that -maxtxfee protects against something drastic. For example, a test where all the coins share 1 large parent? It will overestimate and -maxtxfee should make sure the tx isn’t sent?~ edit: this is totally wrong

  12. in src/txmempool.cpp:1245 in 26f96f0e13 outdated
    1240+                        parent_entry.GetCountWithDescendants() == 1) {
    1241+                        // We don't need to process this entry. Just add it to the processed
    1242+                        // transactions and skip.
    1243+                        std::swap(cluster[i+1], cluster.back());
    1244+                        cluster[i+1] = parent_it;
    1245+                        ++i; 
    


    glozow commented at 7:37 am on September 23, 2022:

    Sorry, lint error is my fault here

    0                        ++i;
    
  13. in src/txmempool.cpp:1248 in a07ac02d2b outdated
    1243+                        std::swap(cluster[i+1], cluster.back());
    1244+                        cluster[i+1] = parent_it;
    1245+                        ++i; 
    1246+                    } else {
    1247+                        cluster.push_back(parent_it);
    1248+                        // we still need to process this 
    


    jonatack commented at 7:52 am on September 23, 2022:

    whitespace linter

    0                        // we still need to process this
    

    Edit (you can set up your editor to highlight these or run test/lint/lint-whitespace.py as part of your local scripted prechecks before pushing, with clang-format, etc.):

     0--- a/src/txmempool.cpp
     1+++ b/src/txmempool.cpp
     2@@ -1242,10 +1242,10 @@ std::vector<CTxMemPool::txiter> CTxMemPool::CalculateCluster(const std::vector<u
     3                         // transactions and skip.
     4                         std::swap(cluster[i+1], cluster.back());
     5                         cluster[i+1] = parent_it;
     6-                        ++i; 
     7+                        ++i;
     8                     } else {
     9                         cluster.push_back(parent_it);
    10-                        // we still need to process this 
    11+                        // we still need to process this
    12                         ++unprocessed_count;
    13                     }
    14                 }
    15@@ -1259,10 +1259,10 @@ std::vector<CTxMemPool::txiter> CTxMemPool::CalculateCluster(const std::vector<u
    16                         // transactions and skip.
    17                         std::swap(cluster[i+1], cluster.back());
    18                         cluster[i+1] = child_it;
    19-                        ++i; 
    20+                        ++i;
    21                     } else {
    22                         cluster.push_back(child_it);
    23-                        // we still need to process this 
    24+                        // we still need to process this
    25                         ++unprocessed_count;
    26                     }
    27                 }
    
  14. jonatack commented at 8:04 am on September 23, 2022: contributor
    Concept ACK
  15. in src/node/mini_miner.h:28 in a07ac02d2b outdated
    19+    CAmount fee_with_ancestors;
    20+    int64_t vsize_individual;
    21+    int64_t vsize_with_ancestors;
    22+    const CTransaction& tx;
    23+
    24+public:
    


    jonatack commented at 8:16 am on September 23, 2022:

    All struct members are public by default, so can either drop public here (and removed the getters), or make the struct a class if any of the data members above are intended to be private (for instance, tx and fee_individual have public getters and could be private, or just be public and drop the getter).

    At first look it seems some of the data members need to be public, in which case their getters should be used or removed. This builds:

     0-struct MockMempoolEntry {
     1+class MockMempoolEntry
     2+{
     3     CAmount fee_individual;
     4-    CAmount fee_with_ancestors;
     5-    int64_t vsize_individual;
     6-    int64_t vsize_with_ancestors;
     7     const CTransaction& tx;
     8 
     9 public:
    10+    CAmount fee_with_ancestors;
    11+    int64_t vsize_individual;
    12+    int64_t vsize_with_ancestors;
    13     explicit MockMempoolEntry(CTxMemPool::txiter entry) :
    14         fee_individual{entry->GetModifiedFee()},
    15         fee_with_ancestors{entry->GetModFeesWithAncestors()},
    

    glozow commented at 10:41 am on September 23, 2022:
    I’d say just remove the getters, it’s fine to keep these public. This struct is only used by MiniMiner. For background, I originally was trying to align the interface with CTxMemPoolEntry like CTxMemPoolModifiedEntry to reuse the CompareTxMemPoolEntryByAncestorFee comparator in a multi index container, but then realized using simple std::maps was enough.
  16. in src/interfaces/chain.h:226 in a07ac02d2b outdated
    221+    //  mempool, it is assumed that the goal is to replace that transaction. As such, the
    222+    //  calculation will exclude the to-be-replaced transaction, but will include the fee-bumping
    223+    //  cost. If bump fees of descendants of the to-be-replaced transaction are requested, the value
    224+    //  will be 0. Fee-related RBF rules are not included as they are logically distinct.
    225+    //
    226+    //  Any outpoints that otherwise unavailable from the mempool (e.g. UTXOs from confirmed
    


    t-bast commented at 8:42 am on September 23, 2022:

    nit:

    0    //  Any outpoints that are otherwise unavailable from the mempool (e.g. UTXOs from confirmed
    
  17. in src/interfaces/chain.h:230 in a07ac02d2b outdated
    225+    //
    226+    //  Any outpoints that otherwise unavailable from the mempool (e.g. UTXOs from confirmed
    227+    //  transactions or transactions not yet broadcast by the wallet) are given a bump fee of 0.
    228+    //
    229+    //  If multiple outpoints come from the same transaction (this should be very rare because
    230+    //  the transaction essentially multiple change outputs or paid the same wallet using multiple
    


    t-bast commented at 8:43 am on September 23, 2022:

    nit:

    0    //  it means the transaction has multiple change outputs or paid the same wallet using multiple
    
  18. t-bast commented at 8:47 am on September 23, 2022: contributor
    Concept ACK, thanks a lot for working on this, it will be very helpful! And this set of internal utility functions will very likely be useful for many things in the future. I’ll create a set of E2E tests in eclair to run against this branch when I have a bit more time, I’ll let you know the results.
  19. glozow added the label Wallet on Sep 23, 2022
  20. t-bast commented at 3:49 pm on September 23, 2022: contributor
    I ran a first set of tests from within eclair against https://github.com/bitcoin/bitcoin/pull/26152/commits/a07ac02d2bc6500a03c29a0413bb913735dca46f, and everything is looking good :+1:
  21. ishaanam commented at 6:52 pm on September 27, 2022: contributor
    Concept ACK
  22. murchandamus force-pushed on Sep 27, 2022
  23. murchandamus commented at 7:19 pm on September 27, 2022: contributor
    @jonatack, @t-bast: Thanks for the review and testing. I made an attempt of getting rid of the getters on MockMempoolEntry, but what I did interfered with the calls made on properties of actual mempool entries. Will have to shift my approach.
  24. murchandamus force-pushed on Sep 27, 2022
  25. murchandamus commented at 8:25 pm on September 27, 2022: contributor

    @glozow: Maybe for

    This needs to obey -maxtxfee

    Maybe we can add a maxtxfee check to the filter introduced in #25729 for max weight after input sets are produced for different subsets of the available coins. Perhaps a separate PR that builds both on this one here and #25729. @jonatack, @t-bast: Fixed whitespace issues, applied the propose change to a class for the struct MockMempoolEntry, amended comments in Chain interface. Thanks!

  26. in src/node/mini_miner.h:31 in 337a24e6e4 outdated
    26+    explicit MockMempoolEntry(CTxMemPool::txiter entry) :
    27+        fee_individual{entry->GetModifiedFee()},
    28+        fee_with_ancestors{entry->GetModFeesWithAncestors()},
    29+        vsize_individual(entry->GetTxSize()),
    30+        vsize_with_ancestors(entry->GetSizeWithAncestors()),
    31+        tx{entry->GetTx()}
    


    glozow commented at 1:45 pm on September 28, 2022:

    in 9b33f5db095c232ac83304c91524f48bf799802f

    These need to match the order of the members, CI -Wreorder-ctor says

  27. in src/txmempool.cpp:1245 in 337a24e6e4 outdated
    1240+                        parent_entry.GetCountWithDescendants() == 1) {
    1241+                        // We don't need to process this entry. Just add it to the processed
    1242+                        // transactions and skip.
    1243+                        std::swap(cluster[i+1], cluster.back());
    1244+                        cluster[i+1] = parent_it;
    1245+                        ++i;
    


    glozow commented at 1:46 pm on September 28, 2022:

    in f40b9fe6a83b6668a625e28520456350f2cd98f0

    As discussed offline, this case can be deleted since it will never be hit (and is also incorrect).

  28. in src/txmempool.cpp:1262 in 337a24e6e4 outdated
    1257+                        child_entry.GetCountWithDescendants() == 1) {
    1258+                        // We don't need to process this entry. Just add it to the processed
    1259+                        // transactions and skip.
    1260+                        std::swap(cluster[i+1], cluster.back());
    1261+                        cluster[i+1] = child_it;
    1262+                        ++i;
    


    glozow commented at 1:47 pm on September 28, 2022:
    this as well in f40b9fe6a83b6668a625e28520456350f2cd98f0
  29. in src/wallet/coinselection.h:77 in 337a24e6e4 outdated
    70@@ -66,7 +71,10 @@ struct COutput {
    71     /** The fee required to spend this output at the consolidation feerate. */
    72     CAmount long_term_fee{0};
    73 
    74-    COutput(const COutPoint& outpoint, const CTxOut& txout, int depth, int input_bytes, bool spendable, bool solvable, bool safe, int64_t time, bool from_me, const std::optional<CFeeRate> feerate = std::nullopt)
    75+    /** The fee necessary to bump this UTXO's ancestor transactions to the target feerate */
    76+    CAmount ancestor_bump_fees{0};
    77+
    78+    COutput(const COutPoint& outpoint, const CTxOut& txout, int depth, int input_bytes, bool spendable, bool solvable, bool safe, int64_t time, bool from_me, const std::optional<CFeeRate> feerate = std::nullopt, const std::optional<std::reference_wrapper<interfaces::Chain>> chain_interface = std::nullopt)
    


    glozow commented at 2:19 pm on September 28, 2022:

    I have 2 concerns with this approach, please let me know what you think:

    (1) The COutput constructor really shouldn’t need to have a reference the chain interface; it smells a bit weird that coinselection (which I interpret to be a relatively well-modularized component thus far since it doesn’t even depend on wallet) has a new dependency on interfaces/chain.h. It’s unclear to me why CalculateBumpFees() needs to be called inside the constructor instead of just having CAmount ancestor_bump_fees be a parameter?

    (2) This means CalculateBumpFees() will be called over and over again for each COutput constructed within the AvailableCoins loop. There’s not a lot of duplicated work if all the outputs are from independent transactions, but it would definitely be faster to call it once with all the outpoints at once.

    Approach-wise, I think it makes more sense to call CalculateBumpFees() just once, with the full list of outpoints. It should be fairly straightforward with preset inputs since you have the list already. For AvailableCoins, since they’re constructed as you iterate through mapWallet, you could populate each output’s ancestor_bump_fees values at the end. If you want to construct them as-is and then not mutate afterwards, then maybe do 2 passes for filtering and constructing?


    murchandamus commented at 6:55 pm on October 27, 2022:
    Now applying the bumpfees to each output after constructing the output.
  30. in test/functional/wallet_spend_unconfirmed.py:491 in 337a24e6e4 outdated
    313+
    314+        # Actual test: Spend chain with low grandparent lower parent
    315+        self.test_chain_of_high_low_below_target_feerate()
    316+
    317+        # Actual test: Check that fee is calculated correctly when bumping while subtracting fee from output
    318+        self.test_target_feerate_unconfirmed_low_sffo()
    


    glozow commented at 2:22 pm on September 28, 2022:

    in 337a24e6e4fd77d2fc3bf94e87c2a172c39a8fb0

    Missing a test for bumpfee RPC?


    murchandamus commented at 6:51 pm on November 7, 2022:
    Added!
  31. glozow commented at 2:24 pm on September 28, 2022: member

    This needs to obey -maxtxfee

    Maybe we can add a maxtxfee check to the filter introduced in #25729 for max weight after input sets are produced for different subsets of the available coins. Perhaps a separate PR that builds both on this one here and #25729.

    ~Not sure if this is scope creep, but seems like breaking maxtxfee would be a bug and should probably be done in the same PR? Why not build this on top of #25729?~ ignore

  32. murchandamus force-pushed on Oct 27, 2022
  33. murchandamus commented at 7:08 pm on October 27, 2022: contributor
    • Reordered members vs initialization
    • Removed special casing of UTXOs without relatives in CalculateBumpFee
    • Call CalculateBumpFee once for the whole UTXO pool instead of introducing chain-interface dependency on every UTXO

    Todos:

    • Prevent exceeding maxtxfee
    • Add test for bumpfee RPC
    • Add secondary modus for CalculateBumpFee that treats the provided UTXOs as being spent together. This allows us to recalculate the bumpfee of all inputs together to resolve our overpayment caveat.
  34. DrahtBot added the label Needs rebase on Oct 27, 2022
  35. murchandamus commented at 4:19 pm on November 2, 2022: contributor
    @glozow: looking more into this, I realized that the maxtxfee refers to an absolute fee, not a feerate. maxtxfee is checked after a transaction is built, so I don’t see how ancestor aware funding changes anything in regard to maxtxfee—we still check at the end whether the amount of fee is allowed, regardless how we calculated the fee. If you meant maxtxfeerate, that is used to check raw transactions on submission in sendrawtx, so it doesn’t apply here either.
  36. glozow commented at 5:42 pm on November 2, 2022: member

    maxtxfee refers to an absolute fee, not a feerate. maxtxfee is checked after a transaction is built, so I don’t see how ancestor aware funding changes anything in regard to maxtxfee

    Ah for some reason I thought it was a feerate, apologies. Question: is it better to only enforce -maxtxfee on the fees paid for the tx itself and not on the fees used to bump its ancestors? Or would the user expect that it’s applied to any tx, bumping or not?

  37. achow101 commented at 6:38 pm on November 2, 2022: member

    Question: is it better to only enforce -maxtxfee on the fees paid for the tx itself and not on the fees used to bump its ancestors? Or would the user expect that it’s applied to any tx, bumping or not?

    I think that -maxtxfee should be expected to behave the same regardless of bumping, it’s a context-free check.

  38. murchandamus force-pushed on Nov 2, 2022
  39. murchandamus commented at 7:26 pm on November 2, 2022: contributor

    Latest changes:

    • Fixed bug where the outpoints spent by a transaction to be replaced got set to a bumpfee of 0
    • Added a test for preset inputs and the bumpfee RPC
    • Rebased

    Remaining Todo:

    • Add secondary modus for CalculateBumpFee that treats the provided UTXOs as being spent together. This allows us to recalculate the bumpfee of all inputs together to resolve our overpayment caveat.
  40. DrahtBot removed the label Needs rebase on Nov 2, 2022
  41. murchandamus force-pushed on Nov 3, 2022
  42. DrahtBot added the label Needs rebase on Nov 5, 2022
  43. murchandamus commented at 2:09 pm on November 5, 2022: contributor

    Update:

    Caveat: If multiple UTXOs share ancestry, this implementation will overpay by bumping shared ancestors once per descendant.

    After calculating an input set candidate, we recalculate the bumpfee for the collective set of inputs and correct our fee estimation if there was an overestimate due to overlapping ancestries.

    Left to do: • rebase • clean up tests • clean up commits • touch up commit messages

  44. murchandamus force-pushed on Nov 7, 2022
  45. murchandamus force-pushed on Nov 7, 2022
  46. murchandamus force-pushed on Nov 7, 2022
  47. murchandamus force-pushed on Nov 7, 2022
  48. DrahtBot removed the label Needs rebase on Nov 7, 2022
  49. murchandamus commented at 7:22 pm on November 7, 2022: contributor

    Cleaned up tests, redrew commits, touched up commit messages, rebased.

    Ready for review. :partying_face:

  50. murchandamus marked this as ready for review on Nov 7, 2022
  51. in src/node/mini_miner.cpp:237 in 898ad9d590 outdated
    232+    for (const auto& outpoint : requested_outpoints) {
    233+        const auto& txid = outpoint.hash;
    234+        // Skip any ancestors that have a higher minerscore already
    235+        if (in_block.find(txid) != in_block.end()) continue;
    236+        auto iter = entries_by_txid.find(outpoint.hash);
    237+        assert(iter != entries_by_txid.end());
    


    ishaanam commented at 4:04 am on November 9, 2022:

    In 8a474b7675d552a9f2951d7cbc59ca36d3a10f11 “Add CalculateTotalBumpFee for overlapping ancestry” I think the following change might make sense because that way even a MiniMiner that was initialized with confirmed outpoints could run CalculateTotalBumpFees. I think it would be better if CalculateTotalBumpFees could handle unconfirmed outpoints for uniformity with CalculateBumpFees.

    0        if (iter == entries_by_txid.end()) continue;
    

    murchandamus commented at 9:56 pm on November 22, 2022:
    Yes, that makes sense, thank you
  52. in src/node/mini_miner.h:63 in b669fd94f8 outdated
    58+    // txids of to-be-replaced transactions
    59+    std::set<uint256> to_be_replaced;
    60+
    61+    // After using the outpoints to figure out which transactions are to be replaced, we can just
    62+    // work with txids (each outpoint from a single tx should have the same bumpfee independently).
    63+    // Cache which outpoint are needed for each tx so we don't have to look up all the outputs.
    


    ishaanam commented at 4:14 am on November 9, 2022:

    nit:

    0    // Cache which outpoints are needed for each tx so we don't have to look up all the outputs.
    

    murchandamus commented at 9:56 pm on November 22, 2022:
    Fixed, thanks!
  53. ishaanam commented at 5:11 am on November 9, 2022: contributor
    Are there any functional tests that test for fee calculation for a transaction with both an input with an unconfirmed ancestor and a confirmed input? I couldn’t find one by looking at the descriptions of the functional tests.
  54. glozow added the label Review club on Nov 14, 2022
  55. in test/functional/wallet_spend_unconfirmed.py:107 in ad8bffe548 outdated
    102+        self.assert_spends_only_parent(ancestor_aware_tx, parent_txid)
    103+
    104+        self.assert_beats_target(ancestor_aware_tx)
    105+        resulting_ancestry_fee_rate = self.calc_set_fee_rate([parent_tx, ancestor_aware_tx])
    106+        assert_greater_than_or_equal(resulting_ancestry_fee_rate, self.target_fee_rate)
    107+        assert_greater_than_or_equal(self.target_fee_rate*1.5, resulting_ancestry_fee_rate)
    


    willcl-ark commented at 9:56 am on November 17, 2022:
    Where does the 1.5 come from here (and in later tests e.g. test_two_low_feerate_unconfirmed_parents() 1.1)?

    murchandamus commented at 6:46 pm on November 19, 2022:
    It’s just to check that the fees are at least what was expected, but do not overshoot too far, e.g. because we selected more inputs than expected.
  56. in src/wallet/feebumper.cpp:88 in 898ad9d590 outdated
    84+    std::vector<COutPoint> reused_inputs;
    85+    for (const CTxIn& txin : wtx.tx->vin) {
    86+        reused_inputs.push_back(txin.prevout);
    87+    }
    88+
    89+    std::map<COutPoint, CAmount> bump_fees = wallet.chain().CalculateBumpFees(reused_inputs, newFeerate);
    


    glozow commented at 11:25 pm on November 18, 2022:
    Would it maybe make sense to use CalculateTotalBumpFees() instead of CalculateBumpFees() here, given that all these inputs are from the same transaction and thus most certainly overlap in ancestry?

    murchandamus commented at 9:06 pm on January 20, 2023:
    Yes, in fact not accounting for overlapping ancestries here might have been a bug.
  57. in src/interfaces/chain.h:228 in 898ad9d590 outdated
    223+    //  If the outpoint comes from an unconfirmed transaction that is already above the target
    224+    //  feerate or bumped by its descendant(s) already, it does not need to be bumped. Its bump fee
    225+    //  is 0. Likewise, if any of the transaction's ancestors are already bumped, they are not
    226+    //  included in the transaction's bump fee.
    227+    //
    228+    //  This includes fee-bumping in RBFs. If an outpoint conflicts with another transaction in the
    


    andrewtoth commented at 4:40 pm on November 19, 2022:
    It’s unclear to me what the first sentence here is saying with in RBFs. Perhaps This includes fee-bumping using RBF for any conflicting transactions.?

    murchandamus commented at 5:04 pm on December 15, 2022:
    Thanks, I’ve updated the comment.
  58. in src/txmempool.cpp:961 in 898ad9d590 outdated
    952@@ -952,6 +953,24 @@ CTxMemPool::setEntries CTxMemPool::GetIterSet(const std::set<uint256>& hashes) c
    953     return ret;
    954 }
    955 
    956+std::vector<CTxMemPool::txiter> CTxMemPool::GetIterVec(const std::vector<uint256>& txids) const
    957+{
    958+    std::vector<txiter> ret;
    


    andrewtoth commented at 4:45 pm on November 19, 2022:
    Should this function start with AssertLockHeld(cs)?

    murchandamus commented at 4:55 pm on December 15, 2022:
    Thanks, fixed
  59. andrewtoth commented at 5:45 pm on November 19, 2022: contributor
    Concept ACK
  60. in src/txmempool.cpp:969 in 898ad9d590 outdated
    964+            ret.push_back(*it);
    965+        } else {
    966+            // return empty vector to let the caller know this failed.
    967+            std::vector<txiter> empty_vector;
    968+            return empty_vector;
    969+        }
    


    stickies-v commented at 4:39 pm on November 21, 2022:

    could be a bit more concise:

    0        if (!it) return {}; // return empty vector to let the caller know this failed
    1        ret.push_back(*it);
    2        }
    

    I would also add a docstring to assert(it) that even though we’re currently asserting this, the interface is that upon failure an empty vector is returned, so this assertion may be removed in the future without breaking the interface?


    mzumsande commented at 9:02 pm on November 22, 2022:
    I’d say we should either assert (and document that assumption) or return an empty vector, but not both. In the current form, this seems dangerous - in the future someone might read the doc for the function, use the generic GetIter for some other usecase and introduce a crash bug in the worst case.

    murchandamus commented at 6:06 pm on December 15, 2022:
    Thanks, removed the assert and simplified as suggested by @stickies-v
  61. in src/txmempool.cpp:961 in 898ad9d590 outdated
    952@@ -952,6 +953,24 @@ CTxMemPool::setEntries CTxMemPool::GetIterSet(const std::set<uint256>& hashes) c
    953     return ret;
    954 }
    955 
    956+std::vector<CTxMemPool::txiter> CTxMemPool::GetIterVec(const std::vector<uint256>& txids) const
    957+{
    958+    std::vector<txiter> ret;
    959+    ret.reserve(txids.size());
    960+    for (const auto& txid : txids) {
    961+        const auto it = GetIter(txid);
    


    stickies-v commented at 4:41 pm on November 21, 2022:

    nit:

    0        const auto it{GetIter(txid)};
    

    murchandamus commented at 6:43 pm on January 20, 2023:
    Fixed, thanks
  62. in src/txmempool.cpp:1224 in 898ad9d590 outdated
    1219+{
    1220+    AssertLockHeld(cs);
    1221+    std::vector<txiter> cluster{GetIterVec(txids)};
    1222+    assert(cluster.size() == txids.size());
    1223+    cluster.reserve(std::accumulate(cluster.cbegin(), cluster.cend(), 0, [](size_t sum, const auto it) {
    1224+        return sum + it->GetCountWithAncestors() + it->GetCountWithDescendants(); }));
    


    stickies-v commented at 4:52 pm on November 21, 2022:
    I think it->GetCountWithAncestors() and it->GetCountWithDescendants() both include the current transaction, so in that case this should be decreased with 1?

    glozow commented at 5:04 pm on December 6, 2022:
    That’s true, -1 makes sense. Though note this is an approximation rather than an exact reservation. It may overestimate because transactions may share ancestors/descendants, and may underestimate because the cluster may include more than just ancestors and descendants.

    murchandamus commented at 6:12 pm on December 15, 2022:
    That’s true, but since this is just allocating enough space for the iterators we are adding to the vector later, I don’t think having one slot more than we might need is going to cause any issues.
  63. in src/txmempool.cpp:1169 in 898ad9d590 outdated
    1218+std::vector<CTxMemPool::txiter> CTxMemPool::CalculateCluster(const std::vector<uint256>& txids) const
    1219+{
    1220+    AssertLockHeld(cs);
    1221+    std::vector<txiter> cluster{GetIterVec(txids)};
    1222+    assert(cluster.size() == txids.size());
    1223+    cluster.reserve(std::accumulate(cluster.cbegin(), cluster.cend(), 0, [](size_t sum, const auto it) {
    


    stickies-v commented at 5:09 pm on November 21, 2022:
    nit: I’m not sure it’d be a wortwhile improvement, but reserving before assigning could be a slight performance improvement so you only need to size the vector once instead of twice?

    murchandamus commented at 6:15 pm on December 15, 2022:
    I do not understand this question. Could you elaborate?

    murchandamus commented at 4:50 pm on February 2, 2023:
    We don’t know the cluster size before, so this doesn’t allow us to optimize here
  64. in src/txmempool.cpp:1232 in 898ad9d590 outdated
    1227+        WITH_FRESH_EPOCH(m_epoch);
    1228+        for (const auto& it : cluster) {
    1229+            visited(it);
    1230+        }
    1231+        // i = index of where the list of unprocessed starts
    1232+        for (size_t i{0}, unprocessed_count{txids.size()}; i < unprocessed_count; ++i) {
    


    stickies-v commented at 5:17 pm on November 21, 2022:

    I think unprocessed_count is incorrect, shouldn’t this be e.g. to_process_count?

    0        for (size_t i{0}, to_process_count{txids.size()}; i < to_process_count; ++i) {
    

    murchandamus commented at 6:22 pm on December 15, 2022:
    Updated the name to your suggestion
  65. in src/txmempool.cpp:1231 in 898ad9d590 outdated
    1226+        // Use epoch: visiting an entry means we have added it to the cluster vector.
    1227+        WITH_FRESH_EPOCH(m_epoch);
    1228+        for (const auto& it : cluster) {
    1229+            visited(it);
    1230+        }
    1231+        // i = index of where the list of unprocessed starts
    


    stickies-v commented at 6:07 pm on November 21, 2022:
    I don’t think this is correct?

    murchandamus commented at 6:25 pm on December 15, 2022:
    Updated the comment to correct
  66. in src/txmempool.cpp:1221 in 898ad9d590 outdated
    1244+                if (!visited(child_it)) {
    1245+                    cluster.push_back(child_it);
    1246+                    // we still need to process this
    1247+                    ++unprocessed_count;
    1248+                }
    1249+            }
    


    stickies-v commented at 7:23 pm on November 21, 2022:

    This can be deduplicated. Also, I think curr is not really more helpful than cluster[i], so I’d just remove that varariable.

    0            auto family{cluster[i]->GetMemPoolParents()};
    1            family.merge(cluster[i]->GetMemPoolChildren());
    2            for (const CTxMemPoolEntry& entry : family) {
    3                const auto tx_iter = mapTx.iterator_to(entry);
    4                if (!visited(tx_iter)) {
    5                    cluster.push_back(tx_iter);
    6                    // we still need to process this
    7                    ++unprocessed_count;
    8                }
    9            }
    

    glozow commented at 5:07 pm on December 6, 2022:
    Is this equivalent, given it’s changing from GetMemPoolChildrenConst to GetMemPoolChildren and getting a mutable reference to m_parents?

    murchandamus commented at 7:36 pm on December 15, 2022:
    TODO: Tried this, but it broke all sorts of things, gonna revisit later.

    glozow commented at 12:00 pm on February 1, 2023:
    Mutable reference = the merge is modifying the entry’s m_parents to now include its children as well. You can make a separate set that copies in the iterators from GetMemPoolParentsConst and GetMemPoolChildrenConst, but I’m not sure that’s worth the lines of code reduction.

    murchandamus commented at 11:39 pm on February 1, 2023:
    Gonna skip this for now, unless there is more demand for it
  67. in src/txmempool.h:704 in 898ad9d590 outdated
    700@@ -694,6 +701,9 @@ class CTxMemPool
    701                                    std::string& errString,
    702                                    bool fSearchForParents = true) const EXCLUSIVE_LOCKS_REQUIRED(cs);
    703 
    704+    /** Get entire list of connected transactions for all transactions in txids. */
    


    stickies-v commented at 7:25 pm on November 21, 2022:
    Would it be helpful to add a @pre indicating that all txids need to be in mempool?

    mzumsande commented at 4:40 pm on November 23, 2022:
    Agree, this should be part of its documentation. I think it means that the caller must ensure that nothing can get removed from the mempool in between preparing the list of txids and calling this function, which seems important.

    murchandamus commented at 6:20 pm on December 15, 2022:
    Added an @pre statement
  68. in src/node/mini_miner.h:24 in 898ad9d590 outdated
    19+    CAmount fee_individual;
    20+    const CTransaction& tx;
    21+
    22+public:
    23+    CAmount fee_with_ancestors;
    24+    int64_t vsize_individual;
    


    stickies-v commented at 7:33 pm on November 21, 2022:
    As per #23962, perhaps better to make these int32_t?

    murchandamus commented at 7:49 pm on December 15, 2022:
    Sure, fixed here and in other instances
  69. in src/node/mini_miner.h:38 in 898ad9d590 outdated
    33+
    34+    CAmount GetModifiedFee() const { return fee_individual; }
    35+    CAmount GetModFeesWithAncestors() const { return fee_with_ancestors; }
    36+    int64_t GetTxSize() const { return vsize_individual; }
    37+    int64_t GetSizeWithAncestors() const { return vsize_with_ancestors; }
    38+    const CTransaction& GetTx() const { return tx; }
    


    stickies-v commented at 7:36 pm on November 21, 2022:
    0    const CTransaction& GetTx() const LIFETIMEBOUND { return tx; }
    

    murchandamus commented at 8:07 pm on December 15, 2022:
    Since this is a reference on an object held in mempool, and we have a lock on mempool, I think this is not necessary.
  70. in src/node/mini_miner.h:27 in 898ad9d590 outdated
    20+    const CTransaction& tx;
    21+
    22+public:
    23+    CAmount fee_with_ancestors;
    24+    int64_t vsize_individual;
    25+    int64_t vsize_with_ancestors;
    


    stickies-v commented at 7:38 pm on November 21, 2022:
    Should these be private instead?

    murchandamus commented at 8:08 pm on December 15, 2022:
    fee_with_ancestors and vsize_with_ancestors get set directly in some code of mini_miner.cpp, so I think they cannot be private without also adding setters. I did make vsize_individual private
  71. in src/node/mini_miner.h:19 in 898ad9d590 outdated
    14+namespace node {
    15+
    16+// Container for tracking updates to ancestor feerate as we include ancestors in the "block"
    17+class MockMempoolEntry
    18+{
    19+    CAmount fee_individual;
    


    stickies-v commented at 7:40 pm on November 21, 2022:

    If tx is const I think this should be too? And same for vsize_individual?

    0    const CAmount fee_individual;
    

    LarryRuane commented at 6:41 am on November 30, 2022:
    This suggestion makes sense since these two individual fields shouldn’t need to change (the ancestor fields do change). Making both const does compile.

    murchandamus commented at 8:27 pm on December 15, 2022:
    Thanks, made the two individuals const
  72. in src/node/mini_miner.h:82 in 898ad9d590 outdated
    77+    CAmount total_fees{0};
    78+    int64_t total_vsize{0};
    79+
    80+    /** Main data structure holding the entries, can be indexed by txid */
    81+    std::map<uint256, MockMempoolEntry> entries_by_txid;
    82+    using MockEntryMap = decltype(entries_by_txid);
    


    stickies-v commented at 7:51 pm on November 21, 2022:

    Since we’re only using the ::iterator attribute, could shorten it a bit more to

    0    using MockEntryMapIter = decltype(entries_by_txid)::iterator;
    

    murchandamus commented at 9:15 pm on December 22, 2022:
    I could not get this to work, could you elaborate what you propose?
  73. in src/node/mini_miner.h:60 in 898ad9d590 outdated
    50+    }
    51+};
    52+
    53+/** A minimal version of BlockAssembler. Allows us to run the mining algorithm on a subset of
    54+ * mempool transactions, ignoring consensus rules, to calculate mining scores. */
    55+class MiniMiner
    


    stickies-v commented at 7:53 pm on November 21, 2022:
    Although MiniMiner sounds catchier, would MiniBlockAssembler be a more appropriate/accurate name?

    murchandamus commented at 9:16 pm on December 22, 2022:
    Mh, I’ll consider it, but it’s also an easy change to make later.
  74. in src/node/mini_miner.h:68 in 898ad9d590 outdated
    63+
    64+    // After using the outpoints to figure out which transactions are to be replaced, we can just
    65+    // work with txids (each outpoint from a single tx should have the same bumpfee independently).
    66+    // Cache which outpoint are needed for each tx so we don't have to look up all the outputs.
    67+    // Excludes to-be-replaced and unavailable transactions (set to 0).
    68+    std::map<uint256, std::vector<COutPoint>> outpoints_needed_by_txid;
    


    stickies-v commented at 8:21 pm on November 21, 2022:

    naming consistency

    0    std::map<uint256, std::vector<COutPoint>> requested_outpoints_by_txid;
    

    murchandamus commented at 6:54 pm on January 20, 2023:
    Thanks, I followed your suggestion
  75. in src/node/mini_miner.h:58 in 898ad9d590 outdated
    53+/** A minimal version of BlockAssembler. Allows us to run the mining algorithm on a subset of
    54+ * mempool transactions, ignoring consensus rules, to calculate mining scores. */
    55+class MiniMiner
    56+{
    57+    // Original outpoints requested
    58+    std::vector<COutPoint> requested_outpoints;
    


    stickies-v commented at 8:33 pm on November 21, 2022:
    Perhaps I’ll answer my own question as I progress with my review, but do we need requested_outpoints? I think this overlaps entirely with the keys of bump_fees? Feel free to ignore/keep it very brief if it’s a dumb remark, I don’t fully understand the PR yet.

    LarryRuane commented at 6:44 am on December 6, 2022:
    Should all these class variable names have the m_ prefix?

    murchandamus commented at 6:56 pm on January 20, 2023:
    I do not know from the top of my head, I will need to look more into this

    murchandamus commented at 10:42 pm on January 23, 2023:
    Yes, yes, they should.

    murchandamus commented at 9:18 pm on January 31, 2023:
    Renamed all MiniMiner class variables to be prefixed with m_

    murchandamus commented at 0:01 am on February 2, 2023:
    Removing requested_outpoints in favor of the keys of bump_fees breaks tests, so I assume they’re not overlapping exactly.
  76. stickies-v commented at 1:30 am on November 22, 2022: contributor
    Concept ACK. Finished going through the code in the first 2 commits, more comments to come as I progress.
  77. in src/node/mini_miner.cpp:45 in b669fd94f8 outdated
    40+            if (it != outpoints_needed_by_txid.end()) {
    41+                it->second.push_back(outpoint);
    42+            } else {
    43+                std::vector<COutPoint> outpoints_of_tx({outpoint});
    44+                outpoints_needed_by_txid.emplace(std::make_pair(outpoint.hash, outpoints_of_tx));
    45+                // Instead of operating on the entire mempool, just run the mining algorithm on the
    


    mzumsande commented at 2:41 am on November 23, 2022:
    is that comment meant to be here? I can’t see the relation to this else branch, and mapModifiedTx doesn’t exist in the MiniMiner.

    murchandamus commented at 7:02 pm on January 20, 2023:
    You are right, I could not place the comment either and removed it.
  78. MarcoFalke commented at 11:30 am on November 29, 2022: member
    Needs rebase after 8597260872bbef86524996ec695ffb30ec596416
  79. in src/node/mini_miner.cpp:37 in 898ad9d590 outdated
    32+
    33+        if (!mempool.exists(GenTxid::Txid(outpoint.hash))) {
    34+            // This UTXO is either confirmed or not yet submitted to mempool.
    35+            // In the former case, no bump fee is required.
    36+            // In the latter case, we have no information, so just return 0.
    37+            this->bump_fees.emplace(std::make_pair(outpoint, 0));
    


    LarryRuane commented at 7:55 pm on November 29, 2022:
    0            bump_fees.emplace(outpoint, 0);
    

    murchandamus commented at 7:03 pm on January 20, 2023:
    Thanks, I used your suggestion.
  80. in src/node/mini_miner.cpp:219 in 898ad9d590 outdated
    214+            for (const auto& outpoint : outpoints) {
    215+                bump_fees.emplace(std::make_pair(outpoint, bump_fee));
    216+            }
    217+        }
    218+    }
    219+    return this->bump_fees;
    


    LarryRuane commented at 7:56 pm on November 29, 2022:
    0    return bump_fees;
    

    murchandamus commented at 7:03 pm on January 20, 2023:
    Fixed
  81. in src/node/mini_miner.cpp:45 in 898ad9d590 outdated
    40+            auto it = outpoints_needed_by_txid.find(outpoint.hash);
    41+            if (it != outpoints_needed_by_txid.end()) {
    42+                it->second.push_back(outpoint);
    43+            } else {
    44+                std::vector<COutPoint> outpoints_of_tx({outpoint});
    45+                outpoints_needed_by_txid.emplace(std::make_pair(outpoint.hash, outpoints_of_tx));
    


    LarryRuane commented at 7:56 pm on November 29, 2022:
    0                outpoints_needed_by_txid.emplace(outpoint.hash, outpoints_of_tx);
    

    Can make a similar change elsewhere in this file.


    murchandamus commented at 7:06 pm on January 20, 2023:
    Thanks, I removed the unnecessary std::make_pair throughout
  82. in src/node/mini_miner.cpp:56 in 898ad9d590 outdated
    51+    // Calculate the cluster and construct the entry map.
    52+    std::vector<uint256> txids_needed;
    53+    std::transform(outpoints_needed_by_txid.cbegin(),
    54+                   outpoints_needed_by_txid.cend(),
    55+                   std::back_inserter(txids_needed),
    56+                   [](const auto& pair) { return pair.first; });
    


    LarryRuane commented at 8:13 pm on November 29, 2022:
    0    for (const auto& [txid, outpoints] : outpoints_needed_by_txid) {
    1        txids_needed.push_back(txid);
    2    }
    

    (nit, simpler)


    murchandamus commented at 7:39 pm on January 20, 2023:
    Thanks, looks great
  83. in src/interfaces/chain.h:219 in 898ad9d590 outdated
    212@@ -213,6 +213,42 @@ class Chain
    213     //! Calculate mempool ancestor and descendant counts for the given transaction.
    214     virtual void getTransactionAncestry(const uint256& txid, size_t& ancestors, size_t& descendants, size_t* ancestorsize = nullptr, CAmount* ancestorfees = nullptr) = 0;
    215 
    216+    //! For each outpoint, calculate the fee-bumping cost to spend this outpoint at the specified
    217+    //  feerate, including bumping its ancestors. For example, if the target feerate is 10sat/vbyte
    218+    //  and this outpoint refers to a mempool transaction at 5sat/vbyte, the bump fee includes the
    219+    //  cost to bump the mempool transaction to 10sat/vbyte (i.e. 5 * mempooltx.vsize). If that
    


    LarryRuane commented at 8:47 pm on November 29, 2022:
    0    //  cost to bump the mempool transaction to 10sat/vbyte (i.e. (10-5) * mempooltx.vsize). If that
    

    and / or maybe use 6 instead of 5. (It’s slightly unclear as is because 10-5 == 5.)


    murchandamus commented at 7:41 pm on January 20, 2023:
    Good idea, clarified the example
  84. in src/node/mini_miner.h:67 in 898ad9d590 outdated
    62+    std::set<uint256> to_be_replaced;
    63+
    64+    // After using the outpoints to figure out which transactions are to be replaced, we can just
    65+    // work with txids (each outpoint from a single tx should have the same bumpfee independently).
    66+    // Cache which outpoint are needed for each tx so we don't have to look up all the outputs.
    67+    // Excludes to-be-replaced and unavailable transactions (set to 0).
    


    LarryRuane commented at 9:49 pm on November 29, 2022:
    0    // If multiple argument outpoints correspond to the same transaction, cache them together in
    1    // a single entry indexed by txid. Then we can just work with txids since all outpoints from
    2    // the same tx will have the same bumpfee. Excludes non-mempool transactions.
    

    This is how I read the code, but check my understanding!


    murchandamus commented at 7:43 pm on January 20, 2023:
    Adopted your phrasing of the comment
  85. in src/interfaces/chain.h:245 in 898ad9d590 outdated
    240+    //  independently, i.e. as if only one of them is spent. This may result in double-fee-bumping. This
    241+    //  caveat can be rectified per use of the sister-function CalculateTotalBumpFees(…).
    242+    virtual std::map<COutPoint, CAmount> CalculateBumpFees(const std::vector<COutPoint>& outpoints, const CFeeRate& target_feerate) = 0;
    243+
    244+    //! Calculate the shared bump fees for a given set of outpoints per the
    245+    //  same strategy as in CalculateBumpFees(…).  Other than the above call,
    


    LarryRuane commented at 10:05 pm on November 29, 2022:
    0    //  same strategy as in CalculateBumpFees(…). Unlike the above call,
    

    murchandamus commented at 7:45 pm on January 20, 2023:
    Sgtm, thanks
  86. in src/interfaces/chain.h:226 in 898ad9d590 outdated
    221+    //  includes the cost to bump the parent (i.e. 9 * parentmempooltx.vsize).
    222+    //
    223+    //  If the outpoint comes from an unconfirmed transaction that is already above the target
    224+    //  feerate or bumped by its descendant(s) already, it does not need to be bumped. Its bump fee
    225+    //  is 0. Likewise, if any of the transaction's ancestors are already bumped, they are not
    226+    //  included in the transaction's bump fee.
    


    LarryRuane commented at 10:12 pm on November 29, 2022:

    they are not included

    Is this true? IIUC, I don’t think it works this way, or it shouldn’t. If there are two outpoints sharing the same ancestor transaction, we don’t know which of those two outpoints coin selection will choose if it chooses only one. If coin selection chooses the one for which we didn’t bump to account for the ancestor, then our fee will be too low. I thought we bump both, and then after coin selection, we make the adjustment.


    murchandamus commented at 7:47 pm on January 20, 2023:
    This refers to transactions already bumped by other transactions in our mempool, not bumped by our calculation of bump fees. I’ve amended the phrase to clarify.
  87. in src/node/mini_miner.cpp:84 in 898ad9d590 outdated
    63+        } else {
    64+            auto outpoints_it = outpoints_needed_by_txid.find(txiter->GetTx().GetHash());
    65+            if (outpoints_it != outpoints_needed_by_txid.end()) {
    66+                for (const auto& outpoint : outpoints_it->second) {
    67+                    this->bump_fees.emplace(std::make_pair(outpoint, 0));
    68+                }
    


    LarryRuane commented at 10:40 pm on November 29, 2022:
    0                for (const auto& outpoint : outpoints_it->second) {
    1                    bump_fees.emplace(outpoint, 0);
    2                }
    3                outpoints_needed_by_txid.erase(outpoints_it);
    

    I’m not sure about this, but doing the erase here would be consistent with the !mempool.exists() case above (an entry for this transaction, none of whose outpoints we will use, is not added to outpoints_needed_by_txid).


    murchandamus commented at 8:52 pm on January 20, 2023:
    Sounds right to me
  88. in src/node/mini_miner.cpp:97 in 898ad9d590 outdated
    92+                } else {
    93+                    cached_descendants.push_back(desc_it);
    94+                }
    95+            }
    96+        }
    97+        if (!remove) descendant_set_by_txid.emplace(std::make_pair(txid, cached_descendants));
    


    LarryRuane commented at 10:53 pm on November 29, 2022:
    0        if (!remove) descendant_set_by_txid.emplace(std::make_pair(txid, std::move(cached_descendants)));
    

    murchandamus commented at 8:53 pm on January 20, 2023:
    Good idea
  89. LarryRuane commented at 10:58 pm on November 29, 2022: contributor
    898ad9d5904f1b689d18d94f20d92500cf443758 Concept ACK LGTM, suggestions are minor. I’ll continue reviewing.
  90. in src/node/mini_miner.h:16 in 898ad9d590 outdated
    11+#include <optional>
    12+#include <stdint.h>
    13+
    14+namespace node {
    15+
    16+// Container for tracking updates to ancestor feerate as we include ancestors in the "block"
    


    LarryRuane commented at 6:32 am on November 30, 2022:

    suggest adding:

    0// This class must be constructed while holding mempool.cs. After construction, the object's
    1// methods can be called without holding that lock.
    

    Or maybe a better place for this comment would be just before the constructor itself.


    murchandamus commented at 9:08 pm on January 20, 2023:
    Added before the constructor
  91. in src/node/mini_miner.h:17 in 898ad9d590 outdated
    12+#include <stdint.h>
    13+
    14+namespace node {
    15+
    16+// Container for tracking updates to ancestor feerate as we include ancestors in the "block"
    17+class MockMempoolEntry
    


    LarryRuane commented at 6:35 am on November 30, 2022:
    Could a better word than “Mock” be used to name this class? I initially thought this class had to do with testing. Since this is the MiniMiner, maybe MiniMempoolEntry?

    murchandamus commented at 9:13 pm on January 20, 2023:
    Renamed to MiniMinerMempoolEntry, although I don’t feel strongly about the name.
  92. DrahtBot added the label Needs rebase on Nov 30, 2022
  93. in src/node/mini_miner.h:20 in 898ad9d590 outdated
    15+
    16+// Container for tracking updates to ancestor feerate as we include ancestors in the "block"
    17+class MockMempoolEntry
    18+{
    19+    CAmount fee_individual;
    20+    const CTransaction& tx;
    


    LarryRuane commented at 0:08 am on December 2, 2022:

    I’m not at all sure about this, but this is a reference variable so it’s (sort of) a pointer, right? We continue to hold this reference after releasing mempool.cs – is it possible for the transaction to go away (its memory deallocated) if, for example, it gets mined, thereby invalidating our reference? I wonder if this might be better:

    0    const CTransaction tx;
    

    The debugger shows sizeof(node::MockMempoolEntry) is only 40 bytes with tx being a reference, and 152 if it’s not a reference, which makes sense because sizeof(CTransaction) is 120.

    Or, maybe it should be const CTransactionRef tx; because then it’s a shared pointer, so if the tx gets removed from the mempool, our reference will remain valid until we’re done with it. The advantage of this would be we wouldn’t be making a full copy of the transaction.


    murchandamus commented at 9:38 pm on January 20, 2023:
    Thanks for diving in this deeply, that sounds correct to me.
  94. in src/interfaces/chain.h:232 in 898ad9d590 outdated
    227+    //
    228+    //  This includes fee-bumping in RBFs. If an outpoint conflicts with another transaction in the
    229+    //  mempool, it is assumed that the goal is to replace that transaction. As such, the
    230+    //  calculation will exclude the to-be-replaced transaction, but will include the fee-bumping
    231+    //  cost. If bump fees of descendants of the to-be-replaced transaction are requested, the value
    232+    //  will be 0. Fee-related RBF rules are not included as they are logically distinct.
    


    LarryRuane commented at 8:07 pm on December 2, 2022:

    Would it be safer not to make this assumption? The cluster would be larger, but since the algorithms are efficient, the performance difference shouldn’t be a problem. The MiniMiner constructor would be simpler too. Even if currently we’re never given a outpoint that conflicts with a transaction not being replaced, could that possibly change in the future?

    Initially, I thought that was I’m suggesting here wouldn’t work, because suppose that the transaction that our outpoint refers to (let’s call it the parent) has a low feerate, but the transaction we’re replacing (the parent’s existing child) has a very high feerate. If, contrary to what the code currently does, we ignore the fact that we’re replacing the child (we keep the child in the cluster, as if we’re spending a different output of the parent), then we might conclude that we don’t need to bump the parent’s fee – whereas we actually do, because the child will no longer exist!

    But I think this won’t happen because rule 6 of our replacement policy requires the replacement transaction to have a higher feerate than all that it’s replacing.

    1. The replacement transaction’s feerate is greater than the feerates of all directly conflicting transactions.

    So it seems like even if we (mistakenly) think that the existing child will remain and that it will bump the parent somewhat, it won’t bump it enough to meet our requested feerate, so we’ll still bump it (as required by our desired feerate).


    murchandamus commented at 0:14 am on February 2, 2023:
    I’m not sure I follow your train of thought here. I think you have since reviewed this further, do you think this still needs to be addressed?

    murchandamus commented at 4:53 pm on February 2, 2023:
    Thinking more about this, I think the assumption is valid and I don’t expect it to change soon. It is also documented here, so if it changes, people should notice the conflict. We will add a test for this.
  95. in src/node/mini_miner.cpp:142 in 898ad9d590 outdated
    137+        auto best_iter = entries.begin();
    138+        assert(best_iter != entries.end());
    139+        const auto ancestor_package_size = (*best_iter)->second.GetSizeWithAncestors();
    140+        const auto ancestor_package_fee = (*best_iter)->second.GetModFeesWithAncestors();
    141+        // Stop here. Everything that didn't "make it into the block" has bumpfee.
    142+        if (best_iter == entries.end() || ancestor_package_fee < target_feerate.GetFee(ancestor_package_size)) {
    


    LarryRuane commented at 8:10 pm on December 2, 2022:

    Simplification, we just asserted that it’s not entries.end()

    0        if (ancestor_package_fee < target_feerate.GetFee(ancestor_package_size)) {
    

    murchandamus commented at 9:47 pm on January 20, 2023:
    Good catch, fixed
  96. in src/node/mini_miner.cpp:79 in 898ad9d590 outdated
    74+    for (const auto& txiter : cluster) {
    75+        const auto& txid = txiter->GetTx().GetHash();
    76+        // Cache descendants for future use. Unlike the real mempool, a descendant MockMempoolEntry
    77+        // will not exist without its ancestor MockMempoolEntry, so these sets won't be invalidated.
    78+        std::vector<MockEntryMap::iterator> cached_descendants;
    79+        cached_descendants.emplace_back(entries_by_txid.find(txid));
    


    LarryRuane commented at 1:21 am on December 6, 2022:
    898ad9d5904f1b689d18d94f20d92500cf443758 The result of this find() could be entries_by_txid.end() (not found), because in the previous loop (also over cluster), if the transaction is found in to_be_replaced (so we take the else path), then the transaction is not added to entries_by_txid. I think it turns out to be harmless, but I just wanted to point it out because it looks like it may be unintentional.

    josibake commented at 10:59 am on December 7, 2022:

    Also noticed this and was a bit confused. In the case where remove is true, cache_descendants never gets added to the descendants_by_txid map, which is fine. In the case where remove is false, then txid should be in entries_by_txid, so it will get added to cache_descendants, along with all of descendants.

    So what gets added to descendants_by_txid is txidA: [txidA, txidB, txidC...], basically the parent + all of its children, which seems incorrect?


    LarryRuane commented at 6:17 am on December 16, 2022:
    Something more important: this emplace_back() shouldn’t be here at all, because each mempool entry is a member of its own descendants’ list. So this “main” transaction gets added to cached_descendants below. This was causing the fee_with_ancestors and vsize_with_ancestors to go negative near the end of BuildMockTemplate() below.

    LarryRuane commented at 6:51 am on December 16, 2022:

    @josibake

    … which seems incorrect?

    No, I think it’s a convention in the strange Land of Mempool that a transaction’s ancestor and descendant lists both include the transaction itself. This relates directly to my comment elsewhere that the emplace_back() should be removed; as it’s currently written, the list would be: txidA: [txidA, txidA, txidB, txidC...]. This later causes a major accounting problem!


    murchandamus commented at 9:08 pm on January 23, 2023:
    Note to self: TODO

    glozow commented at 7:48 pm on February 2, 2023:
    Yes, it would be more correct to gate the emplace_back() on !remove.
  97. in src/node/mini_miner.cpp:193 in 898ad9d590 outdated
    188+    // Build a block template until the target feerate is hit.
    189+    BuildMockTemplate(target_feerate);
    190+    assert(in_block.empty() || CFeeRate(total_fees, total_vsize) >= target_feerate);
    191+
    192+    // Each transaction that "made it into the block" has a bumpfee of 0, i.e. they are part of an
    193+    // ancestor package that exceeds the target feerate and don't need to be bumped.
    


    LarryRuane commented at 1:24 am on December 6, 2022:
    0    // ancestor package with at least the target feerate and don't need to be bumped.
    

    (if the feerates are equal, no bump is required)


    murchandamus commented at 9:12 pm on January 23, 2023:
    Thanks, corrected
  98. in src/txmempool.cpp:962 in 995107782a outdated
    957+{
    958+    std::vector<txiter> ret;
    959+    ret.reserve(txids.size());
    960+    for (const auto& txid : txids) {
    961+        const auto it = GetIter(txid);
    962+        assert(it);
    


    josibake commented at 1:23 pm on December 6, 2022:

    if GetIter(txid) cannot find the txid in mapTx, it will return a std::nullopt, which will then cause the node to crash. this seems really dangerous.

    wouldn’t it be better to remove the assert and just let GetIterVec return early with an empty vector if it’s passed a txid which isn’t in the mempool? based on the comment, that seems to be the intention of this code, so im not sure what good the assert is doing here


    glozow commented at 5:02 pm on December 6, 2022:
    Ah good catch, that else branch is dead since the assert would hit.

    murchandamus commented at 9:21 pm on January 31, 2023:
    Fixed
  99. in src/txmempool.cpp:1222 in 995107782a outdated
    1213@@ -1196,3 +1214,40 @@ const std::string RemovalReasonToString(const MemPoolRemovalReason& r) noexcept
    1214     }
    1215     assert(false);
    1216 }
    1217+
    1218+std::vector<CTxMemPool::txiter> CTxMemPool::CalculateCluster(const std::vector<uint256>& txids) const
    1219+{
    1220+    AssertLockHeld(cs);
    1221+    std::vector<txiter> cluster{GetIterVec(txids)};
    1222+    assert(cluster.size() == txids.size());
    


    josibake commented at 1:28 pm on December 6, 2022:
    the way GetIterVec is written, it will return an empty vector if any of the txids are not found in the mempool, so wouldn’t it be better to check for an empty vector here and return an error to the user letting them know one of the txids they sent wasn’t found in the mempool? the way it’s written now, if an empty vector is returned the node will crash

    murchandamus commented at 9:22 pm on January 31, 2023:
    Fixed
  100. in src/txmempool.h:533 in 995107782a outdated
    654 
    655+    /** Translate a list of hashes into a list of mempool iterators to avoid repeated lookups.
    656+     * The nth element in txids becomes the nth element in the returned vector. If any of the txids
    657+     * don't actually exist in the mempool, returns an empty vector. */
    658+    std::vector<txiter> GetIterVec(const std::vector<uint256>& txids) const EXCLUSIVE_LOCKS_REQUIRED(cs);
    659+
    


    josibake commented at 1:40 pm on December 6, 2022:
    with the assert statements in GetIterVec and CalculateCluster, this is not accurate, unless I am misunderstanding something. if a txid does not exist in the mempool, GetIter returns a nullopt, which then causes GetIterVec to crash. if we remove that assert, then the assert in CalculateCluster checking that txids.size() == cluster.size() will crash the node due to cluster being an empty vector

    murchandamus commented at 9:24 pm on January 31, 2023:
    Fixed
  101. in src/node/mini_miner.cpp:52 in b669fd94f8 outdated
    25+    // Anything otherwise unavailable just has a bump fee of 0
    26+    for (const auto& outpoint : outpoints) {
    27+        if (const auto ptx{mempool.GetConflictTx(outpoint)}) {
    28+            // The conflicting transaction is to-be-replaced
    29+            to_be_replaced.insert(ptx->GetHash());
    30+        }
    


    josibake commented at 2:28 pm on December 6, 2022:

    It could be my own unfamiliarity with the mempool, but I’m not really sure what is happening here. To check my understanding:

    outpoints here refers to a set of unconfirmed UTXOs that our wallet would like to spend, meaning they are tx outputs. GetConflictTx checks if the outpoint exists in mapNextTx, which means it is the prevout (or input) into another tx in the mempool. If there is another transaction in the mempool spending one of these outputs, we put the tx which is spending the outpointt into to_be_replaced. This is because we plan to construct a transaction which would then replace the conflicting tx, so we want to ignore doing anything with the conflicting txs for now?

    It might be helpful to be a little more explicit than just outpoints (or write a more detailed comment for the function) as this can refer to either outputs or prevouts, which in this context makes things confusing to follow.


    glozow commented at 5:01 pm on December 6, 2022:

    To check my understanding:

    outpoints here refers to a set of unconfirmed UTXOs that our wallet would like to spend, meaning they are tx outputs.

    They are not necessarily unconfirmed UTXOs, just some specified outpoints/prevouts. They may refer to confirmed or unconfirmed UTXOs. They may refer to outputs that the node doesn’t think exist.

    GetConflictTx checks if the outpoint exists in mapNextTx, which means it is the prevout (or input) into another tx in the mempool. If there is another transaction in the mempool spending one of these outputs, we put the tx which is spending the outpointt into to_be_replaced.

    correct, GetConflictTx gives you another mempool transaction that spends the same tx.

    This is because we plan to construct a transaction which would then replace the conflicting tx, so we want to ignore doing anything with the conflicting txs for now?

    We want to ensure we provide a bump fee for this UTXO, but ensure we calculate the bump fees exluding the to-be-replaced transaction(s). Once they’re replaced, they won’t be there to bump their ancestors. So if we’re replacing the child of a CPFP (e.g. to increase the bump even more), we want the correct fee to bump that parent without the original child there.


    josibake commented at 9:41 am on December 7, 2022:

    Thanks, this explanation really helps!

    They are not necessarily unconfirmed UTXOs, just some specified outpoints/prevouts. They may refer to confirmed or unconfirmed UTXOs. They may refer to outputs that the node doesn’t think exist.

    Is it correct to say these outpoints are intended to be spent in a new transaction, as in the wallet has a set of UTXOs that it wants to spend in TxB where TxB can be a replacement of an already existing TxA or it can be an entirely new tx? From there, these outpoints can be a mix of confirmed/unconfirmed, but it’s assumed at least one outpoint is unconfirmed


    josibake commented at 1:29 pm on December 7, 2022:
    Another thought I had while looking at this: we check if an outpoint has any conflicting spends in the mempool before we check if the outpoint itself is in the mempool. It seems we are inferring that the outpoint is in the mempool, otherwise it would not be present in mapNextTx. Is this the safest way to do this? Seems fine because mapNextTx is updated every time a tx is removed from the mempool, but figured I’d ask anyway in case there is a race condition / code path I’m not seeing which could make this unreliable in an edge case

    glozow commented at 4:41 pm on December 7, 2022:

    Is it correct to say these outpoints are intended to be spent in a new transaction, as in the wallet has a set of UTXOs that it wants to spend in TxB where TxB can be a replacement of an already existing TxA or it can be an entirely new tx?

    Yes, exactly. If we’re constructing MiniMiner to CalculateBumpFees(), these outpoints are basically all the coins owned by the wallet, i.e. AvailableCoins. If we’re constructing MiniMiner to CalculateTotalBumpFees(), these are the outpoints we’ve decided to use to fund txB.

    From there, these outpoints can be a mix of confirmed/unconfirmed, but it’s assumed at least one outpoint is unconfirmed

    Not exactly. They could all be confirmed / nonexistent - in that case this constructor will end up not constructing any MockMempoolEntrys. When you call CalculateBumpFees() afterwards, it should just return the already-existing map of all-0 bump fees. This is the first test case in miniminer_tests.cpp.

  102. josibake commented at 3:06 pm on December 6, 2022: contributor

    Concept ACK

    Still in the process of reviewing (have reviewed up to and most of b669fd94f84e679d4549ef0abe1b0483e1406152), but left a few comments.

    Some general feedback:

    I noticed a lot of asserts being used. As I understand it, this is going to be a functionality exposed to the wallet, so wouldn’t it be better to replace asserts with error messages that can be returned to the wallet? Seems better than crashing the node, IMO.

    Also, the MiniMiner function is quite complicated. It might be worth adding a high-level overview to the PR description for the MiniMiner::MiniMiner as it seems like this is where a lot of the logic is. In particular, it seems to be working with several caches, so perhaps describing the general flow would be helpful. Take this feedback with a grain of salt, tho, as I’m not super familiar with the mempool and that could be why I’m struggling to follow the steps :sweat_smile:

  103. glozow commented at 5:16 pm on December 6, 2022: member
    Hopefully this is helpful and not annoying, but here’s a branch that makes the first couple commits less assert-happy and applies some of the suggestions to miniminer: https://github.com/glozow/bitcoin/tree/26152-fixups
  104. josibake commented at 9:28 am on December 7, 2022: contributor

    Hopefully this is helpful and not annoying, but here’s a branch that makes the first couple commits less assert-happy and applies some of the suggestions to miniminer: https://github.com/glozow/bitcoin/tree/26152-fixups

    lgtm!

  105. in src/node/mini_miner.cpp:132 in b669fd94f8 outdated
    110+        const CFeeRate a_feerate(a->second.GetModFeesWithAncestors(), a->second.GetSizeWithAncestors());
    111+        const CFeeRate b_feerate(b->second.GetModFeesWithAncestors(), b->second.GetSizeWithAncestors());
    112+        if (a_feerate != b_feerate) {
    113+            return a_feerate > b_feerate;
    114+        }
    115+        return &(*a) > &(*b);
    


    josibake commented at 5:16 pm on December 7, 2022:
    what is the significance here? why not just have return a_feerate >= b_feerate or something like that?

    murchandamus commented at 5:30 pm on February 1, 2023:
    I expect this to be used to make the sorting order stable without introducing a gameable tie-breaker

    murchandamus commented at 7:34 pm on February 2, 2023:
    Added a comment
  106. josibake commented at 4:24 pm on December 9, 2022: contributor

    Two thoughts:

    1. I think this could be split up into two PRs; one for MiniMiner and another for the wallet logic. My reasoning here is the MiniMiner code seems like it could be useful outside of the wallet use case and thus could be merged independently and both parts by themselves seem complex enough that splitting it into two might make things easier to review.

    2. per @LarryRuane ’s comment #26152 (comment), I’m also not totally convinced we need the to_be_replaced logic in MiniMiner. I took out all of the code relating to to_be_replaced and the unit and functional tests still pass. It’s possible this is due to insufficient test coverage, in which case having a test case to demonstrate why this logic is necessary would be super helpful. I have a few more thoughts regarding whether MiniMiner or the wallet should be handling the conflicting transactions logic, but I’ll try to add a concrete example in a test case to first convince myself and also make the conversation more productive

  107. glozow commented at 5:01 pm on December 9, 2022: member

    I’m also not totally convinced we need the to_be_replaced logic in MiniMiner. I took out all of the code relating to to_be_replaced and the unit and functional tests still pass. It’s possible this is due to insufficient test coverage

    I would say this is at least 95% due to insufficient coverage in the unit tests (pretty sparse at the moment). I don’t think it’s possible to hit very much of the replacement-related logic through functional tests, because our (1) RBF rules currently do not allow any additional unconfirmed inputs so we won’t actually ask MiniMiner to calculate much, (2) our wallet will never try to replace something with descendants and (3) our wallet will always use all the inputs from the replacee (so the size of to_be_replaced is always 1 and the set of unconfirmed ancestors is identical before and after).

    But maybe this is a good reason to remove it! If it never executes because of how our wallet operates, and the wallet is the only client of this interface, then it would be complexity for no reason. We could just use CalculateTotalBumpFees() for a replacement (since that’s all the unconfirmed inputs we’ll use) and only add logic for replacements if (1) ever changes in the future.

  108. josibake commented at 3:16 pm on December 11, 2022: contributor

    But maybe this is a good reason to remove it! If it never executes because of how our wallet operates, and the wallet is the only client of this interface, then it would be complexity for no reason.

    I think this is a good reason to leave it out for now. I started writing a unit test to try and cover different replacement scenarios, but it felt a little silly to be writing tests for scenarios that would never actually happen to due the RBF rules and how the wallet behaves.

    I also haven’t been able to think of a scenario where MiniMiner would return an incorrect bumpfee if it didn’t have the “to be replaced logic” if MiniMiner was given a set of “reasonable” outpoints in the first place. @Xekyo perhaps you have an example that I haven’t thought of?

    tldr; imo we should prefer a simpler implementation which makes as few assumptions as possible and add in more complexity if/when it is needed.

  109. in src/node/mini_miner.cpp:174 in 898ad9d590 outdated
    154+            assert(iter != to_process.end());
    155+            const CTransaction& tx = (*iter)->second.GetTx();
    156+            for (const auto& input : tx.vin) {
    157+                if (auto parent_it{entries_by_txid.find(input.prevout.hash)}; parent_it != entries_by_txid.end()) {
    158+                    to_process.insert(parent_it);
    159+                    ancestors.insert(parent_it);
    


    LarryRuane commented at 0:36 am on December 16, 2022:

    The benchmark (part of my branch) experienced a near-infinite loop without this change. The reason is that the benchmark test mempool has such a high degree of fan-out and fan-in that a single transaction could be added to the to_process set over and over. Of course, std::set doesn’t allow duplicates, but I added some debug logging that shows the same tx being added, removed, processed, then added again, many times. This change fixed the problem completely.

    0                    if (!ancestors.count(parent_it)) {
    1                        to_process.insert(parent_it);
    2                        ancestors.insert(parent_it);
    3                    }
    

    murchandamus commented at 6:01 pm on February 1, 2023:
    Great, thanks
  110. in src/node/mini_miner.cpp:356 in 898ad9d590 outdated
    244+        assert(iter != to_process.end());
    245+        const CTransaction& tx = (*iter)->second.GetTx();
    246+        for (const auto& input : tx.vin) {
    247+            if (auto parent_it{entries_by_txid.find(input.prevout.hash)}; parent_it != entries_by_txid.end()) {
    248+                to_process.insert(parent_it);
    249+                ancestors.insert(parent_it);
    


    LarryRuane commented at 1:14 am on December 16, 2022:

    Same change as above – this is much more efficient with a large cluster, for example, running the benchmark.

    0                if (!ancestors.count(parent_it)) {
    1                    to_process.insert(parent_it);
    2                    ancestors.insert(parent_it);
    3                }
    
  111. in src/node/mini_miner.cpp:175 in 898ad9d590 outdated
    170+        for (const auto& anc : ancestors) {
    171+            in_block.insert(anc->second.GetTx().GetHash());
    172+            total_fees += anc->second.GetModifiedFee();
    173+            total_vsize += anc->second.GetTxSize();
    174+            auto it = descendant_set_by_txid.find(anc->second.GetTx().GetHash());
    175+            assert(it != descendant_set_by_txid.end());
    


    LarryRuane commented at 4:28 am on December 16, 2022:
    0            // each entry's descendant set includes itself
    1            assert(it != descendant_set_by_txid.end());
    

    (it took me a little while to figure out why this assertion is valid)

  112. in src/node/mini_miner.cpp:197 in 898ad9d590 outdated
    173+            total_vsize += anc->second.GetTxSize();
    174+            auto it = descendant_set_by_txid.find(anc->second.GetTx().GetHash());
    175+            assert(it != descendant_set_by_txid.end());
    176+            for (const auto& descendant : it->second) {
    177+                descendant->second.vsize_with_ancestors -= anc->second.GetTxSize();
    178+                descendant->second.fee_with_ancestors -= anc->second.GetModifiedFee();
    


    LarryRuane commented at 6:11 am on December 16, 2022:

    You may want to assert that these don’t go negative, I would actually do it before the decrement, like this:

    0assert(descendant->second.vsize_with_ancestors >= anc->second.GetTxSize());
    1descendant->second.vsize_with_ancestors -= anc->second.GetTxSize();
    

    murchandamus commented at 7:40 pm on February 2, 2023:
    Thanks, I’ve adopted your suggestion, although with an Assume instead.
  113. in src/node/mini_miner.cpp:174 in 898ad9d590 outdated
    169+        // "Mine" all transactions in this ancestor set.
    170+        for (const auto& anc : ancestors) {
    171+            in_block.insert(anc->second.GetTx().GetHash());
    172+            total_fees += anc->second.GetModifiedFee();
    173+            total_vsize += anc->second.GetTxSize();
    174+            auto it = descendant_set_by_txid.find(anc->second.GetTx().GetHash());
    


    LarryRuane commented at 6:19 am on December 16, 2022:

    simpler:

    0            auto it = descendant_set_by_txid.find(anc->first);
    
  114. in src/node/mini_miner.cpp:171 in 898ad9d590 outdated
    166+        assert(ancestor_package_fee == std::accumulate(ancestors.cbegin(), ancestors.cend(), 0,
    167+            [](CAmount sum, const auto it) {return sum + it->second.GetModifiedFee();}));
    168+
    169+        // "Mine" all transactions in this ancestor set.
    170+        for (const auto& anc : ancestors) {
    171+            in_block.insert(anc->second.GetTx().GetHash());
    


    LarryRuane commented at 6:20 am on December 16, 2022:
    0            in_block.insert(anc->first);
    

    (functionally the same, just simpler)

  115. LarryRuane commented at 6:31 am on December 16, 2022: contributor
    Just FYI, I added a commit with a benchmark to the branch I mentioned earlier (with an alternate implementation of MiniMiner); feel free to cherry-pick this commit. It also seems useful as a stress test – most of my comments below are the result of running the benchmark.
  116. murchandamus commented at 9:48 pm on January 31, 2023: contributor

    WIP: I’ve addressed all comments up to #26152 (comment)

    I am currently evaluating whether splitting this PR in two makes sense, and still need to investigate @LarryRuane’s alternative branch.

    Redesignating this PR to draft to signal that it’s not ready for review at the moment.

  117. murchandamus force-pushed on Jan 31, 2023
  118. murchandamus marked this as a draft on Jan 31, 2023
  119. DrahtBot removed the label Needs rebase on Jan 31, 2023
  120. DrahtBot added the label Needs rebase on Feb 1, 2023
  121. murchandamus force-pushed on Feb 1, 2023
  122. murchandamus force-pushed on Feb 2, 2023
  123. murchandamus commented at 0:29 am on February 2, 2023: contributor
    I’ve split the four commits that establish the MiniMiner logic into their own PR #27021. I’ll work on implementing the remaining open comments from here in the new PR. New review of the first four commits should please be added to #27021 forth going.
  124. DrahtBot removed the label Needs rebase on Feb 2, 2023
  125. murchandamus force-pushed on Feb 16, 2023
  126. murchandamus force-pushed on Feb 16, 2023
  127. murchandamus force-pushed on Feb 17, 2023
  128. murchandamus force-pushed on Feb 17, 2023
  129. murchandamus commented at 4:16 pm on February 17, 2023: contributor

    Since last comment:

    • Rebased on-top of the latest version of #27021.
    • Addressed that CalculateTotalBumpFee now returns an std::optional because we automatically fail calculation for (too) large clusters of unconfirmed transactions
    • Waiting for #27021 before opening for review
  130. LarryRuane commented at 5:55 pm on February 17, 2023: contributor
    For anyone wanting to review this PR and would like some help with basic mempool concepts, I made a video: https://youtu.be/sQ05azzTp9o.
  131. fanquake added this to the milestone 25.0 on Feb 23, 2023
  132. DrahtBot added the label Needs rebase on Feb 27, 2023
  133. murchandamus force-pushed on Mar 3, 2023
  134. murchandamus commented at 11:13 pm on March 3, 2023: contributor
    Rebased on latest version of #27021
  135. murchandamus force-pushed on Mar 15, 2023
  136. murchandamus commented at 3:04 pm on March 15, 2023: contributor
    Rebased on latest version of #27021. If you are interested in having #26152 in Bitcoin Core v25.0, please consider making time to review #27021.
  137. murchandamus force-pushed on Mar 31, 2023
  138. murchandamus commented at 6:21 pm on March 31, 2023: contributor

    Rebased on latest version of #27021, then rebased on master to resolve merge conflicts.

    Needed to reintroduce access to the Chain interface in ChooseSelectionResult().

  139. DrahtBot removed the label Needs rebase on Apr 2, 2023
  140. glozow removed this from the milestone 25.0 on Apr 5, 2023
  141. DrahtBot added the label Needs rebase on Apr 15, 2023
  142. murchandamus force-pushed on May 17, 2023
  143. murchandamus commented at 2:38 am on May 17, 2023: contributor
    Rebased on #27021, need to rebase on master next, then will incorporate the follow-up nits from #27021
  144. in src/wallet/spend.cpp:629 in 76d31ad381 outdated
    589+        }
    590+        CAmount bump_fee_overestimate = summed_bump_fees - grouped_bump_fees.value();
    591+        if (bump_fee_overestimate) {
    592+            result.SetBumpFeeDiscount(bump_fee_overestimate);
    593+            // Update waste
    594+            result.ComputeAndSetWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee);
    


    S3RK commented at 7:32 am on June 7, 2023:

    in “Amend bumpfee for inputs with overlapping ancestry” 56139fd808250fcb693b5f185e7e510804a33470

    nit: while you’re here, should we drop earlier calls to ComputeAndSetWaste and just call it once for all eligible results before selecting the best one?


    murchandamus commented at 6:12 pm on June 30, 2023:
    Still open

    murchandamus commented at 8:27 pm on July 3, 2023:
    Staring more at this, I am starting to wonder whether this would produce the same waste score for all possible BnB solutions, since BnB calculates the waste score with different parameters at the end of SelectCoinsBnB(). It does pass the coin selection tests. (I’ll let the fuzzer run over night to see whether that finds something else.)

    murchandamus commented at 6:01 pm on July 6, 2023:
    Did about 40M executions, and no issues! Gonna calculate the Waste score below here as discussed.

    S3RK commented at 7:22 am on July 11, 2023:

    In theory the waste score should be the same.

    Because not all of the params are available within BnB we call result.ComputeAndSetWaste(cost_of_change, cost_of_change, 0); instead of result.ComputeAndSetWaste(min_viable_change, cost_of_change, change_fee);

    I believe this should be fine as BnB will not produce results with more than cost_of_change excess and therefore GetChange() will always return 0 and then change_fee doesn’t matter.

  145. murchandamus force-pushed on Jun 12, 2023
  146. murchandamus commented at 4:25 pm on June 12, 2023: contributor
    Rebased on master, incorporated outstanding follow-ups from #27021 (comment)
  147. murchandamus force-pushed on Jun 12, 2023
  148. DrahtBot added the label CI failed on Jun 12, 2023
  149. murchandamus force-pushed on Jun 12, 2023
  150. DrahtBot removed the label Needs rebase on Jun 12, 2023
  151. murchandamus force-pushed on Jun 12, 2023
  152. murchandamus force-pushed on Jun 12, 2023
  153. murchandamus force-pushed on Jun 12, 2023
  154. murchandamus commented at 8:31 pm on June 12, 2023: contributor

    Rebased, added four commits for the follow-ups from #27021, cleaned up the commit messages, added @theStack’s wonderful topology overview for the transactions, built each commit separately to make sure all tests pass.

    Ready for review

  155. murchandamus marked this as ready for review on Jun 12, 2023
  156. DrahtBot removed the label CI failed on Jun 12, 2023
  157. in src/wallet/spend.cpp:201 in 8e3d924f02 outdated
    195@@ -195,6 +196,7 @@ util::Result<PreSelectedInputs> FetchSelectedInputs(const CWallet& wallet, const
    196 
    197         /* Set some defaults for depth, spendable, solvable, safe, time, and from_me as these don't matter for preset inputs since no selection is being done. */
    198         COutput output(outpoint, txout, /*depth=*/ 0, input_bytes, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, coin_selection_params.m_effective_feerate);
    199+        output.ApplyBumpFee(map_of_bump_fees.at(output.outpoint));
    


    ishaanam commented at 7:47 pm on June 20, 2023:
    It would be good to have a test for the application of ancestor bump fees with external pre-selected inputs.

    S3RK commented at 7:26 am on July 6, 2023:
    Isn’t test_preset_input_cpfp testing this?

    ishaanam commented at 12:38 pm on July 6, 2023:
    In that test don’t all of the pre-selected inputs belong to the wallet?

    S3RK commented at 6:29 am on July 10, 2023:
    ah, yes, you’re right
  158. in src/wallet/coinselection.cpp:10 in 8e3d924f02 outdated
     6@@ -7,6 +7,7 @@
     7 #include <common/system.h>
     8 #include <consensus/amount.h>
     9 #include <consensus/consensus.h>
    10+#include <interfaces/chain.h>
    


    ishaanam commented at 7:53 pm on June 20, 2023:

    In 232edb7e5d630d9d687ba3089aa4028f8e0380a4 " Bump unconfirmed parent txs to target feerate "

    Why was this added?


    murchandamus commented at 9:09 pm on July 19, 2023:
    Ah thanks, that belongs to the commit one later, where we need the chain interface to amend the bump fees in case of overlapping ancestries.
  159. in src/wallet/spend.cpp:359 in 8e3d924f02 outdated
    350@@ -346,6 +351,18 @@ CoinsResult AvailableCoins(const CWallet& wallet,
    351         }
    352     }
    353 
    354+    if (feerate.has_value()) {
    355+        std::map<COutPoint, CAmount> map_of_bump_fees = wallet.chain().CalculateBumpFees(outpoints, feerate.value());
    356+
    357+        for (auto& [_, outputs] : result.coins) {
    


    S3RK commented at 7:30 am on June 22, 2023:

    in “Bump unconfirmed parent txs to target feerate” 8e3d924f02ee28043e8844a301389915823e5893

    nit: suggestion

    0for (auto& output : result.coins.All()) {
    1    output.ApplyBumpFee(map_of_bump_fees.at(output.outpoint));
    2}
    

    murchandamus commented at 9:43 pm on June 29, 2023:
    Thanks, went with your suggestion

    achow101 commented at 7:28 pm on June 30, 2023:
    This is incorrect, see #26152 (review)

    S3RK commented at 7:31 am on July 3, 2023:
    Oops, sorry about misleading you 🙄

    murchandamus commented at 6:28 pm on July 3, 2023:
    Ah drats, I did not realize that CoinsResult.All() produces a copy. Reverting this.
  160. in src/wallet/coinselection.cpp:527 in 56139fd808 outdated
    519@@ -512,7 +520,12 @@ CAmount SelectionResult::GetSelectedValue() const
    520 
    521 CAmount SelectionResult::GetSelectedEffectiveValue() const
    522 {
    523-    return std::accumulate(m_selected_inputs.cbegin(), m_selected_inputs.cend(), CAmount{0}, [](CAmount sum, const auto& coin) { return sum + coin->GetEffectiveValue(); });
    524+    return std::accumulate(m_selected_inputs.cbegin(), m_selected_inputs.cend(), CAmount{0}, [](CAmount sum, const auto& coin) { return sum + coin->GetEffectiveValue(); }) + bump_fee_group_discount;
    525+}
    526+
    527+CAmount SelectionResult::GetBumpFeeDiscount() const
    


    S3RK commented at 8:05 am on June 26, 2023:

    in “Amend bumpfee for inputs with overlapping ancestry” 56139fd808250fcb693b5f185e7e510804a33470

    nit: you can drop this method if you decide to have GetTotalBumpFee() instead


    murchandamus commented at 6:11 pm on June 30, 2023:
    I added a function GetSummedBumpFees(), but I have yet to find a neat way how I can drop the discount. I was experimenting with having both a function GetSummedBumpFees() and a function GetCombinedBumpFee() on SelectionResult, but it made it more complicated because I now had to get the Chain interface and target feerate into SelectionResult. I am tending to leaving it as it is now, unless I come up with a more elegant way to transition.

    S3RK commented at 7:48 am on July 3, 2023:
    My idea was to add bump_fee_group_discount within GetTotalBumpFee() function. Probably I’m missing something but I don’t know what. Why wouldn’t it just work?

    murchandamus commented at 6:31 pm on July 3, 2023:
    I would prefer that the method GetTotalBumpFee() does not either return the sum of individual bump fees or the combined bump fee for the input set depending on when it is called. I’d be worried that it would be hard to understand later and might be a source of future bugs.
  161. in src/wallet/spend.h:153 in 56139fd808 outdated
    149@@ -148,8 +150,7 @@ util::Result<SelectionResult> AttemptSelection(const CAmount& nTargetValue, Outp
    150  *                                                  or (2) an specific error message if there was something particularly wrong (e.g. a selection
    151  *                                                  result that surpassed the tx max weight size).
    152  */
    153-util::Result<SelectionResult> ChooseSelectionResult(const CAmount& nTargetValue, Groups& groups, const CoinSelectionParams& coin_selection_params);
    154-
    155+util::Result<SelectionResult> ChooseSelectionResult(const CWallet& wallet, const CAmount& nTargetValue, Groups& groups, const CoinSelectionParams& coin_selection_params);
    


    S3RK commented at 6:37 am on June 27, 2023:

    in “Amend bumpfee for inputs with overlapping ancestry” 56139fd808250fcb693b5f185e7e510804a33470

    nit: we don’t really need the whole wallet there, better to pass just chain interface


    murchandamus commented at 6:31 pm on July 3, 2023:
    Done
  162. in src/wallet/spend.cpp:614 in 56139fd808 outdated
    610@@ -611,6 +611,28 @@ util::Result<SelectionResult> ChooseSelectionResult(const CAmount& nTargetValue,
    611         return errors.empty() ? util::Error() : errors.front();
    612     }
    613 
    614+    // If the chosen input set has unconfirmed inputs, check for synergies from overlapping ancestry
    615+    for (auto& result : results) {
    616+        std::vector<COutPoint> outpoints;
    617+        CAmount summed_bump_fees = 0;
    


    S3RK commented at 6:49 am on June 27, 2023:

    in “Amend bumpfee for inputs with overlapping ancestry” 56139fd808250fcb693b5f185e7e510804a33470

    nit: you can use SelectionResult::GetTotalBumpFee() if you decide to introduce it


    murchandamus commented at 6:12 pm on June 30, 2023:
    Added GetSummedBumpFees()
  163. in src/wallet/spend.cpp:1047 in 8e3d924f02 outdated
    1024@@ -1008,7 +1025,9 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
    1025     // and in the spirit of "smallest possible change from prior
    1026     // behavior."
    1027     const uint32_t nSequence{coin_control.m_signal_bip125_rbf.value_or(wallet.m_signal_rbf) ? MAX_BIP125_RBF_SEQUENCE : CTxIn::MAX_SEQUENCE_NONFINAL};
    1028+    CAmount total_bump_fees{0};
    


    S3RK commented at 7:07 am on June 27, 2023:

    in “Bump unconfirmed parent txs to target feerate” 8e3d924f02ee28043e8844a301389915823e5893

    nit: we can encapsulate bump fee calculation within CoinSelectionResult::GetTotalBumpFee(). It will come handy in the next commit as well


    murchandamus commented at 10:12 pm on June 29, 2023:
    I tried this change, and remembered why I put in the “discount” in the first place: I think I have incorporated the bump fees in the effective values before, so I need to know whether there is a difference between the sum of the bump fees and the combined inputs’ bump fee. I have an idea how to incorporate your suggestion, but I gotta try tomorrow.
  164. S3RK commented at 7:18 am on June 27, 2023: contributor
    Did a first pass of code review. Overall code looks really good, need to spend more time reviewing the tests though.
  165. in test/functional/wallet_spend_unconfirmed.py:75 in 8e3d924f02 outdated
    70+    def test_target_feerate_unconfirmed_high(self):
    71+        self.log.info("Start test feerate with high feerate unconfirmed input")
    72+        wallet = self.setup_and_fund_wallet("unconfirmed_high_wallet")
    73+
    74+        # Send unconfirmed transaction with high feerate to testing wallet
    75+        parent_txid = wallet.sendtoaddress(address=wallet.getnewaddress(), amount=1, fee_rate=100)
    


    S3RK commented at 7:01 am on June 29, 2023:

    in “Bump unconfirmed parent txs to target feerate” 8e3d924f02ee28043e8844a301389915823e5893

    nit: better use a multiple of target_fee_rate


    murchandamus commented at 7:14 pm on July 3, 2023:
    Used 3×target_fee_rate instead
  166. in test/functional/wallet_spend_unconfirmed.py:76 in 8e3d924f02 outdated
    67+        self.generate(self.nodes[0], 1)
    68+        wallet.unloadwallet()
    69+
    70+    def test_target_feerate_unconfirmed_high(self):
    71+        self.log.info("Start test feerate with high feerate unconfirmed input")
    72+        wallet = self.setup_and_fund_wallet("unconfirmed_high_wallet")
    


    S3RK commented at 7:09 am on June 29, 2023:

    in “Bump unconfirmed parent txs to target feerate” 8e3d924f02ee28043e8844a301389915823e5893

    I think all the tests would be easier to understand if we start with an empty testing wallet and send unconfirmed txs to it from def_wallet.

    Now we start with the testing wallet with already one confirmed input, but why if we test spending mostly unconfirmed txs? Current setup requires reader to do more coin tracking in mind.


    murchandamus commented at 8:05 pm on July 3, 2023:

    I’m not sure I see a big difference between setting up the wallet with a confirmed UTXO from which I send an unconfirmed transaction with a varying feerate, or setting up the wallet to have an unconfirmed UTXO with varying feerate. The former may have the advantage that when I need multiple UTXOs, it’s easier to understand what’s different.

    Currently expecting to keep it this way

  167. in test/functional/wallet_spend_unconfirmed.py:85 in 8e3d924f02 outdated
    80+        ancestor_aware_tx = wallet.gettransaction(txid=ancestor_aware_txid, verbose=True)
    81+
    82+        self.assert_spends_only_parent(ancestor_aware_tx, parent_txid)
    83+
    84+        self.assert_beats_target(ancestor_aware_tx)
    85+        resulting_ancestry_fee_rate = self.calc_set_fee_rate([parent_tx, ancestor_aware_tx])
    


    S3RK commented at 7:17 am on June 29, 2023:
    do we want to check naïve ancestry fee rate or the fee_rate at which it will be accepted at a block (i.e. ancestry score)?

    murchandamus commented at 8:08 pm on July 3, 2023:
    Thanks, that check doesn’t make sense, I’ve removed it.
  168. murchandamus force-pushed on Jun 29, 2023
  169. murchandamus commented at 10:12 pm on June 29, 2023: contributor
    Took one suggestion, will revisit the other tomorrow.
  170. DrahtBot added the label CI failed on Jun 29, 2023
  171. murchandamus force-pushed on Jun 30, 2023
  172. murchandamus force-pushed on Jun 30, 2023
  173. murchandamus commented at 6:13 pm on June 30, 2023: contributor
    Renamed the functions on the Chain interface to better express their utility. Added a convenience function for accessing bump fees to SelectionResult.
  174. in src/wallet/spend.cpp:358 in f2daebf8d1 outdated
    350@@ -346,6 +351,15 @@ CoinsResult AvailableCoins(const CWallet& wallet,
    351         }
    352     }
    353 
    354+    if (feerate.has_value()) {
    355+        std::map<COutPoint, CAmount> map_of_bump_fees = wallet.chain().CalculateIndividualBumpFees(outpoints, feerate.value());
    356+
    357+        for (auto& output : result.All()) {
    358+            output.ApplyBumpFee(map_of_bump_fees.at(output.outpoint));
    


    achow101 commented at 7:08 pm on June 30, 2023:

    In f2daebf8d1ac3829faa6443425cb97a763e9f780 “Bump unconfirmed parent txs to target feerate”

    This doesn’t actually apply the bumpfees to all of the outputs in result. All()` will return a copy of all of the outputs, not references to them, so this is just applying the bumpfees to a temporary.

    Because of this, the tests added in this commit fail. The entire PR does not have failing tests because of the adjustment to the fee that is done in the following commit.


    murchandamus commented at 6:46 pm on July 3, 2023:
    Thanks for catching this. Fixed.
  175. in test/functional/wallet_spend_unconfirmed.py:416 in f2daebf8d1 outdated
    411+        self.log.info("Starting UnconfirmedInputTest!")
    412+        self.target_fee_rate = 30
    413+        self.def_wallet  = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
    414+        self.generate(self.nodes[0], 110)
    415+
    416+        # Test that assumptions about meeting feerate and being able to test it hold
    


    achow101 commented at 7:11 pm on June 30, 2023:

    In f2daebf8d1ac3829faa6443425cb97a763e9f780 “Bump unconfirmed parent txs to target feerate”

    nit: I prefer that comments about what a test does are attached to the test implementation itself rather than the caller.


    murchandamus commented at 7:03 pm on July 3, 2023:
    Moved all comments about what the tests do to the implementations of the tests
  176. in src/wallet/feebumper.cpp:89 in f2daebf8d1 outdated
    85+    reused_inputs.reserve(mtx.vin.size());
    86+    for (const CTxIn& txin : mtx.vin) {
    87+        reused_inputs.push_back(txin.prevout);
    88+    }
    89+
    90+    std::map<COutPoint, CAmount> bump_fees = wallet.chain().CalculateBumpFees(reused_inputs, newFeerate);
    


    achow101 commented at 7:18 pm on June 30, 2023:

    In f2daebf8d1ac3829faa6443425cb97a763e9f780 “Bump unconfirmed parent txs to target feerate”

    Compilation failure in this commit.

    0wallet/feebumper.cpp: In function wallet::feebumper::Result wallet::CheckFeeRate(const CWallet&, const CMutableTransaction&, const CFeeRate&, int64_t, CAmount, std::vector<bilingual_str>&):
    1wallet/feebumper.cpp:89:61: error: class interfaces::Chain has no member named CalculateBumpFees
    2   89 |     std::map<COutPoint, CAmount> bump_fees = wallet.chain().CalculateBumpFees(reused_inputs, newFeerate);
    3      |                                                             ^~~~~~~~~~~~~~~~~
    

    This function was renamed to CalculateIndividualBumpFees


    murchandamus commented at 7:49 pm on July 3, 2023:
    Oops, I only fixed the rename in the next commit. Fixing.
  177. in test/functional/wallet_spend_unconfirmed.py:99 in f2daebf8d1 outdated
    94+
    95+        parent_txid = wallet.sendtoaddress(address=wallet.getnewaddress(), amount=1, fee_rate=1)
    96+        parent_tx = wallet.gettransaction(txid=parent_txid, verbose=True)
    97+
    98+        self.assert_undershoots_target(parent_tx)
    99+        resulting_fee_rate_funding = self.calc_fee_rate(parent_tx)
    


    S3RK commented at 7:52 am on July 3, 2023:
    that seems like a duplicate check of assert_undershoots_target

    murchandamus commented at 7:06 pm on July 3, 2023:
    Yeah, you’re right. Removed duplicate check.
  178. S3RK commented at 7:59 am on July 3, 2023: contributor
    Started reviewing tests, but need more time.
  179. t-bast commented at 11:37 am on July 3, 2023: contributor

    I tried to run my own set of external e2e tests against https://github.com/bitcoin/bitcoin/pull/26152/commits/43b86e49351b3160c2e39807997a9373cd88e173 and they all hit the following assertion error:

    0bitcoind: wallet/coinselection.cpp:495: void wallet::SelectionResult::SetBumpFeeDiscount(CAmount): Assertion `discount >= 0' failed
    

    Reading through recent comments, it seems somewhat expected that this version isn’t yet fully working, let me know when you want me to run my tests again!

  180. murchandamus commented at 9:19 pm on July 3, 2023: contributor
    Addressed comments by @S3RK and @achow101, and rebased to fix a conflict.
  181. murchandamus force-pushed on Jul 3, 2023
  182. murchandamus force-pushed on Jul 3, 2023
  183. murchandamus force-pushed on Jul 3, 2023
  184. murchandamus commented at 10:19 pm on July 3, 2023: contributor

    Reading through recent comments, it seems somewhat expected that this version isn’t yet fully working, let me know when you want me to run my tests again!

    Sorry about that, I was a bit in a rush on Friday. I’ve fixed a number of issues, if this latest push’s checks turn up green, it might be more fruitful to try now.

  185. in test/functional/wallet_spend_unconfirmed.py:67 in 232edb7e5d outdated
    62+
    63+        ancestor_aware_txid = wallet.sendtoaddress(address=self.def_wallet.getnewaddress(), amount=0.5, fee_rate=self.target_fee_rate)
    64+        ancestor_aware_tx = wallet.gettransaction(txid=ancestor_aware_txid, verbose=True)
    65+        self.assert_beats_target(ancestor_aware_tx)
    66+
    67+        self.generate(self.nodes[0], 1)
    


    S3RK commented at 6:54 am on July 4, 2023:
    nit: since you create new wallet for every test and never reuse any addresses do you really need to generate blocks? I tried to delete and the tests pass

    murchandamus commented at 3:05 pm on August 11, 2023:
    That’s right, I’ll remove the unnecessary block generation
  186. in test/functional/wallet_spend_unconfirmed.py:129 in 232edb7e5d outdated
    123+        self.assert_undershoots_target(gp_tx)
    124+
    125+        parent_txid = wallet.sendtoaddress(address=wallet.getnewaddress(), amount=1.5, fee_rate=2)
    126+        p_tx = wallet.gettransaction(txid=parent_txid, verbose=True)
    127+
    128+        self.assert_undershoots_target(p_tx)
    


    S3RK commented at 7:07 am on July 4, 2023:
    nit: here and other tests add self.assert_spends_only_parent

    murchandamus commented at 3:33 pm on August 18, 2023:
    Added
  187. in test/functional/wallet_spend_unconfirmed.py:135 in 232edb7e5d outdated
    128+        self.assert_undershoots_target(p_tx)
    129+
    130+        ancestor_aware_txid = wallet.sendtoaddress(address=self.def_wallet.getnewaddress(), amount=1.3, fee_rate=self.target_fee_rate)
    131+        ancestor_aware_tx = wallet.gettransaction(txid=ancestor_aware_txid, verbose=True)
    132+
    133+        self.assert_beats_target(ancestor_aware_tx)
    


    S3RK commented at 7:07 am on July 4, 2023:
    nit: add self.assert_spends_only_parent
  188. in test/functional/wallet_spend_unconfirmed.py:136 in 232edb7e5d outdated
    131+        ancestor_aware_tx = wallet.gettransaction(txid=ancestor_aware_txid, verbose=True)
    132+
    133+        self.assert_beats_target(ancestor_aware_tx)
    134+        resulting_ancestry_fee_rate = self.calc_set_fee_rate([gp_tx, p_tx, ancestor_aware_tx])
    135+        assert_greater_than_or_equal(resulting_ancestry_fee_rate, self.target_fee_rate)
    136+        assert_greater_than_or_equal(self.target_fee_rate*1.5, resulting_ancestry_fee_rate)
    


    S3RK commented at 7:13 am on July 4, 2023:
    Here and other tests.I’m not sure the magic number is needed. I tried setting the multiplier to 1 and the tests still pass. Maybe just check that resulting fee rate is exactly equal to target fee rate?

    murchandamus commented at 3:19 pm on August 11, 2023:
    Every once in a while we’ll get a signature that’s gonna be one byte smaller, so a little bit of a buffer will prevent intermittent failures. I reduced the buffers to 1%, though.
  189. t-bast referenced this in commit 8215e224a7 on Jul 4, 2023
  190. t-bast commented at 2:35 pm on July 4, 2023: contributor

    I’ve fixed a number of issues, if this latest push’s checks turn up green, it might be more fruitful to try now.

    I confirm that, I’ve ran my set of tests against eclair and everything looks good using https://github.com/bitcoin/bitcoin/pull/26152/commits/0f6c13665c4ab7d4928ff0fa63c4e755667f7fd6 :tada: The tests I ran can be found in the last two commits of this branch.

  191. in src/wallet/feebumper.cpp:93 in 0f6c13665c outdated
    94+    if (!combined_bump_fee.has_value()) {
    95+        errors.push_back(strprintf(Untranslated("Failed to calculate bump fees, because unconfirmed UTXOs depend on enormous cluster of unconfirmed transactions.")));
    96     }
    97-
    98-    CAmount new_total_fee = newFeerate.GetFee(maxTxSize) + total_bump_fees;
    99+    CAmount fees_including_bump_fees = newFeerate.GetFee(maxTxSize) + combined_bump_fee.value();
    


    achow101 commented at 5:28 pm on July 5, 2023:

    In 0f6c13665c4ab7d4928ff0fa63c4e755667f7fd6 “Amend bumpfee for inputs with overlapping ancestry”

    nit: I’d prefer if variables were not renamed if they don’t need to be, it makes review of this commit slightly harder and unnecessarily breaks git blame. new_total_fee is still a valid description of what this variable contains.


    murchandamus commented at 5:59 pm on July 5, 2023:
    Okay, will be reverting to new_total_fee
  192. in src/wallet/spend.cpp:1054 in 0f6c13665c outdated
    1051@@ -1034,7 +1052,7 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
    1052     if (nBytes == -1) {
    1053         return util::Error{_("Missing solving data for estimating transaction size")};
    1054     }
    1055-    CAmount fee_needed = coin_selection_params.m_effective_feerate.GetFee(nBytes) + result.GetSummedBumpFees();
    1056+    CAmount fee_needed = coin_selection_params.m_effective_feerate.GetFee(nBytes) + result.GetSummedBumpFees() - result.GetBumpFeeDiscount();
    


    achow101 commented at 5:31 pm on July 5, 2023:

    In 0f6c13665c4ab7d4928ff0fa63c4e755667f7fd6 “Amend bumpfee for inputs with overlapping ancestry”

    GetBumpFeeDiscount is only used here. It doesn’t seem like it’s useful to require the caller to calculate the final bumpfee rather than having SelectionResult just do that when the the discount is applied and have a single method to return the bumpfees required for a particular selection.


    murchandamus commented at 8:42 pm on July 5, 2023:
    I’ve been doing some staring at this today. It seems to me that the waste score of finished input sets should also be adjusted according to the bump fee discount. Gotta do more pondering, will get back to this.

    S3RK commented at 6:32 am on July 12, 2023:
    Yes, the discount value is a function of the effective fee rate, therefore it has to be taken into account in waste calculation. The problem is that GetSelectionWaste doesn’t use SelectionResult.GetEffectiveValue() which already accounts for bump fee discoun

    murchandamus commented at 9:15 pm on August 17, 2023:
    Okay, I replaced GetSummedBumpFees() with GetTotalBumpFees() which deducts the bump_fee_discount if it has been set.
  193. in src/wallet/spend.cpp:625 in 0f6c13665c outdated
    620+            return util::Error{_("Failed to calculate bump fees, because unconfirmed UTXOs depend on enormous cluster of unconfirmed transactions.")};
    621+        }
    622+        CAmount bump_fee_overestimate = result.GetSummedBumpFees() - combined_bump_fee.value();
    623+        if (bump_fee_overestimate) {
    624+            result.SetBumpFeeDiscount(bump_fee_overestimate);
    625+            // Update waste
    


    achow101 commented at 5:34 pm on July 5, 2023:

    In 0f6c13665c4ab7d4928ff0fa63c4e755667f7fd6 “Amend bumpfee for inputs with overlapping ancestry”

    nit: this comment seems to be misplaced?


    murchandamus commented at 6:02 pm on July 5, 2023:
    Yes, thanks, I removed the earlier instances of ComputeAndSetWaste where the results get returned from the various coin selection algorithms and instead of selectively updating here, always calculate it here now. I just forgot to remove the comment.
  194. in src/wallet/coinselection.h:78 in 232edb7e5d outdated
    71@@ -71,6 +72,9 @@ struct COutput {
    72     /** The fee required to spend this output at the consolidation feerate. */
    73     CAmount long_term_fee{0};
    74 
    75+    /** The fee necessary to bump this UTXO's ancestor transactions to the target feerate */
    76+    CAmount ancestor_bump_fees{0};
    77+
    78     COutput(const COutPoint& outpoint, const CTxOut& txout, int depth, int input_bytes, bool spendable, bool solvable, bool safe, int64_t time, bool from_me, const std::optional<CFeeRate> feerate = std::nullopt)
    


    ishaanam commented at 5:55 pm on July 5, 2023:

    In 232edb7e5d630d9d687ba3089aa4028f8e0380a4 " Bump unconfirmed parent txs to target feerate "

    Not directly related to this PR, but it would be helpful to write a comment about how the second COutput constructor (beneath this one) is only used in tests.


    murchandamus commented at 3:01 pm on August 11, 2023:
    That seems a bit out of place in this PR.
  195. in test/functional/wallet_spend_unconfirmed.py:44 in 232edb7e5d outdated
    39+    def calc_set_fee_rate(self, txs):
    40+        fees = Decimal(-1e8) * sum([tx["fee"] for tx in txs]) # fee is negative!
    41+        vsizes = sum([tx["decoded"]["vsize"] for tx in txs])
    42+        return fees / vsizes
    43+
    44+    def assert_spends_only_parent(self, tx, parent_txid):
    


    S3RK commented at 7:10 am on July 6, 2023:
    This could be extended to a set of parent txs to verify cases with multiple parents

    ismaelsadeeq commented at 1:43 pm on July 19, 2023:

    We can have something like

    0
    1    def assert_spends_only_parents(self, tx, parent_txids):
    2        number_inputs = len(tx["decoded"]["vin"])
    3        assert_equal(number_inputs, len(parent_txids))
    4        for i in range(number_inputs):
    5            txid_of_input = tx["decoded"]["vin"][i]["txid"]
    6            assert txid_of_input in parent_txids
    

    murchandamus commented at 3:49 pm on August 11, 2023:

    Thanks, good idea. I went with:

    0    def assert_spends_only_parents(self, tx, parent_txids):
    1        parent_checklist = parent_txids.copy()
    2        number_inputs = len(tx["decoded"]["vin"])
    3        assert_equal(number_inputs, len(parent_txids))
    4        for i in range(number_inputs):
    5            txid_of_input = tx["decoded"]["vin"][i]["txid"]
    6            assert txid_of_input in parent_checklist
    7            parent_checklist.remove(txid_of_input)
    
  196. in test/functional/wallet_spend_unconfirmed.py:228 in 232edb7e5d outdated
    220+        self.assert_beats_target(ancestor_aware_tx)
    221+        resulting_ancestry_fee_rate = self.calc_set_fee_rate([p_tx, ancestor_aware_tx])
    222+        assert_greater_than_or_equal(resulting_ancestry_fee_rate, self.target_fee_rate)
    223+        assert_greater_than_or_equal(self.target_fee_rate*1.1, resulting_ancestry_fee_rate)
    224+        resulting_ancestry_fee_rate_with_high_feerate_gp = self.calc_set_fee_rate([gp_tx, p_tx, ancestor_aware_tx])
    225+        assert_greater_than_or_equal(resulting_ancestry_fee_rate_with_high_feerate_gp, self.target_fee_rate*1.1)
    


    S3RK commented at 7:19 am on July 6, 2023:
    nit: why there is a multiplier here?

    murchandamus commented at 3:53 pm on August 11, 2023:
    The grandparent transaction has a higher feerate, so we only need to bump the parent. We check that if we get the complete chain of unconfirmed transactions, that the resulting feerate is above our target.
  197. in test/functional/wallet_spend_unconfirmed.py:318 in 232edb7e5d outdated
    313+        parent_txid = wallet.sendtoaddress(address=wallet.getnewaddress(), amount=1, fee_rate=1)
    314+        parent_tx = wallet.gettransaction(txid=parent_txid, verbose=True)
    315+
    316+        self.assert_undershoots_target(parent_tx)
    317+
    318+        to_be_rbfed_ancestor_aware_txid = wallet.sendtoaddress(address=self.def_wallet.getnewaddress(), amount=0.5, fee_rate=self.target_fee_rate, subtractfeefromamount=True)
    


    S3RK commented at 7:30 am on July 6, 2023:
    nit: sffo is not needed

    murchandamus commented at 9:25 pm on August 17, 2023:
    Removed SSFO from this test
  198. S3RK commented at 7:37 am on July 6, 2023: contributor
    Continue tests review
  199. DrahtBot removed the label CI failed on Jul 7, 2023
  200. in src/test/miniminer_tests.cpp:310 in 0c4ff49210 outdated
    279@@ -280,141 +280,141 @@ BOOST_FIXTURE_TEST_CASE(miniminer_overlap, TestChain100Setup)
    280     LOCK2(::cs_main, pool.cs);
    281     TestMemPoolEntryHelper entry;
    282 
    


    ismaelsadeeq commented at 1:14 pm on July 19, 2023:

    In 0c4ff4921087baf4ee00995be910241c3595cbe9

    This commit matches the names to the transactions’ vector indices for better readability

    This only applies to miniminer_overlap miniminer_1p1c test case transactions name still starts at 1.


    murchandamus commented at 4:09 pm on August 11, 2023:
    I also updated the miniminer_1p1c test to match the transaction numbers to the indices
  201. in src/test/miniminer_tests.cpp:364 in 3016245bab outdated
    361     const auto tx4_feerate = CFeeRate(high_fee, tx_vsizes[4]);
    362-    const auto tx6_anc_feerate = CFeeRate(low_fee + med_fee, tx_vsizes[5] + tx_vsizes[6]);
    363-    const auto tx7_anc_feerate = CFeeRate(low_fee + high_fee, tx_vsizes[5] + tx_vsizes[7]);
    364+    const auto tx6_anc_feerate = CFeeRate(high_fee + low_fee + med_fee, tx_vsizes[4] + tx_vsizes[5] + tx_vsizes[6]);
    365+    const auto tx6_iter = pool.GetIter(tx6->GetHash());
    366+    BOOST_CHECK(tx6_anc_feerate == CFeeRate(tx6_iter.value()->GetModFeesWithAncestors(), tx6_iter.value()->GetSizeWithAncestors()));
    


    ismaelsadeeq commented at 1:18 pm on July 19, 2023:
    We can use CFeeRate(transaction_iter.value()->GetModFeesWithAncestors(), transaction_iter.value()->GetSizeWithAncestors()) to get the ancestor fee rate why are we calculating manually? Like here tx6_anc_feerate = CFeeRate(high_fee + low_fee + med_fee, tx_vsizes[4] + tx_vsizes[5] + tx_vsizes[6]); and other places?

    murchandamus commented at 4:40 pm on August 11, 2023:
    Yes, we are doing it manually, because we also compare it against what the mempool thinks it is.
  202. in src/node/mini_miner.h:42 in a3b178f3c0 outdated
    38@@ -38,6 +39,10 @@ class MiniMinerMempoolEntry
    39     int64_t GetTxSize() const { return vsize_individual; }
    40     int64_t GetSizeWithAncestors() const { return vsize_with_ancestors; }
    41     const CTransaction& GetTx() const LIFETIMEBOUND { return *tx; }
    42+    void UpdateAncestorState(int64_t vsize_change, CAmount fee_change) {
    


    ismaelsadeeq commented at 1:33 pm on July 19, 2023:
    nit: feel free to ignore slightly more code and less compact, but I think it is better to have a separate function for decrement and increment, its clear and more explicit and you don’t have to negate.

    murchandamus commented at 4:29 pm on August 11, 2023:
    That’s a good idea, but I’m gonna leave this, because this matches how we interact with the mempool entries.
  203. in src/wallet/coinselection.h:117 in 232edb7e5d outdated
    108@@ -104,6 +109,15 @@ struct COutput {
    109         return outpoint < rhs.outpoint;
    110     }
    111 
    112+    void ApplyBumpFee(CAmount bump_fee)
    113+    {
    114+        assert(bump_fee >= 0);
    115+        ancestor_bump_fees = bump_fee;
    116+        *fee += bump_fee;
    


    ismaelsadeeq commented at 1:40 pm on July 19, 2023:

    fee is an optional variable, is it safe to just increment it, as it may be nullptr

    0if (num) {
    1    *num += bump_fee;
    2}
    

    murchandamus commented at 4:41 pm on August 11, 2023:
    I added an assert(fee); right before.
  204. ismaelsadeeq commented at 2:55 pm on July 19, 2023: contributor

    Light code review ACK for commit 0f6c13665c4ab7d4928ff0fa63c4e755667f7fd6:

    As I continue familiarizing myself with the code, my understanding is that this PR addresses follow-up nits up to a3b178f3c082453120e816945cea220bc7397ee5. Additionally, it introduces the use of MiniMiner to calculate the necessary fees required for fee bumping unconfirmed inputs to a target fee rate. The calculated fee bumps values are then added to the transaction’s fee to account for the ancestor fee bump and reach the target fee rate

    Before this PR, unconfirmed transaction outputs were used as inputs in a new transaction without considering the ancestor fee bump. This could result in the transaction having a lower ancestor fee rate if the unconfirmed ancestor’s fee rate is low. with this PR, we can now ensure that the necessary fees to bump a low-fee ancestor to the target fee rate, if required, are added to the transaction’s fee rate.

    This looks good to me, I just suggest nits and questions

  205. murchandamus force-pushed on Aug 11, 2023
  206. murchandamus commented at 6:12 pm on August 11, 2023: contributor
    Sorry, I still need to squash this, but this should be addressing all the open comments
  207. murchandamus force-pushed on Aug 12, 2023
  208. murchandamus force-pushed on Aug 12, 2023
  209. murchandamus force-pushed on Aug 17, 2023
  210. murchandamus commented at 9:27 pm on August 17, 2023: contributor
    I believe that I have now addressed all open feedback. Ready for review
  211. DrahtBot added the label CI failed on Aug 18, 2023
  212. murchandamus force-pushed on Aug 18, 2023
  213. murchandamus commented at 4:22 pm on August 18, 2023: contributor
    Rebased to get rid of CI issues.
  214. DrahtBot removed the label CI failed on Aug 18, 2023
  215. in src/wallet/coinselection.cpp:505 in 3e4fde6b9a outdated
    501+
    502 void SelectionResult::ComputeAndSetWaste(const CAmount min_viable_change, const CAmount change_cost, const CAmount change_fee)
    503 {
    504     const CAmount change = GetChange(min_viable_change, change_fee);
    505 
    506     if (change > 0) {
    


    S3RK commented at 7:08 am on August 21, 2023:
    nit: this if can be removed and instead we can check if change exist directly in GetChange()

    murchandamus commented at 7:14 pm on August 21, 2023:

    I was actually only touching this because moving the function GetSelectionWaste(…) into SelectionResult made it have direct access to the input set. Your comment made me realize that we never use the amount that’s returned, and GetChange(…) already returns 0 if there is no change.

    What do you think about:

     0 void SelectionResult::ComputeAndSetWaste(const CAmount min_viable_change, const CAmount change_cost, const CAmount change_fee)
     1 {
     2-    const CAmount change = GetChange(min_viable_change, change_fee);
     3-
     4-    if (change > 0) {
     5-        m_waste = GetSelectionWaste(change_cost, m_target, m_use_effective);
     6-    } else {
     7-        m_waste = GetSelectionWaste(0, m_target, m_use_effective);
     8-    }
     9+    bool makes_change = (0 != GetChange(min_viable_change, change_fee));
    10+    m_waste = GetSelectionWaste(makes_change ? change_cost : 0, m_target, m_use_effective);
    11 }
    

    murchandamus commented at 4:24 pm on August 29, 2023:
    I started with that, and then realized that ComputeAndSetWaste and GetSelectionWaste now live in the same class, so the former essentially wrapping the latter doesn’t make sense anymore. Then I cleaned that up, but that broke a bunch of the waste calculation tests. So, finally, I propose to clean all this up in a follow-up PR, which I will open shortly.

    S3RK commented at 7:55 pm on August 29, 2023:
    sure, that’s definitely cosmetics and can wait for a follow up 👍

    murchandamus commented at 3:20 pm on August 30, 2023:
    I’ve opened #28366 to collect these changes
  216. in src/wallet/coinselection.cpp:453 in 3e4fde6b9a outdated
    449@@ -449,19 +450,21 @@ void OutputGroupTypeMap::Push(const OutputGroup& group, OutputType type, bool in
    450     }
    451 }
    452 
    453-CAmount GetSelectionWaste(const std::set<std::shared_ptr<COutput>>& inputs, CAmount change_cost, CAmount target, bool use_effective_value)
    454+CAmount SelectionResult::GetSelectionWaste(CAmount change_cost, CAmount target, bool use_effective_value)
    


    S3RK commented at 7:12 am on August 21, 2023:
    we need tests for waste calculation with bumpfee and especially for bumpfee discount

    murchandamus commented at 3:20 pm on August 30, 2023:
    Added a test for waste calculation with bumpfee and bumpfee discount.
  217. in src/wallet/coinselection.cpp:466 in 3e4fde6b9a outdated
    465         const COutput& coin = *coin_ptr;
    466         waste += coin.GetFee() - coin.long_term_fee;
    467-        selected_effective_value += use_effective_value ? coin.GetEffectiveValue() : coin.txout.nValue;
    468     }
    469+    // Bump fee of whole selection may diverge from sum of individual bump fees
    470+    waste += GetTotalBumpFees();
    


    S3RK commented at 7:15 am on August 21, 2023:
    You only need to detract the discount, because coin.GetFee() already accounts for initial bump

    murchandamus commented at 7:22 pm on August 21, 2023:
    Uff, good catch.
  218. in src/wallet/coinselection.cpp:467 in 3e4fde6b9a outdated
    466         waste += coin.GetFee() - coin.long_term_fee;
    467-        selected_effective_value += use_effective_value ? coin.GetEffectiveValue() : coin.txout.nValue;
    468     }
    469+    // Bump fee of whole selection may diverge from sum of individual bump fees
    470+    waste += GetTotalBumpFees();
    471+    selected_effective_value += use_effective_value ? GetSelectedEffectiveValue() : GetSelectedValue();
    


    S3RK commented at 7:16 am on August 21, 2023:
    nit: move the selected_effective_value var and the operation under the else below

    murchandamus commented at 8:11 pm on August 22, 2023:
    Done
  219. murchandamus commented at 4:25 pm on August 29, 2023: contributor
    Addressed S3RK’s comments, added waste calculation test with bump fees and group discount
  220. murchandamus force-pushed on Aug 29, 2023
  221. murchandamus requested review from achow101 on Aug 29, 2023
  222. murchandamus requested review from S3RK on Aug 29, 2023
  223. murchandamus requested review from ismaelsadeeq on Aug 29, 2023
  224. murchandamus requested review from t-bast on Aug 29, 2023
  225. DrahtBot added the label CI failed on Aug 29, 2023
  226. murchandamus force-pushed on Aug 30, 2023
  227. murchandamus commented at 3:03 pm on August 30, 2023: contributor
    Rebased, and fixed comment to mollify tidy. (Ready for review.)
  228. murchandamus force-pushed on Aug 30, 2023
  229. murchandamus requested review from LarryRuane on Aug 30, 2023
  230. DrahtBot removed the label CI failed on Aug 30, 2023
  231. t-bast approved
  232. t-bast commented at 8:40 am on August 31, 2023: contributor
    ACK https://github.com/bitcoin/bitcoin/pull/26152/commits/1b2fb54d5c18579e013b1dfd061c2b29d8cffdc2, I ran my e2e lightning tests on top of that branch and everything is looking good :+1:
  233. DrahtBot removed review request from LarryRuane on Aug 31, 2023
  234. DrahtBot removed review request from ismaelsadeeq on Aug 31, 2023
  235. DrahtBot requested review from ismaelsadeeq on Aug 31, 2023
  236. ismaelsadeeq commented at 1:55 pm on August 31, 2023: contributor

    re ACK 1b2fb54d5c18579e013b1dfd061c2b29d8cffdc2

    Reviewed up to acb6aaeac49dd4543280653f19d2377ffb175c9e and functional test As I am not really familiar with the coinselection part of the PR, I lightly review the commits. I rebased latest master a4e0bcb6c9a8db5a74c74c5cddbb065ba9182482 on this PR, the tests failed and passed after making the branch.

  237. DrahtBot removed review request from ismaelsadeeq on Aug 31, 2023
  238. achow101 commented at 9:52 pm on September 6, 2023: member
    ACK 1b2fb54d5c18579e013b1dfd061c2b29d8cffdc2
  239. DrahtBot removed review request from achow101 on Sep 6, 2023
  240. in src/wallet/spend.cpp:165 in 35a35fa989 outdated
    161@@ -162,6 +162,7 @@ util::Result<PreSelectedInputs> FetchSelectedInputs(const CWallet& wallet, const
    162 {
    163     PreSelectedInputs result;
    164     const bool can_grind_r = wallet.CanGrindR();
    165+    std::map<COutPoint, CAmount> map_of_bump_fees = wallet.chain().CalculateIndividualBumpFees(coin_control.ListSelected(), coin_selection_params.m_effective_feerate);
    


    brunoerg commented at 8:40 pm on September 8, 2023:

    non blocker, just a suggestion: In 35a35fa989f14406f8218c20c4e4e732cee0bd6c: you could move map_of_bump_fees to right before ApplyBumpFee, this way we calculate the individual bump fees when we really sure we’re going to use it:

     0diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp
     1index 15004c67b0..db0870f31b 100644
     2--- a/src/wallet/spend.cpp
     3+++ b/src/wallet/spend.cpp
     4@@ -162,8 +162,8 @@ util::Result<PreSelectedInputs> FetchSelectedInputs(const CWallet& wallet, const
     5 {
     6     PreSelectedInputs result;
     7     const bool can_grind_r = wallet.CanGrindR();
     8-    std::map<COutPoint, CAmount> map_of_bump_fees = wallet.chain().CalculateIndividualBumpFees(coin_control.ListSelected(), coin_selection_params.m_effective_feerate);
     9-    for (const COutPoint& outpoint : coin_control.ListSelected()) {
    10+    const auto list_selected{coin_control.ListSelected()};
    11+    for (const COutPoint& outpoint : list_selected) {
    12         int input_bytes = -1;
    13         CTxOut txout;
    14         if (auto ptr_wtx = wallet.GetWalletTx(outpoint.hash)) {
    15@@ -198,6 +198,7 @@ util::Result<PreSelectedInputs> FetchSelectedInputs(const CWallet& wallet, const
    16 
    17         /* Set some defaults for depth, spendable, solvable, safe, time, and from_me as these don't matter for preset inputs since no selection is being done. */
    18         COutput output(outpoint, txout, /*depth=*/ 0, input_bytes, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, coin_selection_params.m_effective_feerate);
    19+        std::map<COutPoint, CAmount> map_of_bump_fees{wallet.chain().CalculateIndividualBumpFees(list_selected, coin_selection_params.m_effective_feerate)};
    20         output.ApplyBumpFee(map_of_bump_fees.at(output.outpoint));
    21         result.Insert(output, coin_selection_params.m_subtract_fee_outputs);
    22     }
    

    murchandamus commented at 8:14 pm on September 11, 2023:
    Maybe I’m misunderstanding what you propose, but CalculateIndividualBumpFees(…) gets the bump fees for all elements of coin_control.ListSelected() at once, and this would move it into the for-loop, so we’d get the individual bump fees for all n elements n times for n² effort.
  241. in src/wallet/spend.cpp:357 in 35a35fa989 outdated
    352@@ -348,6 +353,16 @@ CoinsResult AvailableCoins(const CWallet& wallet,
    353         }
    354     }
    355 
    356+    if (feerate.has_value()) {
    357+        std::map<COutPoint, CAmount> map_of_bump_fees = wallet.chain().CalculateIndividualBumpFees(outpoints, feerate.value());
    


    brunoerg commented at 8:55 pm on September 8, 2023:
    In 35a35fa989f14406f8218c20c4e4e732cee0bd6c: Shouldn’t we ensure that map_of_bump_fees is not empty?

    murchandamus commented at 8:49 pm on September 11, 2023:
    I guess technically the CalculateIndividualBumpFee(…) call could return an empty map if the feerate changed during the call. I put an assert, and ran all the tests fine. In the worst case, it would fail right below where it applies the bump fees, so I’m not sure an assert would change much.

    brunoerg commented at 9:48 pm on September 13, 2023:
    Makes sense, just wondering about it.

    murchandamus commented at 12:12 pm on September 14, 2023:
    I see, thanks for asking!
  242. in src/wallet/test/coinselector_tests.cpp:839 in 8c9e6342ea outdated
    921-    const CAmount large_fee_diff{90};
    922-    const CAmount target_waste2{-2 * large_fee_diff + change_cost}; // = (2 * fee) - (2 * (fee + large_fee_diff)) + change_cost
    923-    add_coin(1 * COIN, 1, selection, fee, fee + large_fee_diff);
    924-    add_coin(2 * COIN, 2, selection, fee, fee + large_fee_diff);
    925-    BOOST_CHECK_EQUAL(target_waste2, GetSelectionWaste(selection, change_cost, target));
    926+    // The following tests that the waste is calculated correction in various scenarios
    


    S3RK commented at 7:25 am on September 13, 2023:
    nit: correction -> correctly

    murchandamus commented at 5:47 pm on September 13, 2023:
    Done!
  243. in src/wallet/coinselection.cpp:462 in 35a35fa989 outdated
    458@@ -459,7 +459,7 @@ CAmount SelectionResult::GetSelectionWaste(CAmount change_cost, CAmount target,
    459     CAmount selected_effective_value = 0;
    460     for (const auto& coin_ptr : m_selected_inputs) {
    461         const COutput& coin = *coin_ptr;
    462-        waste += coin.GetFee() - coin.long_term_fee;
    463+        waste += coin.GetFee() + coin.ancestor_bump_fees - coin.long_term_fee;
    


    S3RK commented at 7:47 am on September 13, 2023:
    nit: no need to add it here, only to remove in the next commit

    murchandamus commented at 5:48 pm on September 13, 2023:
    If I don’t add it here, I don’t think the tests would pass.

    murchandamus commented at 6:33 pm on September 13, 2023:
    I pulled the part of the waste test that did not deal with the group discount into this commit and actually realized this line was incorrect (and then correctly removed in the next commit). Thank you for pointing this out.
  244. in src/wallet/spend.cpp:547 in 1b2fb54d5c outdated
    543@@ -544,13 +544,13 @@ FilteredOutputGroups GroupOutputs(const CWallet& wallet,
    544 // Returns true if the result contains an error and the message is not empty
    545 static bool HasErrorMsg(const util::Result<SelectionResult>& res) { return !util::ErrorString(res).empty(); }
    546 
    547-util::Result<SelectionResult> AttemptSelection(const CAmount& nTargetValue, OutputGroupTypeMap& groups,
    548+util::Result<SelectionResult> AttemptSelection(const CWallet& wallet, const CAmount& nTargetValue, OutputGroupTypeMap& groups,
    


    S3RK commented at 7:49 am on September 13, 2023:
    nit: could you pass only the ChainInterface here? Let’s not pass the whole wallet around

    murchandamus commented at 4:27 pm on September 13, 2023:
    Absolutely! Done.
  245. in src/wallet/coinselection.cpp:474 in 1b2fb54d5c outdated
    470@@ -470,6 +471,7 @@ CAmount SelectionResult::GetSelectionWaste(CAmount change_cost, CAmount target,
    471         waste += change_cost;
    472     } else {
    473         // When we are not making change (change_cost == 0), consider the excess we are throwing away to fees
    474+        CAmount selected_effective_value = use_effective_value ? GetSelectedEffectiveValue() : GetSelectedValue();
    


    S3RK commented at 7:58 am on September 13, 2023:

    It seems to me in the case with the excess bump_fee_group_discount will be canceled.

    first you subtract the discount waste -= bump_fee_group_discount; but then GetSelectedEffectiveValue() adds it back bump_fee_group_discount

    I think you only need to do waste -= bump_fee_group_discount; when there is a change, but I’m not absolutely sure.

    Could you add a test for the waste calculation with bump_fee and excess?


    murchandamus commented at 5:46 pm on September 13, 2023:
    I’ve added a test. I think the canceling out of the bump_fee_group_discount is expected: both the bump fees and the excess count towards waste in full, so whether we internally attribute the additional fees to the group discount or the excess should not matter for the overall waste score.
  246. S3RK commented at 7:58 am on September 13, 2023: contributor
    Started re-review. Besides few nits, I have a doubt about waste calculation with bump fee and excess. Could you take a look, please?
  247. murchandamus force-pushed on Sep 13, 2023
  248. murchandamus commented at 5:47 pm on September 13, 2023: contributor
    Addressed @s3rk’s review
  249. Match tx names to index in miniminer overlap test
    Follow-up from #27021: In the prior commit, the vector started counting
    at 0, but the transaction names started with 1. This commit matches the
    names to the transactions’ vector indices for better readability.
    
    Co-authored-by: theStack <sebastian.falbesoner@gmail.com>
    a1f7d986e0
  250. Fix calculation of ancestor set feerates in test
    Follow-up from #27021.
    Also included is an ASCII art visualization of the test’s transaction
    topology by theStack.
    
    Co-authored-by: theStack <sebastian.falbesoner@gmail.com>
    d2f90c31ef
  251. Remove unused imports
    Follow-up from #27021
    ac6030e4d8
  252. Make MiniMinerMempoolEntry fields private
    Follow-up from #27021: accessing of fields in MiniMinerMempoolEntry was
    done inconsistently. Even though we had a getter, we would directly
    write to the fields when we needed to update them.
    This commits sets the fields to private and introduces a method for
    updating the ancestor information in transactions using the same method
    name as used for Mempool Entries.
    c24851be94
  253. [node] interface to get bump fees c57889da66
  254. coinselection: Move GetSelectionWaste into SelectionResult
    GetSelectionWaste will need to access more context within a selection
    result, and so should be a private member function rather than a static
    function. It's only use outside of SelectionResult was for tests which
    have now been updated to just make a SelectionResult.
    
    Co-authored-by: Murch <murch@murch.one>
    3e3e052411
  255. Bump unconfirmed parent txs to target feerate
    When a transaction uses an unconfirmed input, preceding this commit it
    would not consider the feerate of the parent transaction. Given a parent
    transaction with a lower ancestor feerate, this resulted in the new
    transaction's ancestor feerate undershooting the target feerate.
    
    This commit changes how we calculate the effective value of unconfirmed UTXOs.
    The effective value of unconfirmed UTXOs is decreased by the fee
    necessary to bump its ancestry to the target feerate. This also impacts
    the calculation of the waste metric: since the estimate for the current
    fee is increased by the bump fees, unconfirmed UTXOs current fees appear less
    favorable compared to their unchanged long term fees.
    
    This has one caveat: if multiple UTXOs have overlapping ancestries, each
    of their individual estimates will account for bumping all ancestors.
    2e35e944da
  256. murchandamus force-pushed on Sep 13, 2023
  257. DrahtBot added the label CI failed on Sep 13, 2023
  258. murchandamus commented at 6:35 pm on September 13, 2023: contributor
    I pulled the part of the waste test that dealt with bump fees without group discount into the earlier commit and fixed an issue in the waste calculation in that commit. The final code is unchanged.
  259. murchandamus force-pushed on Sep 13, 2023
  260. murchandamus commented at 6:54 pm on September 13, 2023: contributor

    I had forgotten to update the AttemptSelection call in src/bench/coin_selection.cpp to the new function header that takes the chain instead of the wallet.

    I should have now addressed all feedback from @brunoerg and @S3RK, and be ready for review again.

  261. Amend bumpfee for inputs with overlapping ancestry
    At the end of coin selection reduce the fees by the difference between
    the individual bump fee estimates and the collective bump fee estimate.
    f18f9ef4d3
  262. murchandamus force-pushed on Sep 13, 2023
  263. murchandamus commented at 7:51 pm on September 13, 2023: contributor

    @S3RK noticed that SelectionResult::GetBumpFeeDiscount() was now obsolete and no longer used. (AFAIR, this would have been a bi-product of moving GetSelectionWaste into SelectionResult.)

    I removed the obsolete method.

  264. S3RK commented at 7:54 pm on September 13, 2023: contributor

    ACK f18f9ef4d31c70e2d71ab90a24511692821418c3

    Great work! This PR not only makes our wallet work properly with unconfirmed inputs, but it also improved the factoring of the code. Happy to see waste calculations as a member function of SelectionResult

  265. DrahtBot requested review from ismaelsadeeq on Sep 13, 2023
  266. DrahtBot requested review from achow101 on Sep 13, 2023
  267. DrahtBot requested review from t-bast on Sep 13, 2023
  268. murchandamus commented at 8:27 pm on September 13, 2023: contributor

    @t-bast, @ismaelsadeeq, @achow101: Changes since 1b2fb54d5c18579e013b1dfd061c2b29d8cffdc2

    • Pass chain interface to AttemptSelection(…) instead of whole wallet
    • Remove obsolete method SelectionResult::GetBumpFeeDiscount()
    • Add waste calculation test for changeless transactions with bump fees
    • Fix typo
  269. DrahtBot removed review request from ismaelsadeeq on Sep 13, 2023
  270. DrahtBot requested review from ismaelsadeeq on Sep 13, 2023
  271. DrahtBot removed review request from ismaelsadeeq on Sep 13, 2023
  272. DrahtBot requested review from ismaelsadeeq on Sep 13, 2023
  273. DrahtBot removed review request from ismaelsadeeq on Sep 13, 2023
  274. DrahtBot requested review from ismaelsadeeq on Sep 13, 2023
  275. DrahtBot removed the label CI failed on Sep 13, 2023
  276. DrahtBot removed review request from ismaelsadeeq on Sep 13, 2023
  277. DrahtBot requested review from ismaelsadeeq on Sep 13, 2023
  278. brunoerg approved
  279. brunoerg commented at 10:11 pm on September 13, 2023: contributor

    crACK f18f9ef4d31c70e2d71ab90a24511692821418c3

    code lgtm, nice improvement!

  280. DrahtBot removed review request from ismaelsadeeq on Sep 13, 2023
  281. DrahtBot requested review from ismaelsadeeq on Sep 13, 2023
  282. ismaelsadeeq commented at 11:49 am on September 14, 2023: contributor
    ACK f18f9ef4d31c70e2d71ab90a24511692821418c3
  283. DrahtBot removed review request from ismaelsadeeq on Sep 14, 2023
  284. fanquake added this to the milestone 26.0 on Sep 14, 2023
  285. t-bast approved
  286. t-bast commented at 2:50 pm on September 14, 2023: contributor
    ACK https://github.com/bitcoin/bitcoin/pull/26152/commits/f18f9ef4d31c70e2d71ab90a24511692821418c3, I reviewed the latest changes and run e2e tests against eclair, everything looks good :+1:
  287. achow101 commented at 8:00 pm on September 14, 2023: member
    ACK f18f9ef4d31c70e2d71ab90a24511692821418c3
  288. DrahtBot removed review request from achow101 on Sep 14, 2023
  289. achow101 merged this on Sep 14, 2023
  290. achow101 closed this on Sep 14, 2023


github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2024-07-01 13:12 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me