Skip to content

Commit e7a0e96

Browse files
committed
Merge bitcoin/bitcoin#23443: p2p: Erlay support signaling
e56d1d2 test: Add functional tests for sendtxrcncl message from outbound (Gleb Naumenko) cfcef60 test: Add functional tests for sendtxrcncl from inbound (Gleb Naumenko) b99ee9d test: Add unit tests for reconciliation negotiation (Gleb Naumenko) f63f1d3 p2p: clear txreconciliation state for non-wtxid peers (Gleb Naumenko) 88d326c p2p: Finish negotiating reconciliation support (Gleb Naumenko) 36cf6bf Add helper to see if a peer is registered for reconciliations (Gleb Naumenko) 4470acf p2p: Forget peer's reconciliation state on disconnect (Gleb Naumenko) 3fcf78e p2p: Announce reconciliation support (Gleb Naumenko) 24e36fa log: Add tx reconciliation log category (Gleb Naumenko) Pull request description: This is a part of the Erlay project: - [parent PR](bitcoin/bitcoin#21515) - [associated BIP-330](bitcoin/bips#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-ACK e56d1d2 sipa: re-ACK e56d1d2. No differences with a rebase of previously reviewed e91690e67dad180c7fb9bed0409a9c4567d3e5df. mzumsande: re-ACK e56d1d2 vasild: ACK e56d1d2 Tree-SHA512: 0db953b7347364e2496ebca3bfe6a27ac336307eec698242523a18336fcfc7a1ab87e3b09ce8b2bdf800ebbb1c9d33736ffdb8f5672f93735318789aa4a45f39
2 parents a52ff61 + e56d1d2 commit e7a0e96

17 files changed

+696
-5
lines changed

src/Makefile.am

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ BITCOIN_CORE_H = \
209209
node/minisketchwrapper.h \
210210
node/psbt.h \
211211
node/transaction.h \
212+
node/txreconciliation.h \
212213
node/utxo_snapshot.h \
213214
node/validation_cache_args.h \
214215
noui.h \
@@ -396,6 +397,7 @@ libbitcoin_node_a_SOURCES = \
396397
node/minisketchwrapper.cpp \
397398
node/psbt.cpp \
398399
node/transaction.cpp \
400+
node/txreconciliation.cpp \
399401
node/utxo_snapshot.cpp \
400402
node/validation_cache_args.cpp \
401403
noui.cpp \

src/Makefile.test.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ BITCOIN_TESTS =\
148148
test/transaction_tests.cpp \
149149
test/txindex_tests.cpp \
150150
test/txpackage_tests.cpp \
151+
test/txreconciliation_tests.cpp \
151152
test/txrequest_tests.cpp \
152153
test/txvalidation_tests.cpp \
153154
test/txvalidationcache_tests.cpp \

src/init.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
#include <node/mempool_args.h>
4646
#include <node/mempool_persist_args.h>
4747
#include <node/miner.h>
48+
#include <node/txreconciliation.h>
4849
#include <node/validation_cache_args.h>
4950
#include <policy/feerate.h>
5051
#include <policy/fees.h>
@@ -484,6 +485,7 @@ void SetupServerArgs(ArgsManager& argsman)
484485
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);
485486
argsman.AddArg("-networkactive", "Enable all P2P network activity (default: 1). Can be changed by the setnetworkactive RPC command", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
486487
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);
488+
argsman.AddArg("-txreconciliation", strprintf("Enable transaction reconciliations per BIP 330 (default: %d)", DEFAULT_TXRECONCILIATION_ENABLE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CONNECTION);
487489
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);
488490
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);
489491
argsman.AddArg("-torpassword=<pass>", "Tor control port password (default: empty)", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::CONNECTION);

src/logging.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ const CLogCategoryDesc LogCategories[] =
180180
#endif
181181
{BCLog::UTIL, "util"},
182182
{BCLog::BLOCKSTORE, "blockstorage"},
183+
{BCLog::TXRECONCILIATION, "txreconciliation"},
183184
{BCLog::ALL, "1"},
184185
{BCLog::ALL, "all"},
185186
};
@@ -280,6 +281,8 @@ std::string LogCategoryToStr(BCLog::LogFlags category)
280281
return "util";
281282
case BCLog::LogFlags::BLOCKSTORE:
282283
return "blockstorage";
284+
case BCLog::LogFlags::TXRECONCILIATION:
285+
return "txreconciliation";
283286
case BCLog::LogFlags::ALL:
284287
return "all";
285288
}

src/logging.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ namespace BCLog {
6666
#endif
6767
UTIL = (1 << 25),
6868
BLOCKSTORE = (1 << 26),
69+
TXRECONCILIATION = (1 << 27),
6970
ALL = ~(uint32_t)0,
7071
};
7172
enum class Level {

src/net_processing.cpp

Lines changed: 89 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <netbase.h>
2121
#include <netmessagemaker.h>
2222
#include <node/blockstorage.h>
23+
#include <node/txreconciliation.h>
2324
#include <policy/fees.h>
2425
#include <policy/policy.h>
2526
#include <policy/settings.h>
@@ -703,6 +704,7 @@ class PeerManagerImpl final : public PeerManager
703704
ChainstateManager& m_chainman;
704705
CTxMemPool& m_mempool;
705706
TxRequestTracker m_txrequest GUARDED_BY(::cs_main);
707+
std::unique_ptr<TxReconciliationTracker> m_txreconciliation;
706708

707709
/** The height of the best chain */
708710
std::atomic<int> m_best_height{-1};
@@ -1492,6 +1494,7 @@ void PeerManagerImpl::FinalizeNode(const CNode& node)
14921494
}
14931495
WITH_LOCK(g_cs_orphans, m_orphanage.EraseForPeer(nodeid));
14941496
m_txrequest.DisconnectedPeer(nodeid);
1497+
if (m_txreconciliation) m_txreconciliation->ForgetPeer(nodeid);
14951498
m_num_preferred_download_peers -= state->fPreferredDownload;
14961499
m_peers_downloading_from -= (state->nBlocksInFlight != 0);
14971500
assert(m_peers_downloading_from >= 0);
@@ -1776,6 +1779,11 @@ PeerManagerImpl::PeerManagerImpl(CConnman& connman, AddrMan& addrman,
17761779
m_mempool(pool),
17771780
m_ignore_incoming_txs(ignore_incoming_txs)
17781781
{
1782+
// While Erlay support is incomplete, it must be enabled explicitly via -txreconciliation.
1783+
// This argument can go away after Erlay support is complete.
1784+
if (gArgs.GetBoolArg("-txreconciliation", DEFAULT_TXRECONCILIATION_ENABLE)) {
1785+
m_txreconciliation = std::make_unique<TxReconciliationTracker>(TXRECONCILIATION_VERSION);
1786+
}
17791787
}
17801788

17811789
void PeerManagerImpl::StartScheduledTasks(CScheduler& scheduler)
@@ -3236,8 +3244,6 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
32363244
m_connman.PushMessage(&pfrom, msg_maker.Make(NetMsgType::SENDADDRV2));
32373245
}
32383246

3239-
m_connman.PushMessage(&pfrom, msg_maker.Make(NetMsgType::VERACK));
3240-
32413247
pfrom.m_has_all_wanted_services = HasAllDesirableServiceFlags(nServices);
32423248
peer->m_their_services = nServices;
32433249
pfrom.SetAddrLocal(addrMe);
@@ -3262,6 +3268,25 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
32623268
if (fRelay) pfrom.m_relays_txs = true;
32633269
}
32643270

3271+
if (greatest_common_version >= WTXID_RELAY_VERSION && m_txreconciliation) {
3272+
// Per BIP-330, we announce txreconciliation support if:
3273+
// - protocol version per the VERSION message supports WTXID_RELAY;
3274+
// - we intended to exchange transactions over this connection while establishing it
3275+
// and the peer indicated support for transaction relay in the VERSION message;
3276+
// - we are not in -blocksonly mode.
3277+
if (pfrom.m_relays_txs && !m_ignore_incoming_txs) {
3278+
const uint64_t recon_salt = m_txreconciliation->PreRegisterPeer(pfrom.GetId());
3279+
// We suggest our txreconciliation role (initiator/responder) based on
3280+
// the connection direction.
3281+
m_connman.PushMessage(&pfrom, msg_maker.Make(NetMsgType::SENDTXRCNCL,
3282+
!pfrom.IsInboundConn(),
3283+
pfrom.IsInboundConn(),
3284+
TXRECONCILIATION_VERSION, recon_salt));
3285+
}
3286+
}
3287+
3288+
m_connman.PushMessage(&pfrom, msg_maker.Make(NetMsgType::VERACK));
3289+
32653290
// Potentially mark this peer as a preferred download peer.
32663291
{
32673292
LOCK(cs_main);
@@ -3389,6 +3414,16 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
33893414
// they may wish to request compact blocks from us
33903415
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::SENDCMPCT, /*high_bandwidth=*/false, /*version=*/CMPCTBLOCKS_VERSION));
33913416
}
3417+
3418+
if (m_txreconciliation) {
3419+
if (!peer->m_wtxid_relay || !m_txreconciliation->IsPeerRegistered(pfrom.GetId())) {
3420+
// We could have optimistically pre-registered/registered the peer. In that case,
3421+
// we should forget about the reconciliation state here if this wasn't followed
3422+
// by WTXIDRELAY (since WTXIDRELAY can't be announced later).
3423+
m_txreconciliation->ForgetPeer(pfrom.GetId());
3424+
}
3425+
}
3426+
33923427
pfrom.fSuccessfullyConnected = true;
33933428
return;
33943429
}
@@ -3452,6 +3487,58 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
34523487
return;
34533488
}
34543489

3490+
// Received from a peer demonstrating readiness to announce transactions via reconciliations.
3491+
// This feature negotiation must happen between VERSION and VERACK to avoid relay problems
3492+
// from switching announcement protocols after the connection is up.
3493+
if (msg_type == NetMsgType::SENDTXRCNCL) {
3494+
if (!m_txreconciliation) {
3495+
LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "sendtxrcncl from peer=%d ignored, as our node does not have txreconciliation enabled\n", pfrom.GetId());
3496+
return;
3497+
}
3498+
3499+
if (pfrom.fSuccessfullyConnected) {
3500+
// Disconnect peers that send a SENDTXRCNCL message after VERACK.
3501+
LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "sendtxrcncl received after verack from peer=%d; disconnecting\n", pfrom.GetId());
3502+
pfrom.fDisconnect = true;
3503+
return;
3504+
}
3505+
3506+
if (!peer->GetTxRelay()) {
3507+
// Disconnect peers that send a SENDTXRCNCL message even though we indicated we don't
3508+
// support transaction relay.
3509+
LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "sendtxrcncl received from peer=%d to which we indicated no tx relay; disconnecting\n", pfrom.GetId());
3510+
pfrom.fDisconnect = true;
3511+
return;
3512+
}
3513+
3514+
bool is_peer_initiator, is_peer_responder;
3515+
uint32_t peer_txreconcl_version;
3516+
uint64_t remote_salt;
3517+
vRecv >> is_peer_initiator >> is_peer_responder >> peer_txreconcl_version >> remote_salt;
3518+
3519+
if (m_txreconciliation->IsPeerRegistered(pfrom.GetId())) {
3520+
// A peer is already registered, meaning we already received SENDTXRCNCL from them.
3521+
LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "txreconciliation protocol violation from peer=%d (sendtxrcncl received from already registered peer); disconnecting\n", pfrom.GetId());
3522+
pfrom.fDisconnect = true;
3523+
return;
3524+
}
3525+
3526+
const ReconciliationRegisterResult result = m_txreconciliation->RegisterPeer(pfrom.GetId(), pfrom.IsInboundConn(),
3527+
is_peer_initiator, is_peer_responder,
3528+
peer_txreconcl_version,
3529+
remote_salt);
3530+
3531+
// If it's a protocol violation, disconnect.
3532+
// If the peer was not found (but something unexpected happened) or it was registered,
3533+
// nothing to be done.
3534+
if (result == ReconciliationRegisterResult::PROTOCOL_VIOLATION) {
3535+
LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "txreconciliation protocol violation from peer=%d; disconnecting\n", pfrom.GetId());
3536+
pfrom.fDisconnect = true;
3537+
return;
3538+
}
3539+
return;
3540+
}
3541+
34553542
if (!pfrom.fSuccessfullyConnected) {
34563543
LogPrint(BCLog::NET, "Unsupported message \"%s\" prior to verack from peer=%d\n", SanitizeString(msg_type), pfrom.GetId());
34573544
return;

src/node/txreconciliation.cpp

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
// Copyright (c) 2022 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <node/txreconciliation.h>
6+
7+
#include <util/check.h>
8+
#include <util/system.h>
9+
10+
#include <unordered_map>
11+
#include <variant>
12+
13+
14+
namespace {
15+
16+
/** Static salt component used to compute short txids for sketch construction, see BIP-330. */
17+
const std::string RECON_STATIC_SALT = "Tx Relay Salting";
18+
const HashWriter RECON_SALT_HASHER = TaggedHash(RECON_STATIC_SALT);
19+
20+
/**
21+
* Salt (specified by BIP-330) constructed from contributions from both peers. It is used
22+
* to compute transaction short IDs, which are then used to construct a sketch representing a set
23+
* of transactions we want to announce to the peer.
24+
*/
25+
uint256 ComputeSalt(uint64_t salt1, uint64_t salt2)
26+
{
27+
// According to BIP-330, salts should be combined in ascending order.
28+
return (HashWriter(RECON_SALT_HASHER) << std::min(salt1, salt2) << std::max(salt1, salt2)).GetSHA256();
29+
}
30+
31+
/**
32+
* Keeps track of txreconciliation-related per-peer state.
33+
*/
34+
class TxReconciliationState
35+
{
36+
public:
37+
/**
38+
* TODO: This field is public to ignore -Wunused-private-field. Make private once used in
39+
* the following commits.
40+
*
41+
* Reconciliation protocol assumes using one role consistently: either a reconciliation
42+
* initiator (requesting sketches), or responder (sending sketches). This defines our role.
43+
*
44+
*/
45+
bool m_we_initiate;
46+
47+
/**
48+
* TODO: These fields are public to ignore -Wunused-private-field. Make private once used in
49+
* the following commits.
50+
*
51+
* These values are used to salt short IDs, which is necessary for transaction reconciliations.
52+
*/
53+
uint64_t m_k0, m_k1;
54+
55+
TxReconciliationState(bool we_initiate, uint64_t k0, uint64_t k1) : m_we_initiate(we_initiate), m_k0(k0), m_k1(k1) {}
56+
};
57+
58+
} // namespace
59+
60+
/** Actual implementation for TxReconciliationTracker's data structure. */
61+
class TxReconciliationTracker::Impl
62+
{
63+
private:
64+
mutable Mutex m_txreconciliation_mutex;
65+
66+
// Local protocol version
67+
uint32_t m_recon_version;
68+
69+
/**
70+
* Keeps track of txreconciliation states of eligible peers.
71+
* For pre-registered peers, the locally generated salt is stored.
72+
* For registered peers, the locally generated salt is forgotten, and the state (including
73+
* "full" salt) is stored instead.
74+
*/
75+
std::unordered_map<NodeId, std::variant<uint64_t, TxReconciliationState>> m_states GUARDED_BY(m_txreconciliation_mutex);
76+
77+
public:
78+
explicit Impl(uint32_t recon_version) : m_recon_version(recon_version) {}
79+
80+
uint64_t PreRegisterPeer(NodeId peer_id) EXCLUSIVE_LOCKS_REQUIRED(!m_txreconciliation_mutex)
81+
{
82+
AssertLockNotHeld(m_txreconciliation_mutex);
83+
LOCK(m_txreconciliation_mutex);
84+
// We do not support txreconciliation salt/version updates.
85+
assert(m_states.find(peer_id) == m_states.end());
86+
87+
LogPrintLevel(BCLog::TXRECONCILIATION, BCLog::Level::Debug, "Pre-register peer=%d\n", peer_id);
88+
const uint64_t local_salt{GetRand(UINT64_MAX)};
89+
90+
// We do this exactly once per peer (which are unique by NodeId, see GetNewNodeId) so it's
91+
// safe to assume we don't have this record yet.
92+
Assert(m_states.emplace(peer_id, local_salt).second);
93+
return local_salt;
94+
}
95+
96+
ReconciliationRegisterResult RegisterPeer(NodeId peer_id, bool is_peer_inbound, bool is_peer_recon_initiator,
97+
bool is_peer_recon_responder, uint32_t peer_recon_version,
98+
uint64_t remote_salt) EXCLUSIVE_LOCKS_REQUIRED(!m_txreconciliation_mutex)
99+
{
100+
AssertLockNotHeld(m_txreconciliation_mutex);
101+
LOCK(m_txreconciliation_mutex);
102+
auto recon_state = m_states.find(peer_id);
103+
104+
// A peer should be in the pre-registered state to proceed here.
105+
if (recon_state == m_states.end()) return NOT_FOUND;
106+
uint64_t* local_salt = std::get_if<uint64_t>(&recon_state->second);
107+
// A peer is already registered. This should be checked by the caller.
108+
Assume(local_salt);
109+
110+
// If the peer supports the version which is lower than ours, we downgrade to the version
111+
// it supports. For now, this only guarantees that nodes with future reconciliation
112+
// versions have the choice of reconciling with this current version. However, they also
113+
// have the choice to refuse supporting reconciliations if the common version is not
114+
// satisfactory (e.g. too low).
115+
const uint32_t recon_version{std::min(peer_recon_version, m_recon_version)};
116+
// v1 is the lowest version, so suggesting something below must be a protocol violation.
117+
if (recon_version < 1) return PROTOCOL_VIOLATION;
118+
119+
// Must match SENDTXRCNCL logic.
120+
const bool they_initiate = is_peer_recon_initiator && is_peer_inbound;
121+
const bool we_initiate = !is_peer_inbound && is_peer_recon_responder;
122+
123+
// If we ever announce support for both requesting and responding, this will need
124+
// tie-breaking. For now, this is mutually exclusive because both are based on the
125+
// inbound flag.
126+
assert(!(they_initiate && we_initiate));
127+
128+
// The peer set both flags to false, we treat it as a protocol violation.
129+
if (!(they_initiate || we_initiate)) return PROTOCOL_VIOLATION;
130+
131+
LogPrintLevel(BCLog::TXRECONCILIATION, BCLog::Level::Debug, "Register peer=%d with the following params: " /* Continued */
132+
"we_initiate=%i, they_initiate=%i.\n",
133+
peer_id, we_initiate, they_initiate);
134+
135+
const uint256 full_salt{ComputeSalt(*local_salt, remote_salt)};
136+
recon_state->second = TxReconciliationState(we_initiate, full_salt.GetUint64(0), full_salt.GetUint64(1));
137+
return SUCCESS;
138+
}
139+
140+
void ForgetPeer(NodeId peer_id) EXCLUSIVE_LOCKS_REQUIRED(!m_txreconciliation_mutex)
141+
{
142+
AssertLockNotHeld(m_txreconciliation_mutex);
143+
LOCK(m_txreconciliation_mutex);
144+
if (m_states.erase(peer_id)) {
145+
LogPrintLevel(BCLog::TXRECONCILIATION, BCLog::Level::Debug, "Forget txreconciliation state of peer=%d\n", peer_id);
146+
}
147+
}
148+
149+
bool IsPeerRegistered(NodeId peer_id) const EXCLUSIVE_LOCKS_REQUIRED(!m_txreconciliation_mutex)
150+
{
151+
AssertLockNotHeld(m_txreconciliation_mutex);
152+
LOCK(m_txreconciliation_mutex);
153+
auto recon_state = m_states.find(peer_id);
154+
return (recon_state != m_states.end() &&
155+
std::holds_alternative<TxReconciliationState>(recon_state->second));
156+
}
157+
};
158+
159+
TxReconciliationTracker::TxReconciliationTracker(uint32_t recon_version) : m_impl{std::make_unique<TxReconciliationTracker::Impl>(recon_version)} {}
160+
161+
TxReconciliationTracker::~TxReconciliationTracker() = default;
162+
163+
uint64_t TxReconciliationTracker::PreRegisterPeer(NodeId peer_id)
164+
{
165+
return m_impl->PreRegisterPeer(peer_id);
166+
}
167+
168+
ReconciliationRegisterResult TxReconciliationTracker::RegisterPeer(NodeId peer_id, bool is_peer_inbound,
169+
bool is_peer_recon_initiator, bool is_peer_recon_responder,
170+
uint32_t peer_recon_version, uint64_t remote_salt)
171+
{
172+
return m_impl->RegisterPeer(peer_id, is_peer_inbound, is_peer_recon_initiator, is_peer_recon_responder,
173+
peer_recon_version, remote_salt);
174+
}
175+
176+
void TxReconciliationTracker::ForgetPeer(NodeId peer_id)
177+
{
178+
m_impl->ForgetPeer(peer_id);
179+
}
180+
181+
bool TxReconciliationTracker::IsPeerRegistered(NodeId peer_id) const
182+
{
183+
return m_impl->IsPeerRegistered(peer_id);
184+
}

0 commit comments

Comments
 (0)