The ccoincontrol fuzzer misses tests for a number of CCoinControl operations. Add them.
While at it, improve the oracle for the existing tests.
CCoinControl methods
#34026
The ccoincontrol fuzzer misses tests for a number of CCoinControl operations. Add them.
While at it, improve the oracle for the existing tests.
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.
For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/34026.
See the guideline for information on the review process.
If your review is incorrectly listed, please copy-paste <!–meta-tag:bot-skip–> into the comment that the bot should ignore.
76@@ -77,6 +77,12 @@ FUZZ_TARGET(coincontrol, .init = initialize_coincontrol)
77 },
78 [&] {
79 (void)coin_control.GetInputWeight(out_point);
80+ },
81+ [&] {
82+ (void)coin_control.GetSequence(out_point);
83+ },
84+ [&] {
85+ (void)coin_control.GetScripts(out_point);
These were indeed missing from https://maflcko.github.io/b-c-cov/fuzz.coverage/src/wallet/coincontrol.cpp.gcov.html - there are quite a few more, consider adding those as well
Do you think it would be better to add the other ones in a separate PR or is it better to add them all in this one?
code review ACK 31904f1ba1de49890f605f1d7c907790780831ec
It’s fine as is, but I think we could improve this harness a little bit by adding some assertions. Some quite simple examples:
0@@ -87,9 +87,7 @@ FUZZ_TARGET(coincontrol, .init = initialize_coincontrol)
1 [&] {
2 uint32_t sequence{fuzzed_data_provider.ConsumeIntegral<uint32_t>()};
3 (void)coin_control.Select(out_point).SetSequence(sequence);
4- },
5- [&] {
6- (void)coin_control.Select(out_point).GetSequence();
7+ assert(coin_control.Select(out_point).GetSequence() == sequence);
8 },
0@@ -108,10 +106,8 @@ FUZZ_TARGET(coincontrol, .init = initialize_coincontrol)
1 [&] {
2 unsigned int pos{fuzzed_data_provider.ConsumeIntegral<unsigned int>()};
3 (void)coin_control.Select(out_point).SetPosition(pos);
4- },
5- [&] {
6- (void)coin_control.Select(out_point).GetPosition();
7- });
8+ assert(coin_control.Select(out_point).GetPosition() == pos);
9+ });
106+ const auto& scripts = coin_control.Select(out_point).GetScripts();
107+ assert(scripts.first && *scripts.first == script);
108+ },
109+ [&] {
110+ const CScriptWitness script_wit{ConsumeScriptWitness(fuzzed_data_provider)};
111+ (void)coin_control.Select(out_point).SetScriptWitness(script_wit);
SetScriptWitness is already void:
0 coin_control.Select(out_point).SetScriptWitness(script_wit);
77+ assert(!coin_control.HasSelected());
78 },
79 [&] {
80- (void)coin_control.ListSelected();
81+ const std::vector<COutPoint> selected = coin_control.ListSelected();
82+ assert(selected.empty() == !coin_control.HasSelected());
78 },
79 [&] {
80- (void)coin_control.ListSelected();
81+ const std::vector<COutPoint> selected = coin_control.ListSelected();
82+ assert(selected.empty() == !coin_control.HasSelected());
83+ for (const auto& out : selected)
84+ assert(coin_control.IsSelected(out));
85 },
86 [&] {
87 int64_t weight{fuzzed_data_provider.ConsumeIntegral<int64_t>()};
88 (void)coin_control.SetInputWeight(out_point, weight);
89+ assert(coin_control.GetInputWeight(out_point) == weight);
GetInputWeight return nullptr? can we exercise that path as well?
GetInputWeight() returns a std::optional<int64_t>, so I don’t think it can return a nullptr. It does return a std::nullopt when the coin is not selected, but I think adding a check for that would be too close to duplicating the implementation.
nullopt of course. We should at least exercise that code path.
102+ [&] {
103+ const CScript script{ConsumeScript(fuzzed_data_provider)};
104+ (void)coin_control.Select(out_point).SetScriptSig(script);
105+ assert(coin_control.Select(out_point).HasScripts());
106+ const auto& scripts = coin_control.Select(out_point).GetScripts();
107+ assert(scripts.first && *scripts.first == script);
Given the std::optional’s operator== overload, wouldn’t this suffice
0 assert(scripts.first == script);
?
100+ assert(coin_control.Select(out_point).GetSequence() == sequence);
101+ },
102+ [&] {
103+ const CScript script{ConsumeScript(fuzzed_data_provider)};
104+ (void)coin_control.Select(out_point).SetScriptSig(script);
105+ assert(coin_control.Select(out_point).HasScripts());
Select mutates m_selection_pos - we could do selection separately
HasSelectedOrder() and GetSelectionPos() from header are still untested
42@@ -43,40 +43,70 @@ FUZZ_TARGET(coincontrol, .init = initialize_coincontrol)
43 }
44 out_point = *optional_out_point;
45 },
46- [&] {
47- (void)coin_control.HasSelected();
115 },
116 [&] {
117- (void)coin_control.GetInputWeight(out_point);
118+ auto& input = coin_control.Select(out_point);
119+ unsigned int pos{fuzzed_data_provider.ConsumeIntegral<unsigned int>()};
120+ input.SetPosition(pos);
coin_control.Select?
Yes, that is correct and intentional.
Select() assigns a default sequential position, but the goal of this test is to exercise the SetPosition() setter directly. We want to verify that the PreselectedInput object handles arbitrary position values correctly, even if they differ from the default sequence assigned by Select().
The improved harness from this PR increased the mutation score of the coin_control target compared to the master branch. See:
The only uncaught mutant is:
0diff --git a/src/wallet/coincontrol.cpp b/src/wallet/coincontrol.cpp
1index 873c5ab383..e83f8d451a 100644
2--- a/src/wallet/coincontrol.cpp
3+++ b/src/wallet/coincontrol.cpp
4@@ -25,7 +25,7 @@ bool CCoinControl::IsSelected(const COutPoint& outpoint) const
5 bool CCoinControl::IsExternalSelected(const COutPoint& outpoint) const
6 {
7 const auto it = m_selected.find(outpoint);
8- return it != m_selected.end() && it->second.HasTxOut();
9+ return it != m_selected.end() || it->second.HasTxOut();
10 }
which can be killed with the following diff - it checks that when there is no tx out for a given input, it’s not external selected:
0 auto& input = coin_control.Select(out_point);
1- input.SetTxOut(tx_out);
2- assert(input.HasTxOut());
3- assert(input.GetTxOut() == tx_out);
4- assert(coin_control.IsExternalSelected(out_point));
5+ const auto set_tx_out{fuzzed_data_provider.ConsumeBool()};
6+ if (set_tx_out) {
7+ input.SetTxOut(tx_out);
8+ }
9+ auto has_tx_out{input.HasTxOut()};
10+ auto is_external_selected{coin_control.IsExternalSelected(out_point)};
11+ if (set_tx_out) {
12+ assert(has_tx_out);
13+ assert(input.GetTxOut() == tx_out);
14+ assert(is_external_selected);
15+ } else if (!has_tx_out) {
16+ assert(!is_external_selected);
17+ }
18 },
The `ccoincontrol` fuzzer misses tests for a number of CCoinControl
operations. Add them.