mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-03-05 14:06:27 -05:00
Merge bitcoin/bitcoin#23443: p2p: Erlay support signaling
e56d1d2afd
test: Add functional tests for sendtxrcncl message from outbound (Gleb Naumenko)cfcef60779
test: Add functional tests for sendtxrcncl from inbound (Gleb Naumenko)b99ee9d22d
test: Add unit tests for reconciliation negotiation (Gleb Naumenko)f63f1d3f4b
p2p: clear txreconciliation state for non-wtxid peers (Gleb Naumenko)88d326c8e3
p2p: Finish negotiating reconciliation support (Gleb Naumenko)36cf6bf216
Add helper to see if a peer is registered for reconciliations (Gleb Naumenko)4470acf076
p2p: Forget peer's reconciliation state on disconnect (Gleb Naumenko)3fcf78ee6a
p2p: Announce reconciliation support (Gleb Naumenko)24e36fac0a
log: Add tx reconciliation log category (Gleb Naumenko) Pull request description: This is a part of the Erlay project: - [parent PR](https://github.com/bitcoin/bitcoin/pull/21515) - [associated BIP-330](https://github.com/bitcoin/bips/pull/1376). ------- This PR adds a new p2p message `sendtxrcncl` signaling for reconciliation support. Before sending that message, a node is supposed to "pre-register" the peer by generating and storing an associated reconciliation salt component. Once the salts are exchanged within this new message, nodes "register" each other for future reconciliations by computing and storing the aggregate salt, along with the reconciliation parameters based on the connection direction. ACKs for top commit: dergoegge: re-ACKe56d1d2afd
sipa: re-ACKe56d1d2afd
. No differences with a rebase of previously reviewed e91690e67dad180c7fb9bed0409a9c4567d3e5df. mzumsande: re-ACKe56d1d2afd
vasild: ACKe56d1d2afd
Tree-SHA512: 0db953b7347364e2496ebca3bfe6a27ac336307eec698242523a18336fcfc7a1ab87e3b09ce8b2bdf800ebbb1c9d33736ffdb8f5672f93735318789aa4a45f39
This commit is contained in:
commit
e7a0e96271
17 changed files with 696 additions and 5 deletions
|
@ -209,6 +209,7 @@ BITCOIN_CORE_H = \
|
|||
node/minisketchwrapper.h \
|
||||
node/psbt.h \
|
||||
node/transaction.h \
|
||||
node/txreconciliation.h \
|
||||
node/utxo_snapshot.h \
|
||||
node/validation_cache_args.h \
|
||||
noui.h \
|
||||
|
@ -396,6 +397,7 @@ libbitcoin_node_a_SOURCES = \
|
|||
node/minisketchwrapper.cpp \
|
||||
node/psbt.cpp \
|
||||
node/transaction.cpp \
|
||||
node/txreconciliation.cpp \
|
||||
node/utxo_snapshot.cpp \
|
||||
node/validation_cache_args.cpp \
|
||||
noui.cpp \
|
||||
|
|
|
@ -148,6 +148,7 @@ BITCOIN_TESTS =\
|
|||
test/transaction_tests.cpp \
|
||||
test/txindex_tests.cpp \
|
||||
test/txpackage_tests.cpp \
|
||||
test/txreconciliation_tests.cpp \
|
||||
test/txrequest_tests.cpp \
|
||||
test/txvalidation_tests.cpp \
|
||||
test/txvalidationcache_tests.cpp \
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
#include <node/mempool_args.h>
|
||||
#include <node/mempool_persist_args.h>
|
||||
#include <node/miner.h>
|
||||
#include <node/txreconciliation.h>
|
||||
#include <node/validation_cache_args.h>
|
||||
#include <policy/feerate.h>
|
||||
#include <policy/fees.h>
|
||||
|
@ -484,6 +485,7 @@ void SetupServerArgs(ArgsManager& argsman)
|
|||
argsman.AddArg("-seednode=<ip>", "Connect to a node to retrieve peer addresses, and disconnect. This option can be specified multiple times to connect to multiple nodes.", 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_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("-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 | 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);
|
||||
|
|
|
@ -180,6 +180,7 @@ const CLogCategoryDesc LogCategories[] =
|
|||
#endif
|
||||
{BCLog::UTIL, "util"},
|
||||
{BCLog::BLOCKSTORE, "blockstorage"},
|
||||
{BCLog::TXRECONCILIATION, "txreconciliation"},
|
||||
{BCLog::ALL, "1"},
|
||||
{BCLog::ALL, "all"},
|
||||
};
|
||||
|
@ -280,6 +281,8 @@ std::string LogCategoryToStr(BCLog::LogFlags category)
|
|||
return "util";
|
||||
case BCLog::LogFlags::BLOCKSTORE:
|
||||
return "blockstorage";
|
||||
case BCLog::LogFlags::TXRECONCILIATION:
|
||||
return "txreconciliation";
|
||||
case BCLog::LogFlags::ALL:
|
||||
return "all";
|
||||
}
|
||||
|
|
|
@ -66,6 +66,7 @@ namespace BCLog {
|
|||
#endif
|
||||
UTIL = (1 << 25),
|
||||
BLOCKSTORE = (1 << 26),
|
||||
TXRECONCILIATION = (1 << 27),
|
||||
ALL = ~(uint32_t)0,
|
||||
};
|
||||
enum class Level {
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include <netbase.h>
|
||||
#include <netmessagemaker.h>
|
||||
#include <node/blockstorage.h>
|
||||
#include <node/txreconciliation.h>
|
||||
#include <policy/fees.h>
|
||||
#include <policy/policy.h>
|
||||
#include <policy/settings.h>
|
||||
|
@ -703,6 +704,7 @@ private:
|
|||
ChainstateManager& m_chainman;
|
||||
CTxMemPool& m_mempool;
|
||||
TxRequestTracker m_txrequest GUARDED_BY(::cs_main);
|
||||
std::unique_ptr<TxReconciliationTracker> m_txreconciliation;
|
||||
|
||||
/** The height of the best chain */
|
||||
std::atomic<int> m_best_height{-1};
|
||||
|
@ -1492,6 +1494,7 @@ void PeerManagerImpl::FinalizeNode(const CNode& node)
|
|||
}
|
||||
WITH_LOCK(g_cs_orphans, m_orphanage.EraseForPeer(nodeid));
|
||||
m_txrequest.DisconnectedPeer(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);
|
||||
|
@ -1776,6 +1779,11 @@ PeerManagerImpl::PeerManagerImpl(CConnman& connman, AddrMan& addrman,
|
|||
m_mempool(pool),
|
||||
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)
|
||||
|
@ -3236,8 +3244,6 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
|
|||
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);
|
||||
|
@ -3262,6 +3268,25 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
|
|||
if (fRelay) pfrom.m_relays_txs = true;
|
||||
}
|
||||
|
||||
if (greatest_common_version >= WTXID_RELAY_VERSION && m_txreconciliation) {
|
||||
// Per BIP-330, we announce txreconciliation support if:
|
||||
// - protocol version per the VERSION message supports WTXID_RELAY;
|
||||
// - 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);
|
||||
|
@ -3389,6 +3414,16 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
|
|||
// they may wish to request compact blocks from us
|
||||
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::SENDCMPCT, /*high_bandwidth=*/false, /*version=*/CMPCTBLOCKS_VERSION));
|
||||
}
|
||||
|
||||
if (m_txreconciliation) {
|
||||
if (!peer->m_wtxid_relay || !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 this wasn't followed
|
||||
// by WTXIDRELAY (since WTXIDRELAY can't be announced later).
|
||||
m_txreconciliation->ForgetPeer(pfrom.GetId());
|
||||
}
|
||||
}
|
||||
|
||||
pfrom.fSuccessfullyConnected = true;
|
||||
return;
|
||||
}
|
||||
|
@ -3452,6 +3487,58 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
|
|||
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) {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "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.
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "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.
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "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.
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "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) {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "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 HashWriter 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());
|
||||
|
||||
LogPrintLevel(BCLog::TXRECONCILIATION, BCLog::Level::Debug, "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;
|
||||
|
||||
LogPrintLevel(BCLog::TXRECONCILIATION, BCLog::Level::Debug, "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)) {
|
||||
LogPrintLevel(BCLog::TXRECONCILIATION, BCLog::Level::Debug, "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
|
|
@ -44,6 +44,7 @@ const char *CFHEADERS="cfheaders";
|
|||
const char *GETCFCHECKPT="getcfcheckpt";
|
||||
const char *CFCHECKPT="cfcheckpt";
|
||||
const char *WTXIDRELAY="wtxidrelay";
|
||||
const char *SENDTXRCNCL="sendtxrcncl";
|
||||
} // namespace NetMsgType
|
||||
|
||||
/** All known message types. Keep this in the same order as the list of
|
||||
|
@ -84,6 +85,7 @@ const static std::string allNetMessageTypes[] = {
|
|||
NetMsgType::GETCFCHECKPT,
|
||||
NetMsgType::CFCHECKPT,
|
||||
NetMsgType::WTXIDRELAY,
|
||||
NetMsgType::SENDTXRCNCL,
|
||||
};
|
||||
const static std::vector<std::string> allNetMessageTypesVec(std::begin(allNetMessageTypes), std::end(allNetMessageTypes));
|
||||
|
||||
|
|
|
@ -258,6 +258,14 @@ extern const char* CFCHECKPT;
|
|||
* @since protocol version 70016 as described by BIP 339.
|
||||
*/
|
||||
extern const char* WTXIDRELAY;
|
||||
/**
|
||||
* 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;
|
||||
}; // namespace NetMsgType
|
||||
|
||||
/* Get a vector of all valid message types (see above) */
|
||||
|
|
|
@ -130,6 +130,7 @@ FUZZ_TARGET_MSG(pong);
|
|||
FUZZ_TARGET_MSG(sendaddrv2);
|
||||
FUZZ_TARGET_MSG(sendcmpct);
|
||||
FUZZ_TARGET_MSG(sendheaders);
|
||||
FUZZ_TARGET_MSG(sendtxrcncl);
|
||||
FUZZ_TARGET_MSG(tx);
|
||||
FUZZ_TARGET_MSG(verack);
|
||||
FUZZ_TARGET_MSG(version);
|
||||
|
|
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()
|
191
test/functional/p2p_sendtxrcncl.py
Executable file
191
test/functional/p2p_sendtxrcncl.py
Executable file
|
@ -0,0 +1,191 @@
|
|||
#!/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,
|
||||
msg_wtxidrelay,
|
||||
)
|
||||
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, wtxidrelay=True):
|
||||
super().__init__(wtxidrelay=wtxidrelay)
|
||||
|
||||
def on_version(self, message):
|
||||
# Avoid sending verack in response to version.
|
||||
# When calling add_p2p_connection, wait_for_verack=False must be set (see
|
||||
# comment in add_p2p_connection).
|
||||
if message.nVersion >= 70016 and self.wtxidrelay:
|
||||
self.send_message(msg_wtxidrelay())
|
||||
|
||||
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-WTXID version should not be sent')
|
||||
peer = self.nodes[0].add_p2p_connection(SendTxrcnclReceiver(), send_version=False, wait_for_verack=False)
|
||||
pre_wtxid_version_msg = msg_version()
|
||||
pre_wtxid_version_msg.nVersion = 70015
|
||||
pre_wtxid_version_msg.strSubVer = P2P_SUBVERSION
|
||||
pre_wtxid_version_msg.nServices = P2P_SERVICES
|
||||
pre_wtxid_version_msg.relay = 1
|
||||
peer.send_message(pre_wtxid_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 without WTXIDRELAY is ignored (recon state is erased after VERACK)')
|
||||
peer = self.nodes[0].add_p2p_connection(PeerNoVerack(wtxidrelay=False), send_version=True, wait_for_verack=False)
|
||||
with self.nodes[0].assert_debug_log(['Forget txreconciliation state of peer']):
|
||||
peer.send_message(create_sendtxrcncl_msg())
|
||||
peer.send_message(msg_verack())
|
||||
peer.peer_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()
|
|
@ -1838,3 +1838,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)
|
||||
|
|
|
@ -62,6 +62,7 @@ from test_framework.messages import (
|
|||
msg_sendaddrv2,
|
||||
msg_sendcmpct,
|
||||
msg_sendheaders,
|
||||
msg_sendtxrcncl,
|
||||
msg_tx,
|
||||
MSG_TX,
|
||||
MSG_TYPE_MASK,
|
||||
|
@ -126,6 +127,7 @@ MESSAGEMAP = {
|
|||
b"sendaddrv2": msg_sendaddrv2,
|
||||
b"sendcmpct": msg_sendcmpct,
|
||||
b"sendheaders": msg_sendheaders,
|
||||
b"sendtxrcncl": msg_sendtxrcncl,
|
||||
b"tx": msg_tx,
|
||||
b"verack": msg_verack,
|
||||
b"version": msg_version,
|
||||
|
@ -421,6 +423,7 @@ class P2PInterface(P2PConnection):
|
|||
def on_sendaddrv2(self, message): pass
|
||||
def on_sendcmpct(self, message): pass
|
||||
def on_sendheaders(self, message): pass
|
||||
def on_sendtxrcncl(self, message): pass
|
||||
def on_tx(self, message): pass
|
||||
def on_wtxidrelay(self, message): pass
|
||||
|
||||
|
|
|
@ -618,7 +618,7 @@ class TestNode():
|
|||
|
||||
return p2p_conn
|
||||
|
||||
def add_outbound_p2p_connection(self, p2p_conn, *, p2p_idx, connection_type="outbound-full-relay", **kwargs):
|
||||
def add_outbound_p2p_connection(self, p2p_conn, *, wait_for_verack=True, p2p_idx, connection_type="outbound-full-relay", **kwargs):
|
||||
"""Add an outbound p2p connection from node. Must be an
|
||||
"outbound-full-relay", "block-relay-only", "addr-fetch" or "feeler" connection.
|
||||
|
||||
|
@ -640,8 +640,9 @@ class TestNode():
|
|||
p2p_conn.wait_for_connect()
|
||||
self.p2ps.append(p2p_conn)
|
||||
|
||||
p2p_conn.wait_for_verack()
|
||||
p2p_conn.sync_with_ping()
|
||||
if wait_for_verack:
|
||||
p2p_conn.wait_for_verack()
|
||||
p2p_conn.sync_with_ping()
|
||||
|
||||
return p2p_conn
|
||||
|
||||
|
|
|
@ -318,6 +318,7 @@ BASE_SCRIPTS = [
|
|||
'rpc_deriveaddresses.py --usecli',
|
||||
'p2p_ping.py',
|
||||
'rpc_scanblocks.py',
|
||||
'p2p_sendtxrcncl.py',
|
||||
'rpc_scantxoutset.py',
|
||||
'feature_txindex_compatibility.py',
|
||||
'feature_unsupported_utxo_db.py',
|
||||
|
|
Loading…
Add table
Reference in a new issue