mirror of
https://github.com/dashpay/dash.git
synced 2024-12-24 19:42:46 +01:00
merge bitcoin#23443: Erlay support signaling
This commit is contained in:
parent
fdc3c07554
commit
6a7868dba7
@ -277,6 +277,7 @@ BITCOIN_CORE_H = \
|
||||
node/minisketchwrapper.h \
|
||||
node/psbt.h \
|
||||
node/transaction.h \
|
||||
node/txreconciliation.h \
|
||||
node/ui_interface.h \
|
||||
node/utxo_snapshot.h \
|
||||
noui.h \
|
||||
@ -509,6 +510,7 @@ libbitcoin_server_a_SOURCES = \
|
||||
node/minisketchwrapper.cpp \
|
||||
node/psbt.cpp \
|
||||
node/transaction.cpp \
|
||||
node/txreconciliation.cpp \
|
||||
node/ui_interface.cpp \
|
||||
noui.cpp \
|
||||
policy/fees.cpp \
|
||||
|
@ -174,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 \
|
||||
|
@ -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>
|
||||
@ -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("-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("-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("-torpassword=<pass>", "Tor control port password (default: empty)", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::CONNECTION);
|
||||
#ifdef USE_UPNP
|
||||
|
@ -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,25 @@ 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 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.
|
||||
{
|
||||
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;
|
||||
return;
|
||||
}
|
||||
@ -3727,6 +3761,58 @@ 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) {
|
||||
// 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) {
|
||||
LogPrint(BCLog::NET, "Unsupported message \"%s\" prior to verack from peer=%d\n", SanitizeString(msg_type), pfrom.GetId());
|
||||
return;
|
||||
|
184
src/node/txreconciliation.cpp
Normal file
184
src/node/txreconciliation.cpp
Normal 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);
|
||||
}
|
90
src/node/txreconciliation.h
Normal file
90
src/node/txreconciliation.h
Normal 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
|
@ -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,14 @@ extern const char* GETCFCHECKPT;
|
||||
* evenly spaced filter headers for blocks on the requested chain.
|
||||
*/
|
||||
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
|
||||
// NOTE: do NOT declare non-implmented here, we don't want them to be exposed to the outside
|
||||
|
@ -174,6 +174,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);
|
||||
|
86
src/test/txreconciliation_tests.cpp
Normal file
86
src/test/txreconciliation_tests.cpp
Normal 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()
|
180
test/functional/p2p_sendtxrcncl.py
Executable file
180
test/functional/p2p_sendtxrcncl.py
Executable 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()
|
@ -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,31 @@ 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__ = ("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)
|
||||
|
@ -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