Skip to content

Commit 7d32cce

Browse files
author
MarcoFalke
committed
Merge #19010: net processing: Add support for getcfheaders
5308c97 [test] Add test for cfheaders (Jim Posen) f6b58c1 [net processing] Message handling for getcfheaders. (Jim Posen) 3bdc7c2 [doc] Add comment for m_headers_cache (John Newbery) Pull request description: Support `getcfheaders` requests when `-peerblockfilters` is set. Does not advertise compact filter support in version messages. ACKs for top commit: jkczyz: ACK 5308c97 MarcoFalke: re-ACK 5308c97 , only change is doc related 🗂 theStack: ACK 5308c97 🚀 Tree-SHA512: 240fc654f6f634c191d9f7628b6c4801f87ed514a1dd55c7de5d454d4012d1c09509a2d5a246bc7da445cd920252b4cd56a493c060cdb207b04af4ffe53b95f7
2 parents 14f8447 + 5308c97 commit 7d32cce

File tree

7 files changed

+208
-6
lines changed

7 files changed

+208
-6
lines changed

src/index/blockfilterindex.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class BlockFilterIndex final : public BaseIndex
3939
size_t WriteFilterToDisk(FlatFilePos& pos, const BlockFilter& filter);
4040

4141
Mutex m_cs_headers_cache;
42+
/** cache of block hash to filter header, to avoid disk access when responding to getcfcheckpt. */
4243
std::unordered_map<uint256, uint256, FilterHeaderHasher> m_headers_cache GUARDED_BY(m_cs_headers_cache);
4344

4445
protected:

src/net_processing.cpp

Lines changed: 83 additions & 3 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 cf hashes that may be requested with one getcfheaders. See BIP 157. */
133+
static constexpr uint32_t MAX_GETCFHEADERS_SIZE = 2000;
132134

133135
struct COrphanTx {
134136
// When modifying, adapt the copy of this definition in tests/DoS_tests.
@@ -1989,14 +1991,16 @@ void static ProcessOrphanTx(CConnman* connman, CTxMemPool& mempool, std::set<uin
19891991
* @param[in] pfrom The peer that we received the request from
19901992
* @param[in] chain_params Chain parameters
19911993
* @param[in] filter_type The filter type the request is for. Must be basic filters.
1994+
* @param[in] start_height The start height for the request
19921995
* @param[in] stop_hash The stop_hash for the request
1996+
* @param[in] max_height_diff The maximum number of items permitted to request, as specified in BIP 157
19931997
* @param[out] stop_index The CBlockIndex for the stop_hash block, if the request can be serviced.
19941998
* @param[out] filter_index The filter index, if the request can be serviced.
19951999
* @return True if the request can be serviced.
19962000
*/
19972001
static bool PrepareBlockFilterRequest(CNode* pfrom, const CChainParams& chain_params,
1998-
BlockFilterType filter_type,
1999-
const uint256& stop_hash,
2002+
BlockFilterType filter_type, uint32_t start_height,
2003+
const uint256& stop_hash, uint32_t max_height_diff,
20002004
const CBlockIndex*& stop_index,
20012005
BlockFilterIndex*& filter_index)
20022006
{
@@ -2023,6 +2027,21 @@ static bool PrepareBlockFilterRequest(CNode* pfrom, const CChainParams& chain_pa
20232027
}
20242028
}
20252029

2030+
uint32_t stop_height = stop_index->nHeight;
2031+
if (start_height > stop_height) {
2032+
LogPrint(BCLog::NET, "peer %d sent invalid getcfilters/getcfheaders with " /* Continued */
2033+
"start height %d and stop height %d\n",
2034+
pfrom->GetId(), start_height, stop_height);
2035+
pfrom->fDisconnect = true;
2036+
return false;
2037+
}
2038+
if (stop_height - start_height >= max_height_diff) {
2039+
LogPrint(BCLog::NET, "peer %d requested too many cfilters/cfheaders: %d / %d\n",
2040+
pfrom->GetId(), stop_height - start_height + 1, max_height_diff);
2041+
pfrom->fDisconnect = true;
2042+
return false;
2043+
}
2044+
20262045
filter_index = GetBlockFilterIndex(filter_type);
20272046
if (!filter_index) {
20282047
LogPrint(BCLog::NET, "Filter index for supported type %s not found\n", BlockFilterTypeName(filter_type));
@@ -2032,6 +2051,61 @@ static bool PrepareBlockFilterRequest(CNode* pfrom, const CChainParams& chain_pa
20322051
return true;
20332052
}
20342053

2054+
/**
2055+
* Handle a cfheaders request.
2056+
*
2057+
* May disconnect from the peer in the case of a bad request.
2058+
*
2059+
* @param[in] pfrom The peer that we received the request from
2060+
* @param[in] vRecv The raw message received
2061+
* @param[in] chain_params Chain parameters
2062+
* @param[in] connman Pointer to the connection manager
2063+
*/
2064+
static void ProcessGetCFHeaders(CNode* pfrom, CDataStream& vRecv, const CChainParams& chain_params,
2065+
CConnman* connman)
2066+
{
2067+
uint8_t filter_type_ser;
2068+
uint32_t start_height;
2069+
uint256 stop_hash;
2070+
2071+
vRecv >> filter_type_ser >> start_height >> stop_hash;
2072+
2073+
const BlockFilterType filter_type = static_cast<BlockFilterType>(filter_type_ser);
2074+
2075+
const CBlockIndex* stop_index;
2076+
BlockFilterIndex* filter_index;
2077+
if (!PrepareBlockFilterRequest(pfrom, chain_params, filter_type, start_height, stop_hash,
2078+
MAX_GETCFHEADERS_SIZE, stop_index, filter_index)) {
2079+
return;
2080+
}
2081+
2082+
uint256 prev_header;
2083+
if (start_height > 0) {
2084+
const CBlockIndex* const prev_block =
2085+
stop_index->GetAncestor(static_cast<int>(start_height - 1));
2086+
if (!filter_index->LookupFilterHeader(prev_block, prev_header)) {
2087+
LogPrint(BCLog::NET, "Failed to find block filter header in index: filter_type=%s, block_hash=%s\n",
2088+
BlockFilterTypeName(filter_type), prev_block->GetBlockHash().ToString());
2089+
return;
2090+
}
2091+
}
2092+
2093+
std::vector<uint256> filter_hashes;
2094+
if (!filter_index->LookupFilterHashRange(start_height, stop_index, filter_hashes)) {
2095+
LogPrint(BCLog::NET, "Failed to find block filter hashes in index: filter_type=%s, start_height=%d, stop_hash=%s\n",
2096+
BlockFilterTypeName(filter_type), start_height, stop_hash.ToString());
2097+
return;
2098+
}
2099+
2100+
CSerializedNetMsg msg = CNetMsgMaker(pfrom->GetSendVersion())
2101+
.Make(NetMsgType::CFHEADERS,
2102+
filter_type_ser,
2103+
stop_index->GetBlockHash(),
2104+
prev_header,
2105+
filter_hashes);
2106+
connman->PushMessage(pfrom, std::move(msg));
2107+
}
2108+
20352109
/**
20362110
* Handle a getcfcheckpt request.
20372111
*
@@ -2054,7 +2128,8 @@ static void ProcessGetCFCheckPt(CNode* pfrom, CDataStream& vRecv, const CChainPa
20542128

20552129
const CBlockIndex* stop_index;
20562130
BlockFilterIndex* filter_index;
2057-
if (!PrepareBlockFilterRequest(pfrom, chain_params, filter_type, stop_hash,
2131+
if (!PrepareBlockFilterRequest(pfrom, chain_params, filter_type, /*start_height=*/0, stop_hash,
2132+
/*max_height_diff=*/std::numeric_limits<uint32_t>::max(),
20582133
stop_index, filter_index)) {
20592134
return;
20602135
}
@@ -3391,6 +3466,11 @@ bool ProcessMessage(CNode* pfrom, const std::string& msg_type, CDataStream& vRec
33913466
return true;
33923467
}
33933468

3469+
if (msg_type == NetMsgType::GETCFHEADERS) {
3470+
ProcessGetCFHeaders(pfrom, vRecv, chainparams, connman);
3471+
return true;
3472+
}
3473+
33943474
if (msg_type == NetMsgType::GETCFCHECKPT) {
33953475
ProcessGetCFCheckPt(pfrom, vRecv, chainparams, connman);
33963476
return true;

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 *GETCFHEADERS="getcfheaders";
44+
const char *CFHEADERS="cfheaders";
4345
const char *GETCFCHECKPT="getcfcheckpt";
4446
const char *CFCHECKPT="cfcheckpt";
4547
} // namespace NetMsgType
@@ -73,6 +75,8 @@ const static std::string allNetMessageTypes[] = {
7375
NetMsgType::CMPCTBLOCK,
7476
NetMsgType::GETBLOCKTXN,
7577
NetMsgType::BLOCKTXN,
78+
NetMsgType::GETCFHEADERS,
79+
NetMsgType::CFHEADERS,
7680
NetMsgType::GETCFCHECKPT,
7781
NetMsgType::CFCHECKPT,
7882
};

src/protocol.h

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,19 @@ extern const char* GETBLOCKTXN;
225225
* @since protocol version 70014 as described by BIP 152
226226
*/
227227
extern const char* BLOCKTXN;
228+
/**
229+
* getcfheaders requests a compact filter header and the filter hashes for a
230+
* range of blocks, which can then be used to reconstruct the filter headers
231+
* for those blocks.
232+
* Only available with service bit NODE_COMPACT_FILTERS as described by
233+
* BIP 157 & 158.
234+
*/
235+
extern const char* GETCFHEADERS;
236+
/**
237+
* cfheaders is a response to a getcfheaders request containing a filter header
238+
* and a vector of filter hashes for each subsequent block in the requested range.
239+
*/
240+
extern const char* CFHEADERS;
228241
/**
229242
* getcfcheckpt requests evenly spaced compact filter headers, enabling
230243
* parallelized download and validation of the headers between them.
@@ -235,8 +248,6 @@ extern const char* GETCFCHECKPT;
235248
/**
236249
* cfcheckpt is a response to a getcfcheckpt request containing a vector of
237250
* evenly spaced filter headers for blocks on the requested chain.
238-
* Only available with service bit NODE_COMPACT_FILTERS as described by
239-
* BIP 157 & 158.
240251
*/
241252
extern const char* CFCHECKPT;
242253
}; // namespace NetMsgType

test/functional/p2p_blockfilters.py

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@
55
"""Tests NODE_COMPACT_FILTERS (BIP 157/158).
66
77
Tests that a node configured with -blockfilterindex and -peerblockfilters can serve
8-
cfcheckpts.
8+
cfheaders and cfcheckpts.
99
"""
1010

1111
from test_framework.messages import (
1212
FILTER_TYPE_BASIC,
13+
hash256,
1314
msg_getcfcheckpt,
15+
msg_getcfheaders,
16+
ser_uint256,
17+
uint256_from_str,
1418
)
1519
from test_framework.mininode import P2PInterface
1620
from test_framework.test_framework import BitcoinTestFramework
@@ -100,12 +104,45 @@ def run_test(self):
100104
[int(header, 16) for header in (stale_cfcheckpt,)]
101105
)
102106

107+
self.log.info("Check that peers can fetch cfheaders on active chain.")
108+
request = msg_getcfheaders(
109+
filter_type=FILTER_TYPE_BASIC,
110+
start_height=1,
111+
stop_hash=int(main_block_hash, 16)
112+
)
113+
node0.send_and_ping(request)
114+
response = node0.last_message['cfheaders']
115+
assert_equal(len(response.hashes), 1000)
116+
assert_equal(
117+
compute_last_header(response.prev_header, response.hashes),
118+
int(main_cfcheckpt, 16)
119+
)
120+
121+
self.log.info("Check that peers can fetch cfheaders on stale chain.")
122+
request = msg_getcfheaders(
123+
filter_type=FILTER_TYPE_BASIC,
124+
start_height=1,
125+
stop_hash=int(stale_block_hash, 16)
126+
)
127+
node0.send_and_ping(request)
128+
response = node0.last_message['cfheaders']
129+
assert_equal(len(response.hashes), 1000)
130+
assert_equal(
131+
compute_last_header(response.prev_header, response.hashes),
132+
int(stale_cfcheckpt, 16)
133+
)
134+
103135
self.log.info("Requests to node 1 without NODE_COMPACT_FILTERS results in disconnection.")
104136
requests = [
105137
msg_getcfcheckpt(
106138
filter_type=FILTER_TYPE_BASIC,
107139
stop_hash=int(main_block_hash, 16)
108140
),
141+
msg_getcfheaders(
142+
filter_type=FILTER_TYPE_BASIC,
143+
start_height=1000,
144+
stop_hash=int(main_block_hash, 16)
145+
),
109146
]
110147
for request in requests:
111148
node1 = self.nodes[1].add_p2p_connection(P2PInterface())
@@ -114,6 +151,12 @@ def run_test(self):
114151

115152
self.log.info("Check that invalid requests result in disconnection.")
116153
requests = [
154+
# Requesting too many filter headers results in disconnection.
155+
msg_getcfheaders(
156+
filter_type=FILTER_TYPE_BASIC,
157+
start_height=0,
158+
stop_hash=int(tip_hash, 16)
159+
),
117160
# Requesting unknown filter type results in disconnection.
118161
msg_getcfcheckpt(
119162
filter_type=255,
@@ -130,5 +173,12 @@ def run_test(self):
130173
node0.send_message(request)
131174
node0.wait_for_disconnect()
132175

176+
def compute_last_header(prev_header, hashes):
177+
"""Compute the last filter header from a starting header and a sequence of filter hashes."""
178+
header = ser_uint256(prev_header)
179+
for filter_hash in hashes:
180+
header = hash256(ser_uint256(filter_hash) + header)
181+
return uint256_from_str(header)
182+
133183
if __name__ == '__main__':
134184
CompactFiltersTest().main()

test/functional/test_framework/messages.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1516,6 +1516,59 @@ class msg_no_witness_blocktxn(msg_blocktxn):
15161516
def serialize(self):
15171517
return self.block_transactions.serialize(with_witness=False)
15181518

1519+
class msg_getcfheaders:
1520+
__slots__ = ("filter_type", "start_height", "stop_hash")
1521+
msgtype = b"getcfheaders"
1522+
1523+
def __init__(self, filter_type, start_height, stop_hash):
1524+
self.filter_type = filter_type
1525+
self.start_height = start_height
1526+
self.stop_hash = stop_hash
1527+
1528+
def deserialize(self, f):
1529+
self.filter_type = struct.unpack("<B", f.read(1))[0]
1530+
self.start_height = struct.unpack("<I", f.read(4))[0]
1531+
self.stop_hash = deser_uint256(f)
1532+
1533+
def serialize(self):
1534+
r = b""
1535+
r += struct.pack("<B", self.filter_type)
1536+
r += struct.pack("<I", self.start_height)
1537+
r += ser_uint256(self.stop_hash)
1538+
return r
1539+
1540+
def __repr__(self):
1541+
return "msg_getcfheaders(filter_type={:#x}, start_height={}, stop_hash={:x})".format(
1542+
self.filter_type, self.start_height, self.stop_hash)
1543+
1544+
class msg_cfheaders:
1545+
__slots__ = ("filter_type", "stop_hash", "prev_header", "hashes")
1546+
msgtype = b"cfheaders"
1547+
1548+
def __init__(self, filter_type=None, stop_hash=None, prev_header=None, hashes=None):
1549+
self.filter_type = filter_type
1550+
self.stop_hash = stop_hash
1551+
self.prev_header = prev_header
1552+
self.hashes = hashes
1553+
1554+
def deserialize(self, f):
1555+
self.filter_type = struct.unpack("<B", f.read(1))[0]
1556+
self.stop_hash = deser_uint256(f)
1557+
self.prev_header = deser_uint256(f)
1558+
self.hashes = deser_uint256_vector(f)
1559+
1560+
def serialize(self):
1561+
r = b""
1562+
r += struct.pack("<B", self.filter_type)
1563+
r += ser_uint256(self.stop_hash)
1564+
r += ser_uint256(self.prev_header)
1565+
r += ser_uint256_vector(self.hashes)
1566+
return r
1567+
1568+
def __repr__(self):
1569+
return "msg_cfheaders(filter_type={:#x}, stop_hash={:x})".format(
1570+
self.filter_type, self.stop_hash)
1571+
15191572
class msg_getcfcheckpt:
15201573
__slots__ = ("filter_type", "stop_hash")
15211574
msgtype = b"getcfcheckpt"

test/functional/test_framework/mininode.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
msg_block,
3232
MSG_BLOCK,
3333
msg_blocktxn,
34+
msg_cfheaders,
3435
msg_cfcheckpt,
3536
msg_cmpctblock,
3637
msg_feefilter,
@@ -68,6 +69,7 @@
6869
b"addr": msg_addr,
6970
b"block": msg_block,
7071
b"blocktxn": msg_blocktxn,
72+
b"cfheaders": msg_cfheaders,
7173
b"cfcheckpt": msg_cfcheckpt,
7274
b"cmpctblock": msg_cmpctblock,
7375
b"feefilter": msg_feefilter,
@@ -330,6 +332,7 @@ def on_close(self):
330332
def on_addr(self, message): pass
331333
def on_block(self, message): pass
332334
def on_blocktxn(self, message): pass
335+
def on_cfheaders(self, message): pass
333336
def on_cfcheckpt(self, message): pass
334337
def on_cmpctblock(self, message): pass
335338
def on_feefilter(self, message): pass

0 commit comments

Comments
 (0)