Add fuzzing harness for node eviction logic.
See doc/fuzzing.md
for information on how to fuzz Bitcoin Core. Don’t forget to contribute any coverage increasing inputs you find to the Bitcoin Core fuzzing corpus repo.
Happy fuzzing :)
Add fuzzing harness for node eviction logic.
See doc/fuzzing.md
for information on how to fuzz Bitcoin Core. Don’t forget to contribute any coverage increasing inputs you find to the Bitcoin Core fuzzing corpus repo.
Happy fuzzing :)
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.
Reviewers, this pull request conflicts with the following ones:
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.
ACK c57cabdea44cab2a9dd2f0443f7a6b28cb41e7b4 code review, ran the fuzzer up to 10M execs, ran bitcoind grepping the net logging and observing the peer conns. Consider maybe running clang-format on the changes.
0[#10750687](/bitcoin-bitcoin/10750687/) REDUCE cov: 1502 ft: 7161 corp: 469/821Kb lim: 4096 exec/s: 527 rss: 631Mb L: 2188/4091 MS: 1 EraseBytes-
1[#10761148](/bitcoin-bitcoin/10761148/) REDUCE cov: 1502 ft: 7161 corp: 469/821Kb lim: 4096 exec/s: 527 rss: 642Mb L: 1430/4091 MS: 1 EraseBytes-
2[#10761655](/bitcoin-bitcoin/10761655/) REDUCE cov: 1502 ft: 7161 corp: 469/821Kb lim: 4096 exec/s: 527 rss: 642Mb L: 3142/4091 MS: 2 PersAutoDict-EraseBytes- DE: "_\xb0\x00\x8d\x8d\x8d\x8d\x8d\x8d\x8d\x8d\x8d"-
3[#10770237](/bitcoin-bitcoin/10770237/) REDUCE cov: 1502 ft: 7161 corp: 469/821Kb lim: 4096 exec/s: 527 rss: 642Mb L: 2961/4091 MS: 2 ChangeBinInt-EraseBytes-
4[#10778939](/bitcoin-bitcoin/10778939/) REDUCE cov: 1502 ft: 7161 corp: 469/821Kb lim: 4096 exec/s: 527 rss: 643Mb L: 3266/4091 MS: 2 ChangeByte-EraseBytes-
(am running the node with this logging)
0+++ b/src/net.cpp
1@@ -972,6 +972,7 @@ bool CConnman::AttemptToEvictConnection()
2
3 const Optional<NodeEvictionCandidate> node_to_evict = SelectNodeToEvict(vEvictionCandidates);
4 if (!node_to_evict) {
5+ LogPrintf("ATEC: no node to evict returned by SelectNodeToEvict()\n");
6 return false;
7 }
8 const NodeId evicted = node_to_evict->id;
9@@ -979,9 +980,12 @@ bool CConnman::AttemptToEvictConnection()
10 for (CNode* pnode : vNodes) {
11 if (pnode->GetId() == evicted) {
12 pnode->fDisconnect = true;
13+ LogPrintf("ATEC: node to evict FOUND in vEvictioncandidates and disconnected: %s\n", evicted);
14 return true;
15 }
16 }
17+ LogPrintf("ATEC: node to evict selected but not found in vEvictioncandidates: %s\n", evicted);
18+
19 return false;
20 }
911- node->m_prefer_evict, node->addr.IsLocal()};
912- vEvictionCandidates.push_back(candidate);
913- }
914- }
915-
916+NODISCARD Optional<NodeEvictionCandidate> SelectNodeToEvict(std::vector<NodeEvictionCandidate> vEvictionCandidates) {
This will cause an unnecessary copy of the vEvictionCandidates
vector when invoking.
As the caller doesn’t need its version anymore after the call, it’s possible to change this parameter to std::vector<NodeEvictionCandidate>&&
, and call it with SelectNodeToEvict(std::move(vEvictionCandidates))
.
Feedback addressed.
Also reduced the scope of std::vector<NodeEvictionCandidate> vEvictionCandidates
to make reasoning about the life-time easier.
13+#include <cassert>
14+#include <cstdint>
15+#include <optional>
16+#include <vector>
17+
18+NODISCARD Optional<NodeEvictionCandidate> SelectNodeToEvict(std::vector<NodeEvictionCandidate> vEvictionCandidates);
net.h
, I’d prefer seeing this declaration moved there, rather than forward declaring it (as it risks going out of sync with the implementation here).
32+ if (node_to_evict) {
33+ assert(std::any_of(eviction_candidates.begin(), eviction_candidates.end(), [&node_to_evict](const NodeEvictionCandidate& eviction_candidate) { return node_to_evict->id == eviction_candidate.id; }));
34+ }
35+ // eviction_candidates.empty() implies !node_to_evict, but !node_to_evict
36+ // does not imply eviction_candidates.empty()).
37+ if (eviction_candidates.empty()) {
node_to_evict
isn’t, the std::any_of
call above must fail.
Rebased on top of master
now that the prerequisite #20477 has been merged.
This PR is now touches only the fuzz tests and should hopefully be easy to review :)
18+FUZZ_TARGET(node_eviction)
19+{
20+ FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
21+ std::vector<NodeEvictionCandidate> eviction_candidates;
22+ while (fuzzed_data_provider.ConsumeBool()) {
23+ eviction_candidates.push_back({fuzzed_data_provider.ConsumeIntegral<NodeId>(), fuzzed_data_provider.ConsumeIntegral<int64_t>(), fuzzed_data_provider.ConsumeIntegral<int64_t>(), fuzzed_data_provider.ConsumeIntegral<int64_t>(), fuzzed_data_provider.ConsumeIntegral<int64_t>(), fuzzed_data_provider.ConsumeBool(), fuzzed_data_provider.ConsumeBool(), fuzzed_data_provider.ConsumeBool(), fuzzed_data_provider.ConsumeIntegral<uint64_t>(), fuzzed_data_provider.ConsumeBool(), fuzzed_data_provider.ConsumeBool()});
Aren’t the code paths already exercised through the unit test?
There are differences: the unit test clamp the eviction candidate parameters (like static_cast<int64_t>(random_context.randrange(100))
) in order to trigger duplicate values, whereas this fuzzing harness can span the entire input space (like fuzzed_data_provider.ConsumeIntegral<int64_t>()
). The latter is good for catching integer overflows and similar stuff resulting from “extreme” inputs.
Also the unit testing performs a couple of thousand test eviction rounds whereas the fuzzing test runs indefinitely :)
I think it makes sense to both unit test and fuzz test SelectNodeToEvict
since that function is of extreme importance :)
29+ fuzzed_data_provider.ConsumeBool(),
30+ fuzzed_data_provider.ConsumeBool(),
31+ fuzzed_data_provider.ConsumeBool(),
32+ fuzzed_data_provider.ConsumeIntegral<uint64_t>(),
33+ fuzzed_data_provider.ConsumeBool(),
34+ fuzzed_data_provider.ConsumeBool(),
practicalswift
jonatack
DrahtBot
sipa
MarcoFalke
Labels
P2P