Branch data Line data Source code
# 1 : : // Copyright (c) 2018-2020 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 <map>
# 6 : :
# 7 : : #include <dbwrapper.h>
# 8 : : #include <index/blockfilterindex.h>
# 9 : : #include <node/blockstorage.h>
# 10 : : #include <util/system.h>
# 11 : :
# 12 : : /* The index database stores three items for each block: the disk location of the encoded filter,
# 13 : : * its dSHA256 hash, and the header. Those belonging to blocks on the active chain are indexed by
# 14 : : * height, and those belonging to blocks that have been reorganized out of the active chain are
# 15 : : * indexed by block hash. This ensures that filter data for any block that becomes part of the
# 16 : : * active chain can always be retrieved, alleviating timing concerns.
# 17 : : *
# 18 : : * The filters themselves are stored in flat files and referenced by the LevelDB entries. This
# 19 : : * minimizes the amount of data written to LevelDB and keeps the database values constant size. The
# 20 : : * disk location of the next block filter to be written (represented as a FlatFilePos) is stored
# 21 : : * under the DB_FILTER_POS key.
# 22 : : *
# 23 : : * Keys for the height index have the type [DB_BLOCK_HEIGHT, uint32 (BE)]. The height is represented
# 24 : : * as big-endian so that sequential reads of filters by height are fast.
# 25 : : * Keys for the hash index have the type [DB_BLOCK_HASH, uint256].
# 26 : : */
# 27 : : constexpr uint8_t DB_BLOCK_HASH{'s'};
# 28 : : constexpr uint8_t DB_BLOCK_HEIGHT{'t'};
# 29 : : constexpr uint8_t DB_FILTER_POS{'P'};
# 30 : :
# 31 : : constexpr unsigned int MAX_FLTR_FILE_SIZE = 0x1000000; // 16 MiB
# 32 : : /** The pre-allocation chunk size for fltr?????.dat files */
# 33 : : constexpr unsigned int FLTR_FILE_CHUNK_SIZE = 0x100000; // 1 MiB
# 34 : : /** Maximum size of the cfheaders cache
# 35 : : * We have a limit to prevent a bug in filling this cache
# 36 : : * potentially turning into an OOM. At 2000 entries, this cache
# 37 : : * is big enough for a 2,000,000 length block chain, which
# 38 : : * we should be enough until ~2047. */
# 39 : : constexpr size_t CF_HEADERS_CACHE_MAX_SZ{2000};
# 40 : :
# 41 : : namespace {
# 42 : :
# 43 : : struct DBVal {
# 44 : : uint256 hash;
# 45 : : uint256 header;
# 46 : : FlatFilePos pos;
# 47 : :
# 48 : 13720 : SERIALIZE_METHODS(DBVal, obj) { READWRITE(obj.hash, obj.header, obj.pos); }
# 49 : : };
# 50 : :
# 51 : : struct DBHeightKey {
# 52 : : int height;
# 53 : :
# 54 : 12890 : explicit DBHeightKey(int height_in) : height(height_in) {}
# 55 : :
# 56 : : template<typename Stream>
# 57 : : void Serialize(Stream& s) const
# 58 : 12022 : {
# 59 : 12022 : ser_writedata8(s, DB_BLOCK_HEIGHT);
# 60 : 12022 : ser_writedata32be(s, height);
# 61 : 12022 : }
# 62 : :
# 63 : : template<typename Stream>
# 64 : : void Unserialize(Stream& s)
# 65 : 2907 : {
# 66 : 2907 : const uint8_t prefix{ser_readdata8(s)};
# 67 [ - + ]: 2907 : if (prefix != DB_BLOCK_HEIGHT) {
# 68 : 0 : throw std::ios_base::failure("Invalid format for block filter index DB height key");
# 69 : 0 : }
# 70 : 2907 : height = ser_readdata32be(s);
# 71 : 2907 : }
# 72 : : };
# 73 : :
# 74 : : struct DBHashKey {
# 75 : : uint256 hash;
# 76 : :
# 77 : 69 : explicit DBHashKey(const uint256& hash_in) : hash(hash_in) {}
# 78 : :
# 79 : 69 : SERIALIZE_METHODS(DBHashKey, obj) {
# 80 : 69 : uint8_t prefix{DB_BLOCK_HASH};
# 81 : 69 : READWRITE(prefix);
# 82 [ - + ]: 69 : if (prefix != DB_BLOCK_HASH) {
# 83 : 0 : throw std::ios_base::failure("Invalid format for block filter index DB hash key");
# 84 : 0 : }
# 85 : :
# 86 : 69 : READWRITE(obj.hash);
# 87 : 69 : }
# 88 : : };
# 89 : :
# 90 : : }; // namespace
# 91 : :
# 92 : : static std::map<BlockFilterType, BlockFilterIndex> g_filter_indexes;
# 93 : :
# 94 : : BlockFilterIndex::BlockFilterIndex(BlockFilterType filter_type,
# 95 : : size_t n_cache_size, bool f_memory, bool f_wipe)
# 96 : : : m_filter_type(filter_type)
# 97 : 15 : {
# 98 : 15 : const std::string& filter_name = BlockFilterTypeName(filter_type);
# 99 [ - + ]: 15 : if (filter_name.empty()) throw std::invalid_argument("unknown filter_type");
# 100 : :
# 101 : 15 : fs::path path = gArgs.GetDataDirNet() / "indexes" / "blockfilter" / filter_name;
# 102 : 15 : fs::create_directories(path);
# 103 : :
# 104 : 15 : m_name = filter_name + " block filter index";
# 105 : 15 : m_db = std::make_unique<BaseIndex::DB>(path / "db", n_cache_size, f_memory, f_wipe);
# 106 : 15 : m_filter_fileseq = std::make_unique<FlatFileSeq>(std::move(path), "fltr", FLTR_FILE_CHUNK_SIZE);
# 107 : 15 : }
# 108 : :
# 109 : : bool BlockFilterIndex::Init()
# 110 : 9 : {
# 111 [ + + ]: 9 : if (!m_db->Read(DB_FILTER_POS, m_next_filter_pos)) {
# 112 : : // Check that the cause of the read failure is that the key does not exist. Any other errors
# 113 : : // indicate database corruption or a disk failure, and starting the index would cause
# 114 : : // further corruption.
# 115 [ - + ]: 8 : if (m_db->Exists(DB_FILTER_POS)) {
# 116 : 0 : return error("%s: Cannot read current %s state; index may be corrupted",
# 117 : 0 : __func__, GetName());
# 118 : 0 : }
# 119 : :
# 120 : : // If the DB_FILTER_POS is not set, then initialize to the first location.
# 121 : 8 : m_next_filter_pos.nFile = 0;
# 122 : 8 : m_next_filter_pos.nPos = 0;
# 123 : 8 : }
# 124 : 9 : return BaseIndex::Init();
# 125 : 9 : }
# 126 : :
# 127 : : bool BlockFilterIndex::CommitInternal(CDBBatch& batch)
# 128 : 21 : {
# 129 : 21 : const FlatFilePos& pos = m_next_filter_pos;
# 130 : :
# 131 : : // Flush current filter file to disk.
# 132 : 21 : CAutoFile file(m_filter_fileseq->Open(pos), SER_DISK, CLIENT_VERSION);
# 133 [ - + ]: 21 : if (file.IsNull()) {
# 134 : 0 : return error("%s: Failed to open filter file %d", __func__, pos.nFile);
# 135 : 0 : }
# 136 [ - + ]: 21 : if (!FileCommit(file.Get())) {
# 137 : 0 : return error("%s: Failed to commit filter file %d", __func__, pos.nFile);
# 138 : 0 : }
# 139 : :
# 140 : 21 : batch.Write(DB_FILTER_POS, pos);
# 141 : 21 : return BaseIndex::CommitInternal(batch);
# 142 : 21 : }
# 143 : :
# 144 : : bool BlockFilterIndex::ReadFilterFromDisk(const FlatFilePos& pos, BlockFilter& filter) const
# 145 : 691 : {
# 146 : 691 : CAutoFile filein(m_filter_fileseq->Open(pos, true), SER_DISK, CLIENT_VERSION);
# 147 [ - + ]: 691 : if (filein.IsNull()) {
# 148 : 0 : return false;
# 149 : 0 : }
# 150 : :
# 151 : 691 : uint256 block_hash;
# 152 : 691 : std::vector<uint8_t> encoded_filter;
# 153 : 691 : try {
# 154 : 691 : filein >> block_hash >> encoded_filter;
# 155 : 691 : filter = BlockFilter(GetFilterType(), block_hash, std::move(encoded_filter));
# 156 : 691 : }
# 157 : 691 : catch (const std::exception& e) {
# 158 : 0 : return error("%s: Failed to deserialize block filter from disk: %s", __func__, e.what());
# 159 : 0 : }
# 160 : :
# 161 : 691 : return true;
# 162 : 691 : }
# 163 : :
# 164 : : size_t BlockFilterIndex::WriteFilterToDisk(FlatFilePos& pos, const BlockFilter& filter)
# 165 : 5134 : {
# 166 : 5134 : assert(filter.GetFilterType() == GetFilterType());
# 167 : :
# 168 : 5134 : size_t data_size =
# 169 : 5134 : GetSerializeSize(filter.GetBlockHash(), CLIENT_VERSION) +
# 170 : 5134 : GetSerializeSize(filter.GetEncodedFilter(), CLIENT_VERSION);
# 171 : :
# 172 : : // If writing the filter would overflow the file, flush and move to the next one.
# 173 [ - + ]: 5134 : if (pos.nPos + data_size > MAX_FLTR_FILE_SIZE) {
# 174 : 0 : CAutoFile last_file(m_filter_fileseq->Open(pos), SER_DISK, CLIENT_VERSION);
# 175 [ # # ]: 0 : if (last_file.IsNull()) {
# 176 : 0 : LogPrintf("%s: Failed to open filter file %d\n", __func__, pos.nFile);
# 177 : 0 : return 0;
# 178 : 0 : }
# 179 [ # # ]: 0 : if (!TruncateFile(last_file.Get(), pos.nPos)) {
# 180 : 0 : LogPrintf("%s: Failed to truncate filter file %d\n", __func__, pos.nFile);
# 181 : 0 : return 0;
# 182 : 0 : }
# 183 [ # # ]: 0 : if (!FileCommit(last_file.Get())) {
# 184 : 0 : LogPrintf("%s: Failed to commit filter file %d\n", __func__, pos.nFile);
# 185 : 0 : return 0;
# 186 : 0 : }
# 187 : :
# 188 : 0 : pos.nFile++;
# 189 : 0 : pos.nPos = 0;
# 190 : 0 : }
# 191 : :
# 192 : : // Pre-allocate sufficient space for filter data.
# 193 : 5134 : bool out_of_space;
# 194 : 5134 : m_filter_fileseq->Allocate(pos, data_size, out_of_space);
# 195 [ - + ]: 5134 : if (out_of_space) {
# 196 : 0 : LogPrintf("%s: out of disk space\n", __func__);
# 197 : 0 : return 0;
# 198 : 0 : }
# 199 : :
# 200 : 5134 : CAutoFile fileout(m_filter_fileseq->Open(pos), SER_DISK, CLIENT_VERSION);
# 201 [ - + ]: 5134 : if (fileout.IsNull()) {
# 202 : 0 : LogPrintf("%s: Failed to open filter file %d\n", __func__, pos.nFile);
# 203 : 0 : return 0;
# 204 : 0 : }
# 205 : :
# 206 : 5134 : fileout << filter.GetBlockHash() << filter.GetEncodedFilter();
# 207 : 5134 : return data_size;
# 208 : 5134 : }
# 209 : :
# 210 : : bool BlockFilterIndex::WriteBlock(const CBlock& block, const CBlockIndex* pindex)
# 211 : 5134 : {
# 212 : 5134 : CBlockUndo block_undo;
# 213 : 5134 : uint256 prev_header;
# 214 : :
# 215 [ + + ]: 5134 : if (pindex->nHeight > 0) {
# 216 [ - + ]: 5126 : if (!UndoReadFromDisk(block_undo, pindex)) {
# 217 : 0 : return false;
# 218 : 0 : }
# 219 : :
# 220 : 5126 : std::pair<uint256, DBVal> read_out;
# 221 [ - + ]: 5126 : if (!m_db->Read(DBHeightKey(pindex->nHeight - 1), read_out)) {
# 222 : 0 : return false;
# 223 : 0 : }
# 224 : :
# 225 : 5126 : uint256 expected_block_hash = pindex->pprev->GetBlockHash();
# 226 [ - + ]: 5126 : if (read_out.first != expected_block_hash) {
# 227 : 0 : return error("%s: previous block header belongs to unexpected block %s; expected %s",
# 228 : 0 : __func__, read_out.first.ToString(), expected_block_hash.ToString());
# 229 : 0 : }
# 230 : :
# 231 : 5126 : prev_header = read_out.second.header;
# 232 : 5126 : }
# 233 : :
# 234 : 5134 : BlockFilter filter(m_filter_type, block, block_undo);
# 235 : :
# 236 : 5134 : size_t bytes_written = WriteFilterToDisk(m_next_filter_pos, filter);
# 237 [ - + ]: 5134 : if (bytes_written == 0) return false;
# 238 : :
# 239 : 5134 : std::pair<uint256, DBVal> value;
# 240 : 5134 : value.first = pindex->GetBlockHash();
# 241 : 5134 : value.second.hash = filter.GetHash();
# 242 : 5134 : value.second.header = filter.ComputeHeader(prev_header);
# 243 : 5134 : value.second.pos = m_next_filter_pos;
# 244 : :
# 245 [ - + ]: 5134 : if (!m_db->Write(DBHeightKey(pindex->nHeight), value)) {
# 246 : 0 : return false;
# 247 : 0 : }
# 248 : :
# 249 : 5134 : m_next_filter_pos.nPos += bytes_written;
# 250 : 5134 : return true;
# 251 : 5134 : }
# 252 : :
# 253 : : static bool CopyHeightIndexToHashIndex(CDBIterator& db_it, CDBBatch& batch,
# 254 : : const std::string& index_name,
# 255 : : int start_height, int stop_height)
# 256 : 6 : {
# 257 : 6 : DBHeightKey key(start_height);
# 258 : 6 : db_it.Seek(key);
# 259 : :
# 260 [ + + ]: 26 : for (int height = start_height; height <= stop_height; ++height) {
# 261 [ - + ][ - + ]: 20 : if (!db_it.GetKey(key) || key.height != height) {
# 262 : 0 : return error("%s: unexpected key in %s: expected (%c, %d)",
# 263 : 0 : __func__, index_name, DB_BLOCK_HEIGHT, height);
# 264 : 0 : }
# 265 : :
# 266 : 20 : std::pair<uint256, DBVal> value;
# 267 [ - + ]: 20 : if (!db_it.GetValue(value)) {
# 268 : 0 : return error("%s: unable to read value in %s at key (%c, %d)",
# 269 : 0 : __func__, index_name, DB_BLOCK_HEIGHT, height);
# 270 : 0 : }
# 271 : :
# 272 : 20 : batch.Write(DBHashKey(value.first), std::move(value.second));
# 273 : :
# 274 : 20 : db_it.Next();
# 275 : 20 : }
# 276 : 6 : return true;
# 277 : 6 : }
# 278 : :
# 279 : : bool BlockFilterIndex::Rewind(const CBlockIndex* current_tip, const CBlockIndex* new_tip)
# 280 : 6 : {
# 281 : 6 : assert(current_tip->GetAncestor(new_tip->nHeight) == new_tip);
# 282 : :
# 283 : 6 : CDBBatch batch(*m_db);
# 284 : 6 : std::unique_ptr<CDBIterator> db_it(m_db->NewIterator());
# 285 : :
# 286 : : // During a reorg, we need to copy all filters for blocks that are getting disconnected from the
# 287 : : // height index to the hash index so we can still find them when the height index entries are
# 288 : : // overwritten.
# 289 [ - + ]: 6 : if (!CopyHeightIndexToHashIndex(*db_it, batch, m_name, new_tip->nHeight, current_tip->nHeight)) {
# 290 : 0 : return false;
# 291 : 0 : }
# 292 : :
# 293 : : // The latest filter position gets written in Commit by the call to the BaseIndex::Rewind.
# 294 : : // But since this creates new references to the filter, the position should get updated here
# 295 : : // atomically as well in case Commit fails.
# 296 : 6 : batch.Write(DB_FILTER_POS, m_next_filter_pos);
# 297 [ - + ]: 6 : if (!m_db->WriteBatch(batch)) return false;
# 298 : :
# 299 : 6 : return BaseIndex::Rewind(current_tip, new_tip);
# 300 : 6 : }
# 301 : :
# 302 : : static bool LookupOne(const CDBWrapper& db, const CBlockIndex* block_index, DBVal& result)
# 303 : 888 : {
# 304 : : // First check if the result is stored under the height index and the value there matches the
# 305 : : // block hash. This should be the case if the block is on the active chain.
# 306 : 888 : std::pair<uint256, DBVal> read_out;
# 307 [ + + ]: 888 : if (!db.Read(DBHeightKey(block_index->nHeight), read_out)) {
# 308 : 404 : return false;
# 309 : 404 : }
# 310 [ + + ]: 484 : if (read_out.first == block_index->GetBlockHash()) {
# 311 : 457 : result = std::move(read_out.second);
# 312 : 457 : return true;
# 313 : 457 : }
# 314 : :
# 315 : : // If value at the height index corresponds to an different block, the result will be stored in
# 316 : : // the hash index.
# 317 : 27 : return db.Read(DBHashKey(block_index->GetBlockHash()), result);
# 318 : 27 : }
# 319 : :
# 320 : : static bool LookupRange(CDBWrapper& db, const std::string& index_name, int start_height,
# 321 : : const CBlockIndex* stop_index, std::vector<DBVal>& results)
# 322 : 868 : {
# 323 [ - + ]: 868 : if (start_height < 0) {
# 324 : 0 : return error("%s: start height (%d) is negative", __func__, start_height);
# 325 : 0 : }
# 326 [ - + ]: 868 : if (start_height > stop_index->nHeight) {
# 327 : 0 : return error("%s: start height (%d) is greater than stop height (%d)",
# 328 : 0 : __func__, start_height, stop_index->nHeight);
# 329 : 0 : }
# 330 : :
# 331 : 868 : size_t results_size = static_cast<size_t>(stop_index->nHeight - start_height + 1);
# 332 : 868 : std::vector<std::pair<uint256, DBVal>> values(results_size);
# 333 : :
# 334 : 868 : DBHeightKey key(start_height);
# 335 : 868 : std::unique_ptr<CDBIterator> db_it(db.NewIterator());
# 336 : 868 : db_it->Seek(DBHeightKey(start_height));
# 337 [ + + ]: 3755 : for (int height = start_height; height <= stop_index->nHeight; ++height) {
# 338 [ + + ][ - + ]: 3291 : if (!db_it->Valid() || !db_it->GetKey(key) || key.height != height) {
# [ - + ]
# 339 : 404 : return false;
# 340 : 404 : }
# 341 : :
# 342 : 2887 : size_t i = static_cast<size_t>(height - start_height);
# 343 [ - + ]: 2887 : if (!db_it->GetValue(values[i])) {
# 344 : 0 : return error("%s: unable to read value in %s at key (%c, %d)",
# 345 : 0 : __func__, index_name, DB_BLOCK_HEIGHT, height);
# 346 : 0 : }
# 347 : :
# 348 : 2887 : db_it->Next();
# 349 : 2887 : }
# 350 : :
# 351 : 868 : results.resize(results_size);
# 352 : :
# 353 : : // Iterate backwards through block indexes collecting results in order to access the block hash
# 354 : : // of each entry in case we need to look it up in the hash index.
# 355 : 464 : for (const CBlockIndex* block_index = stop_index;
# 356 [ + + ][ + + ]: 3351 : block_index && block_index->nHeight >= start_height;
# 357 : 2887 : block_index = block_index->pprev) {
# 358 : 2887 : uint256 block_hash = block_index->GetBlockHash();
# 359 : :
# 360 : 2887 : size_t i = static_cast<size_t>(block_index->nHeight - start_height);
# 361 [ + + ]: 2887 : if (block_hash == values[i].first) {
# 362 : 2865 : results[i] = std::move(values[i].second);
# 363 : 2865 : continue;
# 364 : 2865 : }
# 365 : :
# 366 [ - + ]: 22 : if (!db.Read(DBHashKey(block_hash), results[i])) {
# 367 : 0 : return error("%s: unable to read value in %s at key (%c, %s)",
# 368 : 0 : __func__, index_name, DB_BLOCK_HASH, block_hash.ToString());
# 369 : 0 : }
# 370 : 22 : }
# 371 : :
# 372 : 464 : return true;
# 373 : 464 : }
# 374 : :
# 375 : : bool BlockFilterIndex::LookupFilter(const CBlockIndex* block_index, BlockFilter& filter_out) const
# 376 : 444 : {
# 377 : 444 : DBVal entry;
# 378 [ + + ]: 444 : if (!LookupOne(*m_db, block_index, entry)) {
# 379 : 202 : return false;
# 380 : 202 : }
# 381 : :
# 382 : 242 : return ReadFilterFromDisk(entry.pos, filter_out);
# 383 : 242 : }
# 384 : :
# 385 : : bool BlockFilterIndex::LookupFilterHeader(const CBlockIndex* block_index, uint256& header_out)
# 386 : 450 : {
# 387 : 450 : LOCK(m_cs_headers_cache);
# 388 : :
# 389 : 450 : bool is_checkpoint{block_index->nHeight % CFCHECKPT_INTERVAL == 0};
# 390 : :
# 391 [ + + ]: 450 : if (is_checkpoint) {
# 392 : : // Try to find the block in the headers cache if this is a checkpoint height.
# 393 : 15 : auto header = m_headers_cache.find(block_index->GetBlockHash());
# 394 [ + + ]: 15 : if (header != m_headers_cache.end()) {
# 395 : 6 : header_out = header->second;
# 396 : 6 : return true;
# 397 : 6 : }
# 398 : 444 : }
# 399 : :
# 400 : 444 : DBVal entry;
# 401 [ + + ]: 444 : if (!LookupOne(*m_db, block_index, entry)) {
# 402 : 202 : return false;
# 403 : 202 : }
# 404 : :
# 405 [ + + ]: 242 : if (is_checkpoint &&
# 406 [ + - ]: 242 : m_headers_cache.size() < CF_HEADERS_CACHE_MAX_SZ) {
# 407 : : // Add to the headers cache if this is a checkpoint height.
# 408 : 7 : m_headers_cache.emplace(block_index->GetBlockHash(), entry.header);
# 409 : 7 : }
# 410 : :
# 411 : 242 : header_out = entry.header;
# 412 : 242 : return true;
# 413 : 242 : }
# 414 : :
# 415 : : bool BlockFilterIndex::LookupFilterRange(int start_height, const CBlockIndex* stop_index,
# 416 : : std::vector<BlockFilter>& filters_out) const
# 417 : 434 : {
# 418 : 434 : std::vector<DBVal> entries;
# 419 [ + + ]: 434 : if (!LookupRange(*m_db, m_name, start_height, stop_index, entries)) {
# 420 : 202 : return false;
# 421 : 202 : }
# 422 : :
# 423 : 232 : filters_out.resize(entries.size());
# 424 : 232 : auto filter_pos_it = filters_out.begin();
# 425 [ + + ]: 449 : for (const auto& entry : entries) {
# 426 [ - + ]: 449 : if (!ReadFilterFromDisk(entry.pos, *filter_pos_it)) {
# 427 : 0 : return false;
# 428 : 0 : }
# 429 : 449 : ++filter_pos_it;
# 430 : 449 : }
# 431 : :
# 432 : 232 : return true;
# 433 : 232 : }
# 434 : :
# 435 : : bool BlockFilterIndex::LookupFilterHashRange(int start_height, const CBlockIndex* stop_index,
# 436 : : std::vector<uint256>& hashes_out) const
# 437 : :
# 438 : 434 : {
# 439 : 434 : std::vector<DBVal> entries;
# 440 [ + + ]: 434 : if (!LookupRange(*m_db, m_name, start_height, stop_index, entries)) {
# 441 : 202 : return false;
# 442 : 202 : }
# 443 : :
# 444 : 232 : hashes_out.clear();
# 445 : 232 : hashes_out.reserve(entries.size());
# 446 [ + + ]: 2438 : for (const auto& entry : entries) {
# 447 : 2438 : hashes_out.push_back(entry.hash);
# 448 : 2438 : }
# 449 : 232 : return true;
# 450 : 232 : }
# 451 : :
# 452 : : BlockFilterIndex* GetBlockFilterIndex(BlockFilterType filter_type)
# 453 : 39 : {
# 454 : 39 : auto it = g_filter_indexes.find(filter_type);
# 455 [ + + ]: 39 : return it != g_filter_indexes.end() ? &it->second : nullptr;
# 456 : 39 : }
# 457 : :
# 458 : : void ForEachBlockFilterIndex(std::function<void (BlockFilterIndex&)> fn)
# 459 : 1701 : {
# 460 [ + + ]: 1701 : for (auto& entry : g_filter_indexes) fn(entry.second);
# 461 : 1701 : }
# 462 : :
# 463 : : bool InitBlockFilterIndex(BlockFilterType filter_type,
# 464 : : size_t n_cache_size, bool f_memory, bool f_wipe)
# 465 : 13 : {
# 466 : 13 : auto result = g_filter_indexes.emplace(std::piecewise_construct,
# 467 : 13 : std::forward_as_tuple(filter_type),
# 468 : 13 : std::forward_as_tuple(filter_type,
# 469 : 13 : n_cache_size, f_memory, f_wipe));
# 470 : 13 : return result.second;
# 471 : 13 : }
# 472 : :
# 473 : : bool DestroyBlockFilterIndex(BlockFilterType filter_type)
# 474 : 4 : {
# 475 : 4 : return g_filter_indexes.erase(filter_type);
# 476 : 4 : }
# 477 : :
# 478 : : void DestroyAllBlockFilterIndexes()
# 479 : 665 : {
# 480 : 665 : g_filter_indexes.clear();
# 481 : 665 : }
|