mirror of
https://github.com/dashpay/dash.git
synced 2024-12-26 04:22:55 +01:00
merge bitcoin#19010: Add support for getcfheaders
This commit is contained in:
parent
5996dbe686
commit
216036b1af
@ -39,6 +39,7 @@ private:
|
||||
size_t WriteFilterToDisk(FlatFilePos& pos, const BlockFilter& filter);
|
||||
|
||||
Mutex m_cs_headers_cache;
|
||||
/** cache of block hash to filter header, to avoid disk access when responding to getcfcheckpt. */
|
||||
std::unordered_map<uint256, uint256, FilterHeaderHasher> m_headers_cache GUARDED_BY(m_cs_headers_cache);
|
||||
|
||||
protected:
|
||||
|
@ -152,6 +152,8 @@ static const unsigned int INVENTORY_BROADCAST_INTERVAL = 5;
|
||||
* Limits the impact of low-fee transaction floods.
|
||||
* We have 4 times smaller block times in Dash, so we need to push 4 times more invs per 1MB. */
|
||||
static constexpr unsigned int INVENTORY_BROADCAST_MAX_PER_1MB_BLOCK = 4 * 7 * INVENTORY_BROADCAST_INTERVAL;
|
||||
/** Maximum number of cf hashes that may be requested with one getcfheaders. See BIP 157. */
|
||||
static constexpr uint32_t MAX_GETCFHEADERS_SIZE = 2000;
|
||||
|
||||
struct COrphanTx {
|
||||
// When modifying, adapt the copy of this definition in tests/DoS_tests.
|
||||
@ -2118,14 +2120,16 @@ void static ProcessOrphanTx(CConnman* connman, std::set<uint256>& orphan_work_se
|
||||
* @param[in] pfrom The peer that we received the request from
|
||||
* @param[in] chain_params Chain parameters
|
||||
* @param[in] filter_type The filter type the request is for. Must be basic filters.
|
||||
* @param[in] start_height The start height for the request
|
||||
* @param[in] stop_hash The stop_hash for the request
|
||||
* @param[in] max_height_diff The maximum number of items permitted to request, as specified in BIP 157
|
||||
* @param[out] stop_index The CBlockIndex for the stop_hash block, if the request can be serviced.
|
||||
* @param[out] filter_index The filter index, if the request can be serviced.
|
||||
* @return True if the request can be serviced.
|
||||
*/
|
||||
static bool PrepareBlockFilterRequest(CNode* pfrom, const CChainParams& chain_params,
|
||||
BlockFilterType filter_type,
|
||||
const uint256& stop_hash,
|
||||
BlockFilterType filter_type, uint32_t start_height,
|
||||
const uint256& stop_hash, uint32_t max_height_diff,
|
||||
const CBlockIndex*& stop_index,
|
||||
BlockFilterIndex*& filter_index)
|
||||
{
|
||||
@ -2152,6 +2156,21 @@ static bool PrepareBlockFilterRequest(CNode* pfrom, const CChainParams& chain_pa
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t stop_height = stop_index->nHeight;
|
||||
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;
|
||||
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;
|
||||
return false;
|
||||
}
|
||||
|
||||
filter_index = GetBlockFilterIndex(filter_type);
|
||||
if (!filter_index) {
|
||||
LogPrint(BCLog::NET, "Filter index for supported type %s not found\n", BlockFilterTypeName(filter_type));
|
||||
@ -2161,6 +2180,61 @@ static bool PrepareBlockFilterRequest(CNode* pfrom, const CChainParams& chain_pa
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a cfheaders 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 ProcessGetCFHeaders(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<BlockFilterType>(filter_type_ser);
|
||||
|
||||
const CBlockIndex* stop_index;
|
||||
BlockFilterIndex* filter_index;
|
||||
if (!PrepareBlockFilterRequest(pfrom, chain_params, filter_type, start_height, stop_hash,
|
||||
MAX_GETCFHEADERS_SIZE, stop_index, filter_index)) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint256 prev_header;
|
||||
if (start_height > 0) {
|
||||
const CBlockIndex* const prev_block =
|
||||
stop_index->GetAncestor(static_cast<int>(start_height - 1));
|
||||
if (!filter_index->LookupFilterHeader(prev_block, prev_header)) {
|
||||
LogPrint(BCLog::NET, "Failed to find block filter header in index: filter_type=%s, block_hash=%s\n",
|
||||
BlockFilterTypeName(filter_type), prev_block->GetBlockHash().ToString());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint256> filter_hashes;
|
||||
if (!filter_index->LookupFilterHashRange(start_height, stop_index, filter_hashes)) {
|
||||
LogPrint(BCLog::NET, "Failed to find block filter hashes in index: filter_type=%s, start_height=%d, stop_hash=%s\n",
|
||||
BlockFilterTypeName(filter_type), start_height, stop_hash.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
CSerializedNetMsg msg = CNetMsgMaker(pfrom->GetSendVersion())
|
||||
.Make(NetMsgType::CFHEADERS,
|
||||
filter_type_ser,
|
||||
stop_index->GetBlockHash(),
|
||||
prev_header,
|
||||
filter_hashes);
|
||||
connman->PushMessage(pfrom, std::move(msg));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a getcfcheckpt request.
|
||||
*
|
||||
@ -2183,7 +2257,8 @@ static void ProcessGetCFCheckPt(CNode* pfrom, CDataStream& vRecv, const CChainPa
|
||||
|
||||
const CBlockIndex* stop_index;
|
||||
BlockFilterIndex* filter_index;
|
||||
if (!PrepareBlockFilterRequest(pfrom, chain_params, filter_type, stop_hash,
|
||||
if (!PrepareBlockFilterRequest(pfrom, chain_params, filter_type, /*start_height=*/0, stop_hash,
|
||||
/*max_height_diff=*/std::numeric_limits<uint32_t>::max(),
|
||||
stop_index, filter_index)) {
|
||||
return;
|
||||
}
|
||||
@ -3743,6 +3818,11 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strCommand == NetMsgType::GETCFHEADERS) {
|
||||
ProcessGetCFHeaders(pfrom, vRecv, chainparams, connman);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strCommand == NetMsgType::GETCFCHECKPT) {
|
||||
ProcessGetCFCheckPt(pfrom, vRecv, chainparams, connman);
|
||||
return true;
|
||||
|
@ -38,6 +38,8 @@ const char *SENDCMPCT="sendcmpct";
|
||||
const char *CMPCTBLOCK="cmpctblock";
|
||||
const char *GETBLOCKTXN="getblocktxn";
|
||||
const char *BLOCKTXN="blocktxn";
|
||||
const char *GETCFHEADERS="getcfheaders";
|
||||
const char *CFHEADERS="cfheaders";
|
||||
const char *GETCFCHECKPT="getcfcheckpt";
|
||||
const char *CFCHECKPT="cfcheckpt";
|
||||
// Dash message types
|
||||
@ -110,6 +112,8 @@ const static std::string allNetMessageTypes[] = {
|
||||
NetMsgType::CMPCTBLOCK,
|
||||
NetMsgType::GETBLOCKTXN,
|
||||
NetMsgType::BLOCKTXN,
|
||||
NetMsgType::GETCFHEADERS,
|
||||
NetMsgType::CFHEADERS,
|
||||
NetMsgType::GETCFCHECKPT,
|
||||
NetMsgType::CFCHECKPT,
|
||||
// Dash message types
|
||||
|
@ -216,6 +216,19 @@ extern const char *GETBLOCKTXN;
|
||||
* @since protocol version 70209 as described by BIP 152
|
||||
*/
|
||||
extern const char *BLOCKTXN;
|
||||
/**
|
||||
* 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
|
||||
* for those blocks.
|
||||
* Only available with service bit NODE_COMPACT_FILTERS as described by
|
||||
* BIP 157 & 158.
|
||||
*/
|
||||
extern const char* GETCFHEADERS;
|
||||
/**
|
||||
* cfheaders is a response to a getcfheaders request containing a filter header
|
||||
* and a vector of filter hashes for each subsequent block in the requested range.
|
||||
*/
|
||||
extern const char* CFHEADERS;
|
||||
/**
|
||||
* getcfcheckpt requests evenly spaced compact filter headers, enabling
|
||||
* parallelized download and validation of the headers between them.
|
||||
@ -226,8 +239,6 @@ extern const char *GETCFCHECKPT;
|
||||
/**
|
||||
* cfcheckpt is a response to a getcfcheckpt request containing a vector of
|
||||
* evenly spaced filter headers for blocks on the requested chain.
|
||||
* Only available with service bit NODE_COMPACT_FILTERS as described by
|
||||
* BIP 157 & 158.
|
||||
*/
|
||||
extern const char *CFCHECKPT;
|
||||
|
||||
|
@ -5,12 +5,16 @@
|
||||
"""Tests NODE_COMPACT_FILTERS (BIP 157/158).
|
||||
|
||||
Tests that a node configured with -blockfilterindex and -peerblockfilters can serve
|
||||
cfcheckpts.
|
||||
cfheaders and cfcheckpts.
|
||||
"""
|
||||
|
||||
from test_framework.messages import (
|
||||
FILTER_TYPE_BASIC,
|
||||
hash256,
|
||||
msg_getcfcheckpt,
|
||||
msg_getcfheaders,
|
||||
ser_uint256,
|
||||
uint256_from_str,
|
||||
)
|
||||
from test_framework.mininode import P2PInterface
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
@ -100,12 +104,45 @@ class CompactFiltersTest(BitcoinTestFramework):
|
||||
[int(header, 16) for header in (stale_cfcheckpt,)]
|
||||
)
|
||||
|
||||
self.log.info("Check that peers can fetch cfheaders on active chain.")
|
||||
request = msg_getcfheaders(
|
||||
filter_type=FILTER_TYPE_BASIC,
|
||||
start_height=1,
|
||||
stop_hash=int(main_block_hash, 16)
|
||||
)
|
||||
node0.send_and_ping(request)
|
||||
response = node0.last_message['cfheaders']
|
||||
assert_equal(len(response.hashes), 1000)
|
||||
assert_equal(
|
||||
compute_last_header(response.prev_header, response.hashes),
|
||||
int(main_cfcheckpt, 16)
|
||||
)
|
||||
|
||||
self.log.info("Check that peers can fetch cfheaders on stale chain.")
|
||||
request = msg_getcfheaders(
|
||||
filter_type=FILTER_TYPE_BASIC,
|
||||
start_height=1,
|
||||
stop_hash=int(stale_block_hash, 16)
|
||||
)
|
||||
node0.send_and_ping(request)
|
||||
response = node0.last_message['cfheaders']
|
||||
assert_equal(len(response.hashes), 1000)
|
||||
assert_equal(
|
||||
compute_last_header(response.prev_header, response.hashes),
|
||||
int(stale_cfcheckpt, 16)
|
||||
)
|
||||
|
||||
self.log.info("Requests to node 1 without NODE_COMPACT_FILTERS results in disconnection.")
|
||||
requests = [
|
||||
msg_getcfcheckpt(
|
||||
filter_type=FILTER_TYPE_BASIC,
|
||||
stop_hash=int(main_block_hash, 16)
|
||||
),
|
||||
msg_getcfheaders(
|
||||
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())
|
||||
@ -114,6 +151,12 @@ class CompactFiltersTest(BitcoinTestFramework):
|
||||
|
||||
self.log.info("Check that invalid requests result in disconnection.")
|
||||
requests = [
|
||||
# Requesting too many filter headers results in disconnection.
|
||||
msg_getcfheaders(
|
||||
filter_type=FILTER_TYPE_BASIC,
|
||||
start_height=0,
|
||||
stop_hash=int(tip_hash, 16)
|
||||
),
|
||||
# Requesting unknown filter type results in disconnection.
|
||||
msg_getcfcheckpt(
|
||||
filter_type=255,
|
||||
@ -130,5 +173,12 @@ class CompactFiltersTest(BitcoinTestFramework):
|
||||
node0.send_message(request)
|
||||
node0.wait_for_disconnect()
|
||||
|
||||
def compute_last_header(prev_header, hashes):
|
||||
"""Compute the last filter header from a starting header and a sequence of filter hashes."""
|
||||
header = ser_uint256(prev_header)
|
||||
for filter_hash in hashes:
|
||||
header = hash256(ser_uint256(filter_hash) + header)
|
||||
return uint256_from_str(header)
|
||||
|
||||
if __name__ == '__main__':
|
||||
CompactFiltersTest().main()
|
||||
|
@ -1882,6 +1882,59 @@ class msg_qdata:
|
||||
def __repr__(self):
|
||||
return "msg_qdata(error=%d, quorum_vvec=%d, enc_contributions=%d)" % (self.error, len(self.quorum_vvec),
|
||||
len(self.enc_contributions))
|
||||
class msg_getcfheaders:
|
||||
__slots__ = ("filter_type", "start_height", "stop_hash")
|
||||
command = b"getcfheaders"
|
||||
|
||||
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("<B", f.read(1))[0]
|
||||
self.start_height = struct.unpack("<I", f.read(4))[0]
|
||||
self.stop_hash = deser_uint256(f)
|
||||
|
||||
def serialize(self):
|
||||
r = b""
|
||||
r += struct.pack("<B", self.filter_type)
|
||||
r += struct.pack("<I", self.start_height)
|
||||
r += ser_uint256(self.stop_hash)
|
||||
return r
|
||||
|
||||
def __repr__(self):
|
||||
return "msg_getcfheaders(filter_type={:#x}, start_height={}, stop_hash={:x})".format(
|
||||
self.filter_type, self.start_height, self.stop_hash)
|
||||
|
||||
class msg_cfheaders:
|
||||
__slots__ = ("filter_type", "stop_hash", "prev_header", "hashes")
|
||||
command = b"cfheaders"
|
||||
|
||||
def __init__(self, filter_type=None, stop_hash=None, prev_header=None, hashes=None):
|
||||
self.filter_type = filter_type
|
||||
self.stop_hash = stop_hash
|
||||
self.prev_header = prev_header
|
||||
self.hashes = hashes
|
||||
|
||||
def deserialize(self, f):
|
||||
self.filter_type = struct.unpack("<B", f.read(1))[0]
|
||||
self.stop_hash = deser_uint256(f)
|
||||
self.prev_header = deser_uint256(f)
|
||||
self.hashes = deser_uint256_vector(f)
|
||||
|
||||
def serialize(self):
|
||||
r = b""
|
||||
r += struct.pack("<B", self.filter_type)
|
||||
r += ser_uint256(self.stop_hash)
|
||||
r += ser_uint256(self.prev_header)
|
||||
r += ser_uint256_vector(self.hashes)
|
||||
return r
|
||||
|
||||
def __repr__(self):
|
||||
return "msg_cfheaders(filter_type={:#x}, stop_hash={:x})".format(
|
||||
self.filter_type, self.stop_hash)
|
||||
|
||||
class msg_getcfcheckpt:
|
||||
__slots__ = ("filter_type", "stop_hash")
|
||||
command = b"getcfcheckpt"
|
||||
|
@ -31,6 +31,7 @@ from test_framework.messages import (
|
||||
msg_block,
|
||||
msg_blocktxn,
|
||||
msg_cfcheckpt,
|
||||
msg_cfheaders,
|
||||
msg_clsig,
|
||||
msg_cmpctblock,
|
||||
msg_getaddr,
|
||||
@ -74,6 +75,7 @@ MESSAGEMAP = {
|
||||
b"block": msg_block,
|
||||
b"blocktxn": msg_blocktxn,
|
||||
b"cfcheckpt": msg_cfcheckpt,
|
||||
b"cfheaders": msg_cfheaders,
|
||||
b"cmpctblock": msg_cmpctblock,
|
||||
b"getaddr": msg_getaddr,
|
||||
b"getblocks": msg_getblocks,
|
||||
@ -367,6 +369,7 @@ class P2PInterface(P2PConnection):
|
||||
def on_block(self, message): pass
|
||||
def on_blocktxn(self, message): pass
|
||||
def on_cfcheckpt(self, message): pass
|
||||
def on_cfheaders(self, message): pass
|
||||
def on_cmpctblock(self, message): pass
|
||||
def on_feefilter(self, message): pass
|
||||
def on_getaddr(self, message): pass
|
||||
|
Loading…
Reference in New Issue
Block a user