merge bitcoin#23443: Erlay support signaling

This commit is contained in:
Kittywhiskers Van Gogh 2024-10-20 09:22:25 +00:00
parent fdc3c07554
commit 6a7868dba7
No known key found for this signature in database
GPG Key ID: 30CD0C065E5C4AAD
17 changed files with 679 additions and 3 deletions

View File

@ -277,6 +277,7 @@ BITCOIN_CORE_H = \
node/minisketchwrapper.h \ node/minisketchwrapper.h \
node/psbt.h \ node/psbt.h \
node/transaction.h \ node/transaction.h \
node/txreconciliation.h \
node/ui_interface.h \ node/ui_interface.h \
node/utxo_snapshot.h \ node/utxo_snapshot.h \
noui.h \ noui.h \
@ -509,6 +510,7 @@ libbitcoin_server_a_SOURCES = \
node/minisketchwrapper.cpp \ node/minisketchwrapper.cpp \
node/psbt.cpp \ node/psbt.cpp \
node/transaction.cpp \ node/transaction.cpp \
node/txreconciliation.cpp \
node/ui_interface.cpp \ node/ui_interface.cpp \
noui.cpp \ noui.cpp \
policy/fees.cpp \ policy/fees.cpp \

View File

@ -174,6 +174,7 @@ BITCOIN_TESTS =\
test/torcontrol_tests.cpp \ test/torcontrol_tests.cpp \
test/transaction_tests.cpp \ test/transaction_tests.cpp \
test/txindex_tests.cpp \ test/txindex_tests.cpp \
test/txreconciliation_tests.cpp \
test/txvalidation_tests.cpp \ test/txvalidation_tests.cpp \
test/txvalidationcache_tests.cpp \ test/txvalidationcache_tests.cpp \
test/uint256_tests.cpp \ test/uint256_tests.cpp \

View File

@ -40,6 +40,7 @@
#include <node/blockstorage.h> #include <node/blockstorage.h>
#include <node/context.h> #include <node/context.h>
#include <node/ui_interface.h> #include <node/ui_interface.h>
#include <node/txreconciliation.h>
#include <policy/feerate.h> #include <policy/feerate.h>
#include <policy/fees.h> #include <policy/fees.h>
#include <policy/policy.h> #include <policy/policy.h>
@ -587,6 +588,7 @@ void SetupServerArgs(ArgsManager& argsman)
argsman.AddArg("-socketevents=<mode>", "Socket events mode, which must be one of 'select', 'poll', 'epoll' or 'kqueue', depending on your system (default: Linux - 'epoll', FreeBSD/Apple - 'kqueue', Windows - 'select')", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-socketevents=<mode>", "Socket events mode, which must be one of 'select', 'poll', 'epoll' or 'kqueue', depending on your system (default: Linux - 'epoll', FreeBSD/Apple - 'kqueue', Windows - 'select')", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-networkactive", "Enable all P2P network activity (default: 1). Can be changed by the setnetworkactive RPC command", ArgsManager::ALLOW_BOOL, OptionsCategory::CONNECTION); argsman.AddArg("-networkactive", "Enable all P2P network activity (default: 1). Can be changed by the setnetworkactive RPC command", ArgsManager::ALLOW_BOOL, OptionsCategory::CONNECTION);
argsman.AddArg("-timeout=<n>", strprintf("Specify socket connection timeout in milliseconds. If an initial attempt to connect is unsuccessful after this amount of time, drop it (minimum: 1, default: %d)", DEFAULT_CONNECT_TIMEOUT), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-timeout=<n>", strprintf("Specify socket connection timeout in milliseconds. If an initial attempt to connect is unsuccessful after this amount of time, drop it (minimum: 1, default: %d)", DEFAULT_CONNECT_TIMEOUT), 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("-torcontrol=<ip>:<port>", strprintf("Tor control port to use if onion listening enabled (default: %s)", DEFAULT_TOR_CONTROL), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-torcontrol=<ip>:<port>", strprintf("Tor control port to use if onion listening enabled (default: %s)", DEFAULT_TOR_CONTROL), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-torpassword=<pass>", "Tor control port password (default: empty)", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::CONNECTION); argsman.AddArg("-torpassword=<pass>", "Tor control port password (default: empty)", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::CONNECTION);
#ifdef USE_UPNP #ifdef USE_UPNP

View File

@ -161,6 +161,7 @@ const CLogCategoryDesc LogCategories[] =
{BCLog::I2P, "i2p"}, {BCLog::I2P, "i2p"},
{BCLog::IPC, "ipc"}, {BCLog::IPC, "ipc"},
{BCLog::LOCK, "lock"}, {BCLog::LOCK, "lock"},
{BCLog::TXRECONCILIATION, "txreconciliation"},
{BCLog::ALL, "1"}, {BCLog::ALL, "1"},
{BCLog::ALL, "all"}, {BCLog::ALL, "all"},

View File

@ -62,6 +62,7 @@ namespace BCLog {
I2P = (1 << 22), I2P = (1 << 22),
IPC = (1 << 23), IPC = (1 << 23),
LOCK = (1 << 24), LOCK = (1 << 24),
TXRECONCILIATION = (1 << 27),
//Start Dash //Start Dash
CHAINLOCKS = ((uint64_t)1 << 32), CHAINLOCKS = ((uint64_t)1 << 32),

View File

@ -20,6 +20,7 @@
#include <netbase.h> #include <netbase.h>
#include <net_types.h> #include <net_types.h>
#include <node/blockstorage.h> #include <node/blockstorage.h>
#include <node/txreconciliation.h>
#include <policy/policy.h> #include <policy/policy.h>
#include <primitives/block.h> #include <primitives/block.h>
#include <primitives/transaction.h> #include <primitives/transaction.h>
@ -730,6 +731,7 @@ private:
BanMan* const m_banman; BanMan* const m_banman;
ChainstateManager& m_chainman; ChainstateManager& m_chainman;
CTxMemPool& m_mempool; CTxMemPool& m_mempool;
std::unique_ptr<TxReconciliationTracker> m_txreconciliation;
const std::unique_ptr<CDeterministicMNManager>& m_dmnman; const std::unique_ptr<CDeterministicMNManager>& m_dmnman;
const std::unique_ptr<CJContext>& m_cj_ctx; const std::unique_ptr<CJContext>& m_cj_ctx;
const std::unique_ptr<LLMQContext>& m_llmq_ctx; const std::unique_ptr<LLMQContext>& m_llmq_ctx;
@ -1633,6 +1635,7 @@ void PeerManagerImpl::FinalizeNode(const CNode& node) {
mapBlocksInFlight.erase(entry.pindex->GetBlockHash()); mapBlocksInFlight.erase(entry.pindex->GetBlockHash());
} }
WITH_LOCK(g_cs_orphans, m_orphanage.EraseForPeer(nodeid)); WITH_LOCK(g_cs_orphans, m_orphanage.EraseForPeer(nodeid));
if (m_txreconciliation) m_txreconciliation->ForgetPeer(nodeid);
m_num_preferred_download_peers -= state->fPreferredDownload; m_num_preferred_download_peers -= state->fPreferredDownload;
m_peers_downloading_from -= (state->nBlocksInFlight != 0); m_peers_downloading_from -= (state->nBlocksInFlight != 0);
assert(m_peers_downloading_from >= 0); assert(m_peers_downloading_from >= 0);
@ -1935,6 +1938,11 @@ PeerManagerImpl::PeerManagerImpl(const CChainParams& chainparams, CConnman& conn
m_mn_activeman(mn_activeman), m_mn_activeman(mn_activeman),
m_ignore_incoming_txs(ignore_incoming_txs) 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) 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::SENDADDRV2));
} }
m_connman.PushMessage(&pfrom, msg_maker.Make(NetMsgType::VERACK));
pfrom.m_has_all_wanted_services = HasAllDesirableServiceFlags(nServices); pfrom.m_has_all_wanted_services = HasAllDesirableServiceFlags(nServices);
peer->m_their_services = nServices; peer->m_their_services = nServices;
pfrom.SetAddrLocal(addrMe); pfrom.SetAddrLocal(addrMe);
@ -3536,6 +3542,25 @@ void PeerManagerImpl::ProcessMessage(
if (fRelay) pfrom.m_relays_txs = true; 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 VERSION message supports INCREASE_MAX_HEADERS2_VERSION;
// - we intended to exchange transactions over this connection while establishing it
// and the peer indicated support for transaction relay in the VERSION message;
// - we are not in -blocksonly mode.
if (pfrom.m_relays_txs && !m_ignore_incoming_txs) {
const uint64_t recon_salt = m_txreconciliation->PreRegisterPeer(pfrom.GetId());
// We suggest our txreconciliation role (initiator/responder) based on
// the connection direction.
m_connman.PushMessage(&pfrom, msg_maker.Make(NetMsgType::SENDTXRCNCL,
!pfrom.IsInboundConn(),
pfrom.IsInboundConn(),
TXRECONCILIATION_VERSION, recon_salt));
}
}
m_connman.PushMessage(&pfrom, msg_maker.Make(NetMsgType::VERACK));
// Potentially mark this peer as a preferred download peer. // Potentially mark this peer as a preferred download peer.
{ {
LOCK(cs_main); LOCK(cs_main);
@ -3677,6 +3702,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; pfrom.fSuccessfullyConnected = true;
return; return;
} }
@ -3727,6 +3761,58 @@ void PeerManagerImpl::ProcessMessage(
return; 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) {
// Disconnect peers that send a SENDTXRCNCL message after VERACK.
LogPrint(BCLog::NET, "sendtxrcncl received after verack from peer=%d; disconnecting\n", pfrom.GetId());
pfrom.fDisconnect = true;
return;
}
if (!peer->GetTxRelay()) {
// Disconnect peers that send a SENDTXRCNCL message even though we indicated we don't
// support transaction relay.
LogPrint(BCLog::NET, "sendtxrcncl received from peer=%d to which we indicated no tx relay; disconnecting\n", pfrom.GetId());
pfrom.fDisconnect = true;
return;
}
bool is_peer_initiator, is_peer_responder;
uint32_t peer_txreconcl_version;
uint64_t remote_salt;
vRecv >> is_peer_initiator >> is_peer_responder >> peer_txreconcl_version >> remote_salt;
if (m_txreconciliation->IsPeerRegistered(pfrom.GetId())) {
// A peer is already registered, meaning we already received SENDTXRCNCL from them.
LogPrint(BCLog::NET, "txreconciliation protocol violation from peer=%d (sendtxrcncl received from already registered peer); disconnecting\n", pfrom.GetId());
pfrom.fDisconnect = true;
return;
}
const ReconciliationRegisterResult result = m_txreconciliation->RegisterPeer(pfrom.GetId(), pfrom.IsInboundConn(),
is_peer_initiator, is_peer_responder,
peer_txreconcl_version,
remote_salt);
// If it's a protocol violation, disconnect.
// If the peer was not found (but something unexpected happened) or it was registered,
// nothing to be done.
if (result == ReconciliationRegisterResult::PROTOCOL_VIOLATION) {
LogPrint(BCLog::NET, "txreconciliation protocol violation from peer=%d; disconnecting\n", pfrom.GetId());
pfrom.fDisconnect = true;
return;
}
return;
}
if (!pfrom.fSuccessfullyConnected) { if (!pfrom.fSuccessfullyConnected) {
LogPrint(BCLog::NET, "Unsupported message \"%s\" prior to verack from peer=%d\n", SanitizeString(msg_type), pfrom.GetId()); LogPrint(BCLog::NET, "Unsupported message \"%s\" prior to verack from peer=%d\n", SanitizeString(msg_type), pfrom.GetId());
return; return;

View File

@ -0,0 +1,184 @@
// 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.
*
*/
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);
// We do not support txreconciliation salt/version updates.
assert(m_states.find(peer_id) == m_states.end());
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.
Assert(m_states.emplace(peer_id, local_salt).second);
return local_salt;
}
ReconciliationRegisterResult RegisterPeer(NodeId peer_id, bool is_peer_inbound, bool is_peer_recon_initiator,
bool is_peer_recon_responder, 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);
// A peer should be in the pre-registered state to proceed here.
if (recon_state == m_states.end()) return NOT_FOUND;
uint64_t* local_salt = std::get_if<uint64_t>(&recon_state->second);
// A peer is already registered. This should be checked by the caller.
Assume(local_salt);
// 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 PROTOCOL_VIOLATION;
// Must match SENDTXRCNCL logic.
const bool they_initiate = is_peer_recon_initiator && is_peer_inbound;
const bool we_initiate = !is_peer_inbound && is_peer_recon_responder;
// If we ever announce support for both requesting and responding, this will need
// tie-breaking. For now, this is mutually exclusive because both are based on the
// inbound flag.
assert(!(they_initiate && we_initiate));
// The peer set both flags to false, we treat it as a protocol violation.
if (!(they_initiate || we_initiate)) return PROTOCOL_VIOLATION;
LogPrint(BCLog::TXRECONCILIATION, "Register peer=%d with the following params: " /* Continued */
"we_initiate=%i, they_initiate=%i.\n",
peer_id, we_initiate, they_initiate);
const uint256 full_salt{ComputeSalt(*local_salt, remote_salt)};
recon_state->second = TxReconciliationState(we_initiate, full_salt.GetUint64(0), full_salt.GetUint64(1));
return 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,
bool is_peer_recon_initiator, bool is_peer_recon_responder,
uint32_t peer_recon_version, uint64_t remote_salt)
{
return m_impl->RegisterPeer(peer_id, is_peer_inbound, is_peer_recon_initiator, is_peer_recon_responder,
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,90 @@
// 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 ReconciliationRegisterResult {
NOT_FOUND = 0,
SUCCESS = 1,
PROTOCOL_VIOLATION = 2,
};
/**
* 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, bool is_peer_recon_initiator,
bool is_peer_recon_responder, 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(CFHEADERS, "cfheaders");
MAKE_MSG(GETCFCHECKPT, "getcfcheckpt"); MAKE_MSG(GETCFCHECKPT, "getcfcheckpt");
MAKE_MSG(CFCHECKPT, "cfcheckpt"); MAKE_MSG(CFCHECKPT, "cfcheckpt");
MAKE_MSG(SENDTXRCNCL, "sendtxrcncl");
// Dash message types // Dash message types
MAKE_MSG(SPORK, "spork"); MAKE_MSG(SPORK, "spork");
MAKE_MSG(GETSPORKS, "getsporks"); MAKE_MSG(GETSPORKS, "getsporks");
@ -127,6 +128,7 @@ const static std::string allNetMessageTypes[] = {
NetMsgType::CFHEADERS, NetMsgType::CFHEADERS,
NetMsgType::GETCFCHECKPT, NetMsgType::GETCFCHECKPT,
NetMsgType::CFCHECKPT, NetMsgType::CFCHECKPT,
NetMsgType::SENDTXRCNCL,
// Dash message types // Dash message types
// NOTE: do NOT include non-implmented here, we want them to be "Unknown command" in ProcessMessage() // NOTE: do NOT include non-implmented here, we want them to be "Unknown command" in ProcessMessage()
NetMsgType::SPORK, NetMsgType::SPORK,

View File

@ -254,6 +254,14 @@ extern const char* GETCFCHECKPT;
* evenly spaced filter headers for blocks on the requested chain. * evenly spaced filter headers for blocks on the requested chain.
*/ */
extern const char* CFCHECKPT; extern const char* CFCHECKPT;
/**
* Contains 2 1-byte bools, a 4-byte version number and an 8-byte salt.
* The 2 booleans indicate that a node is willing to participate in transaction
* reconciliation, respectively as an initiator or as a receiver.
* The salt is used to compute short txids needed for efficient
* txreconciliation, as described by BIP 330.
*/
extern const char* SENDTXRCNCL;
// Dash message types // Dash message types
// NOTE: do NOT declare non-implmented here, we don't want them to be exposed to the outside // NOTE: do NOT declare non-implmented here, we don't want them to be exposed to the outside

View File

@ -174,6 +174,7 @@ FUZZ_TARGET_MSG(sendcmpct);
FUZZ_TARGET_MSG(senddsq); FUZZ_TARGET_MSG(senddsq);
FUZZ_TARGET_MSG(sendheaders); FUZZ_TARGET_MSG(sendheaders);
FUZZ_TARGET_MSG(sendheaders2); FUZZ_TARGET_MSG(sendheaders2);
FUZZ_TARGET_MSG(sendtxrcncl);
FUZZ_TARGET_MSG(spork); FUZZ_TARGET_MSG(spork);
FUZZ_TARGET_MSG(ssc); FUZZ_TARGET_MSG(ssc);
FUZZ_TARGET_MSG(tx); FUZZ_TARGET_MSG(tx);

View File

@ -0,0 +1,86 @@
// 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(1);
const uint64_t salt = 0;
// Prepare a peer for reconciliation.
tracker.PreRegisterPeer(0);
// Both roles are false, don't register.
BOOST_CHECK(tracker.RegisterPeer(/*peer_id=*/0, /*is_peer_inbound=*/true,
/*is_peer_recon_initiator=*/false,
/*is_peer_recon_responder=*/false,
/*peer_recon_version=*/1, salt) ==
ReconciliationRegisterResult::PROTOCOL_VIOLATION);
// Invalid roles for the given connection direction.
BOOST_CHECK(tracker.RegisterPeer(0, true, false, true, 1, salt) == ReconciliationRegisterResult::PROTOCOL_VIOLATION);
BOOST_CHECK(tracker.RegisterPeer(0, false, true, false, 1, salt) == ReconciliationRegisterResult::PROTOCOL_VIOLATION);
// Invalid version.
BOOST_CHECK(tracker.RegisterPeer(0, true, true, false, 0, salt) == ReconciliationRegisterResult::PROTOCOL_VIOLATION);
// Valid registration.
BOOST_REQUIRE(!tracker.IsPeerRegistered(0));
BOOST_REQUIRE(tracker.RegisterPeer(0, true, true, false, 1, salt) == ReconciliationRegisterResult::SUCCESS);
BOOST_CHECK(tracker.IsPeerRegistered(0));
// Reconciliation version is higher than ours, should be able to register.
BOOST_REQUIRE(!tracker.IsPeerRegistered(1));
tracker.PreRegisterPeer(1);
BOOST_REQUIRE(tracker.RegisterPeer(1, true, true, false, 2, salt) == ReconciliationRegisterResult::SUCCESS);
BOOST_CHECK(tracker.IsPeerRegistered(1));
// Do not register if there were no pre-registration for the peer.
BOOST_REQUIRE(tracker.RegisterPeer(100, true, true, false, 1, salt) == ReconciliationRegisterResult::NOT_FOUND);
BOOST_CHECK(!tracker.IsPeerRegistered(100));
}
BOOST_AUTO_TEST_CASE(ForgetPeerTest)
{
TxReconciliationTracker tracker(1);
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(tracker.RegisterPeer(peer_id0, true, true, false, 1, 1) == ReconciliationRegisterResult::NOT_FOUND);
// Removing peer after it is registered works.
tracker.PreRegisterPeer(peer_id0);
BOOST_REQUIRE(!tracker.IsPeerRegistered(peer_id0));
BOOST_REQUIRE(tracker.RegisterPeer(peer_id0, true, true, false, 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(1);
NodeId peer_id0 = 0;
BOOST_REQUIRE(!tracker.IsPeerRegistered(peer_id0));
tracker.PreRegisterPeer(peer_id0);
BOOST_REQUIRE(!tracker.IsPeerRegistered(peer_id0));
BOOST_REQUIRE(tracker.RegisterPeer(peer_id0, true, true, false, 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,180 @@
#!/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_verack,
msg_version,
)
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 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(initiator=True):
sendtxrcncl_msg = msg_sendtxrcncl()
sendtxrcncl_msg.initiator = initiator
sendtxrcncl_msg.responder = not initiator
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):
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 not peer.sendtxrcncl_msg_received.initiator
assert peer.sendtxrcncl_msg_received.responder
assert_equal(peer.sendtxrcncl_msg_received.version, 1)
peer.peer_disconnect()
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)
peer.peer_disconnect()
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
peer.peer_disconnect()
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
peer.peer_disconnect()
self.log.info('valid SENDTXRCNCL received')
peer = self.nodes[0].add_p2p_connection(PeerNoVerack(), send_version=True, wait_for_verack=False)
peer.send_message(create_sendtxrcncl_msg())
self.wait_until(lambda : "sendtxrcncl" in self.nodes[0].getpeerinfo()[-1]["bytesrecv_per_msg"])
self.log.info('second SENDTXRCNCL triggers a disconnect')
peer.send_message(create_sendtxrcncl_msg())
peer.wait_for_disconnect()
self.log.info('SENDTXRCNCL with initiator=responder=0 triggers a disconnect')
sendtxrcncl_no_role = create_sendtxrcncl_msg()
sendtxrcncl_no_role.initiator = False
sendtxrcncl_no_role.responder = False
peer = self.nodes[0].add_p2p_connection(PeerNoVerack(), send_version=True, wait_for_verack=False)
peer.send_message(sendtxrcncl_no_role)
peer.wait_for_disconnect()
self.log.info('SENDTXRCNCL with initiator=0 and responder=1 from inbound triggers a disconnect')
sendtxrcncl_wrong_role = create_sendtxrcncl_msg(initiator=False)
peer = self.nodes[0].add_p2p_connection(PeerNoVerack(), send_version=True, wait_for_verack=False)
peer.send_message(sendtxrcncl_wrong_role)
peer.wait_for_disconnect()
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)
peer.send_message(sendtxrcncl_low_version)
peer.wait_for_disconnect()
self.log.info('sending SENDTXRCNCL after sending VERACK triggers a disconnect')
# We use PeerNoVerack even though verack is sent right after, to make sure it was actually
# sent before sendtxrcncl is sent.
peer = self.nodes[0].add_p2p_connection(PeerNoVerack(), send_version=True, wait_for_verack=False)
peer.send_and_ping(msg_verack())
peer.send_message(create_sendtxrcncl_msg())
peer.wait_for_disconnect()
self.log.info('SENDTXRCNCL sent to an outbound')
peer = self.nodes[0].add_outbound_p2p_connection(
SendTxrcnclReceiver(), wait_for_verack=True, p2p_idx=1, connection_type="outbound-full-relay")
assert peer.sendtxrcncl_msg_received
assert peer.sendtxrcncl_msg_received.initiator
assert not peer.sendtxrcncl_msg_received.responder
assert_equal(peer.sendtxrcncl_msg_received.version, 1)
peer.peer_disconnect()
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=2, connection_type="block-relay-only")
assert not peer.sendtxrcncl_msg_received
peer.peer_disconnect()
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=3, connection_type="block-relay-only")
peer.send_message(create_sendtxrcncl_msg(initiator=False))
peer.wait_for_disconnect()
self.log.info('SENDTXRCNCL with initiator=1 and responder=0 from outbound triggers a disconnect')
sendtxrcncl_wrong_role = create_sendtxrcncl_msg(initiator=True)
peer = self.nodes[0].add_outbound_p2p_connection(
P2PInterface(), wait_for_verack=False, p2p_idx=4, connection_type="outbound-full-relay")
peer.send_message(sendtxrcncl_wrong_role)
peer.wait_for_disconnect()
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
peer.peer_disconnect()
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
peer.peer_disconnect()
if __name__ == '__main__':
SendTxRcnclTest().main()

View File

@ -57,7 +57,7 @@ class RpcMiscTest(BitcoinTestFramework):
self.log.info("test logging rpc and help") self.log.info("test logging rpc and help")
# Test logging RPC returns the expected number of logging categories. # 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. # Test toggling a logging category on/off/on with the logging RPC.
assert_equal(node.logging()['qt'], True) assert_equal(node.logging()['qt'], True)

View File

@ -2558,3 +2558,31 @@ class msg_cfcheckpt:
def __repr__(self): def __repr__(self):
return "msg_cfcheckpt(filter_type={:#x}, stop_hash={:x})".format( return "msg_cfcheckpt(filter_type={:#x}, stop_hash={:x})".format(
self.filter_type, self.stop_hash) self.filter_type, self.stop_hash)
class msg_sendtxrcncl:
__slots__ = ("initiator", "responder", "version", "salt")
msgtype = b"sendtxrcncl"
def __init__(self):
self.initiator = False
self.responder = False
self.version = 0
self.salt = 0
def deserialize(self, f):
self.initiator = struct.unpack("<?", f.read(1))[0]
self.responder = struct.unpack("<?", f.read(1))[0]
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("<?", self.initiator)
r += struct.pack("<?", self.responder)
r += struct.pack("<I", self.version)
r += struct.pack("<Q", self.salt)
return r
def __repr__(self):
return "msg_sendtxrcncl(initiator=%i, responder=%i, version=%lu, salt=%lu)" %\
(self.initiator, self.responder, self.version, self.salt)

View File

@ -72,6 +72,7 @@ from test_framework.messages import (
msg_sendcmpct, msg_sendcmpct,
msg_sendheaders, msg_sendheaders,
msg_sendheaders2, msg_sendheaders2,
msg_sendtxrcncl,
msg_tx, msg_tx,
msg_verack, msg_verack,
msg_version, msg_version,
@ -141,6 +142,7 @@ MESSAGEMAP = {
b"sendcmpct": msg_sendcmpct, b"sendcmpct": msg_sendcmpct,
b"sendheaders": msg_sendheaders, b"sendheaders": msg_sendheaders,
b"sendheaders2": msg_sendheaders2, b"sendheaders2": msg_sendheaders2,
b"sendtxrcncl": msg_sendtxrcncl,
b"tx": msg_tx, b"tx": msg_tx,
b"verack": msg_verack, b"verack": msg_verack,
b"version": msg_version, b"version": msg_version,
@ -569,6 +571,7 @@ class P2PInterface(P2PConnection):
def on_sendcmpct(self, message): pass def on_sendcmpct(self, message): pass
def on_sendheaders(self, message): pass def on_sendheaders(self, message): pass
def on_sendheaders2(self, message): pass def on_sendheaders2(self, message): pass
def on_sendtxrcncl(self, message): pass
def on_tx(self, message): pass def on_tx(self, message): pass
def on_inv(self, message): def on_inv(self, message):

View File

@ -360,6 +360,7 @@ BASE_SCRIPTS = [
'rpc_deriveaddresses.py', 'rpc_deriveaddresses.py',
'rpc_deriveaddresses.py --usecli', 'rpc_deriveaddresses.py --usecli',
'p2p_ping.py', 'p2p_ping.py',
'p2p_sendtxrcncl.py',
'rpc_scantxoutset.py', 'rpc_scantxoutset.py',
'feature_txindex_compatibility.py', 'feature_txindex_compatibility.py',
'feature_logging.py', 'feature_logging.py',