BlockFilter::Unserialize is only used in the test code. Moving it to the tests allows for the removal of the optional sanity check in the GCSFilter and BlockFilter constructor, resulting in simpler code.
This is a follow up to #24832.
BlockFilter::Unserialize is only used in the test code. Moving it to the tests allows for the removal of the optional sanity check in the GCSFilter and BlockFilter constructor, resulting in simpler code.
This is a follow up to #24832.
ACK
Some nits if you retouch:
0diff --git a/src/test/blockfilter_tests.cpp b/src/test/blockfilter_tests.cpp
1index a17a71bf9f..a20b49e0a7 100644
2--- a/src/test/blockfilter_tests.cpp
3+++ b/src/test/blockfilter_tests.cpp
4@@ -62,7 +62,7 @@ static BlockFilter UnserializeBlockFilter(Stream& s) {
5 >> block_hash
6 >> encoded_filter;
7
8- BlockFilterType filter_type = static_cast<BlockFilterType>(filter_type_uint8);
9+ const BlockFilterType filter_type = static_cast<BlockFilterType>(filter_type_uint8);
10 BlockFilter block_filter(filter_type, block_hash, std::move(encoded_filter));
11 return block_filter;
12 }
13diff --git a/src/test/fuzz/blockfilter.cpp b/src/test/fuzz/blockfilter.cpp
14index db3788d46f..2a1d81d970 100644
15--- a/src/test/fuzz/blockfilter.cpp
16+++ b/src/test/fuzz/blockfilter.cpp
17@@ -22,23 +22,17 @@ FUZZ_TARGET(blockfilter)
18
19 GCSFilter::ElementSet elements;
20 size_t num_elements = fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(1, 100000);
21- for (size_t i=0; i < num_elements; i++) {
22- std::vector<unsigned char> element = ConsumeRandomLengthIntegralVector<unsigned char>(fuzzed_data_provider, 32);
23- elements.insert(element);
24+ for (size_t i=0; i < num_elements; ++i) {
25+ elements.emplace(ConsumeRandomLengthIntegralVector<unsigned char>(fuzzed_data_provider, /*max_vector_size=*/32));
26char>(fuzzed_data_provider, 32));
27 }
28
29 GCSFilter filter({u256->GetUint64(0), u256->GetUint64(1), BASIC_FILTER_P, BASIC_FILTER_M}, elements);
30- BlockFilter block_filter(BlockFilterType::BASIC, u256.value(), filter.GetEncoded());
31- {
32- (void)block_filter.ComputeHeader(ConsumeUInt256(fuzzed_data_provider));
33- (void)block_filter.GetBlockHash();
34- (void)block_filter.GetEncodedFilter();
35- (void)block_filter.GetHash();
36- }
37- {
38- const BlockFilterType block_filter_type = block_filter.GetFilterType();
39- (void)BlockFilterTypeName(block_filter_type);
40- }
41+ const BlockFilter block_filter(BlockFilterType::BASIC, u256.value(), filter.GetEncoded());
42+ (void)block_filter.ComputeHeader(ConsumeUInt256(fuzzed_data_provider));
43+ (void)block_filter.GetBlockHash();
44+ (void)block_filter.GetEncodedFilter();
45+ (void)block_filter.GetHash();
46+ (void)BlockFilterTypeName(block_filter.GetFilterType());
47 {
48 const GCSFilter gcs_filter = block_filter.GetFilter();
49 (void)gcs_filter.GetN();
ACK f5563da6836433a08438893c77bd87028e4b026c
Since it is my first time reviewing a PR concerning filters and streams, I want to detail my understanding of this PR before proceeding with the review. If my account is erroneous, please do correct me. Thank you.
encoded_filter
always contains the correct number of bits.Eg.
0GCSFilter filter({0, 0, BASIC_FILTER_P, BASIC_FILTER_M}, elements);
encoded_filter
is read from a data stream in a function, a preliminary check is done before passing it through the BlockFilter function.Eg.
0filein >> block_hash >> encoded_filter;
1uint256 result;
2CHash256().Write(encoded_filter).Finalize(result);
3if (result != hash) return error("Checksum mismatch in filter decode.");
encoded_filter
from the data stream and passing it through the GCSFilter()
function without any preliminary checks is the Unserialize function.encoded_filter
correctness in the BlockFilter/GCSFilter functions.encoded_filter
is checked by checking if the encoded_filter
remains unchanged after moving block_filter
in and out of the stream.0stream << block_filter;
1stream >> block_filter2;
2block_filter2 = UnserializeBlockFilter(stream);
3...
4BOOST_CHECK(block_filter.GetEncodedFilter() == block_filter2.GetEncodedFilter());
Review:
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.
- Not sure all the scope brackets in the tests are useful.
- Prefere prefix iterators (faster due to avoiding a copy), see our developer notes
- Avoid unneeded temporaries in loop
- s/insert/emplace/ where it makes sense
- named params and constify where helpful/clearer
Pushed new code with the suggested changes stated here.
GCSFilterDecodeSkipCheck()
benchmark as pointed out below
14@@ -15,22 +15,26 @@
15 FUZZ_TARGET(blockfilter)
16 {
17 FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
18- const std::optional<BlockFilter> block_filter = ConsumeDeserializable<BlockFilter>(fuzzed_data_provider);
19- if (!block_filter) {
20+ const std::optional<uint256> u256 = ConsumeDeserializable<uint256>(fuzzed_data_provider);
u256
?
block_hash
126@@ -112,7 +127,7 @@ BOOST_AUTO_TEST_CASE(blockfilter_basic_test)
127
128 CDataStream stream(SER_NETWORK, PROTOCOL_VERSION);
129 stream << block_filter;
130- stream >> block_filter2;
131+ block_filter2 = UnserializeBlockFilter(stream);
nit: I’d just initialize at once:
0 BlockFilter block_filter2 {UnserializeBlockFilter(stream)};
66@@ -67,7 +67,7 @@ static void GCSFilterDecodeSkipCheck(benchmark::Bench& bench)
67 auto encoded = filter.GetEncoded();
68
69 bench.run([&] {
70- GCSFilter filter({0, 0, BASIC_FILTER_P, BASIC_FILTER_M}, encoded, /*skip_decode_check=*/true);
GCSFilterDecodeSkipCheck()
test can be removed? It’s identical to GCSFilterDecode()
Approach ACK 32953718a , with the caveat that I don’t understand the fuzzing code very well so count that as unreviewed from my end (even though I left a small nit)
I think GCSFilterDecodeSkipCheck()
should be removed before ACK. Besides that, left 2 style comments but none blocking.
Summary: #24832 introduced a new file hash-based check to ensure filter integrity. This hash check (in BlockFilterIndex::ReadFilterFromDisk
) can not be used when filters are unserialized from a stream because the hash is unknown, but that unserialization only happened in the test suite. As such, this PR moving unserialization logic to /src/test/
and simplifying the non-test code removing the skip_decode_check
parameter and path makes sense.
14@@ -15,22 +15,26 @@
15 FUZZ_TARGET(blockfilter)
16 {
17 FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
18- const std::optional<BlockFilter> block_filter = ConsumeDeserializable<BlockFilter>(fuzzed_data_provider);
19- if (!block_filter) {
20+ const std::optional<uint256> u256 = ConsumeDeserializable<uint256>(fuzzed_data_provider);
21+ if (!u256) {
22 return;
23 }
I’m not too deep into fuzzing, but IIUC, we just want a random uint256 here and there is not really any benefit by involving a deserialization (that could fail)?
0 const uint256 u256 = ConsumeUInt256(fuzzed_data_provider);
Could also limit this variable’s scope by moving it down before it’s first use (that is, before GCSFilter is constructed).
we just want a random uint256 here and there is not really any benefit by involving a deserialization (that could fail)?
Yeah that’s right. I’ll apply the suggested change.
Could also limit this variable’s scope by moving it down before it’s first use (that is, before GCSFilter is constructed).
I’m also using the it for entropy with the siphash key. But I could get rid of that and just use the default key and initialize it right before the filter construction.
Oops this is also done during the filter construction. I’ll move it down.
Code-review ACK 32953718a3fb8e2f298b99e1a2ee72af1ea5be8c (modulo potential benchmark/fuzz test improvements)
Left a nit regarding the fuzz test below. I also agree that the benchmark GCSFilterDecodeSkipCheck
can be removed as it’s identical to GCSFilterDecode
now, as suggested by stickies-v (good catch!).
Just commenting on this PR since I got a review request above. I am -0 on this change. I don’t think it’s an improvement, but would not object if others want to go ahead with it.
I think this is worse for code organization and debugability. I think serialize and deserialize methods should live next to each other in the code so they can don’t get out of sync and stay available for understanding and debugging. If there was a large amount of code that was only called by tests and not used by the application, I’d agree it should be moved or removed. But this is a small amount and IMO this change just moves it someplace harder to find.
code review ACK 1204e3d4d
Verified that since https://github.com/bitcoin/bitcoin/commit/32953718a3fb8e2f298b99e1a2ee72af1ea5be8c the only changes made are incorporating (all) the suggestions made by myself and @theStack
I think serialize and deserialize methods should live next to each other in the code so they can don’t get out of sync and stay available for understanding and debugging.
I’m not sufficiently familiar with how (de)serialization is organized across the codebase, so I’ll refrain from going beyond a code review ACK, even though otherwise I think this PR is in good shape.
re-ACK 1204e3d4d35a1adf7e2160f29b604bb1ba1ac899
While I agree that to each serialization routine there should also be a deserialization counterpart and it wouldn’t be a good idea to remove it completely, I think just having it in the unit tests is fine. If serialization and deserialization went out of sync, the serialization/deserialization roundtrip test would likely detect this, no?
62+ >> block_hash
63+ >> encoded_filter;
64+
65+ const BlockFilterType filter_type = static_cast<BlockFilterType>(filter_type_uint8);
66+ BlockFilter block_filter(filter_type, block_hash, std::move(encoded_filter));
67+ return block_filter;
Can drop a temporary here if you retouch
0- BlockFilter block_filter(filter_type, block_hash, std::move(encoded_filter));
1- return block_filter;
2+ return BlockFilter{filter_type, block_hash, std::move(encoded_filter)};
122@@ -108,11 +123,9 @@ BOOST_AUTO_TEST_CASE(blockfilter_basic_test)
123 }
124
125 // Test serialization/unserialization.
126- BlockFilter block_filter2;
127-
128 CDataStream stream(SER_NETWORK, PROTOCOL_VERSION);
129 stream << block_filter;
130- stream >> block_filter2;
131+ BlockFilter block_filter2 {UnserializeBlockFilter(stream)};
clang format
0 BlockFilter block_filter2{UnserializeBlockFilter(stream)};
51@@ -52,6 +52,21 @@ BOOST_AUTO_TEST_CASE(gcsfilter_default_constructor)
52 BOOST_CHECK_EQUAL(params.m_M, 1U);
53 }
54
55+template <typename Stream>
56+static BlockFilter UnserializeBlockFilter(Stream& s) {
clang format
0 template <typename Stream>
1-static BlockFilter UnserializeBlockFilter(Stream& s) {
2+static BlockFilter UnserializeBlockFilter(Stream& s)
3+{
4 std::vector<unsigned char> encoded_filter;
5 uint8_t filter_type_uint8;
6 uint256 block_hash;
7
8- s >> filter_type_uint8
9- >> block_hash
10- >> encoded_filter;
11+ s >> filter_type_uint8 >> block_hash >> encoded_filter;
29- const BlockFilterType block_filter_type = block_filter->GetFilterType();
30- (void)BlockFilterTypeName(block_filter_type);
31+
32+ GCSFilter::ElementSet elements;
33+ size_t num_elements = fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(1, 100000);
34+ for (size_t i=0; i < num_elements; ++i) {
clang format
0 for (size_t i = 0; i < num_elements; ++i) {
ACK 1204e3d4d35a1adf7e2160f29b604bb1ba1ac899
Some nit comments (feel free to ignore), most of which would be pre-empted by running
0git diff -U0 HEAD~1.. | ./contrib/devtools/clang-format-diff.py -p1 -i -v
to apply clang formatting to the changes; see contrib/devtools/clang-format-diff.py
.
ACK 1204e3d
Some nit comments (feel free to ignore), most of which would be pre-empted by running
0git diff -U0 HEAD~1.. | ./contrib/devtools/clang-format-diff.py -p1 -i -v
to apply clang formatting to the changes; see
contrib/devtools/clang-format-diff.py
.
Addressed.
Looks like ae7ae36d311a869b3bda41 added a new check to the Clang-tidy CI (unrelated to changes in the last push) that is red now:
0/src/test/fuzz/blockfilter.cpp:34:25: error: the const qualified variable 'gcs_filter'
1is copy-constructed from a const reference; consider making it a const reference
2[performance-unnecessary-copy-initialization,-warnings-as-errors]
3 const GCSFilter gcs_filter = block_filter.GetFilter();
4 ^
5 &
(ACK 9b87854 per git diff 1204e3d 9b87854
otherwise)
BlockFilter::Unserialize is only used in the test code. Moving it to
the tests allows for the removal of the optional sanity check in the
GCSFilter and BlockFilter constructor, resulting in simpler code.
This is a follow up to #24832.
Code Review re-ACK 07fdd10
Verified that since 1204e3d changes are limited to clang formatting and declaring gcs_filter
as a reference variable.
git diff 1204e3d 07fdd10
re-ACK 07fdd10ca1d0bd2bc1d2756f1bc2c069ae97c45a
(verified via git range-diff 1204e3d4...07fdd10c
that changes since my last ACK are trivial refactors)
Unserialize
method. If that’s the case, then I don’t think it really makes sense to merge this PR now as it would mean that already existing/planned future work will just end up reverting this PR.