Skip to content

Commit 41d2a41

Browse files
committed
WIP fuse filter
1 parent f6d4a4d commit 41d2a41

5 files changed

Lines changed: 211 additions & 3 deletions

File tree

src/blockfilter.cpp

Lines changed: 123 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,14 @@
22
// Distributed under the MIT software license, see the accompanying
33
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44

5+
#include <cassert>
6+
#include <cmath>
7+
#include <csignal>
8+
#include <cstdint>
59
#include <mutex>
10+
#include <queue>
611
#include <set>
12+
#include <stack>
713
#include <string_view>
814

915
#include <blockfilter.h>
@@ -21,6 +27,7 @@ using util::Join;
2127

2228
static const std::map<BlockFilterType, std::string> g_filter_types = {
2329
{BlockFilterType::BASIC, "basic"},
30+
{BlockFilterType::FUSE, "fuse"},
2431
};
2532

2633
uint64_t GCSFilter::HashToRange(const Element& element) const
@@ -145,6 +152,115 @@ bool GCSFilter::MatchAny(const ElementSet& elements) const
145152
return MatchInternal(queries.data(), queries.size());
146153
}
147154

155+
uint64_t BinaryFuseFilter::Hash(const BinaryFuseFilter::Element& element) const
156+
{
157+
return CSipHasher(m_siphash_k0, m_siphash_k1)
158+
.Write(element)
159+
.Finalize();
160+
}
161+
162+
uint64_t BinaryFuseFilter::Mix(uint64_t key) const {
163+
return CSipHasher(m_siphash_k0, m_siphash_k1)
164+
.Write(key)
165+
.Finalize();
166+
}
167+
168+
uint16_t BinaryFuseFilter::Fingerprint(uint64_t key) const
169+
{
170+
return static_cast<uint16_t>(key) ^ key >> 48;
171+
}
172+
173+
std::tuple<uint32_t, uint32_t, uint32_t> BinaryFuseFilter::Slots(const uint64_t key) const
174+
{
175+
uint32_t start_seg = key % (m_num_segments - m_arity + 1);
176+
const auto h0_entropy = Mix(key);
177+
const auto h1_entropy = Mix(h0_entropy);
178+
const auto h2_entropy = Mix(h1_entropy);
179+
uint32_t h0 = static_cast<uint32_t>((start_seg + 0) * m_segment_len + FastRange32(h0_entropy, m_segment_len));
180+
uint32_t h1 = static_cast<uint32_t>((start_seg + 1) * m_segment_len + FastRange32(h1_entropy, m_segment_len));
181+
uint32_t h2 = static_cast<uint32_t>((start_seg + 2) * m_segment_len + FastRange32(h2_entropy, m_segment_len));
182+
return {h0, h1, h2};
183+
}
184+
185+
bool BinaryFuseFilter::Query(const Element& element) const
186+
{
187+
const auto key = Hash(element);
188+
const auto [h0, h1, h2] = Slots(key);
189+
const auto f = Fingerprint(key);
190+
return f == (m_fingerprints[h0] ^ m_fingerprints[h1] ^ m_fingerprints[h2]);
191+
}
192+
193+
BinaryFuseFilter::BinaryFuseFilter(const ElementSet& elements, const uint256& hash) {
194+
m_siphash_k0 = hash.GetUint64(0);
195+
m_siphash_k1 = hash.GetUint64(1);
196+
const uint32_t n = static_cast<uint32_t>(elements.size());
197+
const auto exp = static_cast<uint32_t>(std::floor((std::log(static_cast<double>(n)) / std::log(3.33) + 2.25)));
198+
m_segment_len = std::max(uint32_t{2}, uint32_t{1} << exp);
199+
auto target_array_len = static_cast<uint32_t>(std::ceil(n * 1.125));
200+
m_num_segments = std::max(CeilDiv(target_array_len, m_segment_len), m_arity);
201+
auto array_len = m_num_segments * m_segment_len;
202+
m_fingerprints.assign(array_len, 0);
203+
std::vector degrees = std::vector<Degree>(array_len);
204+
degrees.assign(array_len, Degree());
205+
for (const auto& element : elements) {
206+
const auto key = Hash(element);
207+
auto [h0, h1, h2] = Slots(key);
208+
degrees[h0].m_degree++; degrees[h0].m_xor ^= key;
209+
degrees[h1].m_degree++; degrees[h1].m_xor ^= key;
210+
degrees[h2].m_degree++; degrees[h2].m_xor ^= key;
211+
}
212+
std::queue q = std::queue<uint32_t>{};
213+
for (uint32_t i{0}; i < array_len; ++i) {
214+
if (degrees[i].m_degree == 1) {
215+
q.push(i);
216+
}
217+
}
218+
std::stack p = std::stack<Assignment>{};
219+
while (!q.empty()) {
220+
const auto index = q.front();
221+
q.pop();
222+
if (degrees[index].m_degree != 1) continue;
223+
uint64_t hash = degrees[index].m_xor;
224+
p.emplace(index, hash);
225+
const auto [h0, h1, h2] = Slots(hash);
226+
degrees[h0].m_degree--;
227+
degrees[h1].m_degree--;
228+
degrees[h2].m_degree--;
229+
degrees[h0].m_xor ^= hash;
230+
degrees[h1].m_xor ^= hash;
231+
degrees[h2].m_xor ^= hash;
232+
if (degrees[h0].m_degree == 1) q.push(h0);
233+
if (degrees[h1].m_degree == 1) q.push(h1);
234+
if (degrees[h2].m_degree == 1) q.push(h2);
235+
}
236+
// check P is size N
237+
// assert(p.size() == n);
238+
while (!p.empty()) {
239+
const auto assignment = p.top();
240+
p.pop();
241+
const auto hash = assignment.m_hash;
242+
const auto f = Fingerprint(hash);
243+
const auto [h0, h1, h2] = Slots(hash);
244+
const auto i = assignment.m_index;
245+
m_fingerprints[i] = f ^ m_fingerprints[h0] ^ m_fingerprints[h1] ^ m_fingerprints[h2];
246+
}
247+
DataStream writer{m_encoded};
248+
this->Serialize(writer);
249+
}
250+
251+
bool BinaryFuseFilter::MatchAny(const ElementSet& elements) const {
252+
for (const auto& element : elements) {
253+
if (Match(element)) {
254+
return true;
255+
}
256+
}
257+
return false;
258+
}
259+
260+
bool BinaryFuseFilter::Match(const Element& element) const {
261+
return Query(element);
262+
}
263+
148264
const std::string& BlockFilterTypeName(BlockFilterType filter_type)
149265
{
150266
static std::string unknown_retval;
@@ -226,7 +342,12 @@ BlockFilter::BlockFilter(BlockFilterType filter_type, const CBlock& block, const
226342
if (!BuildParams(params)) {
227343
throw std::invalid_argument("unknown filter_type");
228344
}
229-
m_filter = std::make_unique<GCSFilter>(params, BasicFilterElements(block, block_undo));
345+
if (filter_type == BlockFilterType::BASIC) {
346+
m_filter = std::make_unique<GCSFilter>(params, BasicFilterElements(block, block_undo));
347+
}
348+
if (filter_type == BlockFilterType::FUSE) {
349+
m_filter = std::make_unique<BinaryFuseFilter>(BinaryFuseFilter::build(BasicFilterElements(block, block_undo), m_block_hash));
350+
}
230351
}
231352

232353
bool BlockFilter::BuildParams(GCSFilter::Params& params) const
@@ -238,6 +359,7 @@ bool BlockFilter::BuildParams(GCSFilter::Params& params) const
238359
params.m_P = BASIC_FILTER_P;
239360
params.m_M = BASIC_FILTER_M;
240361
return true;
362+
case BlockFilterType::FUSE: return true;
241363
case BlockFilterType::INVALID:
242364
return false;
243365
}

src/blockfilter.h

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <vector>
1818

1919
#include <attributes.h>
20+
#include <serialize.h>
2021
#include <uint256.h>
2122
#include <util/bytevectorhash.h>
2223

@@ -106,12 +107,64 @@ class GCSFilter : public BlockFilterBase
106107
bool MatchAny(const ElementSet& elements) const override;
107108
};
108109

110+
class BinaryFuseFilter : public BlockFilterBase
111+
{
112+
private:
113+
struct Degree
114+
{
115+
uint32_t m_degree = 0;
116+
uint64_t m_xor = 0;
117+
};
118+
119+
struct Assignment
120+
{
121+
uint32_t m_index = 0;
122+
uint64_t m_hash = 0;
123+
};
124+
125+
static constexpr uint32_t m_arity{3};
126+
static constexpr double m_load_factor{1.125};
127+
uint64_t m_siphash_k0;
128+
uint64_t m_siphash_k1;
129+
uint32_t m_num_segments;
130+
uint32_t m_segment_len;
131+
std::vector<uint16_t> m_fingerprints;
132+
std::vector<unsigned char> m_encoded;
133+
134+
uint64_t Hash(const Element& element) const;
135+
136+
uint64_t Mix(uint64_t key) const;
137+
138+
uint16_t Fingerprint(uint64_t key) const;
139+
140+
std::tuple<uint32_t, uint32_t, uint32_t> Slots(uint64_t key) const;
141+
142+
bool Query(const Element& element) const;
143+
144+
BinaryFuseFilter(const ElementSet& elements, const uint256& hash);
145+
146+
public:
147+
static BinaryFuseFilter build(const ElementSet& elements, const uint256& hash) {
148+
return BinaryFuseFilter{elements, hash};
149+
}
150+
151+
SERIALIZE_METHODS(BinaryFuseFilter, obj) { READWRITE(obj.m_fingerprints); }
152+
153+
const std::vector<unsigned char>& GetEncoded() const LIFETIMEBOUND override { return m_encoded; };
154+
155+
bool Match(const Element& element) const override;
156+
157+
bool MatchAny(const ElementSet& elements) const override;
158+
159+
};
160+
109161
constexpr uint8_t BASIC_FILTER_P = 19;
110162
constexpr uint32_t BASIC_FILTER_M = 784931;
111163

112164
enum class BlockFilterType : uint8_t
113165
{
114166
BASIC = 0,
167+
FUSE = 1,
115168
INVALID = 255,
116169
};
117170

src/init.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -995,7 +995,7 @@ bool AppInitParameterInteraction(const ArgsManager& args)
995995

996996
// Signal NODE_COMPACT_FILTERS if peerblockfilters and basic filters index are both enabled.
997997
if (args.GetBoolArg("-peerblockfilters", DEFAULT_PEERBLOCKFILTERS)) {
998-
if (!g_enabled_filter_types.contains(BlockFilterType::BASIC)) {
998+
if (!g_enabled_filter_types.contains(BlockFilterType::BASIC) && !g_enabled_filter_types.contains(BlockFilterType::FUSE)) {
999999
return InitError(_("Cannot set -peerblockfilters without -blockfilterindex."));
10001000
}
10011001

src/net_processing.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3267,7 +3267,7 @@ bool PeerManagerImpl::PrepareBlockFilterRequest(CNode& node, Peer& peer,
32673267
BlockFilterIndex*& filter_index)
32683268
{
32693269
const bool supported_filter_type =
3270-
(filter_type == BlockFilterType::BASIC &&
3270+
((filter_type == BlockFilterType::BASIC || filter_type == BlockFilterType::FUSE) &&
32713271
(peer.m_our_services & NODE_COMPACT_FILTERS));
32723272
if (!supported_filter_type) {
32733273
LogDebug(BCLog::NET, "peer requested unsupported block filter type: %d, %s",

src/test/blockfilter_tests.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,29 @@ BOOST_AUTO_TEST_CASE(gcsfilter_test)
4141
}
4242
}
4343

44+
BOOST_AUTO_TEST_CASE(binaryfusefilter_test)
45+
{
46+
BinaryFuseFilter::ElementSet included_elements, excluded_elements;
47+
for (int i = 0; i < 100; ++i) {
48+
BinaryFuseFilter::Element element1(32);
49+
element1[0] = i;
50+
included_elements.insert(std::move(element1));
51+
52+
BinaryFuseFilter::Element element2(32);
53+
element2[1] = i;
54+
excluded_elements.insert(std::move(element2));
55+
}
56+
57+
const BinaryFuseFilter filter = BinaryFuseFilter::build(included_elements, uint256{1 < 100});
58+
for (const auto& element : included_elements) {
59+
BOOST_CHECK(filter.Match(element));
60+
61+
auto insertion = excluded_elements.insert(element);
62+
BOOST_CHECK(filter.MatchAny(excluded_elements));
63+
excluded_elements.erase(insertion.first);
64+
}
65+
}
66+
4467
BOOST_AUTO_TEST_CASE(gcsfilter_default_constructor)
4568
{
4669
GCSFilter filter;
@@ -109,6 +132,16 @@ BOOST_AUTO_TEST_CASE(blockfilter_basic_test)
109132
BOOST_CHECK(!filter.Match(GCSFilter::Element(script.begin(), script.end())));
110133
}
111134

135+
BlockFilter block_filter_2(BlockFilterType::FUSE, block, block_undo);
136+
const BlockFilterBase& filter_2 = block_filter_2.GetFilter();
137+
138+
for (const CScript& script : included_scripts) {
139+
BOOST_CHECK(filter_2.Match(BlockFilterBase::Element(script.begin(), script.end())));
140+
}
141+
for (const CScript& script : excluded_scripts) {
142+
BOOST_CHECK(!filter_2.Match(BlockFilterBase::Element(script.begin(), script.end())));
143+
}
144+
112145
// Test serialization/unserialization.
113146
BlockFilter block_filter2;
114147

0 commit comments

Comments
 (0)