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