From bb911ae7f5cbe4974ec61266d2334b95067fa49d Mon Sep 17 00:00:00 2001 From: John Newbery Date: Tue, 26 May 2020 17:24:17 -0400 Subject: [PATCH 1/4] [refactor] Pass CNode and CConnman by reference Pass CNode and CConnman by reference instead of by pointer to ProcessGetCFCheckPt() and ProcessGetCFHeaders(). --- src/net_processing.cpp | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 159036a2376..cc5b4e43623 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -1998,7 +1998,7 @@ void static ProcessOrphanTx(CConnman* connman, CTxMemPool& mempool, std::setGetId(), static_cast(filter_type)); - pfrom->fDisconnect = true; + pfrom.GetId(), static_cast(filter_type)); + pfrom.fDisconnect = true; return false; } @@ -2021,8 +2021,8 @@ static bool PrepareBlockFilterRequest(CNode* pfrom, const CChainParams& chain_pa // Check that the stop block exists and the peer would be allowed to fetch it. if (!stop_index || !BlockRequestAllowed(stop_index, chain_params.GetConsensus())) { LogPrint(BCLog::NET, "peer %d requested invalid block hash: %s\n", - pfrom->GetId(), stop_hash.ToString()); - pfrom->fDisconnect = true; + pfrom.GetId(), stop_hash.ToString()); + pfrom.fDisconnect = true; return false; } } @@ -2031,14 +2031,14 @@ static bool PrepareBlockFilterRequest(CNode* pfrom, const CChainParams& chain_pa if (start_height > stop_height) { LogPrint(BCLog::NET, "peer %d sent invalid getcfilters/getcfheaders with " /* Continued */ "start height %d and stop height %d\n", - pfrom->GetId(), start_height, stop_height); - pfrom->fDisconnect = true; + pfrom.GetId(), start_height, stop_height); + pfrom.fDisconnect = true; return false; } if (stop_height - start_height >= max_height_diff) { LogPrint(BCLog::NET, "peer %d requested too many cfilters/cfheaders: %d / %d\n", - pfrom->GetId(), stop_height - start_height + 1, max_height_diff); - pfrom->fDisconnect = true; + pfrom.GetId(), stop_height - start_height + 1, max_height_diff); + pfrom.fDisconnect = true; return false; } @@ -2061,8 +2061,8 @@ static bool PrepareBlockFilterRequest(CNode* pfrom, const CChainParams& chain_pa * @param[in] chain_params Chain parameters * @param[in] connman Pointer to the connection manager */ -static void ProcessGetCFHeaders(CNode* pfrom, CDataStream& vRecv, const CChainParams& chain_params, - CConnman* connman) +static void ProcessGetCFHeaders(CNode& pfrom, CDataStream& vRecv, const CChainParams& chain_params, + CConnman& connman) { uint8_t filter_type_ser; uint32_t start_height; @@ -2097,13 +2097,13 @@ static void ProcessGetCFHeaders(CNode* pfrom, CDataStream& vRecv, const CChainPa return; } - CSerializedNetMsg msg = CNetMsgMaker(pfrom->GetSendVersion()) + CSerializedNetMsg msg = CNetMsgMaker(pfrom.GetSendVersion()) .Make(NetMsgType::CFHEADERS, filter_type_ser, stop_index->GetBlockHash(), prev_header, filter_hashes); - connman->PushMessage(pfrom, std::move(msg)); + connman.PushMessage(&pfrom, std::move(msg)); } /** @@ -2116,8 +2116,8 @@ static void ProcessGetCFHeaders(CNode* pfrom, CDataStream& vRecv, const CChainPa * @param[in] chain_params Chain parameters * @param[in] connman Pointer to the connection manager */ -static void ProcessGetCFCheckPt(CNode* pfrom, CDataStream& vRecv, const CChainParams& chain_params, - CConnman* connman) +static void ProcessGetCFCheckPt(CNode& pfrom, CDataStream& vRecv, const CChainParams& chain_params, + CConnman& connman) { uint8_t filter_type_ser; uint256 stop_hash; @@ -2149,12 +2149,12 @@ static void ProcessGetCFCheckPt(CNode* pfrom, CDataStream& vRecv, const CChainPa } } - CSerializedNetMsg msg = CNetMsgMaker(pfrom->GetSendVersion()) + CSerializedNetMsg msg = CNetMsgMaker(pfrom.GetSendVersion()) .Make(NetMsgType::CFCHECKPT, filter_type_ser, stop_index->GetBlockHash(), headers); - connman->PushMessage(pfrom, std::move(msg)); + connman.PushMessage(&pfrom, std::move(msg)); } 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& interruptMsgProc) @@ -3467,12 +3467,12 @@ bool ProcessMessage(CNode* pfrom, const std::string& msg_type, CDataStream& vRec } if (msg_type == NetMsgType::GETCFHEADERS) { - ProcessGetCFHeaders(pfrom, vRecv, chainparams, connman); + ProcessGetCFHeaders(*pfrom, vRecv, chainparams, *connman); return true; } if (msg_type == NetMsgType::GETCFCHECKPT) { - ProcessGetCFCheckPt(pfrom, vRecv, chainparams, connman); + ProcessGetCFCheckPt(*pfrom, vRecv, chainparams, *connman); return true; } From e535670726952e43483763dfca6fc6ec2f4b0691 Mon Sep 17 00:00:00 2001 From: Jim Posen Date: Sun, 30 Jun 2019 16:23:44 +0200 Subject: [PATCH 2/4] [indexes] Fix default [de]serialization of BlockFilter. This only changes network serialization. Disk serialization does not include the filter_type and is defined in ReadFilterFromDisk()/WriteFilterToDisk(). --- src/blockfilter.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/blockfilter.h b/src/blockfilter.h index ff8744b2179..96cefbf3b2f 100644 --- a/src/blockfilter.h +++ b/src/blockfilter.h @@ -144,8 +144,8 @@ public: template void Serialize(Stream& s) const { - s << m_block_hash - << static_cast(m_filter_type) + s << static_cast(m_filter_type) + << m_block_hash << m_filter.GetEncoded(); } @@ -154,8 +154,8 @@ public: std::vector encoded_filter; uint8_t filter_type; - s >> m_block_hash - >> filter_type + s >> filter_type + >> m_block_hash >> encoded_filter; m_filter_type = static_cast(filter_type); From 11106a4722558765a44ae45c7892724a73ce514c Mon Sep 17 00:00:00 2001 From: Jim Posen Date: Sun, 30 Jun 2019 15:42:13 +0200 Subject: [PATCH 3/4] [net processing] Message handling for getcfilters. Handle getcfilters request if -peercfilter is configured. --- src/net_processing.cpp | 50 ++++++++++++++++++++++++++++++++++++++++++ src/protocol.cpp | 4 ++++ src/protocol.h | 11 ++++++++++ 3 files changed, 65 insertions(+) diff --git a/src/net_processing.cpp b/src/net_processing.cpp index cc5b4e43623..4536c737d1f 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -129,6 +129,8 @@ static constexpr unsigned int INVENTORY_BROADCAST_MAX = 7 * INVENTORY_BROADCAST_ static constexpr unsigned int AVG_FEEFILTER_BROADCAST_INTERVAL = 10 * 60; /** Maximum feefilter broadcast delay after significant change. */ static constexpr unsigned int MAX_FEEFILTER_CHANGE_DELAY = 5 * 60; +/** Maximum number of compact filters that may be requested with one getcfilters. See BIP 157. */ +static constexpr uint32_t MAX_GETCFILTERS_SIZE = 1000; /** Maximum number of cf hashes that may be requested with one getcfheaders. See BIP 157. */ static constexpr uint32_t MAX_GETCFHEADERS_SIZE = 2000; @@ -2051,6 +2053,49 @@ static bool PrepareBlockFilterRequest(CNode& pfrom, const CChainParams& chain_pa return true; } +/** + * Handle a cfilters request. + * + * May disconnect from the peer in the case of a bad request. + * + * @param[in] pfrom The peer that we received the request from + * @param[in] vRecv The raw message received + * @param[in] chain_params Chain parameters + * @param[in] connman Pointer to the connection manager + */ +static void ProcessGetCFilters(CNode& pfrom, CDataStream& vRecv, const CChainParams& chain_params, + CConnman& connman) +{ + uint8_t filter_type_ser; + uint32_t start_height; + uint256 stop_hash; + + vRecv >> filter_type_ser >> start_height >> stop_hash; + + const BlockFilterType filter_type = static_cast(filter_type_ser); + + const CBlockIndex* stop_index; + BlockFilterIndex* filter_index; + if (!PrepareBlockFilterRequest(pfrom, chain_params, filter_type, start_height, stop_hash, + MAX_GETCFILTERS_SIZE, stop_index, filter_index)) { + return; + } + + std::vector filters; + + if (!filter_index->LookupFilterRange(start_height, stop_index, filters)) { + LogPrint(BCLog::NET, "Failed to find block filter in index: filter_type=%s, start_height=%d, stop_hash=%s\n", + BlockFilterTypeName(filter_type), start_height, stop_hash.ToString()); + return; + } + + for (const auto& filter : filters) { + CSerializedNetMsg msg = CNetMsgMaker(pfrom.GetSendVersion()) + .Make(NetMsgType::CFILTER, filter); + connman.PushMessage(&pfrom, std::move(msg)); + } +} + /** * Handle a cfheaders request. * @@ -3466,6 +3511,11 @@ bool ProcessMessage(CNode* pfrom, const std::string& msg_type, CDataStream& vRec return true; } + if (msg_type == NetMsgType::GETCFILTERS) { + ProcessGetCFilters(*pfrom, vRecv, chainparams, *connman); + return true; + } + if (msg_type == NetMsgType::GETCFHEADERS) { ProcessGetCFHeaders(*pfrom, vRecv, chainparams, *connman); return true; diff --git a/src/protocol.cpp b/src/protocol.cpp index 243111c4491..947f33c3dfa 100644 --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -40,6 +40,8 @@ const char *SENDCMPCT="sendcmpct"; const char *CMPCTBLOCK="cmpctblock"; const char *GETBLOCKTXN="getblocktxn"; const char *BLOCKTXN="blocktxn"; +const char *GETCFILTERS="getcfilters"; +const char *CFILTER="cfilter"; const char *GETCFHEADERS="getcfheaders"; const char *CFHEADERS="cfheaders"; const char *GETCFCHECKPT="getcfcheckpt"; @@ -75,6 +77,8 @@ const static std::string allNetMessageTypes[] = { NetMsgType::CMPCTBLOCK, NetMsgType::GETBLOCKTXN, NetMsgType::BLOCKTXN, + NetMsgType::GETCFILTERS, + NetMsgType::CFILTER, NetMsgType::GETCFHEADERS, NetMsgType::CFHEADERS, NetMsgType::GETCFCHECKPT, diff --git a/src/protocol.h b/src/protocol.h index 9527dce9605..f61b724b742 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -225,6 +225,17 @@ extern const char* GETBLOCKTXN; * @since protocol version 70014 as described by BIP 152 */ extern const char* BLOCKTXN; +/** + * getcfilters requests compact filters for a range of blocks. + * Only available with service bit NODE_COMPACT_FILTERS as described by + * BIP 157 & 158. + */ +extern const char* GETCFILTERS; +/** + * cfilter is a response to a getcfilters request containing a single compact + * filter. + */ +extern const char* CFILTER; /** * getcfheaders requests a compact filter header and the filter hashes for a * range of blocks, which can then be used to reconstruct the filter headers From 9e36067d8cd02830c7e5a88a391dff6ac3adbe0c Mon Sep 17 00:00:00 2001 From: Jim Posen Date: Mon, 4 May 2020 14:27:29 -0400 Subject: [PATCH 4/4] [test] Add test for cfilters. --- test/functional/p2p_blockfilters.py | 74 ++++++++++++++++++++-- test/functional/test_framework/messages.py | 51 +++++++++++++++ test/functional/test_framework/mininode.py | 9 ++- 3 files changed, 127 insertions(+), 7 deletions(-) diff --git a/test/functional/p2p_blockfilters.py b/test/functional/p2p_blockfilters.py index 9ff76b4b3d7..6d947ac660e 100755 --- a/test/functional/p2p_blockfilters.py +++ b/test/functional/p2p_blockfilters.py @@ -13,6 +13,7 @@ from test_framework.messages import ( hash256, msg_getcfcheckpt, msg_getcfheaders, + msg_getcfilters, ser_uint256, uint256_from_str, ) @@ -25,6 +26,21 @@ from test_framework.util import ( wait_until, ) +class CFiltersClient(P2PInterface): + def __init__(self): + super().__init__() + # Store the cfilters received. + self.cfilters = [] + + def pop_cfilters(self): + cfilters = self.cfilters + self.cfilters = [] + return cfilters + + def on_cfilter(self, message): + """Store cfilters received in a list.""" + self.cfilters.append(message) + class CompactFiltersTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True @@ -37,8 +53,8 @@ class CompactFiltersTest(BitcoinTestFramework): def run_test(self): # Node 0 supports COMPACT_FILTERS, node 1 does not. - node0 = self.nodes[0].add_p2p_connection(P2PInterface()) - node1 = self.nodes[1].add_p2p_connection(P2PInterface()) + node0 = self.nodes[0].add_p2p_connection(CFiltersClient()) + node1 = self.nodes[1].add_p2p_connection(CFiltersClient()) # Nodes 0 & 1 share the same first 999 blocks in the chain. self.nodes[0].generate(999) @@ -112,7 +128,8 @@ class CompactFiltersTest(BitcoinTestFramework): ) node0.send_and_ping(request) response = node0.last_message['cfheaders'] - assert_equal(len(response.hashes), 1000) + main_cfhashes = response.hashes + assert_equal(len(main_cfhashes), 1000) assert_equal( compute_last_header(response.prev_header, response.hashes), int(main_cfcheckpt, 16) @@ -126,12 +143,50 @@ class CompactFiltersTest(BitcoinTestFramework): ) node0.send_and_ping(request) response = node0.last_message['cfheaders'] - assert_equal(len(response.hashes), 1000) + stale_cfhashes = response.hashes + assert_equal(len(stale_cfhashes), 1000) assert_equal( compute_last_header(response.prev_header, response.hashes), int(stale_cfcheckpt, 16) ) + self.log.info("Check that peers can fetch cfilters.") + stop_hash = self.nodes[0].getblockhash(10) + request = msg_getcfilters( + filter_type=FILTER_TYPE_BASIC, + start_height=1, + stop_hash=int(stop_hash, 16) + ) + node0.send_message(request) + node0.sync_with_ping() + response = node0.pop_cfilters() + assert_equal(len(response), 10) + + self.log.info("Check that cfilter responses are correct.") + for cfilter, cfhash, height in zip(response, main_cfhashes, range(1, 11)): + block_hash = self.nodes[0].getblockhash(height) + assert_equal(cfilter.filter_type, FILTER_TYPE_BASIC) + assert_equal(cfilter.block_hash, int(block_hash, 16)) + computed_cfhash = uint256_from_str(hash256(cfilter.filter_data)) + assert_equal(computed_cfhash, cfhash) + + self.log.info("Check that peers can fetch cfilters for stale blocks.") + request = msg_getcfilters( + filter_type=FILTER_TYPE_BASIC, + start_height=1000, + stop_hash=int(stale_block_hash, 16) + ) + node0.send_message(request) + node0.sync_with_ping() + response = node0.pop_cfilters() + assert_equal(len(response), 1) + + cfilter = response[0] + assert_equal(cfilter.filter_type, FILTER_TYPE_BASIC) + assert_equal(cfilter.block_hash, int(stale_block_hash, 16)) + computed_cfhash = uint256_from_str(hash256(cfilter.filter_data)) + assert_equal(computed_cfhash, stale_cfhashes[999]) + self.log.info("Requests to node 1 without NODE_COMPACT_FILTERS results in disconnection.") requests = [ msg_getcfcheckpt( @@ -143,6 +198,11 @@ class CompactFiltersTest(BitcoinTestFramework): start_height=1000, stop_hash=int(main_block_hash, 16) ), + msg_getcfilters( + filter_type=FILTER_TYPE_BASIC, + start_height=1000, + stop_hash=int(main_block_hash, 16) + ), ] for request in requests: node1 = self.nodes[1].add_p2p_connection(P2PInterface()) @@ -151,6 +211,12 @@ class CompactFiltersTest(BitcoinTestFramework): self.log.info("Check that invalid requests result in disconnection.") requests = [ + # Requesting too many filters results in disconnection. + msg_getcfilters( + filter_type=FILTER_TYPE_BASIC, + start_height=0, + stop_hash=int(main_block_hash, 16) + ), # Requesting too many filter headers results in disconnection. msg_getcfheaders( filter_type=FILTER_TYPE_BASIC, diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index d178e795416..4d1dd4422e5 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -1516,6 +1516,57 @@ class msg_no_witness_blocktxn(msg_blocktxn): def serialize(self): return self.block_transactions.serialize(with_witness=False) + +class msg_getcfilters: + __slots__ = ("filter_type", "start_height", "stop_hash") + msgtype = b"getcfilters" + + def __init__(self, filter_type, start_height, stop_hash): + self.filter_type = filter_type + self.start_height = start_height + self.stop_hash = stop_hash + + def deserialize(self, f): + self.filter_type = struct.unpack("