diff --git a/src/Makefile.am b/src/Makefile.am index 88c62d51779..5347aa5ddef 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 \ diff --git a/src/init.cpp b/src/init.cpp index 8ffab646226..1b3162bf398 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -45,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -484,6 +485,7 @@ void SetupServerArgs(ArgsManager& argsman) argsman.AddArg("-seednode=", "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=", 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=", 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=:", strprintf("Tor control port to use if onion listening enabled (default: %s)", DEFAULT_TOR_CONTROL), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-torpassword=", "Tor control port password (default: empty)", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::CONNECTION); diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 75537a2d98b..a10b6bfc471 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -703,6 +704,7 @@ private: ChainstateManager& m_chainman; CTxMemPool& m_mempool; TxRequestTracker m_txrequest GUARDED_BY(::cs_main); + std::unique_ptr m_txreconciliation; /** The height of the best chain */ std::atomic m_best_height{-1}; @@ -1776,6 +1778,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(); + } } void PeerManagerImpl::StartScheduledTasks(CScheduler& scheduler) @@ -3236,8 +3243,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 +3267,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); diff --git a/src/node/txreconciliation.cpp b/src/node/txreconciliation.cpp new file mode 100644 index 00000000000..dfac2ad82ed --- /dev/null +++ b/src/node/txreconciliation.cpp @@ -0,0 +1,66 @@ +// 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 + +#include +#include + +#include +#include + + +namespace { + +/** + * Keeps track of txreconciliation-related per-peer state. + */ +class TxReconciliationState +{ +}; + +} // namespace + +/** Actual implementation for TxReconciliationTracker's data structure. */ +class TxReconciliationTracker::Impl +{ +private: + mutable Mutex m_txreconciliation_mutex; + + /** + * 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> m_states GUARDED_BY(m_txreconciliation_mutex); + +public: + explicit Impl() {} + + 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; + } +}; + +TxReconciliationTracker::TxReconciliationTracker() : m_impl{std::make_unique()} {} + +TxReconciliationTracker::~TxReconciliationTracker() = default; + +uint64_t TxReconciliationTracker::PreRegisterPeer(NodeId peer_id) +{ + return m_impl->PreRegisterPeer(peer_id); +} diff --git a/src/node/txreconciliation.h b/src/node/txreconciliation.h new file mode 100644 index 00000000000..6e94a51cf24 --- /dev/null +++ b/src/node/txreconciliation.h @@ -0,0 +1,66 @@ +// 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 +#include + +#include +#include + +/** 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}; + +/** + * 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 m_impl; + +public: + explicit TxReconciliationTracker(); + ~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); +}; + +#endif // BITCOIN_NODE_TXRECONCILIATION_H diff --git a/src/protocol.cpp b/src/protocol.cpp index bdd1cc2aff2..23c68b335bf 100644 --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -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 allNetMessageTypesVec(std::begin(allNetMessageTypes), std::end(allNetMessageTypes)); diff --git a/src/protocol.h b/src/protocol.h index b85dc0d8201..17a363b1d32 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -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) */ diff --git a/src/test/fuzz/process_message.cpp b/src/test/fuzz/process_message.cpp index f6a35da4fc0..5a4df735da7 100644 --- a/src/test/fuzz/process_message.cpp +++ b/src/test/fuzz/process_message.cpp @@ -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);