Bitcoin Core 32.99.0
P2P Digital Currency
Loading...
Searching...
No Matches
blockfilterindex.cpp
Go to the documentation of this file.
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
6
7#include <blockfilter.h>
8#include <chain.h>
9#include <common/args.h>
10#include <dbwrapper.h>
11#include <flatfile.h>
12#include <hash.h>
13#include <index/base.h>
14#include <index/db_key.h>
15#include <interfaces/chain.h>
16#include <interfaces/types.h>
17#include <serialize.h>
18#include <streams.h>
19#include <sync.h>
20#include <uint256.h>
21#include <util/check.h>
22#include <util/byte_units.h>
23#include <util/fs.h>
24#include <util/hasher.h>
25#include <util/log.h>
26#include <util/syserror.h>
27
28#include <cerrno>
29#include <exception>
30#include <map>
31#include <optional>
32#include <stdexcept>
33#include <string>
34#include <tuple>
35#include <utility>
36#include <vector>
37
38/* The index database stores three items for each block: the disk location of the encoded filter,
39 * its dSHA256 hash, and the header. Those belonging to blocks on the active chain are indexed by
40 * height, and those belonging to blocks that have been reorganized out of the active chain are
41 * indexed by block hash. This ensures that filter data for any block that becomes part of the
42 * active chain can always be retrieved, alleviating timing concerns.
43 *
44 * The filters themselves are stored in flat files and referenced by the LevelDB entries. This
45 * minimizes the amount of data written to LevelDB and keeps the database values constant size. The
46 * disk location of the next block filter to be written (represented as a FlatFilePos) is stored
47 * under the DB_FILTER_POS key.
48 *
49 * The logic for keys is shared with other indexes, see index/db_key.h.
50 */
51constexpr uint8_t DB_FILTER_POS{'P'};
52
53constexpr unsigned int MAX_FLTR_FILE_SIZE{16_MiB};
55constexpr unsigned int FLTR_FILE_CHUNK_SIZE{1_MiB};
61constexpr size_t CF_HEADERS_CACHE_MAX_SZ{2000};
62
63namespace {
64
66{
67 switch (filter_type) {
68 case BlockFilterType::BASIC: return "blkfltbscidx";
69 case BlockFilterType::INVALID: return "";
70 } // no default case, so the compiler can warn about missing cases
71 assert(false);
72}
73
74struct DBVal {
75 uint256 hash;
76 uint256 header;
77 FlatFilePos pos;
78
79 SERIALIZE_METHODS(DBVal, obj) { READWRITE(obj.hash, obj.header, obj.pos); }
80};
81
82}; // namespace
83
84static std::map<BlockFilterType, BlockFilterIndex> g_filter_indexes;
85
86BlockFilterIndex::BlockFilterIndex(std::unique_ptr<interfaces::Chain> chain, BlockFilterType filter_type,
87 size_t n_cache_size, bool f_memory, bool f_wipe)
88 : BaseIndex(std::move(chain), BlockFilterTypeName(filter_type) + " block filter index", BlockFilterThreadName(filter_type))
89 , m_filter_type(filter_type)
90{
91 const std::string& filter_name = BlockFilterTypeName(filter_type);
92 if (filter_name.empty()) throw std::invalid_argument("unknown filter_type");
93
94 fs::path path = gArgs.GetDataDirNet() / "indexes" / "blockfilter" / fs::u8path(filter_name);
95 fs::create_directories(path);
96
97 m_db = std::make_unique<BaseIndex::DB>(path / "db", n_cache_size, f_memory, f_wipe);
98 m_filter_fileseq = std::make_unique<FlatFileSeq>(std::move(path), "fltr", FLTR_FILE_CHUNK_SIZE);
99}
100
107
108bool BlockFilterIndex::CustomInit(const std::optional<interfaces::BlockRef>& block)
109{
110 if (!m_db->Read(DB_FILTER_POS, m_next_filter_pos)) {
111 // Check that the cause of the read failure is that the key does not exist. Any other errors
112 // indicate database corruption or a disk failure, and starting the index would cause
113 // further corruption.
114 if (m_db->Exists(DB_FILTER_POS)) {
115 LogError("Cannot read current %s state; index may be corrupted",
116 GetName());
117 return false;
118 }
119
120 // If the DB_FILTER_POS is not set, then initialize to the first location.
123 }
124
125 if (block) {
126 auto op_last_header = ReadFilterHeader(block->height, block->hash);
127 if (!op_last_header) {
128 LogError("Cannot read last block filter header; index may be corrupted");
129 return false;
130 }
132 }
133
134 return true;
135}
136
138{
139 const FlatFilePos& pos = m_next_filter_pos;
140
141 // Flush current filter file to disk.
142 AutoFile file{m_filter_fileseq->Open(pos)};
143 if (file.IsNull()) {
144 LogError("Failed to open filter file %d", pos.nFile);
145 return false;
146 }
147 if (!file.Commit()) {
148 LogError("Failed to commit filter file %d", pos.nFile);
149 (void)file.fclose();
150 return false;
151 }
152 if (file.fclose() != 0) {
153 LogError("Failed to close filter file %d after commit: %s", pos.nFile, SysErrorString(errno));
154 return false;
155 }
156
157 batch.Write(DB_FILTER_POS, pos);
158 return true;
159}
160
161bool BlockFilterIndex::ReadFilterFromDisk(const FlatFilePos& pos, const uint256& hash, BlockFilter& filter) const
162{
163 AutoFile filein{m_filter_fileseq->Open(pos, true)};
164 if (filein.IsNull()) {
165 return false;
166 }
167
168 // Check that the hash of the encoded_filter matches the one stored in the db.
169 uint256 block_hash;
170 std::vector<uint8_t> encoded_filter;
171 try {
172 filein >> block_hash >> encoded_filter;
173 if (Hash(encoded_filter) != hash) {
174 LogError("Checksum mismatch in filter decode.");
175 return false;
176 }
177 filter = BlockFilter(GetFilterType(), block_hash, std::move(encoded_filter), /*skip_decode_check=*/true);
178 }
179 catch (const std::exception& e) {
180 LogError("Failed to deserialize block filter from disk: %s", e.what());
181 return false;
182 }
183
184 return true;
185}
186
188{
189 assert(filter.GetFilterType() == GetFilterType());
190
194
195 // If writing the filter would overflow the file, flush and move to the next one.
196 if (pos.nPos + data_size > MAX_FLTR_FILE_SIZE) {
198 if (last_file.IsNull()) {
199 LogError("Failed to open filter file %d", pos.nFile);
200 return 0;
201 }
202 if (!last_file.Truncate(pos.nPos)) {
203 LogError("Failed to truncate filter file %d", pos.nFile);
204 return 0;
205 }
206 if (!last_file.Commit()) {
207 LogError("Failed to commit filter file %d", pos.nFile);
208 (void)last_file.fclose();
209 return 0;
210 }
211 if (last_file.fclose() != 0) {
212 LogError("Failed to close filter file %d after commit: %s", pos.nFile, SysErrorString(errno));
213 return 0;
214 }
215
216 pos.nFile++;
217 pos.nPos = 0;
218 }
219
220 // Pre-allocate sufficient space for filter data.
221 bool out_of_space;
222 m_filter_fileseq->Allocate(pos, data_size, out_of_space);
223 if (out_of_space) {
224 LogError("out of disk space");
225 return 0;
226 }
227
229 if (fileout.IsNull()) {
230 LogError("Failed to open filter file %d", pos.nFile);
231 return 0;
232 }
233
234 fileout << filter.GetBlockHash() << filter.GetEncodedFilter();
235
236 if (fileout.fclose() != 0) {
237 LogError("Failed to close filter file %d: %s", pos.nFile, SysErrorString(errno));
238 return 0;
239 }
240
241 return data_size;
242}
243
244std::optional<uint256> BlockFilterIndex::ReadFilterHeader(int height, const uint256& expected_block_hash)
245{
246 std::pair<uint256, DBVal> read_out;
247 if (!m_db->Read(index_util::DBHeightKey(height), read_out)) {
248 return std::nullopt;
249 }
250
251 if (read_out.first != expected_block_hash) {
252 LogError("previous block header belongs to unexpected block %s; expected %s",
253 read_out.first.ToString(), expected_block_hash.ToString());
254 return std::nullopt;
255 }
256
257 return read_out.second.header;
258}
259
261{
262 BlockFilter filter(m_filter_type, *Assert(block.data), *Assert(block.undo_data));
263 const uint256& header = filter.ComputeHeader(m_last_header);
264 bool res = Write(filter, block.height, header);
265 if (res) m_last_header = header; // update last header
266 return res;
267}
268
269bool BlockFilterIndex::Write(const BlockFilter& filter, uint32_t block_height, const uint256& filter_header)
270{
272 if (bytes_written == 0) return false;
273
274 std::pair<uint256, DBVal> value;
275 value.first = filter.GetBlockHash();
276 value.second.hash = filter.GetHash();
277 value.second.header = filter_header;
278 value.second.pos = m_next_filter_pos;
279
280 m_db->Write(index_util::DBHeightKey(block_height), value);
281
283 return true;
284}
285
287{
288 CDBBatch batch(*m_db);
289 std::unique_ptr<CDBIterator> db_it(m_db->NewIterator());
290
291 // During a reorg, we need to copy block filter that is getting disconnected from the
292 // height index to the hash index so we can still find it when the height index entry
293 // is overwritten.
294 if (!index_util::CopyHeightIndexToHashIndex<DBVal>(*db_it, batch, m_name, block.height)) {
295 return false;
296 }
297
298 // The latest filter position gets written in Commit by the call to the BaseIndex::Rewind.
299 // But since this creates new references to the filter, the position should get updated here
300 // atomically as well in case Commit fails.
302 m_db->WriteBatch(batch);
303
304 // Update cached header to the previous block hash
306 return true;
307}
308
309static bool LookupRange(CDBWrapper& db, const std::string& index_name, int start_height,
310 const CBlockIndex* stop_index, std::vector<DBVal>& results)
311{
312 if (start_height < 0) {
313 LogError("start height (%d) is negative", start_height);
314 return false;
315 }
316 if (start_height > stop_index->nHeight) {
317 LogError("start height (%d) is greater than stop height (%d)",
318 start_height, stop_index->nHeight);
319 return false;
320 }
321
322 size_t results_size = static_cast<size_t>(stop_index->nHeight - start_height + 1);
323 std::vector<std::pair<uint256, DBVal>> values(results_size);
324
326 std::unique_ptr<CDBIterator> db_it(db.NewIterator());
328 for (int height = start_height; height <= stop_index->nHeight; ++height) {
329 if (!db_it->Valid() || !db_it->GetKey(key) || key.height != height) {
330 return false;
331 }
332
333 size_t i = static_cast<size_t>(height - start_height);
334 if (!db_it->GetValue(values[i])) {
335 LogError("unable to read value in %s at key (%c, %d)",
337 return false;
338 }
339
340 db_it->Next();
341 }
342
343 results.resize(results_size);
344
345 // Iterate backwards through block indexes collecting results in order to access the block hash
346 // of each entry in case we need to look it up in the hash index.
347 for (const CBlockIndex* block_index = stop_index;
349 block_index = block_index->pprev) {
350 uint256 block_hash = block_index->GetBlockHash();
351
352 size_t i = static_cast<size_t>(block_index->nHeight - start_height);
353 if (block_hash == values[i].first) {
354 results[i] = std::move(values[i].second);
355 continue;
356 }
357
358 if (!db.Read(index_util::DBHashKey(block_hash), results[i])) {
359 LogError("unable to read value in %s at key (%c, %s)",
361 return false;
362 }
363 }
364
365 return true;
366}
367
369{
370 DBVal entry;
371 if (!index_util::LookUpOne(*m_db, {block_index->GetBlockHash(), block_index->nHeight}, entry)) {
372 return false;
373 }
374
375 return ReadFilterFromDisk(entry.pos, entry.hash, filter_out);
376}
377
379{
381
382 bool is_checkpoint{block_index->nHeight % CFCHECKPT_INTERVAL == 0};
383
384 if (is_checkpoint) {
385 // Try to find the block in the headers cache if this is a checkpoint height.
386 auto header = m_headers_cache.find(block_index->GetBlockHash());
387 if (header != m_headers_cache.end()) {
388 header_out = header->second;
389 return true;
390 }
391 }
392
393 DBVal entry;
394 if (!index_util::LookUpOne(*m_db, {block_index->GetBlockHash(), block_index->nHeight}, entry)) {
395 return false;
396 }
397
398 if (is_checkpoint &&
400 // Add to the headers cache if this is a checkpoint height.
401 m_headers_cache.emplace(block_index->GetBlockHash(), entry.header);
402 }
403
404 header_out = entry.header;
405 return true;
406}
407
409 std::vector<BlockFilter>& filters_out) const
410{
411 std::vector<DBVal> entries;
412 if (!LookupRange(*m_db, m_name, start_height, stop_index, entries)) {
413 return false;
414 }
415
416 filters_out.resize(entries.size());
417 auto filter_pos_it = filters_out.begin();
418 for (const auto& entry : entries) {
419 if (!ReadFilterFromDisk(entry.pos, entry.hash, *filter_pos_it)) {
420 return false;
421 }
423 }
424
425 return true;
426}
427
429 std::vector<uint256>& hashes_out) const
430
431{
432 std::vector<DBVal> entries;
433 if (!LookupRange(*m_db, m_name, start_height, stop_index, entries)) {
434 return false;
435 }
436
437 hashes_out.clear();
438 hashes_out.reserve(entries.size());
439 for (const auto& entry : entries) {
440 hashes_out.push_back(entry.hash);
441 }
442 return true;
443}
444
446{
447 auto it = g_filter_indexes.find(filter_type);
448 return it != g_filter_indexes.end() ? &it->second : nullptr;
449}
450
451void ForEachBlockFilterIndex(std::function<void (BlockFilterIndex&)> fn)
452{
453 for (auto& entry : g_filter_indexes) fn(entry.second);
454}
455
456bool InitBlockFilterIndex(std::function<std::unique_ptr<interfaces::Chain>()> make_chain, BlockFilterType filter_type,
457 size_t n_cache_size, bool f_memory, bool f_wipe)
458{
459 auto result = g_filter_indexes.emplace(std::piecewise_construct,
460 std::forward_as_tuple(filter_type),
461 std::forward_as_tuple(make_chain(), filter_type,
463 return result.second;
464}
465
470
472{
473 g_filter_indexes.clear();
474}
ArgsManager gArgs
Definition args.cpp:38
const std::string & BlockFilterTypeName(BlockFilterType filter_type)
Get the human-readable name for a filter type.
BlockFilterType
Definition blockfilter.h:94
constexpr unsigned int FLTR_FILE_CHUNK_SIZE
The pre-allocation chunk size for fltr?????.dat files.
bool DestroyBlockFilterIndex(BlockFilterType filter_type)
Destroy the block filter index with the given type.
void DestroyAllBlockFilterIndexes()
Destroy all open block filter indexes.
BlockFilterIndex * GetBlockFilterIndex(BlockFilterType filter_type)
Get a block filter index by type.
constexpr uint8_t DB_FILTER_POS
constexpr unsigned int MAX_FLTR_FILE_SIZE
void ForEachBlockFilterIndex(std::function< void(BlockFilterIndex &)> fn)
Iterate over all running block filter indexes, invoking fn on each.
constexpr size_t CF_HEADERS_CACHE_MAX_SZ
Maximum size of the cfheaders cache We have a limit to prevent a bug in filling this cache potentiall...
bool InitBlockFilterIndex(std::function< std::unique_ptr< interfaces::Chain >()> make_chain, BlockFilterType filter_type, size_t n_cache_size, bool f_memory, bool f_wipe)
Initialize a block filter index for the given type if one does not already exist.
static bool LookupRange(CDBWrapper &db, const std::string &index_name, int start_height, const CBlockIndex *stop_index, std::vector< DBVal > &results)
static std::map< BlockFilterType, BlockFilterIndex > g_filter_indexes
constexpr int CFCHECKPT_INTERVAL
Interval between compact filter checkpoints.
#define Assert(val)
Identity function.
Definition check.h:119
fs::path GetDataDirNet() const EXCLUSIVE_LOCKS_REQUIRED(!cs_args)
Get data directory path with appended network identifier.
Definition args.cpp:328
Non-refcounted RAII wrapper for FILE*.
Definition streams.h:395
Base class for indices of blockchain data.
Definition base.h:55
const std::string & GetName() const LIFETIMEBOUND
Get the name of the index for display in logs.
Definition base.h:147
const std::string m_name
Definition base.h:116
Complete block filter struct as defined in BIP 157.
const uint256 & GetBlockHash() const LIFETIMEBOUND
const std::vector< unsigned char > & GetEncodedFilter() const LIFETIMEBOUND
uint256 ComputeHeader(const uint256 &prev_header) const
Compute the filter header given the previous one.
BlockFilterType GetFilterType() const
uint256 GetHash() const
Compute the filter hash.
BlockFilterIndex is used to store and retrieve block filters, hashes, and headers for a range of bloc...
std::unique_ptr< BaseIndex::DB > m_db
bool CustomInit(const std::optional< interfaces::BlockRef > &block) override
Initialize internal state from the database and block index.
bool LookupFilterRange(int start_height, const CBlockIndex *stop_index, std::vector< BlockFilter > &filters_out) const
Get a range of filters between two heights on a chain.
BlockFilterType GetFilterType() const
bool CustomRemove(const interfaces::BlockInfo &block) override
Rewind index by one block during a chain reorg.
bool CustomCommit(CDBBatch &batch) override
Virtual method called internally by Commit that can be overridden to atomically commit more index sta...
BlockFilterType m_filter_type
BlockFilterIndex(std::unique_ptr< interfaces::Chain > chain, BlockFilterType filter_type, size_t n_cache_size, bool f_memory=false, bool f_wipe=false)
Constructs the index, which becomes available to be queried.
interfaces::Chain::NotifyOptions CustomOptions() override
Return custom notification options for index.
std::unique_ptr< FlatFileSeq > m_filter_fileseq
bool LookupFilter(const CBlockIndex *block_index, BlockFilter &filter_out) const
Get a single filter by block.
bool ReadFilterFromDisk(const FlatFilePos &pos, const uint256 &hash, BlockFilter &filter) const
bool LookupFilterHashRange(int start_height, const CBlockIndex *stop_index, std::vector< uint256 > &hashes_out) const
Get a range of filter hashes between two heights on a chain.
bool CustomAppend(const interfaces::BlockInfo &block) override
Write update index entries for a newly connected block.
size_t WriteFilterToDisk(FlatFilePos &pos, const BlockFilter &filter)
bool LookupFilterHeader(const CBlockIndex *block_index, uint256 &header_out) EXCLUSIVE_LOCKS_REQUIRED(!m_cs_headers_cache)
Get a single filter header by block.
std::optional< uint256 > ReadFilterHeader(int height, const uint256 &expected_block_hash)
bool Write(const BlockFilter &filter, uint32_t block_height, const uint256 &filter_header)
FlatFilePos m_next_filter_pos
The block chain is a tree shaped structure starting with the genesis block at the root,...
Definition chain.h:94
int nHeight
height of the entry in the chain. The genesis block has height 0
Definition chain.h:106
Batch of changes queued to be written to a CDBWrapper.
Definition dbwrapper.h:90
void Write(const K &key, const V &value)
Definition dbwrapper.h:114
bool Read(const K &key, V &value) const
Wrapper around TryRead() that preserves the original Read() semantics: returns true on success,...
Definition dbwrapper.h:288
CDBIterator * NewIterator()
std::string ToString() const
Definition uint256.cpp:21
256-bit opaque blob.
Definition uint256.h:196
static path u8path(std::string_view utf8_str)
Definition fs.h:80
uint256 Hash(const T &in1)
Compute the 256-bit hash of an object.
Definition hash.h:83
#define LogError(...)
Definition log.h:138
static bool LookUpOne(const CDBWrapper &db, const interfaces::BlockRef &block, DBVal &result)
Definition db_key.h:96
constexpr uint8_t DB_BLOCK_HASH
Definition db_key.h:29
constexpr uint8_t DB_BLOCK_HEIGHT
Definition db_key.h:30
static const int64_t values[]
A selection of numbers that do not trigger int64_t overflow when added/subtracted.
#define SERIALIZE_METHODS(cls, obj)
Implement the Serialize and Unserialize methods by delegating to a single templated static method tha...
Definition serialize.h:232
uint64_t GetSerializeSize(const T &t)
Definition serialize.h:1157
#define READWRITE(...)
Definition serialize.h:148
uint32_t nPos
Definition flatfile.h:17
int32_t nFile
Definition flatfile.h:16
Block data sent with blockConnected, blockDisconnected notifications.
Definition chain.h:19
const uint256 * prev_hash
Definition chain.h:21
const CBlock * data
Definition chain.h:25
const CBlockUndo * undo_data
Definition chain.h:26
Options specifying which chain notifications are required.
Definition chain.h:320
bool connect_undo_data
Include undo data with block connected notifications.
Definition chain.h:322
#define LOCK(cs)
Definition sync.h:268
std::string SysErrorString(int err)
Return system error string from errno value.
Definition syserror.cpp:18
CDBWrapper db
constexpr auto Ticks(Dur2 d)
Helper to count the seconds of a duration/time_point.
Definition time.h:82
assert(!tx.IsCoinBase())