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:
    utACK cc98f9e724
  UdjinM6:
    utACK cc98f9e724

Tree-SHA512: fe985f4df6c96c0a7b984882815a1bce7f23c54370198d099e41a59ac4c46c283a2b8dd95f5c8fc12eb1dc1330c4e5c21626b76d33d83d395326e8eb19d564ce
This commit is contained in:
pasta 2024-10-28 10:46:49 -05:00
commit 75fd7b5ea2
No known key found for this signature in database
GPG Key ID: E2F3D7916E722D38
25 changed files with 796 additions and 14 deletions

View File

@ -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 \

View File

@ -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)

View File

@ -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 \

View File

@ -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;
}

View File

@ -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

View File

@ -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);

View File

@ -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"},

View File

@ -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),

View File

@ -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;

View File

@ -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>

View File

@ -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

View 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);
}

View 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

View File

@ -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,

View File

@ -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

View 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));
}
}
}

View File

@ -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);

View File

@ -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);

View File

@ -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);

View 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()

View 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()

View File

@ -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)

View File

@ -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)

View File

@ -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):

View File

@ -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',