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

    <!--e57a25ab6845829454e8d69fc972939a-->

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

    <!--021abf342d371248e50ceaed478a90ca-->

    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.

    <!--174a7506f384e20aa4161008e828411d-->

    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:
            // 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:
    File "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).
    ERROR: 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:

    �[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

                            ++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

                            // 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.):

    --- a/src/txmempool.cpp
    +++ b/src/txmempool.cpp
    @@ -1242,10 +1242,10 @@ std::vector<CTxMemPool::txiter> CTxMemPool::CalculateCluster(const std::vector<u
                             // transactions and skip.
                             std::swap(cluster[i+1], cluster.back());
                             cluster[i+1] = parent_it;
    -                        ++i; 
    +                        ++i;
                         } else {
                             cluster.push_back(parent_it);
    -                        // we still need to process this 
    +                        // we still need to process this
                             ++unprocessed_count;
                         }
                     }
    @@ -1259,10 +1259,10 @@ std::vector<CTxMemPool::txiter> CTxMemPool::CalculateCluster(const std::vector<u
                             // transactions and skip.
                             std::swap(cluster[i+1], cluster.back());
                             cluster[i+1] = child_it;
    -                        ++i; 
    +                        ++i;
                         } else {
                             cluster.push_back(child_it);
    -                        // we still need to process this 
    +                        // we still need to process this
                             ++unprocessed_count;
                         }
                     }
    
  14. jonatack commented at 8:04 AM on September 23, 2022: member

    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:

    -struct MockMempoolEntry {
    +class MockMempoolEntry
    +{
         CAmount fee_individual;
    -    CAmount fee_with_ancestors;
    -    int64_t vsize_individual;
    -    int64_t vsize_with_ancestors;
         const CTransaction& tx;
     
     public:
    +    CAmount fee_with_ancestors;
    +    int64_t vsize_individual;
    +    int64_t vsize_with_ancestors;
         explicit MockMempoolEntry(CTxMemPool::txiter entry) :
             fee_individual{entry->GetModifiedFee()},
             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:

        //  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:

        //  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:

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

    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.

            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:

        // 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:

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

    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:

            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?

            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.

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

    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:
        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?

        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

        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

        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 12: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. maflcko 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:
                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:
        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:
                    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:
        for (const auto& [txid, outpoints] : outpoints_needed_by_txid) {
            txids_needed.push_back(txid);
        }
    

    (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:
        //  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:
        // If multiple argument outpoints correspond to the same transaction, cache them together in
        // a single entry indexed by txid. Then we can just work with txids since all outpoints from
        // 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:
        //  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:
                    for (const auto& outpoint : outpoints_it->second) {
                        bump_fees.emplace(outpoint, 0);
                    }
                    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:
            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:

    // This class must be constructed while holding mempool.cs. After construction, the object's
    // 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 12: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:

        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 12: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()

            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:
        // 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: member

    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: 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

    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: member

    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: member

    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 12: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.

                        if (!ancestors.count(parent_it)) {
                            to_process.insert(parent_it);
                            ancestors.insert(parent_it);
                        }
    

    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.

                    if (!ancestors.count(parent_it)) {
                        to_process.insert(parent_it);
                        ancestors.insert(parent_it);
                    }
    
  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:
                // each entry's descendant set includes itself
                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:

    assert(descendant->second.vsize_with_ancestors >= anc->second.GetTxSize());
    descendant->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:

                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:
                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 12: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<CAmount> 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

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

    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.

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

    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:

    bitcoind: 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

    
        def assert_spends_only_parents(self, tx, parent_txids):
            number_inputs = len(tx["decoded"]["vin"])
            assert_equal(number_inputs, len(parent_txids))
            for i in range(number_inputs):
                txid_of_input = tx["decoded"]["vin"][i]["txid"]
                assert txid_of_input in parent_txids
    

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

    Thanks, good idea. I went with:

        def assert_spends_only_parents(self, tx, parent_txids):
            parent_checklist = parent_txids.copy()
            number_inputs = len(tx["decoded"]["vin"])
            assert_equal(number_inputs, len(parent_txids))
            for i in range(number_inputs):
                txid_of_input = tx["decoded"]["vin"][i]["txid"]
                assert txid_of_input in parent_checklist
                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

    if (num) {
        *num += bump_fee;
    }
    

    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: member

    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:

     void SelectionResult::ComputeAndSetWaste(const CAmount min_viable_change, const CAmount change_cost, const CAmount change_fee)
     {
    -    const CAmount change = GetChange(min_viable_change, change_fee);
    -
    -    if (change > 0) {
    -        m_waste = GetSelectionWaste(change_cost, m_target, m_use_effective);
    -    } else {
    -        m_waste = GetSelectionWaste(0, m_target, m_use_effective);
    -    }
    +    bool makes_change = (0 != GetChange(min_viable_change, change_fee));
    +    m_waste = GetSelectionWaste(makes_change ? change_cost : 0, m_target, m_use_effective);
     }
    

    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: member

    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:

    diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp
    index 15004c67b0..db0870f31b 100644
    --- a/src/wallet/spend.cpp
    +++ b/src/wallet/spend.cpp
    @@ -162,8 +162,8 @@ util::Result<PreSelectedInputs> FetchSelectedInputs(const CWallet& wallet, const
     {
         PreSelectedInputs result;
         const bool can_grind_r = wallet.CanGrindR();
    -    std::map<COutPoint, CAmount> map_of_bump_fees = wallet.chain().CalculateIndividualBumpFees(coin_control.ListSelected(), coin_selection_params.m_effective_feerate);
    -    for (const COutPoint& outpoint : coin_control.ListSelected()) {
    +    const auto list_selected{coin_control.ListSelected()};
    +    for (const COutPoint& outpoint : list_selected) {
             int input_bytes = -1;
             CTxOut txout;
             if (auto ptr_wtx = wallet.GetWalletTx(outpoint.hash)) {
    @@ -198,6 +198,7 @@ util::Result<PreSelectedInputs> FetchSelectedInputs(const CWallet& wallet, const
     
             /* 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. */
             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);
    +        std::map<COutPoint, CAmount> map_of_bump_fees{wallet.chain().CalculateIndividualBumpFees(list_selected, coin_selection_params.m_effective_feerate)};
             output.ApplyBumpFee(map_of_bump_fees.at(output.outpoint));
             result.Insert(output, coin_selection_params.m_subtract_fee_outputs);
         }
    

    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: member

    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

  291. Frank-GER referenced this in commit 7b33cf9e20 on Sep 19, 2023
  292. achow101 referenced this in commit 701b0cf2f3 on Jun 4, 2024
  293. bitcoin locked this on Sep 13, 2024

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: 2026-05-02 18:13 UTC

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