Skip to content

Commit 07d0e0d

Browse files
author
MarcoFalke
committed
Merge #19044: net processing: Add support for getcfilters
9e36067 [test] Add test for cfilters. (Jim Posen) 11106a4 [net processing] Message handling for getcfilters. (Jim Posen) e535670 [indexes] Fix default [de]serialization of BlockFilter. (Jim Posen) bb911ae [refactor] Pass CNode and CConnman by reference (John Newbery) Pull request description: Support `getcfilters` requests when `-peerblockfilters` is set. Does not advertise compact filter support in version messages. ACKs for top commit: Empact: re-Code Review ACK bitcoin/bitcoin@9e36067 MarcoFalke: re-ACK 9e36067 , only change is adding commit "[refactor] Pass CNode and CConnman by reference" 🥑 jkczyz: ACK 9e36067 fjahr: Code review ACK 9e36067 Tree-SHA512: b45b42a25905ef0bd9e195029185300c86856c87f78cbe17921f4a25e159ae0f6f003e61714fa43779017eb97cd89d3568419be88e47d19dc8095562939e7887
2 parents 091cc4b + 9e36067 commit 07d0e0d

File tree

7 files changed

+215
-30
lines changed

7 files changed

+215
-30
lines changed

src/blockfilter.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,8 @@ class BlockFilter
144144

145145
template <typename Stream>
146146
void Serialize(Stream& s) const {
147-
s << m_block_hash
148-
<< static_cast<uint8_t>(m_filter_type)
147+
s << static_cast<uint8_t>(m_filter_type)
148+
<< m_block_hash
149149
<< m_filter.GetEncoded();
150150
}
151151

@@ -154,8 +154,8 @@ class BlockFilter
154154
std::vector<unsigned char> encoded_filter;
155155
uint8_t filter_type;
156156

157-
s >> m_block_hash
158-
>> filter_type
157+
s >> filter_type
158+
>> m_block_hash
159159
>> encoded_filter;
160160

161161
m_filter_type = static_cast<BlockFilterType>(filter_type);

src/net_processing.cpp

Lines changed: 69 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ static constexpr unsigned int INVENTORY_BROADCAST_MAX = 7 * INVENTORY_BROADCAST_
129129
static constexpr unsigned int AVG_FEEFILTER_BROADCAST_INTERVAL = 10 * 60;
130130
/** Maximum feefilter broadcast delay after significant change. */
131131
static constexpr unsigned int MAX_FEEFILTER_CHANGE_DELAY = 5 * 60;
132+
/** Maximum number of compact filters that may be requested with one getcfilters. See BIP 157. */
133+
static constexpr uint32_t MAX_GETCFILTERS_SIZE = 1000;
132134
/** Maximum number of cf hashes that may be requested with one getcfheaders. See BIP 157. */
133135
static constexpr uint32_t MAX_GETCFHEADERS_SIZE = 2000;
134136

@@ -1999,7 +2001,7 @@ void static ProcessOrphanTx(CConnman* connman, CTxMemPool& mempool, std::set<uin
19992001
* @param[out] filter_index The filter index, if the request can be serviced.
20002002
* @return True if the request can be serviced.
20012003
*/
2002-
static bool PrepareBlockFilterRequest(CNode* pfrom, const CChainParams& chain_params,
2004+
static bool PrepareBlockFilterRequest(CNode& pfrom, const CChainParams& chain_params,
20032005
BlockFilterType filter_type, uint32_t start_height,
20042006
const uint256& stop_hash, uint32_t max_height_diff,
20052007
const CBlockIndex*& stop_index,
@@ -2010,8 +2012,8 @@ static bool PrepareBlockFilterRequest(CNode* pfrom, const CChainParams& chain_pa
20102012
gArgs.GetBoolArg("-peerblockfilters", DEFAULT_PEERBLOCKFILTERS));
20112013
if (!supported_filter_type) {
20122014
LogPrint(BCLog::NET, "peer %d requested unsupported block filter type: %d\n",
2013-
pfrom->GetId(), static_cast<uint8_t>(filter_type));
2014-
pfrom->fDisconnect = true;
2015+
pfrom.GetId(), static_cast<uint8_t>(filter_type));
2016+
pfrom.fDisconnect = true;
20152017
return false;
20162018
}
20172019

@@ -2022,8 +2024,8 @@ static bool PrepareBlockFilterRequest(CNode* pfrom, const CChainParams& chain_pa
20222024
// Check that the stop block exists and the peer would be allowed to fetch it.
20232025
if (!stop_index || !BlockRequestAllowed(stop_index, chain_params.GetConsensus())) {
20242026
LogPrint(BCLog::NET, "peer %d requested invalid block hash: %s\n",
2025-
pfrom->GetId(), stop_hash.ToString());
2026-
pfrom->fDisconnect = true;
2027+
pfrom.GetId(), stop_hash.ToString());
2028+
pfrom.fDisconnect = true;
20272029
return false;
20282030
}
20292031
}
@@ -2032,14 +2034,14 @@ static bool PrepareBlockFilterRequest(CNode* pfrom, const CChainParams& chain_pa
20322034
if (start_height > stop_height) {
20332035
LogPrint(BCLog::NET, "peer %d sent invalid getcfilters/getcfheaders with " /* Continued */
20342036
"start height %d and stop height %d\n",
2035-
pfrom->GetId(), start_height, stop_height);
2036-
pfrom->fDisconnect = true;
2037+
pfrom.GetId(), start_height, stop_height);
2038+
pfrom.fDisconnect = true;
20372039
return false;
20382040
}
20392041
if (stop_height - start_height >= max_height_diff) {
20402042
LogPrint(BCLog::NET, "peer %d requested too many cfilters/cfheaders: %d / %d\n",
2041-
pfrom->GetId(), stop_height - start_height + 1, max_height_diff);
2042-
pfrom->fDisconnect = true;
2043+
pfrom.GetId(), stop_height - start_height + 1, max_height_diff);
2044+
pfrom.fDisconnect = true;
20432045
return false;
20442046
}
20452047

@@ -2052,6 +2054,49 @@ static bool PrepareBlockFilterRequest(CNode* pfrom, const CChainParams& chain_pa
20522054
return true;
20532055
}
20542056

2057+
/**
2058+
* Handle a cfilters request.
2059+
*
2060+
* May disconnect from the peer in the case of a bad request.
2061+
*
2062+
* @param[in] pfrom The peer that we received the request from
2063+
* @param[in] vRecv The raw message received
2064+
* @param[in] chain_params Chain parameters
2065+
* @param[in] connman Pointer to the connection manager
2066+
*/
2067+
static void ProcessGetCFilters(CNode& pfrom, CDataStream& vRecv, const CChainParams& chain_params,
2068+
CConnman& connman)
2069+
{
2070+
uint8_t filter_type_ser;
2071+
uint32_t start_height;
2072+
uint256 stop_hash;
2073+
2074+
vRecv >> filter_type_ser >> start_height >> stop_hash;
2075+
2076+
const BlockFilterType filter_type = static_cast<BlockFilterType>(filter_type_ser);
2077+
2078+
const CBlockIndex* stop_index;
2079+
BlockFilterIndex* filter_index;
2080+
if (!PrepareBlockFilterRequest(pfrom, chain_params, filter_type, start_height, stop_hash,
2081+
MAX_GETCFILTERS_SIZE, stop_index, filter_index)) {
2082+
return;
2083+
}
2084+
2085+
std::vector<BlockFilter> filters;
2086+
2087+
if (!filter_index->LookupFilterRange(start_height, stop_index, filters)) {
2088+
LogPrint(BCLog::NET, "Failed to find block filter in index: filter_type=%s, start_height=%d, stop_hash=%s\n",
2089+
BlockFilterTypeName(filter_type), start_height, stop_hash.ToString());
2090+
return;
2091+
}
2092+
2093+
for (const auto& filter : filters) {
2094+
CSerializedNetMsg msg = CNetMsgMaker(pfrom.GetSendVersion())
2095+
.Make(NetMsgType::CFILTER, filter);
2096+
connman.PushMessage(&pfrom, std::move(msg));
2097+
}
2098+
}
2099+
20552100
/**
20562101
* Handle a cfheaders request.
20572102
*
@@ -2062,8 +2107,8 @@ static bool PrepareBlockFilterRequest(CNode* pfrom, const CChainParams& chain_pa
20622107
* @param[in] chain_params Chain parameters
20632108
* @param[in] connman Pointer to the connection manager
20642109
*/
2065-
static void ProcessGetCFHeaders(CNode* pfrom, CDataStream& vRecv, const CChainParams& chain_params,
2066-
CConnman* connman)
2110+
static void ProcessGetCFHeaders(CNode& pfrom, CDataStream& vRecv, const CChainParams& chain_params,
2111+
CConnman& connman)
20672112
{
20682113
uint8_t filter_type_ser;
20692114
uint32_t start_height;
@@ -2098,13 +2143,13 @@ static void ProcessGetCFHeaders(CNode* pfrom, CDataStream& vRecv, const CChainPa
20982143
return;
20992144
}
21002145

2101-
CSerializedNetMsg msg = CNetMsgMaker(pfrom->GetSendVersion())
2146+
CSerializedNetMsg msg = CNetMsgMaker(pfrom.GetSendVersion())
21022147
.Make(NetMsgType::CFHEADERS,
21032148
filter_type_ser,
21042149
stop_index->GetBlockHash(),
21052150
prev_header,
21062151
filter_hashes);
2107-
connman->PushMessage(pfrom, std::move(msg));
2152+
connman.PushMessage(&pfrom, std::move(msg));
21082153
}
21092154

21102155
/**
@@ -2117,8 +2162,8 @@ static void ProcessGetCFHeaders(CNode* pfrom, CDataStream& vRecv, const CChainPa
21172162
* @param[in] chain_params Chain parameters
21182163
* @param[in] connman Pointer to the connection manager
21192164
*/
2120-
static void ProcessGetCFCheckPt(CNode* pfrom, CDataStream& vRecv, const CChainParams& chain_params,
2121-
CConnman* connman)
2165+
static void ProcessGetCFCheckPt(CNode& pfrom, CDataStream& vRecv, const CChainParams& chain_params,
2166+
CConnman& connman)
21222167
{
21232168
uint8_t filter_type_ser;
21242169
uint256 stop_hash;
@@ -2150,12 +2195,12 @@ static void ProcessGetCFCheckPt(CNode* pfrom, CDataStream& vRecv, const CChainPa
21502195
}
21512196
}
21522197

2153-
CSerializedNetMsg msg = CNetMsgMaker(pfrom->GetSendVersion())
2198+
CSerializedNetMsg msg = CNetMsgMaker(pfrom.GetSendVersion())
21542199
.Make(NetMsgType::CFCHECKPT,
21552200
filter_type_ser,
21562201
stop_index->GetBlockHash(),
21572202
headers);
2158-
connman->PushMessage(pfrom, std::move(msg));
2203+
connman.PushMessage(&pfrom, std::move(msg));
21592204
}
21602205

21612206
bool ProcessMessage(CNode* pfrom, const std::string& msg_type, CDataStream& vRecv, int64_t nTimeReceived, const CChainParams& chainparams, ChainstateManager& chainman, CTxMemPool& mempool, CConnman* connman, BanMan* banman, const std::atomic<bool>& interruptMsgProc)
@@ -3467,13 +3512,18 @@ bool ProcessMessage(CNode* pfrom, const std::string& msg_type, CDataStream& vRec
34673512
return true;
34683513
}
34693514

3515+
if (msg_type == NetMsgType::GETCFILTERS) {
3516+
ProcessGetCFilters(*pfrom, vRecv, chainparams, *connman);
3517+
return true;
3518+
}
3519+
34703520
if (msg_type == NetMsgType::GETCFHEADERS) {
3471-
ProcessGetCFHeaders(pfrom, vRecv, chainparams, connman);
3521+
ProcessGetCFHeaders(*pfrom, vRecv, chainparams, *connman);
34723522
return true;
34733523
}
34743524

34753525
if (msg_type == NetMsgType::GETCFCHECKPT) {
3476-
ProcessGetCFCheckPt(pfrom, vRecv, chainparams, connman);
3526+
ProcessGetCFCheckPt(*pfrom, vRecv, chainparams, *connman);
34773527
return true;
34783528
}
34793529

src/protocol.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ const char *SENDCMPCT="sendcmpct";
4040
const char *CMPCTBLOCK="cmpctblock";
4141
const char *GETBLOCKTXN="getblocktxn";
4242
const char *BLOCKTXN="blocktxn";
43+
const char *GETCFILTERS="getcfilters";
44+
const char *CFILTER="cfilter";
4345
const char *GETCFHEADERS="getcfheaders";
4446
const char *CFHEADERS="cfheaders";
4547
const char *GETCFCHECKPT="getcfcheckpt";
@@ -75,6 +77,8 @@ const static std::string allNetMessageTypes[] = {
7577
NetMsgType::CMPCTBLOCK,
7678
NetMsgType::GETBLOCKTXN,
7779
NetMsgType::BLOCKTXN,
80+
NetMsgType::GETCFILTERS,
81+
NetMsgType::CFILTER,
7882
NetMsgType::GETCFHEADERS,
7983
NetMsgType::CFHEADERS,
8084
NetMsgType::GETCFCHECKPT,

src/protocol.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,17 @@ extern const char* GETBLOCKTXN;
225225
* @since protocol version 70014 as described by BIP 152
226226
*/
227227
extern const char* BLOCKTXN;
228+
/**
229+
* getcfilters requests compact filters for a range of blocks.
230+
* Only available with service bit NODE_COMPACT_FILTERS as described by
231+
* BIP 157 & 158.
232+
*/
233+
extern const char* GETCFILTERS;
234+
/**
235+
* cfilter is a response to a getcfilters request containing a single compact
236+
* filter.
237+
*/
238+
extern const char* CFILTER;
228239
/**
229240
* getcfheaders requests a compact filter header and the filter hashes for a
230241
* range of blocks, which can then be used to reconstruct the filter headers

test/functional/p2p_blockfilters.py

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
hash256,
1414
msg_getcfcheckpt,
1515
msg_getcfheaders,
16+
msg_getcfilters,
1617
ser_uint256,
1718
uint256_from_str,
1819
)
@@ -25,6 +26,21 @@
2526
wait_until,
2627
)
2728

29+
class CFiltersClient(P2PInterface):
30+
def __init__(self):
31+
super().__init__()
32+
# Store the cfilters received.
33+
self.cfilters = []
34+
35+
def pop_cfilters(self):
36+
cfilters = self.cfilters
37+
self.cfilters = []
38+
return cfilters
39+
40+
def on_cfilter(self, message):
41+
"""Store cfilters received in a list."""
42+
self.cfilters.append(message)
43+
2844
class CompactFiltersTest(BitcoinTestFramework):
2945
def set_test_params(self):
3046
self.setup_clean_chain = True
@@ -37,8 +53,8 @@ def set_test_params(self):
3753

3854
def run_test(self):
3955
# Node 0 supports COMPACT_FILTERS, node 1 does not.
40-
node0 = self.nodes[0].add_p2p_connection(P2PInterface())
41-
node1 = self.nodes[1].add_p2p_connection(P2PInterface())
56+
node0 = self.nodes[0].add_p2p_connection(CFiltersClient())
57+
node1 = self.nodes[1].add_p2p_connection(CFiltersClient())
4258

4359
# Nodes 0 & 1 share the same first 999 blocks in the chain.
4460
self.nodes[0].generate(999)
@@ -112,7 +128,8 @@ def run_test(self):
112128
)
113129
node0.send_and_ping(request)
114130
response = node0.last_message['cfheaders']
115-
assert_equal(len(response.hashes), 1000)
131+
main_cfhashes = response.hashes
132+
assert_equal(len(main_cfhashes), 1000)
116133
assert_equal(
117134
compute_last_header(response.prev_header, response.hashes),
118135
int(main_cfcheckpt, 16)
@@ -126,12 +143,50 @@ def run_test(self):
126143
)
127144
node0.send_and_ping(request)
128145
response = node0.last_message['cfheaders']
129-
assert_equal(len(response.hashes), 1000)
146+
stale_cfhashes = response.hashes
147+
assert_equal(len(stale_cfhashes), 1000)
130148
assert_equal(
131149
compute_last_header(response.prev_header, response.hashes),
132150
int(stale_cfcheckpt, 16)
133151
)
134152

153+
self.log.info("Check that peers can fetch cfilters.")
154+
stop_hash = self.nodes[0].getblockhash(10)
155+
request = msg_getcfilters(
156+
filter_type=FILTER_TYPE_BASIC,
157+
start_height=1,
158+
stop_hash=int(stop_hash, 16)
159+
)
160+
node0.send_message(request)
161+
node0.sync_with_ping()
162+
response = node0.pop_cfilters()
163+
assert_equal(len(response), 10)
164+
165+
self.log.info("Check that cfilter responses are correct.")
166+
for cfilter, cfhash, height in zip(response, main_cfhashes, range(1, 11)):
167+
block_hash = self.nodes[0].getblockhash(height)
168+
assert_equal(cfilter.filter_type, FILTER_TYPE_BASIC)
169+
assert_equal(cfilter.block_hash, int(block_hash, 16))
170+
computed_cfhash = uint256_from_str(hash256(cfilter.filter_data))
171+
assert_equal(computed_cfhash, cfhash)
172+
173+
self.log.info("Check that peers can fetch cfilters for stale blocks.")
174+
request = msg_getcfilters(
175+
filter_type=FILTER_TYPE_BASIC,
176+
start_height=1000,
177+
stop_hash=int(stale_block_hash, 16)
178+
)
179+
node0.send_message(request)
180+
node0.sync_with_ping()
181+
response = node0.pop_cfilters()
182+
assert_equal(len(response), 1)
183+
184+
cfilter = response[0]
185+
assert_equal(cfilter.filter_type, FILTER_TYPE_BASIC)
186+
assert_equal(cfilter.block_hash, int(stale_block_hash, 16))
187+
computed_cfhash = uint256_from_str(hash256(cfilter.filter_data))
188+
assert_equal(computed_cfhash, stale_cfhashes[999])
189+
135190
self.log.info("Requests to node 1 without NODE_COMPACT_FILTERS results in disconnection.")
136191
requests = [
137192
msg_getcfcheckpt(
@@ -143,6 +198,11 @@ def run_test(self):
143198
start_height=1000,
144199
stop_hash=int(main_block_hash, 16)
145200
),
201+
msg_getcfilters(
202+
filter_type=FILTER_TYPE_BASIC,
203+
start_height=1000,
204+
stop_hash=int(main_block_hash, 16)
205+
),
146206
]
147207
for request in requests:
148208
node1 = self.nodes[1].add_p2p_connection(P2PInterface())
@@ -151,6 +211,12 @@ def run_test(self):
151211

152212
self.log.info("Check that invalid requests result in disconnection.")
153213
requests = [
214+
# Requesting too many filters results in disconnection.
215+
msg_getcfilters(
216+
filter_type=FILTER_TYPE_BASIC,
217+
start_height=0,
218+
stop_hash=int(main_block_hash, 16)
219+
),
154220
# Requesting too many filter headers results in disconnection.
155221
msg_getcfheaders(
156222
filter_type=FILTER_TYPE_BASIC,

0 commit comments

Comments
 (0)