/tmp/bitcoin/src/blockfilter.cpp
Line | Count | Source |
1 | | // Copyright (c) 2018-present The Bitcoin Core developers |
2 | | // Distributed under the MIT software license, see the accompanying |
3 | | // file COPYING or http://www.opensource.org/licenses/mit-license.php. |
4 | | |
5 | | #include <mutex> |
6 | | #include <set> |
7 | | #include <string_view> |
8 | | |
9 | | #include <blockfilter.h> |
10 | | #include <crypto/siphash.h> |
11 | | #include <hash.h> |
12 | | #include <primitives/block.h> |
13 | | #include <primitives/transaction.h> |
14 | | #include <script/script.h> |
15 | | #include <streams.h> |
16 | | #include <undo.h> |
17 | | #include <util/golombrice.h> |
18 | | #include <util/string.h> |
19 | | |
20 | | using util::Join; |
21 | | |
22 | | static const std::map<BlockFilterType, std::string> g_filter_types = { |
23 | | {BlockFilterType::BASIC, "basic"}, |
24 | | }; |
25 | | |
26 | | uint64_t GCSFilter::HashToRange(const Element& element) const |
27 | 528k | { |
28 | 528k | uint64_t hash = CSipHasher(m_params.m_siphash_k0, m_params.m_siphash_k1) |
29 | 528k | .Write(element) |
30 | 528k | .Finalize(); |
31 | 528k | return FastRange64(hash, m_F); |
32 | 528k | } |
33 | | |
34 | | std::vector<uint64_t> GCSFilter::BuildHashedSet(const ElementSet& elements) const |
35 | 8.80k | { |
36 | 8.80k | std::vector<uint64_t> hashed_elements; |
37 | 8.80k | hashed_elements.reserve(elements.size()); |
38 | 527k | for (const Element& element : elements) { |
39 | 527k | hashed_elements.push_back(HashToRange(element)); |
40 | 527k | } |
41 | 8.80k | std::sort(hashed_elements.begin(), hashed_elements.end()); |
42 | 8.80k | return hashed_elements; |
43 | 8.80k | } |
44 | | |
45 | | GCSFilter::GCSFilter(const Params& params) |
46 | 10.5k | : m_params(params), m_N(0), m_F(0), m_encoded{0} |
47 | 10.5k | {} |
48 | | |
49 | | GCSFilter::GCSFilter(const Params& params, std::vector<unsigned char> encoded_filter, bool skip_decode_check) |
50 | 1.39k | : m_params(params), m_encoded(std::move(encoded_filter)) |
51 | 1.39k | { |
52 | 1.39k | SpanReader stream{m_encoded}; |
53 | | |
54 | 1.39k | uint64_t N = ReadCompactSize(stream); |
55 | 1.39k | m_N = static_cast<uint32_t>(N); |
56 | 1.39k | if (m_N != N) { |
57 | 0 | throw std::ios_base::failure("N must be <2^32"); |
58 | 0 | } |
59 | 1.39k | m_F = static_cast<uint64_t>(m_N) * static_cast<uint64_t>(m_params.m_M); |
60 | | |
61 | 1.39k | if (skip_decode_check) return; |
62 | | |
63 | | // Verify that the encoded filter contains exactly N elements. If it has too much or too little |
64 | | // data, a std::ios_base::failure exception will be raised. |
65 | 1 | BitStreamReader bitreader{stream}; |
66 | 6 | for (uint64_t i = 0; i < m_N; ++i) { |
67 | 5 | GolombRiceDecode(bitreader, m_params.m_P); |
68 | 5 | } |
69 | 1 | if (!stream.empty()) { |
70 | 0 | throw std::ios_base::failure("encoded_filter contains excess data"); |
71 | 0 | } |
72 | 1 | } |
73 | | |
74 | | GCSFilter::GCSFilter(const Params& params, const ElementSet& elements) |
75 | 7.67k | : m_params(params) |
76 | 7.67k | { |
77 | 7.67k | size_t N = elements.size(); |
78 | 7.67k | m_N = static_cast<uint32_t>(N); |
79 | 7.67k | if (m_N != N) { |
80 | 0 | throw std::invalid_argument("N must be <2^32"); |
81 | 0 | } |
82 | 7.67k | m_F = static_cast<uint64_t>(m_N) * static_cast<uint64_t>(m_params.m_M); |
83 | | |
84 | 7.67k | VectorWriter stream{m_encoded, 0}; |
85 | | |
86 | 7.67k | WriteCompactSize(stream, m_N); |
87 | | |
88 | 7.67k | if (elements.empty()) { |
89 | 1 | return; |
90 | 1 | } |
91 | | |
92 | 7.67k | BitStreamWriter bitwriter{stream}; |
93 | | |
94 | 7.67k | uint64_t last_value = 0; |
95 | 8.09k | for (uint64_t value : BuildHashedSet(elements)) { |
96 | 8.09k | uint64_t delta = value - last_value; |
97 | 8.09k | GolombRiceEncode(bitwriter, m_params.m_P, delta); |
98 | 8.09k | last_value = value; |
99 | 8.09k | } |
100 | | |
101 | 7.67k | bitwriter.Flush(); |
102 | 7.67k | } |
103 | | |
104 | | bool GCSFilter::MatchInternal(const uint64_t* element_hashes, size_t size) const |
105 | 1.24k | { |
106 | 1.24k | SpanReader stream{m_encoded}; |
107 | | |
108 | | // Seek forward by size of N |
109 | 1.24k | uint64_t N = ReadCompactSize(stream); |
110 | 1.24k | assert(N == m_N); |
111 | | |
112 | 1.24k | BitStreamReader bitreader{stream}; |
113 | | |
114 | 1.24k | uint64_t value = 0; |
115 | 1.24k | size_t hashes_index = 0; |
116 | 11.4k | for (uint32_t i = 0; i < m_N; ++i) { |
117 | 10.7k | uint64_t delta = GolombRiceDecode(bitreader, m_params.m_P); |
118 | 10.7k | value += delta; |
119 | | |
120 | 258k | while (true) { |
121 | 258k | if (hashes_index == size) { |
122 | 206 | return false; |
123 | 258k | } else if (element_hashes[hashes_index] == value) { |
124 | 277 | return true; |
125 | 258k | } else if (element_hashes[hashes_index] > value) { |
126 | 10.2k | break; |
127 | 10.2k | } |
128 | | |
129 | 248k | hashes_index++; |
130 | 248k | } |
131 | 10.7k | } |
132 | | |
133 | 758 | return false; |
134 | 1.24k | } |
135 | | |
136 | | bool GCSFilter::Match(const Element& element) const |
137 | 109 | { |
138 | 109 | uint64_t query = HashToRange(element); |
139 | 109 | return MatchInternal(&query, 1); |
140 | 109 | } |
141 | | |
142 | | bool GCSFilter::MatchAny(const ElementSet& elements) const |
143 | 1.13k | { |
144 | 1.13k | const std::vector<uint64_t> queries = BuildHashedSet(elements); |
145 | 1.13k | return MatchInternal(queries.data(), queries.size()); |
146 | 1.13k | } |
147 | | |
148 | | const std::string& BlockFilterTypeName(BlockFilterType filter_type) |
149 | 4.79k | { |
150 | 4.79k | static std::string unknown_retval; |
151 | 4.79k | auto it = g_filter_types.find(filter_type); |
152 | 4.79k | return it != g_filter_types.end() ? it->second : unknown_retval; |
153 | 4.79k | } |
154 | | |
155 | | bool BlockFilterTypeByName(std::string_view name, BlockFilterType& filter_type) |
156 | 40 | { |
157 | 40 | for (const auto& entry : g_filter_types) { |
158 | 40 | if (entry.second == name) { |
159 | 35 | filter_type = entry.first; |
160 | 35 | return true; |
161 | 35 | } |
162 | 40 | } |
163 | 5 | return false; |
164 | 40 | } |
165 | | |
166 | | const std::set<BlockFilterType>& AllBlockFilterTypes() |
167 | 49 | { |
168 | 49 | static std::set<BlockFilterType> types; |
169 | | |
170 | 49 | static std::once_flag flag; |
171 | 49 | std::call_once(flag, []() { |
172 | 49 | for (const auto& entry : g_filter_types) { |
173 | 49 | types.insert(entry.first); |
174 | 49 | } |
175 | 49 | }); |
176 | | |
177 | 49 | return types; |
178 | 49 | } |
179 | | |
180 | | const std::string& ListBlockFilterTypes() |
181 | 1.84k | { |
182 | 1.84k | static std::string type_list{Join(g_filter_types, ", ", [](const auto& entry) { return entry.second; })}; |
183 | | |
184 | 1.84k | return type_list; |
185 | 1.84k | } |
186 | | |
187 | | static GCSFilter::ElementSet BasicFilterElements(const CBlock& block, |
188 | | const CBlockUndo& block_undo) |
189 | 7.67k | { |
190 | 7.67k | GCSFilter::ElementSet elements; |
191 | | |
192 | 7.96k | for (const CTransactionRef& tx : block.vtx) { |
193 | 15.6k | for (const CTxOut& txout : tx->vout) { |
194 | 15.6k | const CScript& script = txout.scriptPubKey; |
195 | 15.6k | if (script.empty() || script[0] == OP_RETURN) continue; |
196 | 8.02k | elements.emplace(script.begin(), script.end()); |
197 | 8.02k | } |
198 | 7.96k | } |
199 | | |
200 | 7.67k | for (const CTxUndo& tx_undo : block_undo.vtxundo) { |
201 | 314 | for (const Coin& prevout : tx_undo.vprevout) { |
202 | 314 | const CScript& script = prevout.out.scriptPubKey; |
203 | 314 | if (script.empty()) continue; |
204 | 310 | elements.emplace(script.begin(), script.end()); |
205 | 310 | } |
206 | 297 | } |
207 | | |
208 | 7.67k | return elements; |
209 | 7.67k | } |
210 | | |
211 | | BlockFilter::BlockFilter(BlockFilterType filter_type, const uint256& block_hash, |
212 | | std::vector<unsigned char> filter, bool skip_decode_check) |
213 | 1.38k | : m_filter_type(filter_type), m_block_hash(block_hash) |
214 | 1.38k | { |
215 | 1.38k | GCSFilter::Params params; |
216 | 1.38k | if (!BuildParams(params)) { |
217 | 0 | throw std::invalid_argument("unknown filter_type"); |
218 | 0 | } |
219 | 1.38k | m_filter = GCSFilter(params, std::move(filter), skip_decode_check); |
220 | 1.38k | } |
221 | | |
222 | | BlockFilter::BlockFilter(BlockFilterType filter_type, const CBlock& block, const CBlockUndo& block_undo) |
223 | 7.67k | : m_filter_type(filter_type), m_block_hash(block.GetHash()) |
224 | 7.67k | { |
225 | 7.67k | GCSFilter::Params params; |
226 | 7.67k | if (!BuildParams(params)) { |
227 | 0 | throw std::invalid_argument("unknown filter_type"); |
228 | 0 | } |
229 | 7.67k | m_filter = GCSFilter(params, BasicFilterElements(block, block_undo)); |
230 | 7.67k | } |
231 | | |
232 | | bool BlockFilter::BuildParams(GCSFilter::Params& params) const |
233 | 9.06k | { |
234 | 9.06k | switch (m_filter_type) { |
235 | 9.06k | case BlockFilterType::BASIC: |
236 | 9.06k | params.m_siphash_k0 = m_block_hash.GetUint64(0); |
237 | 9.06k | params.m_siphash_k1 = m_block_hash.GetUint64(1); |
238 | 9.06k | params.m_P = BASIC_FILTER_P; |
239 | 9.06k | params.m_M = BASIC_FILTER_M; |
240 | 9.06k | return true; |
241 | 0 | case BlockFilterType::INVALID: |
242 | 0 | return false; |
243 | 9.06k | } |
244 | | |
245 | 0 | return false; |
246 | 9.06k | } |
247 | | |
248 | | uint256 BlockFilter::GetHash() const |
249 | 15.7k | { |
250 | 15.7k | return Hash(GetEncodedFilter()); |
251 | 15.7k | } |
252 | | |
253 | | uint256 BlockFilter::ComputeHeader(const uint256& prev_header) const |
254 | 7.67k | { |
255 | 7.67k | return Hash(GetHash(), prev_header); |
256 | 7.67k | } |