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