Skip to content

Commit e4beef6

Browse files
author
MarcoFalke
committed
Merge #14121: Index for BIP 157 block filters
c7efb65 blockfilter: Update BIP 158 test vectors. (Jim Posen) 19308c9 rpc: Add getblockfilter RPC method. (Jim Posen) ff35105 init: Add CLI option to enable block filter index. (Jim Posen) accc8b8 index: Access functions for global block filter indexes. (Jim Posen) 2bc90e4 test: Unit test for block filter index reorg handling. (Jim Posen) 6bcf099 test: Unit tests for block index filter. (Jim Posen) b5e8200 index: Implement lookup methods on block filter index. (Jim Posen) 75a76e3 index: Implement block filter index with write operations. (Jim Posen) 2ad2338 serialize: Serialization support for big-endian 32-bit ints. (Jim Posen) ba6ff9a blockfilter: Functions to translate filter types to/from names. (Jim Posen) 62b7a4f index: Ensure block locator is not stale after chain reorg. (Jim Posen) 4368384 index: Allow atomic commits of index state to be extended. (Jim Posen) Pull request description: This introduces a new BlockFilterIndex class, which is required for BIP 157 support. The index is uses the asynchronous BaseIndex infrastructure driven by the ValidationInterface callbacks. Filters are stored sequentially in flat files and the disk location of each filter is indexed in LevelDB along with the filter hash and header. The index is designed to ensure persistence of filters reorganized out of the main chain to simplify the BIP 157 net implementation. Stats (block height = 565500): - Syncing the index from scratch takes 45m - Total index size is 3.8 GiB ACKs for commit c7efb6: MarcoFalke: utACK c7efb65 ryanofsky: Slightly tested ACK c7efb65 (I just rebuilt the index with the updated PR and tested the RPC). Changes since last review: rebase, fixed compile errors in internal commits, new comments, updated error messages, tweaked cache size logic, renamed commit method, renamed constants and globals, fixed whitespace, extra BlockFilterIndex::Init error check. Tree-SHA512: f8ed7a9b6f76df45933aa5eba92b27b3af83f6df2ccb3728a5c89eec80f654344dc14f055f6f63eb9b3a7649dd8af6553fe14969889e7e2fd2f8461574d18f28
2 parents dae7299 + c7efb65 commit e4beef6

18 files changed

+1240
-22
lines changed

src/Makefile.am

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ BITCOIN_CORE_H = \
134134
httprpc.h \
135135
httpserver.h \
136136
index/base.h \
137+
index/blockfilterindex.h \
137138
index/txindex.h \
138139
indirectmap.h \
139140
init.h \
@@ -263,6 +264,7 @@ libbitcoin_server_a_SOURCES = \
263264
httprpc.cpp \
264265
httpserver.cpp \
265266
index/base.cpp \
267+
index/blockfilterindex.cpp \
266268
index/txindex.cpp \
267269
interfaces/chain.cpp \
268270
interfaces/node.cpp \

src/Makefile.test.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ BITCOIN_TESTS =\
9292
test/blockchain_tests.cpp \
9393
test/blockencodings_tests.cpp \
9494
test/blockfilter_tests.cpp \
95+
test/blockfilter_index_tests.cpp \
9596
test/bloom_tests.cpp \
9697
test/bswap_tests.cpp \
9798
test/checkqueue_tests.cpp \

src/blockfilter.cpp

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
// Distributed under the MIT software license, see the accompanying
33
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44

5+
#include <mutex>
6+
#include <sstream>
7+
58
#include <blockfilter.h>
69
#include <crypto/siphash.h>
710
#include <hash.h>
@@ -15,6 +18,10 @@ static constexpr int GCS_SER_TYPE = SER_NETWORK;
1518
/// Protocol version used to serialize parameters in GCS filter encoding.
1619
static constexpr int GCS_SER_VERSION = 0;
1720

21+
static const std::map<BlockFilterType, std::string> g_filter_types = {
22+
{BlockFilterType::BASIC, "basic"},
23+
};
24+
1825
template <typename OStream>
1926
static void GolombRiceEncode(BitStreamWriter<OStream>& bitwriter, uint8_t P, uint64_t x)
2027
{
@@ -197,6 +204,57 @@ bool GCSFilter::MatchAny(const ElementSet& elements) const
197204
return MatchInternal(queries.data(), queries.size());
198205
}
199206

207+
const std::string& BlockFilterTypeName(BlockFilterType filter_type)
208+
{
209+
static std::string unknown_retval = "";
210+
auto it = g_filter_types.find(filter_type);
211+
return it != g_filter_types.end() ? it->second : unknown_retval;
212+
}
213+
214+
bool BlockFilterTypeByName(const std::string& name, BlockFilterType& filter_type) {
215+
for (const auto& entry : g_filter_types) {
216+
if (entry.second == name) {
217+
filter_type = entry.first;
218+
return true;
219+
}
220+
}
221+
return false;
222+
}
223+
224+
const std::vector<BlockFilterType>& AllBlockFilterTypes()
225+
{
226+
static std::vector<BlockFilterType> types;
227+
228+
static std::once_flag flag;
229+
std::call_once(flag, []() {
230+
types.reserve(g_filter_types.size());
231+
for (auto entry : g_filter_types) {
232+
types.push_back(entry.first);
233+
}
234+
});
235+
236+
return types;
237+
}
238+
239+
const std::string& ListBlockFilterTypes()
240+
{
241+
static std::string type_list;
242+
243+
static std::once_flag flag;
244+
std::call_once(flag, []() {
245+
std::stringstream ret;
246+
bool first = true;
247+
for (auto entry : g_filter_types) {
248+
if (!first) ret << ", ";
249+
ret << entry.second;
250+
first = false;
251+
}
252+
type_list = ret.str();
253+
});
254+
255+
return type_list;
256+
}
257+
200258
static GCSFilter::ElementSet BasicFilterElements(const CBlock& block,
201259
const CBlockUndo& block_undo)
202260
{

src/blockfilter.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#define BITCOIN_BLOCKFILTER_H
77

88
#include <stdint.h>
9+
#include <string>
910
#include <unordered_set>
1011
#include <vector>
1112

@@ -89,6 +90,18 @@ enum class BlockFilterType : uint8_t
8990
INVALID = 255,
9091
};
9192

93+
/** Get the human-readable name for a filter type. Returns empty string for unknown types. */
94+
const std::string& BlockFilterTypeName(BlockFilterType filter_type);
95+
96+
/** Find a filter type by its human-readable name. */
97+
bool BlockFilterTypeByName(const std::string& name, BlockFilterType& filter_type);
98+
99+
/** Get a list of known filter types. */
100+
const std::vector<BlockFilterType>& AllBlockFilterTypes();
101+
102+
/** Get a comma-separated list of known filter type names. */
103+
const std::string& ListBlockFilterTypes();
104+
92105
/**
93106
* Complete block filter struct as defined in BIP 157. Serialization matches
94107
* payload of "cfilter" messages.

src/index/base.cpp

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@ bool BaseIndex::DB::ReadBestBlock(CBlockLocator& locator) const
4141
return success;
4242
}
4343

44-
bool BaseIndex::DB::WriteBestBlock(const CBlockLocator& locator)
44+
void BaseIndex::DB::WriteBestBlock(CDBBatch& batch, const CBlockLocator& locator)
4545
{
46-
return Write(DB_BEST_BLOCK, locator);
46+
batch.Write(DB_BEST_BLOCK, locator);
4747
}
4848

4949
BaseIndex::~BaseIndex()
@@ -95,19 +95,29 @@ void BaseIndex::ThreadSync()
9595
int64_t last_locator_write_time = 0;
9696
while (true) {
9797
if (m_interrupt) {
98-
WriteBestBlock(pindex);
98+
m_best_block_index = pindex;
99+
// No need to handle errors in Commit. If it fails, the error will be already be
100+
// logged. The best way to recover is to continue, as index cannot be corrupted by
101+
// a missed commit to disk for an advanced index state.
102+
Commit();
99103
return;
100104
}
101105

102106
{
103107
LOCK(cs_main);
104108
const CBlockIndex* pindex_next = NextSyncBlock(pindex);
105109
if (!pindex_next) {
106-
WriteBestBlock(pindex);
107110
m_best_block_index = pindex;
108111
m_synced = true;
112+
// No need to handle errors in Commit. See rationale above.
113+
Commit();
109114
break;
110115
}
116+
if (pindex_next->pprev != pindex && !Rewind(pindex, pindex_next->pprev)) {
117+
FatalError("%s: Failed to rewind index %s to a previous chain tip",
118+
__func__, GetName());
119+
return;
120+
}
111121
pindex = pindex_next;
112122
}
113123

@@ -119,8 +129,10 @@ void BaseIndex::ThreadSync()
119129
}
120130

121131
if (last_locator_write_time + SYNC_LOCATOR_WRITE_INTERVAL < current_time) {
122-
WriteBestBlock(pindex);
132+
m_best_block_index = pindex;
123133
last_locator_write_time = current_time;
134+
// No need to handle errors in Commit. See rationale above.
135+
Commit();
124136
}
125137

126138
CBlock block;
@@ -144,12 +156,35 @@ void BaseIndex::ThreadSync()
144156
}
145157
}
146158

147-
bool BaseIndex::WriteBestBlock(const CBlockIndex* block_index)
159+
bool BaseIndex::Commit()
160+
{
161+
CDBBatch batch(GetDB());
162+
if (!CommitInternal(batch) || !GetDB().WriteBatch(batch)) {
163+
return error("%s: Failed to commit latest %s state", __func__, GetName());
164+
}
165+
return true;
166+
}
167+
168+
bool BaseIndex::CommitInternal(CDBBatch& batch)
148169
{
149170
LOCK(cs_main);
150-
if (!GetDB().WriteBestBlock(chainActive.GetLocator(block_index))) {
151-
return error("%s: Failed to write locator to disk", __func__);
171+
GetDB().WriteBestBlock(batch, chainActive.GetLocator(m_best_block_index));
172+
return true;
173+
}
174+
175+
bool BaseIndex::Rewind(const CBlockIndex* current_tip, const CBlockIndex* new_tip)
176+
{
177+
assert(current_tip == m_best_block_index);
178+
assert(current_tip->GetAncestor(new_tip->nHeight) == new_tip);
179+
180+
// In the case of a reorg, ensure persisted block locator is not stale.
181+
m_best_block_index = new_tip;
182+
if (!Commit()) {
183+
// If commit fails, revert the best block index to avoid corruption.
184+
m_best_block_index = current_tip;
185+
return false;
152186
}
187+
153188
return true;
154189
}
155190

@@ -180,6 +215,11 @@ void BaseIndex::BlockConnected(const std::shared_ptr<const CBlock>& block, const
180215
best_block_index->GetBlockHash().ToString());
181216
return;
182217
}
218+
if (best_block_index != pindex->pprev && !Rewind(best_block_index, pindex->pprev)) {
219+
FatalError("%s: Failed to rewind index %s to a previous chain tip",
220+
__func__, GetName());
221+
return;
222+
}
183223
}
184224

185225
if (WriteBlock(*block, pindex)) {
@@ -224,9 +264,10 @@ void BaseIndex::ChainStateFlushed(const CBlockLocator& locator)
224264
return;
225265
}
226266

227-
if (!GetDB().WriteBestBlock(locator)) {
228-
error("%s: Failed to write locator to disk", __func__);
229-
}
267+
// No need to handle errors in Commit. If it fails, the error will be already be logged. The
268+
// best way to recover is to continue, as index cannot be corrupted by a missed commit to disk
269+
// for an advanced index state.
270+
Commit();
230271
}
231272

232273
bool BaseIndex::BlockUntilSyncedToCurrentChain()

src/index/base.h

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class BaseIndex : public CValidationInterface
3232
bool ReadBestBlock(CBlockLocator& locator) const;
3333

3434
/// Write block locator of the chain that the txindex is in sync with.
35-
bool WriteBestBlock(const CBlockLocator& locator);
35+
void WriteBestBlock(CDBBatch& batch, const CBlockLocator& locator);
3636
};
3737

3838
private:
@@ -54,8 +54,15 @@ class BaseIndex : public CValidationInterface
5454
/// over and the sync thread exits.
5555
void ThreadSync();
5656

57-
/// Write the current chain block locator to the DB.
58-
bool WriteBestBlock(const CBlockIndex* block_index);
57+
/// Write the current index state (eg. chain block locator and subclass-specific items) to disk.
58+
///
59+
/// Recommendations for error handling:
60+
/// If called on a successor of the previous committed best block in the index, the index can
61+
/// continue processing without risk of corruption, though the index state will need to catch up
62+
/// from further behind on reboot. If the new state is not a successor of the previous state (due
63+
/// to a chain reorganization), the index must halt until Commit succeeds or else it could end up
64+
/// getting corrupted.
65+
bool Commit();
5966

6067
protected:
6168
void BlockConnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* pindex,
@@ -69,6 +76,14 @@ class BaseIndex : public CValidationInterface
6976
/// Write update index entries for a newly connected block.
7077
virtual bool WriteBlock(const CBlock& block, const CBlockIndex* pindex) { return true; }
7178

79+
/// Virtual method called internally by Commit that can be overridden to atomically
80+
/// commit more index state.
81+
virtual bool CommitInternal(CDBBatch& batch);
82+
83+
/// Rewind index to an earlier chain tip during a chain reorg. The tip must
84+
/// be an ancestor of the current best block.
85+
virtual bool Rewind(const CBlockIndex* current_tip, const CBlockIndex* new_tip);
86+
7287
virtual DB& GetDB() const = 0;
7388

7489
/// Get the name of the index for display in logs.

0 commit comments

Comments
 (0)