mirror of
https://github.com/dashpay/dash.git
synced 2024-12-25 03:52:49 +01:00
merge bitcoin#18877: Serve cfcheckpt requests
This commit is contained in:
parent
84c25db459
commit
5b22d6d0ac
@ -553,6 +553,7 @@ void SetupServerArgs()
|
||||
gArgs.AddArg("-maxuploadtarget=<n>", strprintf("Tries to keep outbound traffic under the given target (in MiB per 24h), 0 = no limit (default: %d)", DEFAULT_MAX_UPLOAD_TARGET), false, OptionsCategory::CONNECTION);
|
||||
gArgs.AddArg("-onion=<ip:port>", "Use separate SOCKS5 proxy to reach peers via Tor hidden services, set -noonion to disable (default: -proxy)", false, OptionsCategory::CONNECTION);
|
||||
gArgs.AddArg("-onlynet=<net>", "Make outgoing connections only through network <net> (ipv4, ipv6 or onion). Incoming connections are not affected by this option. This option can be specified multiple times to allow multiple networks.", false, OptionsCategory::CONNECTION);
|
||||
gArgs.AddArg("-peerblockfilters", strprintf("Serve compact block filters to peers per BIP 157 (default: %u)", DEFAULT_PEERBLOCKFILTERS), false, OptionsCategory::CONNECTION);
|
||||
gArgs.AddArg("-peerbloomfilters", strprintf("Support filtering of blocks and transaction with bloom filters (default: %u)", DEFAULT_PEERBLOOMFILTERS), false, OptionsCategory::CONNECTION);
|
||||
gArgs.AddArg("-peertimeout=<n>", strprintf("Specify p2p connection timeout in seconds. This option determines the amount of time a peer may be inactive before the connection to it is dropped. (minimum: 1, default: %d)", DEFAULT_PEER_CONNECT_TIMEOUT), false, OptionsCategory::CONNECTION);
|
||||
gArgs.AddArg("-permitbaremultisig", strprintf("Relay non-P2SH multisig (default: %u)", DEFAULT_PERMIT_BAREMULTISIG), false, OptionsCategory::CONNECTION);
|
||||
@ -1304,6 +1305,13 @@ bool AppInitParameterInteraction()
|
||||
}
|
||||
}
|
||||
|
||||
// Basic filters are the only supported filters. The basic filters index must be enabled
|
||||
// to serve compact filters
|
||||
if (gArgs.GetBoolArg("-peerblockfilters", DEFAULT_PEERBLOCKFILTERS) &&
|
||||
g_enabled_filter_types.count(BlockFilterType::BASIC_FILTER) != 1) {
|
||||
return InitError(_("Cannot set -peerblockfilters without -blockfilterindex."));
|
||||
}
|
||||
|
||||
// if using block pruning, then disallow txindex and require disabling governance validation
|
||||
if (gArgs.GetArg("-prune", 0)) {
|
||||
if (gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX))
|
||||
|
@ -9,9 +9,12 @@
|
||||
#include <banman.h>
|
||||
#include <arith_uint256.h>
|
||||
#include <blockencodings.h>
|
||||
#include <blockfilter.h>
|
||||
#include <chainparams.h>
|
||||
#include <consensus/validation.h>
|
||||
#include <hash.h>
|
||||
#include <index/blockfilterindex.h>
|
||||
#include <validation.h>
|
||||
#include <merkleblock.h>
|
||||
#include <netmessagemaker.h>
|
||||
#include <netbase.h>
|
||||
@ -149,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;
|
||||
/** Interval between compact filter checkpoints. See BIP 157. */
|
||||
static constexpr int CFCHECKPT_INTERVAL = 1000;
|
||||
|
||||
struct COrphanTx {
|
||||
// When modifying, adapt the copy of this definition in tests/DoS_tests.
|
||||
@ -2107,6 +2112,107 @@ void static ProcessOrphanTx(CConnman* connman, std::set<uint256>& orphan_work_se
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validation logic for compact filters request handling.
|
||||
*
|
||||
* 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] chain_params Chain parameters
|
||||
* @param[in] filter_type The filter type the request is for. Must be basic filters.
|
||||
* @param[in] stop_hash The stop_hash for the request
|
||||
* @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,
|
||||
const CBlockIndex*& stop_index,
|
||||
const BlockFilterIndex*& filter_index)
|
||||
{
|
||||
const bool supported_filter_type =
|
||||
(filter_type == BlockFilterType::BASIC_FILTER &&
|
||||
gArgs.GetBoolArg("-peerblockfilters", DEFAULT_PEERBLOCKFILTERS));
|
||||
if (!supported_filter_type) {
|
||||
LogPrint(BCLog::NET, "peer %d requested unsupported block filter type: %d\n",
|
||||
pfrom->GetId(), static_cast<uint8_t>(filter_type));
|
||||
pfrom->fDisconnect = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
LOCK(cs_main);
|
||||
stop_index = LookupBlockIndex(stop_hash);
|
||||
|
||||
// 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;
|
||||
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));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a getcfcheckpt 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 ProcessGetCFCheckPt(CNode* pfrom, CDataStream& vRecv, const CChainParams& chain_params,
|
||||
CConnman* connman)
|
||||
{
|
||||
uint8_t filter_type_ser;
|
||||
uint256 stop_hash;
|
||||
|
||||
vRecv >> filter_type_ser >> stop_hash;
|
||||
|
||||
const BlockFilterType filter_type = static_cast<BlockFilterType>(filter_type_ser);
|
||||
|
||||
const CBlockIndex* stop_index;
|
||||
const BlockFilterIndex* filter_index;
|
||||
if (!PrepareBlockFilterRequest(pfrom, chain_params, filter_type, stop_hash,
|
||||
stop_index, filter_index)) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<uint256> headers(stop_index->nHeight / CFCHECKPT_INTERVAL);
|
||||
|
||||
// Populate headers.
|
||||
const CBlockIndex* block_index = stop_index;
|
||||
for (int i = headers.size() - 1; i >= 0; i--) {
|
||||
int height = (i + 1) * CFCHECKPT_INTERVAL;
|
||||
block_index = block_index->GetAncestor(height);
|
||||
|
||||
if (!filter_index->LookupFilterHeader(block_index, headers[i])) {
|
||||
LogPrint(BCLog::NET, "Failed to find block filter header in index: filter_type=%s, block_hash=%s\n",
|
||||
BlockFilterTypeName(filter_type), block_index->GetBlockHash().ToString());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
CSerializedNetMsg msg = CNetMsgMaker(pfrom->GetSendVersion())
|
||||
.Make(NetMsgType::CFCHECKPT,
|
||||
filter_type_ser,
|
||||
stop_index->GetBlockHash(),
|
||||
headers);
|
||||
connman->PushMessage(pfrom, std::move(msg));
|
||||
}
|
||||
|
||||
std::string RejectCodeToString(const unsigned char code)
|
||||
{
|
||||
if (code == REJECT_MALFORMED)
|
||||
@ -3639,6 +3745,11 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strCommand == NetMsgType::GETCFCHECKPT) {
|
||||
ProcessGetCFCheckPt(pfrom, vRecv, chainparams, connman);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
if (strCommand == NetMsgType::MNLISTDIFF) {
|
||||
// we have never requested this
|
||||
|
@ -21,6 +21,7 @@ static const unsigned int DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN = 100;
|
||||
/** Default for BIP61 (sending reject messages) */
|
||||
static constexpr bool DEFAULT_ENABLE_BIP61 = true;
|
||||
static const bool DEFAULT_PEERBLOOMFILTERS = true;
|
||||
static const bool DEFAULT_PEERBLOCKFILTERS = false;
|
||||
|
||||
class PeerLogicValidation final : public CValidationInterface, public NetEventsInterface {
|
||||
private:
|
||||
|
@ -38,6 +38,8 @@ const char *SENDCMPCT="sendcmpct";
|
||||
const char *CMPCTBLOCK="cmpctblock";
|
||||
const char *GETBLOCKTXN="getblocktxn";
|
||||
const char *BLOCKTXN="blocktxn";
|
||||
const char *GETCFCHECKPT="getcfcheckpt";
|
||||
const char *CFCHECKPT="cfcheckpt";
|
||||
// Dash message types
|
||||
const char *LEGACYTXLOCKREQUEST="ix";
|
||||
const char *SPORK="spork";
|
||||
@ -108,6 +110,8 @@ const static std::string allNetMessageTypes[] = {
|
||||
NetMsgType::CMPCTBLOCK,
|
||||
NetMsgType::GETBLOCKTXN,
|
||||
NetMsgType::BLOCKTXN,
|
||||
NetMsgType::GETCFCHECKPT,
|
||||
NetMsgType::CFCHECKPT,
|
||||
// Dash message types
|
||||
// NOTE: do NOT include non-implmented here, we want them to be "Unknown command" in ProcessMessage()
|
||||
NetMsgType::LEGACYTXLOCKREQUEST,
|
||||
|
@ -216,6 +216,20 @@ extern const char *GETBLOCKTXN;
|
||||
* @since protocol version 70209 as described by BIP 152
|
||||
*/
|
||||
extern const char *BLOCKTXN;
|
||||
/**
|
||||
* getcfcheckpt requests evenly spaced compact filter headers, enabling
|
||||
* parallelized download and validation of the headers between them.
|
||||
* Only available with service bit NODE_COMPACT_FILTERS as described by
|
||||
* BIP 157 & 158.
|
||||
*/
|
||||
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;
|
||||
|
||||
// Dash message types
|
||||
// NOTE: do NOT declare non-implmented here, we don't want them to be exposed to the outside
|
||||
|
134
test/functional/p2p_blockfilters.py
Executable file
134
test/functional/p2p_blockfilters.py
Executable file
@ -0,0 +1,134 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2019 The Bitcoin Core developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
"""Tests NODE_COMPACT_FILTERS (BIP 157/158).
|
||||
|
||||
Tests that a node configured with -blockfilterindex and -peerblockfilters can serve
|
||||
cfcheckpts.
|
||||
"""
|
||||
|
||||
from test_framework.messages import (
|
||||
FILTER_TYPE_BASIC,
|
||||
msg_getcfcheckpt,
|
||||
)
|
||||
from test_framework.mininode import P2PInterface
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
connect_nodes,
|
||||
disconnect_nodes,
|
||||
wait_until,
|
||||
)
|
||||
|
||||
class CompactFiltersTest(BitcoinTestFramework):
|
||||
def set_test_params(self):
|
||||
self.setup_clean_chain = True
|
||||
self.rpc_timeout = 480
|
||||
self.num_nodes = 2
|
||||
self.extra_args = [
|
||||
["-blockfilterindex", "-peerblockfilters"],
|
||||
["-blockfilterindex"],
|
||||
]
|
||||
|
||||
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())
|
||||
|
||||
# Nodes 0 & 1 share the same first 999 blocks in the chain.
|
||||
self.nodes[0].generate(999)
|
||||
self.sync_blocks(timeout=600)
|
||||
|
||||
# Stale blocks by disconnecting nodes 0 & 1, mining, then reconnecting
|
||||
disconnect_nodes(self.nodes[0], 1)
|
||||
|
||||
self.nodes[0].generate(1)
|
||||
wait_until(lambda: self.nodes[0].getblockcount() == 1000)
|
||||
stale_block_hash = self.nodes[0].getblockhash(1000)
|
||||
|
||||
self.nodes[1].generate(1001)
|
||||
wait_until(lambda: self.nodes[1].getblockcount() == 2000)
|
||||
|
||||
self.log.info("get cfcheckpt on chain to be re-orged out.")
|
||||
request = msg_getcfcheckpt(
|
||||
filter_type=FILTER_TYPE_BASIC,
|
||||
stop_hash=int(stale_block_hash, 16)
|
||||
)
|
||||
node0.send_and_ping(message=request)
|
||||
response = node0.last_message['cfcheckpt']
|
||||
assert_equal(response.filter_type, request.filter_type)
|
||||
assert_equal(response.stop_hash, request.stop_hash)
|
||||
assert_equal(len(response.headers), 1)
|
||||
|
||||
self.log.info("Reorg node 0 to a new chain.")
|
||||
connect_nodes(self.nodes[0], 1)
|
||||
self.sync_blocks(timeout=600)
|
||||
|
||||
main_block_hash = self.nodes[0].getblockhash(1000)
|
||||
assert main_block_hash != stale_block_hash, "node 0 chain did not reorganize"
|
||||
|
||||
self.log.info("Check that peers can fetch cfcheckpt on active chain.")
|
||||
tip_hash = self.nodes[0].getbestblockhash()
|
||||
request = msg_getcfcheckpt(
|
||||
filter_type=FILTER_TYPE_BASIC,
|
||||
stop_hash=int(tip_hash, 16)
|
||||
)
|
||||
node0.send_and_ping(request)
|
||||
response = node0.last_message['cfcheckpt']
|
||||
assert_equal(response.filter_type, request.filter_type)
|
||||
assert_equal(response.stop_hash, request.stop_hash)
|
||||
|
||||
main_cfcheckpt = self.nodes[0].getblockfilter(main_block_hash, 'basic')['header']
|
||||
tip_cfcheckpt = self.nodes[0].getblockfilter(tip_hash, 'basic')['header']
|
||||
assert_equal(
|
||||
response.headers,
|
||||
[int(header, 16) for header in (main_cfcheckpt, tip_cfcheckpt)]
|
||||
)
|
||||
|
||||
self.log.info("Check that peers can fetch cfcheckpt on stale chain.")
|
||||
request = msg_getcfcheckpt(
|
||||
filter_type=FILTER_TYPE_BASIC,
|
||||
stop_hash=int(stale_block_hash, 16)
|
||||
)
|
||||
node0.send_and_ping(request)
|
||||
response = node0.last_message['cfcheckpt']
|
||||
|
||||
stale_cfcheckpt = self.nodes[0].getblockfilter(stale_block_hash, 'basic')['header']
|
||||
assert_equal(
|
||||
response.headers,
|
||||
[int(header, 16) for header in (stale_cfcheckpt,)]
|
||||
)
|
||||
|
||||
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)
|
||||
),
|
||||
]
|
||||
for request in requests:
|
||||
node1 = self.nodes[1].add_p2p_connection(P2PInterface())
|
||||
node1.send_message(request)
|
||||
node1.wait_for_disconnect()
|
||||
|
||||
self.log.info("Check that invalid requests result in disconnection.")
|
||||
requests = [
|
||||
# Requesting unknown filter type results in disconnection.
|
||||
msg_getcfcheckpt(
|
||||
filter_type=255,
|
||||
stop_hash=int(main_block_hash, 16)
|
||||
),
|
||||
# Requesting unknown hash results in disconnection.
|
||||
msg_getcfcheckpt(
|
||||
filter_type=FILTER_TYPE_BASIC,
|
||||
stop_hash=123456789,
|
||||
),
|
||||
]
|
||||
for request in requests:
|
||||
node0 = self.nodes[0].add_p2p_connection(P2PInterface())
|
||||
node0.send_message(request)
|
||||
node0.wait_for_disconnect()
|
||||
|
||||
if __name__ == '__main__':
|
||||
CompactFiltersTest().main()
|
@ -48,6 +48,8 @@ NODE_NETWORK = (1 << 0)
|
||||
NODE_BLOOM = (1 << 2)
|
||||
NODE_NETWORK_LIMITED = (1 << 10)
|
||||
|
||||
FILTER_TYPE_BASIC = 0
|
||||
|
||||
# Serialization/deserialization tools
|
||||
def sha256(s):
|
||||
return hashlib.new('sha256', s).digest()
|
||||
@ -1880,3 +1882,49 @@ 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_getcfcheckpt:
|
||||
__slots__ = ("filter_type", "stop_hash")
|
||||
command = b"getcfcheckpt"
|
||||
|
||||
def __init__(self, filter_type, stop_hash):
|
||||
self.filter_type = filter_type
|
||||
self.stop_hash = stop_hash
|
||||
|
||||
def deserialize(self, f):
|
||||
self.filter_type = struct.unpack("<B", f.read(1))[0]
|
||||
self.stop_hash = deser_uint256(f)
|
||||
|
||||
def serialize(self):
|
||||
r = b""
|
||||
r += struct.pack("<B", self.filter_type)
|
||||
r += ser_uint256(self.stop_hash)
|
||||
return r
|
||||
|
||||
def __repr__(self):
|
||||
return "msg_getcfcheckpt(filter_type={:#x}, stop_hash={:x})".format(
|
||||
self.filter_type, self.stop_hash)
|
||||
|
||||
class msg_cfcheckpt:
|
||||
__slots__ = ("filter_type", "stop_hash", "headers")
|
||||
command = b"cfcheckpt"
|
||||
|
||||
def __init__(self, filter_type=None, stop_hash=None, headers=None):
|
||||
self.filter_type = filter_type
|
||||
self.stop_hash = stop_hash
|
||||
self.headers = headers
|
||||
|
||||
def deserialize(self, f):
|
||||
self.filter_type = struct.unpack("<B", f.read(1))[0]
|
||||
self.stop_hash = deser_uint256(f)
|
||||
self.headers = 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_vector(self.headers)
|
||||
return r
|
||||
|
||||
def __repr__(self):
|
||||
return "msg_cfcheckpt(filter_type={:#x}, stop_hash={:x})".format(
|
||||
self.filter_type, self.stop_hash)
|
||||
|
@ -30,6 +30,7 @@ from test_framework.messages import (
|
||||
msg_addrv2,
|
||||
msg_block,
|
||||
msg_blocktxn,
|
||||
msg_cfcheckpt,
|
||||
msg_clsig,
|
||||
msg_cmpctblock,
|
||||
msg_getaddr,
|
||||
@ -72,6 +73,7 @@ MESSAGEMAP = {
|
||||
b"addrv2": msg_addrv2,
|
||||
b"block": msg_block,
|
||||
b"blocktxn": msg_blocktxn,
|
||||
b"cfcheckpt": msg_cfcheckpt,
|
||||
b"cmpctblock": msg_cmpctblock,
|
||||
b"getaddr": msg_getaddr,
|
||||
b"getblocks": msg_getblocks,
|
||||
@ -364,6 +366,7 @@ class P2PInterface(P2PConnection):
|
||||
def on_addrv2(self, message): pass
|
||||
def on_block(self, message): pass
|
||||
def on_blocktxn(self, message): pass
|
||||
def on_cfcheckpt(self, message): pass
|
||||
def on_cmpctblock(self, message): pass
|
||||
def on_feefilter(self, message): pass
|
||||
def on_getaddr(self, message): pass
|
||||
|
@ -190,6 +190,7 @@ BASE_SCRIPTS = [
|
||||
'feature_dip0020_activation.py',
|
||||
'feature_uacomment.py',
|
||||
'p2p_unrequested_blocks.py',
|
||||
'p2p_blockfilters.py',
|
||||
'feature_asmap.py',
|
||||
'feature_includeconf.py',
|
||||
'rpc_scantxoutset.py',
|
||||
|
Loading…
Reference in New Issue
Block a user