mirror of
https://github.com/dashpay/dash.git
synced 2024-12-25 03:52:49 +01:00
Merge #6333: backport: merge bitcoin#23496, #26272, #23443, #26381, #26396, #26448, #26359, #26686, #23670, partial bitcoin#19953 (Erlay support)
cc98f9e724
merge bitcoin#23670: Build minisketch test in make check, not in make (Kittywhiskers Van Gogh)606a444296
merge bitcoin#26686: Enable erlay setting in process_message(s) targets (Kittywhiskers Van Gogh)38a16a24d7
merge bitcoin#26359: Erlay support signaling follow-ups (Kittywhiskers Van Gogh)b55a6f794b
merge bitcoin#26448: fix intermittent failure in p2p_sendtxrcncl.py (Kittywhiskers Van Gogh)36be978fc9
merge bitcoin#26396: Avoid SetTxRelay for feeler connections (Kittywhiskers Van Gogh)62dc9cb17b
merge bitcoin#26381: Fix intermittent issue in p2p_sendtxrcncl.py (Kittywhiskers Van Gogh)6a7868dba7
merge bitcoin#23443: Erlay support signaling (Kittywhiskers Van Gogh)fdc3c07554
partial bitcoin#19953: Implement BIP 340-342 validation (Kittywhiskers Van Gogh)477157d40b
merge bitcoin#26272: Prevent UB in `minisketch_tests.cpp` (Kittywhiskers Van Gogh)0cf7401173
merge bitcoin#23496: Add minisketch fuzz test (Kittywhiskers Van Gogh)49ef53ce00
merge bitcoin#23491: Move minisketchwrapper to src/node (Kittywhiskers Van Gogh) Pull request description: ## Additional Information * Dependent on https://github.com/dashpay/dash/pull/6332 * Dependent on https://github.com/dashpay/dash/pull/6365 * Erlay requires nodes to be `WTXIDRELAY` (as defined by BIP-339, [source](17c04f9fa1/bip-0339.mediawiki
))-capable ([source](17c04f9fa1/bip-0330.mediawiki (sendtxrcncl)
)) as prior to `WTXIDRELAY` adoption, TXIDs of SegWit transactions didn't include the witness data in the hash, which meant, the witness data was malleable ([source](https://bitcoin.stackexchange.com/a/107394)), which would be a relevant factor when you are building out a reconciliation system where you need TXIDs to authoritatively identify a transaction's contents. As Dash _doesn't_ support SegWit, this requirement can be dispensed with. It has instead been replaced with checking if the node is running Dash Core v22 or above to retain the underlying test logic (but this can also be dispensed with as `SENDTXRCNCL` will simply be ignored by older nodes and major releases are _generally_ mandatory upgrades anyways) ## Breaking Changes None expected. ## Checklist - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas **(note: N/A)** - [x] I have added or updated relevant unit/integration/functional/e2e tests - [x] I have made corresponding changes to the documentation **(note: N/A)** - [x] I have assigned this pull request to a milestone _(for repository code-owners and collaborators only)_ ACKs for top commit: PastaPastaPasta: utACKcc98f9e724
UdjinM6: utACKcc98f9e724
Tree-SHA512: fe985f4df6c96c0a7b984882815a1bce7f23c54370198d099e41a59ac4c46c283a2b8dd95f5c8fc12eb1dc1330c4e5c21626b76d33d83d395326e8eb19d564ce
This commit is contained in:
commit
75fd7b5ea2
@ -104,6 +104,7 @@ noinst_LTLIBRARIES =
|
||||
|
||||
bin_PROGRAMS =
|
||||
noinst_PROGRAMS =
|
||||
check_PROGRAMS =
|
||||
TESTS =
|
||||
BENCHMARKS =
|
||||
|
||||
@ -258,7 +259,6 @@ BITCOIN_CORE_H = \
|
||||
memusage.h \
|
||||
merkleblock.h \
|
||||
messagesigner.h \
|
||||
minisketchwrapper.h \
|
||||
net.h \
|
||||
net_permissions.h \
|
||||
net_processing.h \
|
||||
@ -275,8 +275,10 @@ BITCOIN_CORE_H = \
|
||||
node/context.h \
|
||||
node/eviction.h \
|
||||
node/miner.h \
|
||||
node/minisketchwrapper.h \
|
||||
node/psbt.h \
|
||||
node/transaction.h \
|
||||
node/txreconciliation.h \
|
||||
node/ui_interface.h \
|
||||
node/utxo_snapshot.h \
|
||||
noui.h \
|
||||
@ -494,7 +496,6 @@ libbitcoin_server_a_SOURCES = \
|
||||
masternode/payments.cpp \
|
||||
masternode/sync.cpp \
|
||||
masternode/utils.cpp \
|
||||
minisketchwrapper.cpp \
|
||||
net.cpp \
|
||||
netfulfilledman.cpp \
|
||||
netgroup.cpp \
|
||||
@ -507,8 +508,10 @@ libbitcoin_server_a_SOURCES = \
|
||||
node/eviction.cpp \
|
||||
node/interfaces.cpp \
|
||||
node/miner.cpp \
|
||||
node/minisketchwrapper.cpp \
|
||||
node/psbt.cpp \
|
||||
node/transaction.cpp \
|
||||
node/txreconciliation.cpp \
|
||||
node/ui_interface.cpp \
|
||||
noui.cpp \
|
||||
policy/fees.cpp \
|
||||
|
@ -31,7 +31,7 @@ if ENABLE_TESTS
|
||||
if !ENABLE_FUZZ
|
||||
MINISKETCH_TEST = minisketch/test
|
||||
TESTS += $(MINISKETCH_TEST)
|
||||
noinst_PROGRAMS += $(MINISKETCH_TEST)
|
||||
check_PROGRAMS += $(MINISKETCH_TEST)
|
||||
|
||||
minisketch_test_SOURCES = $(MINISKETCH_TEST_SOURCES_INT)
|
||||
minisketch_test_CPPFLAGS = $(AM_CPPFLAGS) $(LIBMINISKETCH_CPPFLAGS)
|
||||
|
@ -57,6 +57,7 @@ FUZZ_SUITE_LD_COMMON = \
|
||||
$(LIBLEVELDB_SSE42) \
|
||||
$(LIBMEMENV) \
|
||||
$(LIBSECP256K1) \
|
||||
$(MINISKETCH_LIBS) \
|
||||
$(EVENT_LIBS) \
|
||||
$(EVENT_PTHREADS_LIBS) \
|
||||
$(GMP_LIBS) \
|
||||
@ -173,6 +174,7 @@ BITCOIN_TESTS =\
|
||||
test/torcontrol_tests.cpp \
|
||||
test/transaction_tests.cpp \
|
||||
test/txindex_tests.cpp \
|
||||
test/txreconciliation_tests.cpp \
|
||||
test/txvalidation_tests.cpp \
|
||||
test/txvalidationcache_tests.cpp \
|
||||
test/uint256_tests.cpp \
|
||||
@ -289,6 +291,7 @@ test_fuzz_fuzz_SOURCES = \
|
||||
test/fuzz/locale.cpp \
|
||||
test/fuzz/merkleblock.cpp \
|
||||
test/fuzz/message.cpp \
|
||||
test/fuzz/minisketch.cpp \
|
||||
test/fuzz/muhash.cpp \
|
||||
test/fuzz/multiplication_overflow.cpp \
|
||||
test/fuzz/net.cpp \
|
||||
|
10
src/hash.cpp
10
src/hash.cpp
@ -7,6 +7,7 @@
|
||||
#include <crypto/common.h>
|
||||
#include <crypto/hmac_sha512.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
inline uint32_t ROTL32(uint32_t x, int8_t r)
|
||||
{
|
||||
@ -84,3 +85,12 @@ uint256 SHA256Uint256(const uint256& input)
|
||||
CSHA256().Write(input.begin(), 32).Finalize(result.begin());
|
||||
return result;
|
||||
}
|
||||
|
||||
CHashWriter TaggedHash(const std::string& tag)
|
||||
{
|
||||
CHashWriter writer(SER_GETHASH, 0);
|
||||
uint256 taghash;
|
||||
CSHA256().Write((const unsigned char*)tag.data(), tag.size()).Finalize(taghash.begin());
|
||||
writer << taghash << taghash;
|
||||
return writer;
|
||||
}
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include <uint256.h>
|
||||
#include <version.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
typedef uint256 ChainCode;
|
||||
@ -241,4 +242,12 @@ unsigned int MurmurHash3(unsigned int nHashSeed, Span<const unsigned char> vData
|
||||
|
||||
void BIP32Hash(const ChainCode &chainCode, unsigned int nChild, unsigned char header, const unsigned char data[32], unsigned char output[64]);
|
||||
|
||||
/** Return a CHashWriter primed for tagged hashes (as specified in BIP 340).
|
||||
*
|
||||
* The returned object will have SHA256(tag) written to it twice (= 64 bytes).
|
||||
* A tagged hash can be computed by feeding the message into this object, and
|
||||
* then calling CHashWriter::GetSHA256().
|
||||
*/
|
||||
CHashWriter TaggedHash(const std::string& tag);
|
||||
|
||||
#endif // BITCOIN_HASH_H
|
||||
|
@ -40,6 +40,7 @@
|
||||
#include <node/blockstorage.h>
|
||||
#include <node/context.h>
|
||||
#include <node/ui_interface.h>
|
||||
#include <node/txreconciliation.h>
|
||||
#include <policy/feerate.h>
|
||||
#include <policy/fees.h>
|
||||
#include <policy/policy.h>
|
||||
@ -578,6 +579,7 @@ void SetupServerArgs(ArgsManager& argsman)
|
||||
argsman.AddArg("-v2transport", strprintf("Support v2 transport (default: %u)", DEFAULT_V2_TRANSPORT), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
|
||||
argsman.AddArg("-peerblockfilters", strprintf("Serve compact block filters to peers per BIP 157 (default: %u)", DEFAULT_PEERBLOCKFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
|
||||
argsman.AddArg("-peerbloomfilters", strprintf("Support filtering of blocks and transaction with bloom filters (default: %u)", DEFAULT_PEERBLOOMFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
|
||||
argsman.AddArg("-txreconciliation", strprintf("Enable transaction reconciliations per BIP 330 (default: %d)", DEFAULT_TXRECONCILIATION_ENABLE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CONNECTION);
|
||||
argsman.AddArg("-peertimeout=<n>", strprintf("Specify a p2p connection timeout delay in seconds. After connecting to a peer, wait this amount of time before considering disconnection based on inactivity (minimum: 1, default: %d)", DEFAULT_PEER_CONNECT_TIMEOUT), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
|
||||
argsman.AddArg("-permitbaremultisig", strprintf("Relay non-P2SH multisig (default: %u)", DEFAULT_PERMIT_BAREMULTISIG), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
|
||||
argsman.AddArg("-port=<port>", strprintf("Listen for connections on <port>. Nodes not using the default ports (default: %u, testnet: %u, regtest: %u) are unlikely to get incoming connections. Not relevant for I2P (see doc/i2p.md).", defaultChainParams->GetDefaultPort(), testnetChainParams->GetDefaultPort(), regtestChainParams->GetDefaultPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION);
|
||||
|
@ -161,6 +161,7 @@ const CLogCategoryDesc LogCategories[] =
|
||||
{BCLog::I2P, "i2p"},
|
||||
{BCLog::IPC, "ipc"},
|
||||
{BCLog::LOCK, "lock"},
|
||||
{BCLog::TXRECONCILIATION, "txreconciliation"},
|
||||
{BCLog::ALL, "1"},
|
||||
{BCLog::ALL, "all"},
|
||||
|
||||
|
@ -62,6 +62,7 @@ namespace BCLog {
|
||||
I2P = (1 << 22),
|
||||
IPC = (1 << 23),
|
||||
LOCK = (1 << 24),
|
||||
TXRECONCILIATION = (1 << 27),
|
||||
|
||||
//Start Dash
|
||||
CHAINLOCKS = ((uint64_t)1 << 32),
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include <netbase.h>
|
||||
#include <net_types.h>
|
||||
#include <node/blockstorage.h>
|
||||
#include <node/txreconciliation.h>
|
||||
#include <policy/policy.h>
|
||||
#include <primitives/block.h>
|
||||
#include <primitives/transaction.h>
|
||||
@ -730,6 +731,7 @@ private:
|
||||
BanMan* const m_banman;
|
||||
ChainstateManager& m_chainman;
|
||||
CTxMemPool& m_mempool;
|
||||
std::unique_ptr<TxReconciliationTracker> m_txreconciliation;
|
||||
const std::unique_ptr<CDeterministicMNManager>& m_dmnman;
|
||||
const std::unique_ptr<CJContext>& m_cj_ctx;
|
||||
const std::unique_ptr<LLMQContext>& m_llmq_ctx;
|
||||
@ -1633,6 +1635,7 @@ void PeerManagerImpl::FinalizeNode(const CNode& node) {
|
||||
mapBlocksInFlight.erase(entry.pindex->GetBlockHash());
|
||||
}
|
||||
WITH_LOCK(g_cs_orphans, m_orphanage.EraseForPeer(nodeid));
|
||||
if (m_txreconciliation) m_txreconciliation->ForgetPeer(nodeid);
|
||||
m_num_preferred_download_peers -= state->fPreferredDownload;
|
||||
m_peers_downloading_from -= (state->nBlocksInFlight != 0);
|
||||
assert(m_peers_downloading_from >= 0);
|
||||
@ -1935,6 +1938,11 @@ PeerManagerImpl::PeerManagerImpl(const CChainParams& chainparams, CConnman& conn
|
||||
m_mn_activeman(mn_activeman),
|
||||
m_ignore_incoming_txs(ignore_incoming_txs)
|
||||
{
|
||||
// While Erlay support is incomplete, it must be enabled explicitly via -txreconciliation.
|
||||
// This argument can go away after Erlay support is complete.
|
||||
if (gArgs.GetBoolArg("-txreconciliation", DEFAULT_TXRECONCILIATION_ENABLE)) {
|
||||
m_txreconciliation = std::make_unique<TxReconciliationTracker>(TXRECONCILIATION_VERSION);
|
||||
}
|
||||
}
|
||||
|
||||
void PeerManagerImpl::StartScheduledTasks(CScheduler& scheduler)
|
||||
@ -3509,8 +3517,6 @@ void PeerManagerImpl::ProcessMessage(
|
||||
m_connman.PushMessage(&pfrom, msg_maker.Make(NetMsgType::SENDADDRV2));
|
||||
}
|
||||
|
||||
m_connman.PushMessage(&pfrom, msg_maker.Make(NetMsgType::VERACK));
|
||||
|
||||
pfrom.m_has_all_wanted_services = HasAllDesirableServiceFlags(nServices);
|
||||
peer->m_their_services = nServices;
|
||||
pfrom.SetAddrLocal(addrMe);
|
||||
@ -3536,6 +3542,22 @@ void PeerManagerImpl::ProcessMessage(
|
||||
if (fRelay) pfrom.m_relays_txs = true;
|
||||
}
|
||||
|
||||
if (greatest_common_version >= INCREASE_MAX_HEADERS2_VERSION && m_txreconciliation) {
|
||||
// Per BIP-330, we announce txreconciliation support if:
|
||||
// - protocol version per the peer's VERSION message supports INCREASE_MAX_HEADERS2_VERSION;
|
||||
// - transaction relay is supported per the peer's VERSION message (see m_relays_txs);
|
||||
// - this is not a block-relay-only connection and not a feeler (see m_relays_txs);
|
||||
// - this is not an addr fetch connection;
|
||||
// - we are not in -blocksonly mode.
|
||||
if (pfrom.m_relays_txs && !pfrom.IsAddrFetchConn() && !m_ignore_incoming_txs) {
|
||||
const uint64_t recon_salt = m_txreconciliation->PreRegisterPeer(pfrom.GetId());
|
||||
m_connman.PushMessage(&pfrom, msg_maker.Make(NetMsgType::SENDTXRCNCL,
|
||||
TXRECONCILIATION_VERSION, recon_salt));
|
||||
}
|
||||
}
|
||||
|
||||
m_connman.PushMessage(&pfrom, msg_maker.Make(NetMsgType::VERACK));
|
||||
|
||||
// Potentially mark this peer as a preferred download peer.
|
||||
{
|
||||
LOCK(cs_main);
|
||||
@ -3677,6 +3699,15 @@ void PeerManagerImpl::ProcessMessage(
|
||||
}
|
||||
}
|
||||
|
||||
if (m_txreconciliation) {
|
||||
if (pfrom.nVersion < INCREASE_MAX_HEADERS2_VERSION || !m_txreconciliation->IsPeerRegistered(pfrom.GetId())) {
|
||||
// We could have optimistically pre-registered/registered the peer. In that case,
|
||||
// we should forget about the reconciliation state here if the node version is below
|
||||
// our minimum supported version.
|
||||
m_txreconciliation->ForgetPeer(pfrom.GetId());
|
||||
}
|
||||
}
|
||||
|
||||
pfrom.fSuccessfullyConnected = true;
|
||||
return;
|
||||
}
|
||||
@ -3727,6 +3758,61 @@ void PeerManagerImpl::ProcessMessage(
|
||||
return;
|
||||
}
|
||||
|
||||
// Received from a peer demonstrating readiness to announce transactions via reconciliations.
|
||||
// This feature negotiation must happen between VERSION and VERACK to avoid relay problems
|
||||
// from switching announcement protocols after the connection is up.
|
||||
if (msg_type == NetMsgType::SENDTXRCNCL) {
|
||||
if (!m_txreconciliation) {
|
||||
LogPrint(BCLog::NET, "sendtxrcncl from peer=%d ignored, as our node does not have txreconciliation enabled\n", pfrom.GetId());
|
||||
return;
|
||||
}
|
||||
|
||||
if (pfrom.fSuccessfullyConnected) {
|
||||
LogPrint(BCLog::NET, "sendtxrcncl received after verack from peer=%d; disconnecting\n", pfrom.GetId());
|
||||
pfrom.fDisconnect = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Peer must not offer us reconciliations if we specified no tx relay support in VERSION.
|
||||
if (RejectIncomingTxs(pfrom)) {
|
||||
LogPrint(BCLog::NET, "sendtxrcncl received from peer=%d to which we indicated no tx relay; disconnecting\n", pfrom.GetId());
|
||||
pfrom.fDisconnect = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Peer must not offer us reconciliations if they specified no tx relay support in VERSION.
|
||||
// This flag might also be false in other cases, but the RejectIncomingTxs check above
|
||||
// eliminates them, so that this flag fully represents what we are looking for.
|
||||
if (!pfrom.m_relays_txs) {
|
||||
LogPrint(BCLog::NET, "sendtxrcncl received from peer=%d which indicated no tx relay to us; disconnecting\n", pfrom.GetId());
|
||||
pfrom.fDisconnect = true;
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t peer_txreconcl_version;
|
||||
uint64_t remote_salt;
|
||||
vRecv >> peer_txreconcl_version >> remote_salt;
|
||||
|
||||
const ReconciliationRegisterResult result = m_txreconciliation->RegisterPeer(pfrom.GetId(), pfrom.IsInboundConn(),
|
||||
peer_txreconcl_version, remote_salt);
|
||||
switch (result) {
|
||||
case ReconciliationRegisterResult::NOT_FOUND:
|
||||
LogPrint(BCLog::NET, "Ignore unexpected txreconciliation signal from peer=%d\n", pfrom.GetId());
|
||||
break;
|
||||
case ReconciliationRegisterResult::SUCCESS:
|
||||
break;
|
||||
case ReconciliationRegisterResult::ALREADY_REGISTERED:
|
||||
LogPrint(BCLog::NET, "txreconciliation protocol violation from peer=%d (sendtxrcncl received from already registered peer); disconnecting\n", pfrom.GetId());
|
||||
pfrom.fDisconnect = true;
|
||||
return;
|
||||
case ReconciliationRegisterResult::PROTOCOL_VIOLATION:
|
||||
LogPrint(BCLog::NET, "txreconciliation protocol violation from peer=%d; disconnecting\n", pfrom.GetId());
|
||||
pfrom.fDisconnect = true;
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pfrom.fSuccessfullyConnected) {
|
||||
LogPrint(BCLog::NET, "Unsupported message \"%s\" prior to verack from peer=%d\n", SanitizeString(msg_type), pfrom.GetId());
|
||||
return;
|
||||
|
@ -2,7 +2,7 @@
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <minisketchwrapper.h>
|
||||
#include <node/minisketchwrapper.h>
|
||||
|
||||
#include <logging.h>
|
||||
#include <util/time.h>
|
@ -2,8 +2,8 @@
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#ifndef BITCOIN_MINISKETCHWRAPPER_H
|
||||
#define BITCOIN_MINISKETCHWRAPPER_H
|
||||
#ifndef BITCOIN_NODE_MINISKETCHWRAPPER_H
|
||||
#define BITCOIN_NODE_MINISKETCHWRAPPER_H
|
||||
|
||||
#include <minisketch.h>
|
||||
#include <cstddef>
|
||||
@ -14,4 +14,4 @@ Minisketch MakeMinisketch32(size_t capacity);
|
||||
/** Wrapper around Minisketch::CreateFP. */
|
||||
Minisketch MakeMinisketch32FP(size_t max_elements, uint32_t fpbits);
|
||||
|
||||
#endif // BITCOIN_DBWRAPPER_H
|
||||
#endif // BITCOIN_NODE_MINISKETCHWRAPPER_H
|
168
src/node/txreconciliation.cpp
Normal file
168
src/node/txreconciliation.cpp
Normal file
@ -0,0 +1,168 @@
|
||||
// Copyright (c) 2022 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <node/txreconciliation.h>
|
||||
|
||||
#include <util/check.h>
|
||||
#include <util/system.h>
|
||||
|
||||
#include <unordered_map>
|
||||
#include <variant>
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
/** Static salt component used to compute short txids for sketch construction, see BIP-330. */
|
||||
const std::string RECON_STATIC_SALT = "Tx Relay Salting";
|
||||
const CHashWriter RECON_SALT_HASHER = TaggedHash(RECON_STATIC_SALT);
|
||||
|
||||
/**
|
||||
* Salt (specified by BIP-330) constructed from contributions from both peers. It is used
|
||||
* to compute transaction short IDs, which are then used to construct a sketch representing a set
|
||||
* of transactions we want to announce to the peer.
|
||||
*/
|
||||
uint256 ComputeSalt(uint64_t salt1, uint64_t salt2)
|
||||
{
|
||||
// According to BIP-330, salts should be combined in ascending order.
|
||||
return (HashWriter(RECON_SALT_HASHER) << std::min(salt1, salt2) << std::max(salt1, salt2)).GetSHA256();
|
||||
}
|
||||
|
||||
/**
|
||||
* Keeps track of txreconciliation-related per-peer state.
|
||||
*/
|
||||
class TxReconciliationState
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* TODO: This field is public to ignore -Wunused-private-field. Make private once used in
|
||||
* the following commits.
|
||||
*
|
||||
* Reconciliation protocol assumes using one role consistently: either a reconciliation
|
||||
* initiator (requesting sketches), or responder (sending sketches). This defines our role,
|
||||
* based on the direction of the p2p connection.
|
||||
*
|
||||
*/
|
||||
bool m_we_initiate;
|
||||
|
||||
/**
|
||||
* TODO: These fields are public to ignore -Wunused-private-field. Make private once used in
|
||||
* the following commits.
|
||||
*
|
||||
* These values are used to salt short IDs, which is necessary for transaction reconciliations.
|
||||
*/
|
||||
uint64_t m_k0, m_k1;
|
||||
|
||||
TxReconciliationState(bool we_initiate, uint64_t k0, uint64_t k1) : m_we_initiate(we_initiate), m_k0(k0), m_k1(k1) {}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
/** Actual implementation for TxReconciliationTracker's data structure. */
|
||||
class TxReconciliationTracker::Impl
|
||||
{
|
||||
private:
|
||||
mutable Mutex m_txreconciliation_mutex;
|
||||
|
||||
// Local protocol version
|
||||
uint32_t m_recon_version;
|
||||
|
||||
/**
|
||||
* Keeps track of txreconciliation states of eligible peers.
|
||||
* For pre-registered peers, the locally generated salt is stored.
|
||||
* For registered peers, the locally generated salt is forgotten, and the state (including
|
||||
* "full" salt) is stored instead.
|
||||
*/
|
||||
std::unordered_map<NodeId, std::variant<uint64_t, TxReconciliationState>> m_states GUARDED_BY(m_txreconciliation_mutex);
|
||||
|
||||
public:
|
||||
explicit Impl(uint32_t recon_version) : m_recon_version(recon_version) {}
|
||||
|
||||
uint64_t PreRegisterPeer(NodeId peer_id) EXCLUSIVE_LOCKS_REQUIRED(!m_txreconciliation_mutex)
|
||||
{
|
||||
AssertLockNotHeld(m_txreconciliation_mutex);
|
||||
LOCK(m_txreconciliation_mutex);
|
||||
|
||||
LogPrint(BCLog::TXRECONCILIATION, "Pre-register peer=%d\n", peer_id);
|
||||
const uint64_t local_salt{GetRand(UINT64_MAX)};
|
||||
|
||||
// We do this exactly once per peer (which are unique by NodeId, see GetNewNodeId) so it's
|
||||
// safe to assume we don't have this record yet.
|
||||
Assume(m_states.emplace(peer_id, local_salt).second);
|
||||
return local_salt;
|
||||
}
|
||||
|
||||
ReconciliationRegisterResult RegisterPeer(NodeId peer_id, bool is_peer_inbound, uint32_t peer_recon_version,
|
||||
uint64_t remote_salt) EXCLUSIVE_LOCKS_REQUIRED(!m_txreconciliation_mutex)
|
||||
{
|
||||
AssertLockNotHeld(m_txreconciliation_mutex);
|
||||
LOCK(m_txreconciliation_mutex);
|
||||
auto recon_state = m_states.find(peer_id);
|
||||
|
||||
if (recon_state == m_states.end()) return ReconciliationRegisterResult::NOT_FOUND;
|
||||
|
||||
if (std::holds_alternative<TxReconciliationState>(recon_state->second)) {
|
||||
return ReconciliationRegisterResult::ALREADY_REGISTERED;
|
||||
}
|
||||
|
||||
uint64_t local_salt = *std::get_if<uint64_t>(&recon_state->second);
|
||||
|
||||
// If the peer supports the version which is lower than ours, we downgrade to the version
|
||||
// it supports. For now, this only guarantees that nodes with future reconciliation
|
||||
// versions have the choice of reconciling with this current version. However, they also
|
||||
// have the choice to refuse supporting reconciliations if the common version is not
|
||||
// satisfactory (e.g. too low).
|
||||
const uint32_t recon_version{std::min(peer_recon_version, m_recon_version)};
|
||||
// v1 is the lowest version, so suggesting something below must be a protocol violation.
|
||||
if (recon_version < 1) return ReconciliationRegisterResult::PROTOCOL_VIOLATION;
|
||||
|
||||
LogPrint(BCLog::TXRECONCILIATION, "Register peer=%d (inbound=%i)\n", peer_id, is_peer_inbound);
|
||||
|
||||
const uint256 full_salt{ComputeSalt(local_salt, remote_salt)};
|
||||
recon_state->second = TxReconciliationState(!is_peer_inbound, full_salt.GetUint64(0), full_salt.GetUint64(1));
|
||||
return ReconciliationRegisterResult::SUCCESS;
|
||||
}
|
||||
|
||||
void ForgetPeer(NodeId peer_id) EXCLUSIVE_LOCKS_REQUIRED(!m_txreconciliation_mutex)
|
||||
{
|
||||
AssertLockNotHeld(m_txreconciliation_mutex);
|
||||
LOCK(m_txreconciliation_mutex);
|
||||
if (m_states.erase(peer_id)) {
|
||||
LogPrint(BCLog::TXRECONCILIATION, "Forget txreconciliation state of peer=%d\n", peer_id);
|
||||
}
|
||||
}
|
||||
|
||||
bool IsPeerRegistered(NodeId peer_id) const EXCLUSIVE_LOCKS_REQUIRED(!m_txreconciliation_mutex)
|
||||
{
|
||||
AssertLockNotHeld(m_txreconciliation_mutex);
|
||||
LOCK(m_txreconciliation_mutex);
|
||||
auto recon_state = m_states.find(peer_id);
|
||||
return (recon_state != m_states.end() &&
|
||||
std::holds_alternative<TxReconciliationState>(recon_state->second));
|
||||
}
|
||||
};
|
||||
|
||||
TxReconciliationTracker::TxReconciliationTracker(uint32_t recon_version) : m_impl{std::make_unique<TxReconciliationTracker::Impl>(recon_version)} {}
|
||||
|
||||
TxReconciliationTracker::~TxReconciliationTracker() = default;
|
||||
|
||||
uint64_t TxReconciliationTracker::PreRegisterPeer(NodeId peer_id)
|
||||
{
|
||||
return m_impl->PreRegisterPeer(peer_id);
|
||||
}
|
||||
|
||||
ReconciliationRegisterResult TxReconciliationTracker::RegisterPeer(NodeId peer_id, bool is_peer_inbound,
|
||||
uint32_t peer_recon_version, uint64_t remote_salt)
|
||||
{
|
||||
return m_impl->RegisterPeer(peer_id, is_peer_inbound, peer_recon_version, remote_salt);
|
||||
}
|
||||
|
||||
void TxReconciliationTracker::ForgetPeer(NodeId peer_id)
|
||||
{
|
||||
m_impl->ForgetPeer(peer_id);
|
||||
}
|
||||
|
||||
bool TxReconciliationTracker::IsPeerRegistered(NodeId peer_id) const
|
||||
{
|
||||
return m_impl->IsPeerRegistered(peer_id);
|
||||
}
|
91
src/node/txreconciliation.h
Normal file
91
src/node/txreconciliation.h
Normal file
@ -0,0 +1,91 @@
|
||||
// Copyright (c) 2022 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#ifndef BITCOIN_NODE_TXRECONCILIATION_H
|
||||
#define BITCOIN_NODE_TXRECONCILIATION_H
|
||||
|
||||
#include <net.h>
|
||||
#include <sync.h>
|
||||
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
|
||||
/** Whether transaction reconciliation protocol should be enabled by default. */
|
||||
static constexpr bool DEFAULT_TXRECONCILIATION_ENABLE{false};
|
||||
/** Supported transaction reconciliation protocol version */
|
||||
static constexpr uint32_t TXRECONCILIATION_VERSION{1};
|
||||
|
||||
enum class ReconciliationRegisterResult {
|
||||
NOT_FOUND,
|
||||
SUCCESS,
|
||||
ALREADY_REGISTERED,
|
||||
PROTOCOL_VIOLATION,
|
||||
};
|
||||
|
||||
/**
|
||||
* Transaction reconciliation is a way for nodes to efficiently announce transactions.
|
||||
* This object keeps track of all txreconciliation-related communications with the peers.
|
||||
* The high-level protocol is:
|
||||
* 0. Txreconciliation protocol handshake.
|
||||
* 1. Once we receive a new transaction, add it to the set instead of announcing immediately.
|
||||
* 2. At regular intervals, a txreconciliation initiator requests a sketch from a peer, where a
|
||||
* sketch is a compressed representation of short form IDs of the transactions in their set.
|
||||
* 3. Once the initiator received a sketch from the peer, the initiator computes a local sketch,
|
||||
* and combines the two sketches to attempt finding the difference in *sets*.
|
||||
* 4a. If the difference was not larger than estimated, see SUCCESS below.
|
||||
* 4b. If the difference was larger than estimated, initial txreconciliation fails. The initiator
|
||||
* requests a larger sketch via an extension round (allowed only once).
|
||||
* - If extension succeeds (a larger sketch is sufficient), see SUCCESS below.
|
||||
* - If extension fails (a larger sketch is insufficient), see FAILURE below.
|
||||
*
|
||||
* SUCCESS. The initiator knows full symmetrical difference and can request what the initiator is
|
||||
* missing and announce to the peer what the peer is missing.
|
||||
*
|
||||
* FAILURE. The initiator notifies the peer about the failure and announces all transactions from
|
||||
* the corresponding set. Once the peer received the failure notification, the peer
|
||||
* announces all transactions from their set.
|
||||
|
||||
* This is a modification of the Erlay protocol (https://arxiv.org/abs/1905.10518) with two
|
||||
* changes (sketch extensions instead of bisections, and an extra INV exchange round), both
|
||||
* are motivated in BIP-330.
|
||||
*/
|
||||
class TxReconciliationTracker
|
||||
{
|
||||
private:
|
||||
class Impl;
|
||||
const std::unique_ptr<Impl> m_impl;
|
||||
|
||||
public:
|
||||
explicit TxReconciliationTracker(uint32_t recon_version);
|
||||
~TxReconciliationTracker();
|
||||
|
||||
/**
|
||||
* Step 0. Generates initial part of the state (salt) required to reconcile txs with the peer.
|
||||
* The salt is used for short ID computation required for txreconciliation.
|
||||
* The function returns the salt.
|
||||
* A peer can't participate in future txreconciliations without this call.
|
||||
* This function must be called only once per peer.
|
||||
*/
|
||||
uint64_t PreRegisterPeer(NodeId peer_id);
|
||||
|
||||
/**
|
||||
* Step 0. Once the peer agreed to reconcile txs with us, generate the state required to track
|
||||
* ongoing reconciliations. Must be called only after pre-registering the peer and only once.
|
||||
*/
|
||||
ReconciliationRegisterResult RegisterPeer(NodeId peer_id, bool is_peer_inbound,
|
||||
uint32_t peer_recon_version, uint64_t remote_salt);
|
||||
|
||||
/**
|
||||
* Attempts to forget txreconciliation-related state of the peer (if we previously stored any).
|
||||
* After this, we won't be able to reconcile transactions with the peer.
|
||||
*/
|
||||
void ForgetPeer(NodeId peer_id);
|
||||
|
||||
/**
|
||||
* Check if a peer is registered to reconcile transactions with us.
|
||||
*/
|
||||
bool IsPeerRegistered(NodeId peer_id) const;
|
||||
};
|
||||
|
||||
#endif // BITCOIN_NODE_TXRECONCILIATION_H
|
@ -48,6 +48,7 @@ MAKE_MSG(GETCFHEADERS, "getcfheaders");
|
||||
MAKE_MSG(CFHEADERS, "cfheaders");
|
||||
MAKE_MSG(GETCFCHECKPT, "getcfcheckpt");
|
||||
MAKE_MSG(CFCHECKPT, "cfcheckpt");
|
||||
MAKE_MSG(SENDTXRCNCL, "sendtxrcncl");
|
||||
// Dash message types
|
||||
MAKE_MSG(SPORK, "spork");
|
||||
MAKE_MSG(GETSPORKS, "getsporks");
|
||||
@ -127,6 +128,7 @@ const static std::string allNetMessageTypes[] = {
|
||||
NetMsgType::CFHEADERS,
|
||||
NetMsgType::GETCFCHECKPT,
|
||||
NetMsgType::CFCHECKPT,
|
||||
NetMsgType::SENDTXRCNCL,
|
||||
// Dash message types
|
||||
// NOTE: do NOT include non-implmented here, we want them to be "Unknown command" in ProcessMessage()
|
||||
NetMsgType::SPORK,
|
||||
|
@ -254,6 +254,12 @@ extern const char* GETCFCHECKPT;
|
||||
* evenly spaced filter headers for blocks on the requested chain.
|
||||
*/
|
||||
extern const char* CFCHECKPT;
|
||||
/**
|
||||
* Contains a 4-byte version number and an 8-byte salt.
|
||||
* The salt is used to compute short txids needed for efficient
|
||||
* txreconciliation, as described by BIP 330.
|
||||
*/
|
||||
extern const char* SENDTXRCNCL;
|
||||
|
||||
// Dash message types
|
||||
// NOTE: do NOT declare non-implmented here, we don't want them to be exposed to the outside
|
||||
|
64
src/test/fuzz/minisketch.cpp
Normal file
64
src/test/fuzz/minisketch.cpp
Normal file
@ -0,0 +1,64 @@
|
||||
// Copyright (c) 2021 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <minisketch.h>
|
||||
#include <node/minisketchwrapper.h>
|
||||
#include <test/fuzz/FuzzedDataProvider.h>
|
||||
#include <test/fuzz/fuzz.h>
|
||||
#include <test/fuzz/util.h>
|
||||
#include <util/check.h>
|
||||
|
||||
#include <map>
|
||||
#include <numeric>
|
||||
|
||||
FUZZ_TARGET(minisketch)
|
||||
{
|
||||
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
|
||||
const auto capacity{fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, 200)};
|
||||
Minisketch sketch_a{Assert(MakeMinisketch32(capacity))};
|
||||
Minisketch sketch_b{Assert(MakeMinisketch32(capacity))};
|
||||
|
||||
// Fill two sets and keep the difference in a map
|
||||
std::map<uint32_t, bool> diff;
|
||||
LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000)
|
||||
{
|
||||
const auto entry{fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, std::numeric_limits<uint32_t>::max() - 1)};
|
||||
const auto KeepDiff{[&] {
|
||||
bool& mut{diff[entry]};
|
||||
mut = !mut;
|
||||
}};
|
||||
CallOneOf(
|
||||
fuzzed_data_provider,
|
||||
[&] {
|
||||
sketch_a.Add(entry);
|
||||
KeepDiff();
|
||||
},
|
||||
[&] {
|
||||
sketch_b.Add(entry);
|
||||
KeepDiff();
|
||||
},
|
||||
[&] {
|
||||
sketch_a.Add(entry);
|
||||
sketch_b.Add(entry);
|
||||
});
|
||||
}
|
||||
const auto num_diff{std::accumulate(diff.begin(), diff.end(), size_t{0}, [](auto n, const auto& e) { return n + e.second; })};
|
||||
|
||||
Minisketch sketch_ar{MakeMinisketch32(capacity)};
|
||||
Minisketch sketch_br{MakeMinisketch32(capacity)};
|
||||
sketch_ar.Deserialize(sketch_a.Serialize());
|
||||
sketch_br.Deserialize(sketch_b.Serialize());
|
||||
|
||||
Minisketch sketch_diff{std::move(fuzzed_data_provider.ConsumeBool() ? sketch_a : sketch_ar)};
|
||||
sketch_diff.Merge(fuzzed_data_provider.ConsumeBool() ? sketch_b : sketch_br);
|
||||
|
||||
if (capacity >= num_diff) {
|
||||
const auto max_elements{fuzzed_data_provider.ConsumeIntegralInRange<size_t>(num_diff, capacity)};
|
||||
const auto dec{*Assert(sketch_diff.Decode(max_elements))};
|
||||
Assert(dec.size() == num_diff);
|
||||
for (auto d : dec) {
|
||||
Assert(diff.at(d));
|
||||
}
|
||||
}
|
||||
}
|
@ -64,7 +64,9 @@ void initialize_process_message()
|
||||
{
|
||||
Assert(GetNumMsgTypes() == getAllNetMessageTypes().size()); // If this fails, add or remove the message type below
|
||||
|
||||
static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>();
|
||||
static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(
|
||||
/*chain_name=*/CBaseChainParams::REGTEST,
|
||||
/*extra_args=*/{"-txreconciliation"});
|
||||
g_setup = testing_setup.get();
|
||||
for (int i = 0; i < 2 * COINBASE_MATURITY; i++) {
|
||||
MineBlock(g_setup->m_node, CScript() << OP_TRUE);
|
||||
@ -174,6 +176,7 @@ FUZZ_TARGET_MSG(sendcmpct);
|
||||
FUZZ_TARGET_MSG(senddsq);
|
||||
FUZZ_TARGET_MSG(sendheaders);
|
||||
FUZZ_TARGET_MSG(sendheaders2);
|
||||
FUZZ_TARGET_MSG(sendtxrcncl);
|
||||
FUZZ_TARGET_MSG(spork);
|
||||
FUZZ_TARGET_MSG(ssc);
|
||||
FUZZ_TARGET_MSG(tx);
|
||||
|
@ -23,7 +23,9 @@ const TestingSetup* g_setup;
|
||||
|
||||
void initialize_process_messages()
|
||||
{
|
||||
static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>();
|
||||
static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(
|
||||
/*chain_name=*/CBaseChainParams::REGTEST,
|
||||
/*extra_args=*/{"-txreconciliation"});
|
||||
g_setup = testing_setup.get();
|
||||
for (int i = 0; i < 2 * COINBASE_MATURITY; i++) {
|
||||
MineBlock(g_setup->m_node, CScript() << OP_TRUE);
|
||||
|
@ -3,7 +3,7 @@
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <minisketch.h>
|
||||
#include <minisketchwrapper.h>
|
||||
#include <node/minisketchwrapper.h>
|
||||
#include <random.h>
|
||||
#include <test/util/setup_common.h>
|
||||
|
||||
@ -38,7 +38,7 @@ BOOST_AUTO_TEST_CASE(minisketch_test)
|
||||
Minisketch sketch_c = std::move(sketch_ar);
|
||||
sketch_c.Merge(sketch_br);
|
||||
auto dec = sketch_c.Decode(errors);
|
||||
BOOST_CHECK(dec.has_value());
|
||||
BOOST_REQUIRE(dec.has_value());
|
||||
auto sols = std::move(*dec);
|
||||
std::sort(sols.begin(), sols.end());
|
||||
for (uint32_t i = 0; i < a_not_b; ++i) BOOST_CHECK_EQUAL(sols[i], start_a + i);
|
||||
|
84
src/test/txreconciliation_tests.cpp
Normal file
84
src/test/txreconciliation_tests.cpp
Normal file
@ -0,0 +1,84 @@
|
||||
// Copyright (c) 2021 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <node/txreconciliation.h>
|
||||
|
||||
#include <test/util/setup_common.h>
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE(txreconciliation_tests, BasicTestingSetup)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(RegisterPeerTest)
|
||||
{
|
||||
TxReconciliationTracker tracker(TXRECONCILIATION_VERSION);
|
||||
const uint64_t salt = 0;
|
||||
|
||||
// Prepare a peer for reconciliation.
|
||||
tracker.PreRegisterPeer(0);
|
||||
|
||||
// Invalid version.
|
||||
BOOST_CHECK_EQUAL(tracker.RegisterPeer(/*peer_id=*/0, /*is_peer_inbound=*/true,
|
||||
/*peer_recon_version=*/0, salt),
|
||||
ReconciliationRegisterResult::PROTOCOL_VIOLATION);
|
||||
|
||||
// Valid registration (inbound and outbound peers).
|
||||
BOOST_REQUIRE(!tracker.IsPeerRegistered(0));
|
||||
BOOST_REQUIRE_EQUAL(tracker.RegisterPeer(0, true, 1, salt), ReconciliationRegisterResult::SUCCESS);
|
||||
BOOST_CHECK(tracker.IsPeerRegistered(0));
|
||||
BOOST_REQUIRE(!tracker.IsPeerRegistered(1));
|
||||
tracker.PreRegisterPeer(1);
|
||||
BOOST_REQUIRE(tracker.RegisterPeer(1, false, 1, salt) == ReconciliationRegisterResult::SUCCESS);
|
||||
BOOST_CHECK(tracker.IsPeerRegistered(1));
|
||||
|
||||
// Reconciliation version is higher than ours, should be able to register.
|
||||
BOOST_REQUIRE(!tracker.IsPeerRegistered(2));
|
||||
tracker.PreRegisterPeer(2);
|
||||
BOOST_REQUIRE(tracker.RegisterPeer(2, true, 2, salt) == ReconciliationRegisterResult::SUCCESS);
|
||||
BOOST_CHECK(tracker.IsPeerRegistered(2));
|
||||
|
||||
// Try registering for the second time.
|
||||
BOOST_REQUIRE(tracker.RegisterPeer(1, false, 1, salt) == ReconciliationRegisterResult::ALREADY_REGISTERED);
|
||||
|
||||
// Do not register if there were no pre-registration for the peer.
|
||||
BOOST_REQUIRE_EQUAL(tracker.RegisterPeer(100, true, 1, salt), ReconciliationRegisterResult::NOT_FOUND);
|
||||
BOOST_CHECK(!tracker.IsPeerRegistered(100));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(ForgetPeerTest)
|
||||
{
|
||||
TxReconciliationTracker tracker(TXRECONCILIATION_VERSION);
|
||||
NodeId peer_id0 = 0;
|
||||
|
||||
// Removing peer after pre-registring works and does not let to register the peer.
|
||||
tracker.PreRegisterPeer(peer_id0);
|
||||
tracker.ForgetPeer(peer_id0);
|
||||
BOOST_CHECK_EQUAL(tracker.RegisterPeer(peer_id0, true, 1, 1), ReconciliationRegisterResult::NOT_FOUND);
|
||||
|
||||
// Removing peer after it is registered works.
|
||||
tracker.PreRegisterPeer(peer_id0);
|
||||
BOOST_REQUIRE(!tracker.IsPeerRegistered(peer_id0));
|
||||
BOOST_REQUIRE_EQUAL(tracker.RegisterPeer(peer_id0, true, 1, 1), ReconciliationRegisterResult::SUCCESS);
|
||||
BOOST_CHECK(tracker.IsPeerRegistered(peer_id0));
|
||||
tracker.ForgetPeer(peer_id0);
|
||||
BOOST_CHECK(!tracker.IsPeerRegistered(peer_id0));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(IsPeerRegisteredTest)
|
||||
{
|
||||
TxReconciliationTracker tracker(TXRECONCILIATION_VERSION);
|
||||
NodeId peer_id0 = 0;
|
||||
|
||||
BOOST_REQUIRE(!tracker.IsPeerRegistered(peer_id0));
|
||||
tracker.PreRegisterPeer(peer_id0);
|
||||
BOOST_REQUIRE(!tracker.IsPeerRegistered(peer_id0));
|
||||
|
||||
BOOST_REQUIRE_EQUAL(tracker.RegisterPeer(peer_id0, true, 1, 1), ReconciliationRegisterResult::SUCCESS);
|
||||
BOOST_CHECK(tracker.IsPeerRegistered(peer_id0));
|
||||
|
||||
tracker.ForgetPeer(peer_id0);
|
||||
BOOST_CHECK(!tracker.IsPeerRegistered(peer_id0));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
221
test/functional/p2p_sendtxrcncl.py
Executable file
221
test/functional/p2p_sendtxrcncl.py
Executable file
@ -0,0 +1,221 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2022 The Bitcoin Core developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
"""Test SENDTXRCNCL message
|
||||
"""
|
||||
|
||||
from test_framework.messages import (
|
||||
msg_sendtxrcncl,
|
||||
msg_version,
|
||||
NODE_BLOOM,
|
||||
)
|
||||
from test_framework.p2p import (
|
||||
P2PInterface,
|
||||
P2P_SERVICES,
|
||||
P2P_SUBVERSION,
|
||||
P2P_VERSION,
|
||||
)
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import assert_equal
|
||||
|
||||
class PeerNoVerack(P2PInterface):
|
||||
def __init__(self,):
|
||||
super().__init__()
|
||||
|
||||
def on_version(self, message):
|
||||
# Avoid sending verack in response to version.
|
||||
pass
|
||||
|
||||
class SendTxrcnclReceiver(P2PInterface):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.sendtxrcncl_msg_received = None
|
||||
|
||||
def on_sendtxrcncl(self, message):
|
||||
self.sendtxrcncl_msg_received = message
|
||||
|
||||
|
||||
class P2PFeelerReceiver(SendTxrcnclReceiver):
|
||||
def on_version(self, message):
|
||||
pass # feeler connections can not send any message other than their own version
|
||||
|
||||
|
||||
class PeerTrackMsgOrder(P2PInterface):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.messages = []
|
||||
|
||||
def on_message(self, message):
|
||||
super().on_message(message)
|
||||
self.messages.append(message)
|
||||
|
||||
def create_sendtxrcncl_msg():
|
||||
sendtxrcncl_msg = msg_sendtxrcncl()
|
||||
sendtxrcncl_msg.version = 1
|
||||
sendtxrcncl_msg.salt = 2
|
||||
return sendtxrcncl_msg
|
||||
|
||||
class SendTxRcnclTest(BitcoinTestFramework):
|
||||
def set_test_params(self):
|
||||
self.num_nodes = 1
|
||||
self.extra_args = [['-txreconciliation']]
|
||||
|
||||
def run_test(self):
|
||||
# Check everything concerning *sending* SENDTXRCNCL
|
||||
# First, *sending* to *inbound*.
|
||||
self.log.info('SENDTXRCNCL sent to an inbound')
|
||||
peer = self.nodes[0].add_p2p_connection(SendTxrcnclReceiver(), send_version=True, wait_for_verack=True)
|
||||
assert peer.sendtxrcncl_msg_received
|
||||
assert_equal(peer.sendtxrcncl_msg_received.version, 1)
|
||||
self.nodes[0].disconnect_p2ps()
|
||||
|
||||
self.log.info('SENDTXRCNCL should be sent before VERACK')
|
||||
peer = self.nodes[0].add_p2p_connection(PeerTrackMsgOrder(), send_version=True, wait_for_verack=True)
|
||||
peer.wait_for_verack()
|
||||
verack_index = [i for i, msg in enumerate(peer.messages) if msg.msgtype == b'verack'][0]
|
||||
sendtxrcncl_index = [i for i, msg in enumerate(peer.messages) if msg.msgtype == b'sendtxrcncl'][0]
|
||||
assert sendtxrcncl_index < verack_index
|
||||
self.nodes[0].disconnect_p2ps()
|
||||
|
||||
self.log.info('SENDTXRCNCL on pre-v22 version should not be sent')
|
||||
peer = self.nodes[0].add_p2p_connection(SendTxrcnclReceiver(), send_version=False, wait_for_verack=False)
|
||||
pre_v22_version_msg = msg_version()
|
||||
pre_v22_version_msg.nVersion = 70234
|
||||
pre_v22_version_msg.strSubVer = P2P_SUBVERSION
|
||||
pre_v22_version_msg.nServices = P2P_SERVICES
|
||||
pre_v22_version_msg.relay = 1
|
||||
peer.send_message(pre_v22_version_msg)
|
||||
peer.wait_for_verack()
|
||||
assert not peer.sendtxrcncl_msg_received
|
||||
self.nodes[0].disconnect_p2ps()
|
||||
|
||||
self.log.info('SENDTXRCNCL for fRelay=false should not be sent')
|
||||
peer = self.nodes[0].add_p2p_connection(SendTxrcnclReceiver(), send_version=False, wait_for_verack=False)
|
||||
no_txrelay_version_msg = msg_version()
|
||||
no_txrelay_version_msg.nVersion = P2P_VERSION
|
||||
no_txrelay_version_msg.strSubVer = P2P_SUBVERSION
|
||||
no_txrelay_version_msg.nServices = P2P_SERVICES
|
||||
no_txrelay_version_msg.relay = 0
|
||||
peer.send_message(no_txrelay_version_msg)
|
||||
peer.wait_for_verack()
|
||||
assert not peer.sendtxrcncl_msg_received
|
||||
self.nodes[0].disconnect_p2ps()
|
||||
|
||||
self.log.info('SENDTXRCNCL for fRelay=false should not be sent (with NODE_BLOOM offered)')
|
||||
self.restart_node(0, ["-peerbloomfilters", "-txreconciliation"])
|
||||
peer = self.nodes[0].add_p2p_connection(SendTxrcnclReceiver(), send_version=False, wait_for_verack=False)
|
||||
no_txrelay_version_msg = msg_version()
|
||||
no_txrelay_version_msg.nVersion = P2P_VERSION
|
||||
no_txrelay_version_msg.strSubVer = P2P_SUBVERSION
|
||||
no_txrelay_version_msg.nServices = P2P_SERVICES
|
||||
no_txrelay_version_msg.relay = 0
|
||||
peer.send_message(no_txrelay_version_msg)
|
||||
peer.wait_for_verack()
|
||||
assert peer.nServices & NODE_BLOOM != 0
|
||||
assert not peer.sendtxrcncl_msg_received
|
||||
self.nodes[0].disconnect_p2ps()
|
||||
|
||||
# Now, *sending* to *outbound*.
|
||||
self.log.info('SENDTXRCNCL sent to an outbound')
|
||||
peer = self.nodes[0].add_outbound_p2p_connection(
|
||||
SendTxrcnclReceiver(), wait_for_verack=True, p2p_idx=0, connection_type="outbound-full-relay")
|
||||
assert peer.sendtxrcncl_msg_received
|
||||
assert_equal(peer.sendtxrcncl_msg_received.version, 1)
|
||||
self.nodes[0].disconnect_p2ps()
|
||||
|
||||
self.log.info('SENDTXRCNCL should not be sent if block-relay-only')
|
||||
peer = self.nodes[0].add_outbound_p2p_connection(
|
||||
SendTxrcnclReceiver(), wait_for_verack=True, p2p_idx=0, connection_type="block-relay-only")
|
||||
assert not peer.sendtxrcncl_msg_received
|
||||
self.nodes[0].disconnect_p2ps()
|
||||
|
||||
self.log.info("SENDTXRCNCL should not be sent if feeler")
|
||||
peer = self.nodes[0].add_outbound_p2p_connection(P2PFeelerReceiver(), p2p_idx=0, connection_type="feeler")
|
||||
assert not peer.sendtxrcncl_msg_received
|
||||
self.nodes[0].disconnect_p2ps()
|
||||
|
||||
self.log.info("SENDTXRCNCL should not be sent if addrfetch")
|
||||
peer = self.nodes[0].add_outbound_p2p_connection(
|
||||
SendTxrcnclReceiver(), wait_for_verack=True, p2p_idx=0, connection_type="addr-fetch")
|
||||
assert not peer.sendtxrcncl_msg_received
|
||||
self.nodes[0].disconnect_p2ps()
|
||||
|
||||
self.log.info('SENDTXRCNCL not sent if -txreconciliation flag is not set')
|
||||
self.restart_node(0, [])
|
||||
peer = self.nodes[0].add_p2p_connection(SendTxrcnclReceiver(), send_version=True, wait_for_verack=True)
|
||||
assert not peer.sendtxrcncl_msg_received
|
||||
self.nodes[0].disconnect_p2ps()
|
||||
|
||||
self.log.info('SENDTXRCNCL not sent if blocksonly is set')
|
||||
self.restart_node(0, ["-txreconciliation", "-blocksonly"])
|
||||
peer = self.nodes[0].add_p2p_connection(SendTxrcnclReceiver(), send_version=True, wait_for_verack=True)
|
||||
assert not peer.sendtxrcncl_msg_received
|
||||
self.nodes[0].disconnect_p2ps()
|
||||
|
||||
# Check everything concerning *receiving* SENDTXRCNCL
|
||||
# First, receiving from *inbound*.
|
||||
self.restart_node(0, ["-txreconciliation"])
|
||||
self.log.info('valid SENDTXRCNCL received')
|
||||
peer = self.nodes[0].add_p2p_connection(PeerNoVerack(), send_version=True, wait_for_verack=False)
|
||||
with self.nodes[0].assert_debug_log(["received: sendtxrcncl"]):
|
||||
peer.send_message(create_sendtxrcncl_msg())
|
||||
self.log.info('second SENDTXRCNCL triggers a disconnect')
|
||||
with self.nodes[0].assert_debug_log(["(sendtxrcncl received from already registered peer); disconnecting"]):
|
||||
peer.send_message(create_sendtxrcncl_msg())
|
||||
peer.wait_for_disconnect()
|
||||
|
||||
self.restart_node(0, [])
|
||||
self.log.info('SENDTXRCNCL if no txreconciliation supported is ignored')
|
||||
peer = self.nodes[0].add_p2p_connection(PeerNoVerack(), send_version=True, wait_for_verack=False)
|
||||
with self.nodes[0].assert_debug_log(['ignored, as our node does not have txreconciliation enabled']):
|
||||
peer.send_message(create_sendtxrcncl_msg())
|
||||
self.nodes[0].disconnect_p2ps()
|
||||
|
||||
self.restart_node(0, ["-txreconciliation"])
|
||||
|
||||
self.log.info('SENDTXRCNCL with version=0 triggers a disconnect')
|
||||
sendtxrcncl_low_version = create_sendtxrcncl_msg()
|
||||
sendtxrcncl_low_version.version = 0
|
||||
peer = self.nodes[0].add_p2p_connection(PeerNoVerack(), send_version=True, wait_for_verack=False)
|
||||
with self.nodes[0].assert_debug_log(["txreconciliation protocol violation"]):
|
||||
peer.send_message(sendtxrcncl_low_version)
|
||||
peer.wait_for_disconnect()
|
||||
|
||||
self.log.info('SENDTXRCNCL with version=2 is valid')
|
||||
sendtxrcncl_higher_version = create_sendtxrcncl_msg()
|
||||
sendtxrcncl_higher_version.version = 2
|
||||
peer = self.nodes[0].add_p2p_connection(PeerNoVerack(), send_version=True, wait_for_verack=False)
|
||||
with self.nodes[0].assert_debug_log(['Register peer=1']):
|
||||
peer.send_message(sendtxrcncl_higher_version)
|
||||
self.nodes[0].disconnect_p2ps()
|
||||
|
||||
self.log.info('unexpected SENDTXRCNCL is ignored')
|
||||
peer = self.nodes[0].add_p2p_connection(PeerNoVerack(), send_version=False, wait_for_verack=False)
|
||||
old_version_msg = msg_version()
|
||||
old_version_msg.nVersion = 70234
|
||||
old_version_msg.strSubVer = P2P_SUBVERSION
|
||||
old_version_msg.nServices = P2P_SERVICES
|
||||
old_version_msg.relay = 1
|
||||
peer.send_message(old_version_msg)
|
||||
with self.nodes[0].assert_debug_log(['Ignore unexpected txreconciliation signal']):
|
||||
peer.send_message(create_sendtxrcncl_msg())
|
||||
self.nodes[0].disconnect_p2ps()
|
||||
|
||||
self.log.info('sending SENDTXRCNCL after sending VERACK triggers a disconnect')
|
||||
peer = self.nodes[0].add_p2p_connection(P2PInterface())
|
||||
with self.nodes[0].assert_debug_log(["sendtxrcncl received after verack"]):
|
||||
peer.send_message(create_sendtxrcncl_msg())
|
||||
peer.wait_for_disconnect()
|
||||
|
||||
# Now, *receiving* from *outbound*.
|
||||
self.log.info('SENDTXRCNCL if block-relay-only triggers a disconnect')
|
||||
peer = self.nodes[0].add_outbound_p2p_connection(
|
||||
PeerNoVerack(), wait_for_verack=False, p2p_idx=0, connection_type="block-relay-only")
|
||||
with self.nodes[0].assert_debug_log(["we indicated no tx relay; disconnecting"]):
|
||||
peer.send_message(create_sendtxrcncl_msg())
|
||||
peer.wait_for_disconnect()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
SendTxRcnclTest().main()
|
@ -57,7 +57,7 @@ class RpcMiscTest(BitcoinTestFramework):
|
||||
self.log.info("test logging rpc and help")
|
||||
|
||||
# Test logging RPC returns the expected number of logging categories.
|
||||
assert_equal(len(node.logging()), 38)
|
||||
assert_equal(len(node.logging()), 39)
|
||||
|
||||
# Test toggling a logging category on/off/on with the logging RPC.
|
||||
assert_equal(node.logging()['qt'], True)
|
||||
|
@ -2558,3 +2558,25 @@ class msg_cfcheckpt:
|
||||
def __repr__(self):
|
||||
return "msg_cfcheckpt(filter_type={:#x}, stop_hash={:x})".format(
|
||||
self.filter_type, self.stop_hash)
|
||||
|
||||
class msg_sendtxrcncl:
|
||||
__slots__ = ("version", "salt")
|
||||
msgtype = b"sendtxrcncl"
|
||||
|
||||
def __init__(self):
|
||||
self.version = 0
|
||||
self.salt = 0
|
||||
|
||||
def deserialize(self, f):
|
||||
self.version = struct.unpack("<I", f.read(4))[0]
|
||||
self.salt = struct.unpack("<Q", f.read(8))[0]
|
||||
|
||||
def serialize(self):
|
||||
r = b""
|
||||
r += struct.pack("<I", self.version)
|
||||
r += struct.pack("<Q", self.salt)
|
||||
return r
|
||||
|
||||
def __repr__(self):
|
||||
return "msg_sendtxrcncl(version=%lu, salt=%lu)" %\
|
||||
(self.version, self.salt)
|
||||
|
@ -72,6 +72,7 @@ from test_framework.messages import (
|
||||
msg_sendcmpct,
|
||||
msg_sendheaders,
|
||||
msg_sendheaders2,
|
||||
msg_sendtxrcncl,
|
||||
msg_tx,
|
||||
msg_verack,
|
||||
msg_version,
|
||||
@ -141,6 +142,7 @@ MESSAGEMAP = {
|
||||
b"sendcmpct": msg_sendcmpct,
|
||||
b"sendheaders": msg_sendheaders,
|
||||
b"sendheaders2": msg_sendheaders2,
|
||||
b"sendtxrcncl": msg_sendtxrcncl,
|
||||
b"tx": msg_tx,
|
||||
b"verack": msg_verack,
|
||||
b"version": msg_version,
|
||||
@ -569,6 +571,7 @@ class P2PInterface(P2PConnection):
|
||||
def on_sendcmpct(self, message): pass
|
||||
def on_sendheaders(self, message): pass
|
||||
def on_sendheaders2(self, message): pass
|
||||
def on_sendtxrcncl(self, message): pass
|
||||
def on_tx(self, message): pass
|
||||
|
||||
def on_inv(self, message):
|
||||
|
@ -360,6 +360,7 @@ BASE_SCRIPTS = [
|
||||
'rpc_deriveaddresses.py',
|
||||
'rpc_deriveaddresses.py --usecli',
|
||||
'p2p_ping.py',
|
||||
'p2p_sendtxrcncl.py',
|
||||
'rpc_scantxoutset.py',
|
||||
'feature_txindex_compatibility.py',
|
||||
'feature_logging.py',
|
||||
|
Loading…
Reference in New Issue
Block a user