fuzz: improve coinselection #27585

pull brunoerg wants to merge 9 commits into bitcoin:master from brunoerg:2023-05-fuzz-coinselection changing 1 files +90 −17
  1. brunoerg commented at 5:54 pm on May 5, 2023: contributor

    This PR:

    • Moves coin creation to its own function called CreateCoins.
    • Add coverage for EligibleForSpending
    • Add coverage for AddInputs: get result of each algorithm (srd, knapsack and bnb), call CreateCoins and add into them.
    • Add coverage for GetShuffledInputVector and GetInputSet using the result of each algorithm (srd, knapsack and bnb).
    • Add coverage for Merge: Call SRD with the new utxos and, if successful, try to merge with the previous SRD result.
  2. DrahtBot commented at 5:54 pm on May 5, 2023: contributor

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

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    ACK furszy, murchandamus, achow101

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

    Conflicts

    No conflicts as of last run.

  3. DrahtBot added the label Tests on May 5, 2023
  4. brunoerg force-pushed on May 5, 2023
  5. DrahtBot added the label CI failed on May 5, 2023
  6. DrahtBot removed the label CI failed on May 5, 2023
  7. maflcko added the label Wallet on May 8, 2023
  8. fanquake commented at 2:46 pm on May 8, 2023: member
  9. in src/wallet/test/fuzz/coinselection.cpp:147 in 6151b914ff outdated
    124+                   return std::make_shared<COutput>(output);
    125+                 });
    126+
    127+    if (result_srd) result_srd->AddInputs(utxos_set, subtract_fee_outputs);
    128+    if (result_knapsack) result_knapsack->AddInputs(utxos_set, subtract_fee_outputs);
    129+    if (result_bnb) result_bnb->AddInputs(utxos_set, subtract_fee_outputs);
    


    furszy commented at 3:38 pm on May 8, 2023:

    If CreateCoins generates a coin that is already inside any of the results, AddInputs will throw a runtime error, which the fuzzing framework will detect as a failure when it is not. It is part of the class boundaries.

    Same apply for the fuzz Merge function commit.


    brunoerg commented at 3:50 pm on May 8, 2023:

    If CreateCoins generates a coin that is already inside any of the results, AddInputs will throw a runtime error, which the fuzzing framework will detect as a failure when it is not. It is part of the class boundaries.

    Yes, that’s why it calls CreateCoins twice and with different “utxos pool”. Is there any risk yet?


    furszy commented at 4:09 pm on May 8, 2023:
    No ok. The reason why this doesn’t fails is the test global increasing next_locktime which ensures that all UTXOs receive a different hash even when the fuzzer provided the same data for them. So, nvm. A comment wouldn’t hurt but nothing serious.

    brunoerg commented at 6:05 pm on May 8, 2023:
    Got it, thanks.
  10. in src/wallet/test/fuzz/coinselection.cpp:55 in d5282a45bd outdated
    50+    CAmount total_balance{0};
    51+    int next_locktime{0};
    52+    LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000)
    53+    {
    54+        const int n_input{fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 10)};
    55+        const int n_input_bytes{fuzzed_data_provider.ConsumeIntegralInRange<int>(100, 10000)};
    


    murchandamus commented at 5:37 pm on June 1, 2023:

    Let’s allow the lower bound of what’s possible:

    0        const int n_input_bytes{fuzzed_data_provider.ConsumeIntegralInRange<int>(41, 10000)};
    

    brunoerg commented at 8:37 pm on June 1, 2023:
    Great, done!
  11. in src/wallet/test/fuzz/coinselection.cpp:111 in d5282a45bd outdated
    121+    }
    122 
    123     auto result_srd = SelectCoinsSRD(group_pos, target, fast_random_context, MAX_STANDARD_TX_WEIGHT);
    124-    if (result_srd) result_srd->ComputeAndSetWaste(cost_of_change, cost_of_change, 0);
    125+    if (result_srd) {
    126+        result_srd->ComputeAndSetWaste(cost_of_change, cost_of_change, 0);
    


    murchandamus commented at 5:44 pm on June 1, 2023:

    It’s not clear to me why the ComputeAndSetWaste() is called with these parameters. The first should be min_viable_change which should be bigger than change_fee but not really related to cost_of_change. I guess that’s fine for this test. It’s also not clear to me, though why the change_fee is set to 0 here. Wouldn’t it be better to have it derive from the current coin_params?

    coin_params.m_change_fee = coin_params.m_effective_feerate.GetFee(coin_params.change_output_size);

    Not sure if it adds interesting test coverage here, but I could see some ComputeAndSetWaste() come to different conclusions occasionally when a fee is considered.


    brunoerg commented at 8:56 pm on June 1, 2023:

    I didn’t touch ComputeAndSetWaste parameters value, perhaps who did it copied (?) from: https://github.com/bitcoin/bitcoin/blob/34ac3f438a268e83af6cd11e2981e5bc07f699c9/src/wallet/coinselection.cpp#L179

    I’m going to change it to make it more “realistic”/robust.


    brunoerg commented at 9:39 pm on June 1, 2023:
    Done
  12. murchandamus commented at 6:03 pm on June 1, 2023: contributor

    I’m pretty new to fuzz testing, so I am not sure whether we want to have all these additional calls for each selection result. It seems to me that if we wanted to test the behavior of SelectionResult, it might make more sense to separately fuzz that instead of fuzzing that in the context of running all the different coin selection algorithms, since it might lower the number of executions we get on fuzzing coin selection.

    OTOH, it’s great to get fuzzing coverage for these codepaths.

    Code changes look good to me.

    Tentative ACK d5282a45bd452844dc815181825e3dd46133ac51, except that I’d be curious to hear what others think about expanding the responsibility of this fuzz target vs adding a second fuzz target for testing SelectionResult.

  13. brunoerg force-pushed on Jun 1, 2023
  14. brunoerg commented at 9:40 pm on June 1, 2023: contributor
    Force-pushed addressing #27585 (review) and #27585 (review)
  15. brunoerg force-pushed on Jun 1, 2023
  16. in src/wallet/test/fuzz/coinselection.cpp:131 in 2d739a425c outdated
    124+    }
    125 
    126     auto result_srd = SelectCoinsSRD(group_pos, target, fast_random_context, MAX_STANDARD_TX_WEIGHT);
    127-    if (result_srd) result_srd->ComputeAndSetWaste(cost_of_change, cost_of_change, 0);
    128+    if (result_srd) {
    129+        result_srd->ComputeAndSetWaste(coin_params.min_viable_change, coin_params.m_cost_of_change, coin_params.m_change_fee);
    


    murchandamus commented at 6:14 pm on June 9, 2023:
    I think you might get some funny behavior here, if min_viable_change is smaller than m_change_fee as mentioned above, also see https://github.com/bitcoin/bitcoin/pull/27846

    murchandamus commented at 4:12 pm on June 13, 2023:
    Nevermind that. I ran about 500k executions on the coinselection fuzz test and could not substantiate my concern.
  17. murchandamus approved
  18. murchandamus commented at 4:18 pm on June 13, 2023: contributor

    ACK 2d739a425c92e2dd409c45273e8e376d39ead1cc

    My concern about the min_viable_change and change_fee causing issues did not substantiate after some light fuzzing. Since it does not seem to cause crashes, having independent values for the two parameters may be better. Thanks for adding more coverage.

  19. DrahtBot added the label Needs rebase on Jun 23, 2023
  20. brunoerg force-pushed on Jun 26, 2023
  21. brunoerg commented at 8:20 pm on June 26, 2023: contributor
    Rebased
  22. DrahtBot removed the label Needs rebase on Jun 26, 2023
  23. fanquake requested review from murchandamus on Jul 5, 2023
  24. fanquake requested review from furszy on Jul 5, 2023
  25. murchandamus commented at 2:59 pm on July 5, 2023: contributor
    reACK bd6315a2a75d2891a8d3476c492d6b53309b7296
  26. in src/wallet/test/fuzz/coinselection.cpp:128 in df15464a9c outdated
    123+    CreateCoins(fuzzed_data_provider, utxos, coin_params);
    124+    std::transform(utxos.begin(), utxos.end(),
    125+                 std::inserter(utxos_set, utxos_set.begin()),
    126+                 [](const COutput& output) {
    127+                   return std::make_shared<COutput>(output);
    128+                 });
    


    furszy commented at 2:38 pm on July 24, 2023:
    nit: To not have to do the transformation, could move all the std::vector<COutput> to std::vector<std::shared_ptr<COutput>>

    brunoerg commented at 8:46 pm on August 3, 2023:
    Done
  27. in src/wallet/test/fuzz/coinselection.cpp:125 in 34f6fd2287 outdated
    100@@ -101,16 +101,23 @@ FUZZ_TARGET(coinselection)
    101 
    102     // Run coinselection algorithms
    103     auto result_bnb = SelectCoinsBnB(group_pos, target, cost_of_change, MAX_STANDARD_TX_WEIGHT);
    104+    if (result_bnb) {
    105+        (void)result_bnb->GetShuffledInputVector();
    106+    }
    


    furszy commented at 2:42 pm on July 24, 2023:
    Couldn’t we assert that BnB GetChange() is always 0?

    brunoerg commented at 8:54 pm on August 3, 2023:
    Does it worth? BnB internally calls ComputeSetAndWaste which checks change. Any unexpected action regarding it would affect the assertion - assert(best_waste == result.GetWaste());.

    furszy commented at 1:32 pm on August 4, 2023:

    I think that it worth. One of fuzzing goals is to assert expected behavior externally. Black-box style. So if the internal implementation changes by mistake (or if there is a way to by-pass the expected behavior that we aren’t aware of), the fuzzer catches it.

    Also, the internal BnB ComputeSetAndWaste call checks that the final selection is within a certain range: target <= value < target + change cost. It is indirectly checking GetChange() result but I don’t think that we should rely on that. If change_cost is also 0, the GetChange result doesn’t really matter, the same code will be executed in both branches of the ComputeAndSetWaste function.


    brunoerg commented at 1:35 pm on August 4, 2023:
    Fair enough, gonna address it.

    brunoerg commented at 7:48 pm on August 4, 2023:
    Done!
  28. in src/wallet/test/fuzz/coinselection.cpp:180 in d72c092640 outdated
    144+    std::vector<OutputGroup> new_group_pos;
    145+    GroupCoins(fuzzed_data_provider, utxos, coin_params, /*positive_only=*/true, new_group_pos);
    146+    auto new_srd = SelectCoinsSRD(new_group_pos, target, coin_params.m_change_fee, fast_random_context, MAX_STANDARD_TX_WEIGHT);
    147+    if (new_srd && result_srd) {
    148+        new_srd->Merge(result_srd.value());
    149+    }
    


    furszy commented at 2:47 pm on July 24, 2023:

    In d72c0926:

    Should check the Merge outcome. e.g. the result target and weight need to be the sum of the two merged targets and weights. Moreover, if one result uses the effective value and the other one not, the use_effective field must be updated.


    brunoerg commented at 8:46 pm on August 3, 2023:
    Done
  29. furszy commented at 2:57 pm on July 24, 2023: member

    Left few comments. I think that we could go further in few places and check that the called functions are actually doing what they suppose to be doing.

    For instance, all valid results must have a target below the sum of the selected inputs amounts. Also, waste on results with more difference between target and inputs sum should be greater than results with closer diff between target and inputs sum.

  30. brunoerg force-pushed on Aug 3, 2023
  31. brunoerg commented at 8:50 pm on August 3, 2023: contributor

    Force-pushed:

    Addressed:

    Also, I added support for GetSelectedValue(), asserting that its value is always greater or equal than target in a valid result.

  32. brunoerg commented at 7:49 pm on August 4, 2023: contributor
    Force-pushed addressing @furszy’s suggestion to check whether GetChange is always 0 for any BnB result.
  33. in src/wallet/test/fuzz/coinselection.cpp:180 in d402ca1bb6 outdated
    147+        const CAmount old_target{new_srd->GetTarget()};
    148+        const int old_weight{new_srd->GetWeight()};
    149+        new_srd->Merge(result_srd.value());
    150+        assert(new_srd->GetTarget() == old_target + result_srd->GetTarget());
    151+        assert(new_srd->GetWeight() == old_weight + result_srd->GetWeight());
    152+    }
    


    furszy commented at 1:20 am on August 8, 2023:

    Could also check the resulting inputs size and the use_effective_fee field.

    Also, we only use of the Merge function in the sources to combine a MANUAL selection with one of the automatic selections. We never combine two srd solutions.


    brunoerg commented at 10:56 pm on August 9, 2023:
    Just changed it to not use two srd solutions and I created a function to “simulate” a manual selection. Also, added more asserts, including checking the input set.
  34. in src/wallet/test/fuzz/coinselection.cpp:131 in da3b3b3dee outdated
    126+        utxos_set.insert(std::make_shared<COutput>(utxo));
    127+    }
    128+
    129+    if (result_srd) result_srd->AddInputs(utxos_set, subtract_fee_outputs);
    130+    if (result_knapsack) result_knapsack->AddInputs(utxos_set, subtract_fee_outputs);
    131+    if (result_bnb) result_bnb->AddInputs(utxos_set, subtract_fee_outputs);
    


    furszy commented at 6:26 pm on August 8, 2023:
    what about asserting that the inputs are actually added and checking the total weight?

    brunoerg commented at 10:57 pm on August 9, 2023:
    Sounds good. Just did it.
  35. in src/wallet/test/fuzz/coinselection.cpp:80 in 1e0f5f088f outdated
    76@@ -77,7 +77,7 @@ FUZZ_TARGET(coinselection)
    77     const CFeeRate effective_fee_rate{ConsumeMoney(fuzzed_data_provider, /*max=*/COIN)};
    78     const CAmount cost_of_change{ConsumeMoney(fuzzed_data_provider, /*max=*/COIN)};
    79     const CAmount min_viable_change{ConsumeMoney(fuzzed_data_provider, /*max=*/COIN)};
    80-    const CAmount target{fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(1, MAX_MONEY)};
    81+    const CAmount target{fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(50000, MAX_MONEY)};
    


    furszy commented at 6:28 pm on August 8, 2023:
    what was the reason behind this change?

    brunoerg commented at 10:58 pm on August 9, 2023:
    just changed to 1000. but the reason is to make it more realistic. e.g. do we really use it for 1 sat?
  36. furszy commented at 6:29 pm on August 8, 2023: member
    Code review ACK 1e0f5f088.
  37. brunoerg force-pushed on Aug 9, 2023
  38. brunoerg commented at 11:02 pm on August 9, 2023: contributor

    Force-pushed:

    • Improved Merge: now it checks the input set and it tries to merge every solution with a “manual” one.
    • Improved AddInputs to check the total weight.
  39. in src/wallet/test/fuzz/coinselection.cpp:163 in b3dba38d0b outdated
    154@@ -144,6 +155,21 @@ FUZZ_TARGET(coinselection)
    155             assert(result->GetWeight() > weight);
    156         }
    157     }
    158+
    159+    std::vector<COutput> manual_utxos;
    160+    CAmount manual_balance{CreateCoins(fuzzed_data_provider, manual_utxos, coin_params)};
    161+    if (manual_balance <= 0) return;
    162+    for (auto& result : results) {
    163+        if (!result) break;
    


    furszy commented at 0:13 am on August 10, 2023:
    continue?

    brunoerg commented at 2:22 am on August 10, 2023:
    yea, done!
  40. in src/wallet/test/fuzz/coinselection.cpp:131 in 60d92200f9 outdated
    126+        std::set<std::shared_ptr<COutput>> utxos_set;
    127+        for (const auto& utxo : utxos) {
    128+            utxos_set.insert(std::make_shared<COutput>(utxo));
    129+        }
    130+        for (auto& result : results) {
    131+            if (!result) break;
    


    furszy commented at 0:16 am on August 10, 2023:
    continue?
  41. in src/wallet/test/fuzz/coinselection.cpp:163 in 60d92200f9 outdated
    129+        }
    130+        for (auto& result : results) {
    131+            if (!result) break;
    132+            const auto weight{result->GetWeight()};
    133+            result->AddInputs(utxos_set, subtract_fee_outputs);
    134+            assert(result->GetWeight() > weight);
    


    furszy commented at 0:16 am on August 10, 2023:
    nit: could verify the exact weight by summing up all the utxos weight.
  42. in src/wallet/test/fuzz/coinselection.cpp:161 in b3dba38d0b outdated
    154@@ -144,6 +155,21 @@ FUZZ_TARGET(coinselection)
    155             assert(result->GetWeight() > weight);
    156         }
    157     }
    158+
    159+    std::vector<COutput> manual_utxos;
    160+    CAmount manual_balance{CreateCoins(fuzzed_data_provider, manual_utxos, coin_params)};
    161+    if (manual_balance <= 0) return;
    


    furszy commented at 0:18 am on August 10, 2023:
    can’t be negative. It’s a ranged amount.

    brunoerg commented at 2:22 am on August 10, 2023:
    Done
  43. in src/wallet/test/fuzz/coinselection.cpp:164 in b3dba38d0b outdated
    159+    std::vector<COutput> manual_utxos;
    160+    CAmount manual_balance{CreateCoins(fuzzed_data_provider, manual_utxos, coin_params)};
    161+    if (manual_balance <= 0) return;
    162+    for (auto& result : results) {
    163+        if (!result) break;
    164+        auto manual_selection{ManualSelection(manual_utxos, manual_balance, coin_params.m_subtract_fee_outputs)};
    


    furszy commented at 0:19 am on August 10, 2023:
    The manual selection is always the same. Could place it outside of the loop.

    brunoerg commented at 2:22 am on August 10, 2023:
    Done
  44. in src/wallet/test/fuzz/coinselection.cpp:169 in b3dba38d0b outdated
    164+        auto manual_selection{ManualSelection(manual_utxos, manual_balance, coin_params.m_subtract_fee_outputs)};
    165+        const CAmount old_target{result->GetTarget()};
    166+        const std::set<std::shared_ptr<COutput>> input_set{result->GetInputSet()};
    167+        const int old_weight{result->GetWeight()};
    168+        result->Merge(manual_selection);
    169+        assert(result->GetInputSet().size() > input_set.size());
    


    furszy commented at 0:21 am on August 10, 2023:
    0assert(result->GetInputSet().size() == input_set.size() + manual_utxos.size());
    

    brunoerg commented at 2:22 am on August 10, 2023:
    Done
  45. brunoerg commented at 2:22 am on August 10, 2023: contributor
  46. brunoerg force-pushed on Aug 10, 2023
  47. furszy commented at 4:22 pm on August 10, 2023: member
    utACK 1942ea0
  48. DrahtBot requested review from murchandamus on Aug 10, 2023
  49. in src/wallet/test/fuzz/coinselection.cpp:126 in 2eea7336e5 outdated
    117@@ -118,6 +118,22 @@ FUZZ_TARGET(coinselection)
    118     if (total_balance >= target && subtract_fee_outputs && !HasErrorMsg(result_knapsack)) {
    119         assert(result_knapsack);
    120     }
    121+
    122+    std::vector<COutput> utxos;
    123+    std::vector<util::Result<SelectionResult>> results{result_srd, result_knapsack, result_bnb};
    124+    CAmount new_total_balance{CreateCoins(fuzzed_data_provider, utxos, coin_params)};
    125+    if (new_total_balance > 0) {
    126+        std::set<std::shared_ptr<COutput>> utxos_set;
    


    murchandamus commented at 8:53 pm on August 14, 2023:
    Nit: The “UTXO set” refers to the global state of the Bitcoin blockchain. A wallet’s available coins are its UTXO pool.

    brunoerg commented at 1:35 am on August 16, 2023:
    Done!
  50. in src/wallet/test/fuzz/coinselection.cpp:75 in 88f270cace outdated
    70+    SelectionResult result(total_amount, SelectionAlgorithm::MANUAL);
    71+    std::set<std::shared_ptr<COutput>> utxos_set;
    72+    for (const auto& utxo : utxos) {
    73+        utxos_set.insert(std::make_shared<COutput>(utxo));
    74+    }
    75+    result.AddInputs(utxos_set, subtract_fee_outputs);
    


    murchandamus commented at 8:56 pm on August 14, 2023:

    Nit:

    0    std::set<std::shared_ptr<COutput>> utxo_pool;
    1    for (const auto& utxo : utxos) {
    2        utxo_pool.insert(std::make_shared<COutput>(utxo));
    3    }
    4    result.AddInputs(utxo_pool, subtract_fee_outputs);
    

    brunoerg commented at 1:35 am on August 16, 2023:
    Done!
  51. in src/wallet/test/fuzz/coinselection.cpp:159 in 88f270cace outdated
    154@@ -144,6 +155,21 @@ FUZZ_TARGET(coinselection)
    155             assert(result->GetWeight() > weight);
    156         }
    157     }
    158+
    159+    std::vector<COutput> manual_utxos;
    


    murchandamus commented at 8:57 pm on August 14, 2023:
    Nit: Maybe call this manual_inputs.

    brunoerg commented at 1:36 am on August 16, 2023:
    Done!
  52. in src/wallet/test/fuzz/coinselection.cpp:59 in 2a00c2c5d4 outdated
    87@@ -88,7 +88,7 @@ FUZZ_TARGET(coinselection)
    88     const CFeeRate effective_fee_rate{ConsumeMoney(fuzzed_data_provider, /*max=*/COIN)};
    89     const CAmount cost_of_change{ConsumeMoney(fuzzed_data_provider, /*max=*/COIN)};
    90     const CAmount min_viable_change{ConsumeMoney(fuzzed_data_provider, /*max=*/COIN)};
    91-    const CAmount target{fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(1, MAX_MONEY)};
    


    murchandamus commented at 9:00 pm on August 14, 2023:

    While we don’t broadcast transactions with outputs of that amount (except OP_RETURN maybe), if it doesn’t break anywhere, a target of 1 ṩ should maybe be legal for coin selection?

    IIRC, Bitcoin Core permits 294 ṩ for P2WPKH outputs, so it should at least not be bigger than that.


    brunoerg commented at 9:18 pm on August 14, 2023:
    I checked that 1ṩ may break assert(result_bnb->GetChange(coin_params.m_cost_of_change, CAmount{0}) == 0);

    murchandamus commented at 9:47 pm on August 14, 2023:

    Ah, my bad, I thought this target referred to the sum of recipient outputs, but it’s actually recipient_sum + not_input_fees.

    Is it possible that this then would need to be at least as large as the cost for the header + change output? 1000 ṩ might not be enough for every feerate then.


    I got a crash after 130k attempts when I tried to lower the target to 1 ṩ. Gonna fuzz with the 1000 ṩ tonight, will revisit tomorrow.


    brunoerg commented at 0:25 am on August 15, 2023:
    Nice, will leave it running tonight with some values (1000, 5000, etc) to check it as well.
  53. murchandamus approved
  54. murchandamus commented at 9:03 pm on August 14, 2023: contributor
    Please lower the minimum target, otherwise 1942ea0e44c8b4143c9d97f23ff31dc9a6f64a56 looks ready to me. Nits optional of course. :)
  55. DrahtBot requested review from murchandamus on Aug 14, 2023
  56. murchandamus commented at 5:15 pm on August 15, 2023: contributor

    The fuzz test crashed for me after 7.4M attempts:

    fuzz: ../../src/wallet/test/fuzz/coinselection.cpp:120: void wallet::coinselection_fuzz_target(FuzzBufferType): Assertion `result_bnb->GetChange(coin_params.m_cost_of_change, CAmount{0}) == 0' failed.`
    

    You should be able to replicate the issue with:

    0$ echo "MP3//////wT/LzMBEABL////////Wv///yWyEABLAADoQP//PP//CAAAPQAAAAAAAAAAPQEAAAAA AAAA/V12+w==" | base64 -d > crash.input 
    1$ FUZZ=coinselection src/test/fuzz/fuzz crash.input
    
  57. brunoerg commented at 5:54 pm on August 15, 2023: contributor
    @murchandamus I got a crash with 5000 as well, perhaps we should increase the value even more.
  58. murchandamus commented at 6:39 pm on August 15, 2023: contributor

    I don’t think that an absolute value will work. My guess is that the crash may be caused by m_cost_of_change being generated randomly independent from m_change_fee. Usually, cost_of_change cannot ever be smaller than m_change_fee, so if it happens to be smaller here, it may interfere with the change calculation?

    I attempted to calculate cost_of_change instead from m_change_fee + input_size×LTFRE, leaving the fuzz input consumption unchanged. It made the test pass:

    0@@ -97,9 +97,9 @@ FUZZ_TARGET(coinselection)
    1     coin_params.m_long_term_feerate = long_term_fee_rate;
    2     coin_params.m_effective_feerate = effective_fee_rate;
    3     coin_params.min_viable_change = min_viable_change;
    4-    coin_params.m_cost_of_change = cost_of_change;
    5     coin_params.change_output_size = fuzzed_data_provider.ConsumeIntegralInRange<int>(10, 1000);
    6     coin_params.m_change_fee = effective_fee_rate.GetFee(coin_params.change_output_size);
    7+    coin_params.m_cost_of_change = coin_params.m_change_fee +  long_term_fee_rate.GetFee(58);
    

    I’m about 2.1M runs in, and no issues so far. :smile:

  59. brunoerg commented at 9:00 pm on August 15, 2023: contributor

    I don’t think that an absolute value will work. My guess is that the crash may be caused by m_cost_of_change being generated randomly independent from m_change_fee. Usually, cost_of_change cannot ever be smaller than m_change_fee, so if it happens to be smaller here, it may interfere with the change calculation?

    Bingo, I think so!


    Also, in long_term_fee_rate.GetFee(58) why 58?

  60. murchandamus commented at 9:32 pm on August 15, 2023: contributor

    That was loosely inspired by the vsize of a P2TR input (rounded up).

    I’m just seeing that we are calculating the cost_of_change actually with the discard_feerate rather than the LTFRE, though:

    src/wallet/spend.cpp: coin_selection_params.m_cost_of_change = coin_selection_params.m_discard_feerate.GetFee(coin_selection_params.change_spend_size) + coin_selection_params.m_change_fee;

    You could then also make the change_spend_size or change_input_size a random variable in the range 41…1000. Inputs cannot ever be smaller than 41 vB (32+4+4+1).

  61. brunoerg force-pushed on Aug 16, 2023
  62. brunoerg commented at 1:38 am on August 16, 2023: contributor
    Force pushed addressing #27585 (review), #27585 (review), #27585 (review) and changed the cost_of_change according to spend.cpp, it avoids any issue with low targets.
  63. DrahtBot added the label CI failed on Aug 16, 2023
  64. brunoerg force-pushed on Aug 16, 2023
  65. murchandamus commented at 3:39 pm on August 16, 2023: contributor

    Code changes look good to me, crACK d501e1a2283d82b833260bf3372512d616a62a4c

    Started some fuzzing, will let you know if it finds anything.

  66. DrahtBot requested review from furszy on Aug 16, 2023
  67. DrahtBot removed the label CI failed on Aug 16, 2023
  68. murchandamus commented at 6:13 pm on August 16, 2023: contributor

    My fuzzer has performed over 10M runs without uncovering an issue.

    ACK https://github.com/bitcoin/bitcoin/commit/d501e1a2283d82b833260bf3372512d616a62a4c

  69. in src/wallet/test/fuzz/coinselection.cpp:52 in d501e1a228 outdated
    44@@ -45,6 +45,37 @@ static void GroupCoins(FuzzedDataProvider& fuzzed_data_provider, const std::vect
    45     if (valid_outputgroup) output_groups.push_back(output_group);
    46 }
    47 
    48+static CAmount CreateCoins(FuzzedDataProvider& fuzzed_data_provider, std::vector<COutput>& utxo_pool, CoinSelectionParams& coin_params)
    49+{
    50+    // Create some coins
    51+    CAmount total_balance{0};
    52+    int next_locktime{0};
    


    furszy commented at 1:41 pm on August 22, 2023:

    Need to make next_locktime a global variable (or receive it as ref here).

    This variable prevents CreateCoins from generating duplicated transaction hashes (look at AddCoin()). If the variable is reset on every CreateCoins call, as is now, could be the case where the same coins are created twice. Which will cause a runtime_error in AddInputs() and Merge() calls (see SelectionResult::InsertInputs()).


    brunoerg commented at 5:59 pm on August 22, 2023:
    Done! Thanks!
  70. DrahtBot requested review from furszy on Aug 22, 2023
  71. fuzz: coinselection, add `CreateCoins`
    Move coins creation for a specific function. It
    allows us to use it in other parts of the code.
    2a031cb2c2
  72. fuzz: coinselection, add coverage for `EligibleForSpending` 90c4e6a241
  73. brunoerg force-pushed on Aug 22, 2023
  74. brunoerg commented at 5:59 pm on August 22, 2023: contributor
    Force-pushed fixing next_locktime in CreateCoins. Thanks, @furszy
  75. in src/wallet/test/fuzz/coinselection.cpp:158 in 562a577d0f outdated
    122+    CAmount new_total_balance{CreateCoins(fuzzed_data_provider, utxos, coin_params, next_locktime)};
    123+    if (new_total_balance > 0) {
    124+        std::set<std::shared_ptr<COutput>> utxo_pool;
    125+        for (const auto& utxo : utxos) {
    126+            utxo_pool.insert(std::make_shared<COutput>(utxo));
    127+        }
    


    furszy commented at 3:52 pm on August 23, 2023:

    In 562a577d0:

    nit: this variable shadows another utxo_pool declared above.

    Also, if you want to go further, probably could get rid of this extra loops by making CreateCoins create a set of shared pointers.


    brunoerg commented at 8:24 pm on August 23, 2023:
    Done
  76. in src/wallet/test/fuzz/coinselection.cpp:88 in 5f0237f2b6 outdated
    83@@ -84,7 +84,8 @@ FUZZ_TARGET(coinselection)
    84 
    85     const CFeeRate long_term_fee_rate{ConsumeMoney(fuzzed_data_provider, /*max=*/COIN)};
    86     const CFeeRate effective_fee_rate{ConsumeMoney(fuzzed_data_provider, /*max=*/COIN)};
    87-    const CAmount cost_of_change{ConsumeMoney(fuzzed_data_provider, /*max=*/COIN)};
    88+    // Discard feerate must be at least dust relay feerate
    89+    const CFeeRate discard_fee_rate{fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(3000, COIN)};
    


    furszy commented at 3:58 pm on August 23, 2023:

    In 5f0237f2:

    nit: could you take the dust relay feerate from somewhere instead of hardcoding it?


    brunoerg commented at 8:24 pm on August 23, 2023:
    Done
  77. furszy commented at 4:01 pm on August 23, 2023: member

    Code review ACK 5f0237f2

    As an extra small nit, I would merge the GetInputSet and GetShuffledInputSet commits into one (ec82d1c3a523a9b28adf66f840e239f4adead37a, 4531ab1aee85a6801e0de00816b3764b8462771c) as they are basically doing the same. But, not a big deal if you don’t do it anyway.

  78. DrahtBot requested review from murchandamus on Aug 23, 2023
  79. fuzz: coinselection, add coverage for `AddInputs` 808618b8a2
  80. fuzz: coinselection, add coverage for `GetShuffledInputVector`/`GetInputSet` f0244a8614
  81. fuzz: coinselection, add coverage for `Merge` 1e351e5db1
  82. fuzz: coinselection, improve `ComputeAndSetWaste`
    Instead of using `cost_of_change` for `min_viable_change`
    and `change_cost`, and 0 for `change_fee`, use values from
    `coin_params`. The previous values don't generate any effects
    that is relevant for that context.
    0df0438c60
  83. fuzz: coinselection, compare `GetSelectedValue` with target
    The valid results should have a target below the sum of
    the selected inputs amounts. Also, it increases the
    minimum value for target to make it more realistic.
    b2eb558407
  84. fuzz: coinselection, BnB should never produce change 6d9b26d56a
  85. fuzz: coinselection, fix `m_cost_of_change`
    `m_cost_of_change` must not be generated randomly
    independent from m_change_fee. This commit changes
    it to set it up according to `wallet/spend`.
    bf26f978ff
  86. brunoerg force-pushed on Aug 23, 2023
  87. brunoerg commented at 8:25 pm on August 23, 2023: contributor
    Force-pushed: changed to use DUST_RELAY_TX_FEE instead of hardcoding the value and squashed GetInputSet and GetShuffledInputSet commits.
  88. furszy commented at 8:36 pm on August 23, 2023: member
    re-ACK bf26f97
  89. murchandamus commented at 2:29 pm on August 24, 2023: contributor
    reACK with some minimal fuzzing bf26f978ffbe7e2fc681825de631600e24e5c93e
  90. DrahtBot removed review request from murchandamus on Aug 24, 2023
  91. achow101 commented at 8:04 pm on August 24, 2023: member
    ACK bf26f978ffbe7e2fc681825de631600e24e5c93e
  92. achow101 merged this on Aug 24, 2023
  93. achow101 closed this on Aug 24, 2023

  94. maflcko commented at 5:59 am on August 25, 2023: member

    This crashes:

    0fuzz: wallet/test/fuzz/coinselection.cpp:121: void wallet::coinselection_fuzz_target(FuzzBufferType): Assertion `result_bnb->GetChange(coin_params.m_cost_of_change, CAmount{0}) == 0' failed.
    
    0ACwnYWEAAAABAAABAAAgAAAAAI8AAAABAAABIAAAAACPAQAAAQAAAQB6AAAAAAAAAAAAAQAAAQAA
    1jwAAAAEAAAE=
    
  95. fanquake commented at 9:08 am on August 27, 2023: member

    This crashes:

    Yea. OSS-Fuzz has this this now as well https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=61790:

    0Crash Type: ASSERT
    1Crash Address: 
    2Crash State:
    3  result_bnb->GetChange(coin_params.m_cost_of_change, CAmount{0}) == 0
    4  wallet::coinselection_fuzz_target
    5  std::__1::__function::__func<void
    
  96. maflcko added the label Bug on Aug 27, 2023
  97. brunoerg commented at 10:09 am on August 27, 2023: contributor
    Investigating.
  98. murchandamus commented at 8:14 pm on August 30, 2023: contributor
    Follow-up in #28372
  99. brunoerg commented at 11:40 pm on September 2, 2023: contributor
    Fuzz found an issue! Fix #28395
  100. Frank-GER referenced this in commit 4a0e6b7fee on Sep 8, 2023
  101. maflcko referenced this in commit bca66b8161 on Sep 14, 2023
  102. bitcoin locked this on Sep 1, 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: 2024-12-03 15:12 UTC

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