Skip to content

Commit 6a7868d

Browse files
committed
merge bitcoin#23443: Erlay support signaling
1 parent fdc3c07 commit 6a7868d

17 files changed

+679
-3
lines changed

src/Makefile.am

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ BITCOIN_CORE_H = \
277277
node/minisketchwrapper.h \
278278
node/psbt.h \
279279
node/transaction.h \
280+
node/txreconciliation.h \
280281
node/ui_interface.h \
281282
node/utxo_snapshot.h \
282283
noui.h \
@@ -509,6 +510,7 @@ libbitcoin_server_a_SOURCES = \
509510
node/minisketchwrapper.cpp \
510511
node/psbt.cpp \
511512
node/transaction.cpp \
513+
node/txreconciliation.cpp \
512514
node/ui_interface.cpp \
513515
noui.cpp \
514516
policy/fees.cpp \

src/Makefile.test.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ BITCOIN_TESTS =\
174174
test/torcontrol_tests.cpp \
175175
test/transaction_tests.cpp \
176176
test/txindex_tests.cpp \
177+
test/txreconciliation_tests.cpp \
177178
test/txvalidation_tests.cpp \
178179
test/txvalidationcache_tests.cpp \
179180
test/uint256_tests.cpp \

src/init.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
#include <node/blockstorage.h>
4141
#include <node/context.h>
4242
#include <node/ui_interface.h>
43+
#include <node/txreconciliation.h>
4344
#include <policy/feerate.h>
4445
#include <policy/fees.h>
4546
#include <policy/policy.h>
@@ -587,6 +588,7 @@ void SetupServerArgs(ArgsManager& argsman)
587588
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);
588589
argsman.AddArg("-networkactive", "Enable all P2P network activity (default: 1). Can be changed by the setnetworkactive RPC command", ArgsManager::ALLOW_BOOL, OptionsCategory::CONNECTION);
589590
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);
591+
argsman.AddArg("-txreconciliation", strprintf("Enable transaction reconciliations per BIP 330 (default: %d)", DEFAULT_TXRECONCILIATION_ENABLE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CONNECTION);
590592
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);
591593
argsman.AddArg("-torpassword=<pass>", "Tor control port password (default: empty)", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::CONNECTION);
592594
#ifdef USE_UPNP

src/logging.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ const CLogCategoryDesc LogCategories[] =
161161
{BCLog::I2P, "i2p"},
162162
{BCLog::IPC, "ipc"},
163163
{BCLog::LOCK, "lock"},
164+
{BCLog::TXRECONCILIATION, "txreconciliation"},
164165
{BCLog::ALL, "1"},
165166
{BCLog::ALL, "all"},
166167

src/logging.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ namespace BCLog {
6262
I2P = (1 << 22),
6363
IPC = (1 << 23),
6464
LOCK = (1 << 24),
65+
TXRECONCILIATION = (1 << 27),
6566

6667
//Start Dash
6768
CHAINLOCKS = ((uint64_t)1 << 32),

src/net_processing.cpp

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <netbase.h>
2121
#include <net_types.h>
2222
#include <node/blockstorage.h>
23+
#include <node/txreconciliation.h>
2324
#include <policy/policy.h>
2425
#include <primitives/block.h>
2526
#include <primitives/transaction.h>
@@ -730,6 +731,7 @@ class PeerManagerImpl final : public PeerManager
730731
BanMan* const m_banman;
731732
ChainstateManager& m_chainman;
732733
CTxMemPool& m_mempool;
734+
std::unique_ptr<TxReconciliationTracker> m_txreconciliation;
733735
const std::unique_ptr<CDeterministicMNManager>& m_dmnman;
734736
const std::unique_ptr<CJContext>& m_cj_ctx;
735737
const std::unique_ptr<LLMQContext>& m_llmq_ctx;
@@ -1633,6 +1635,7 @@ void PeerManagerImpl::FinalizeNode(const CNode& node) {
16331635
mapBlocksInFlight.erase(entry.pindex->GetBlockHash());
16341636
}
16351637
WITH_LOCK(g_cs_orphans, m_orphanage.EraseForPeer(nodeid));
1638+
if (m_txreconciliation) m_txreconciliation->ForgetPeer(nodeid);
16361639
m_num_preferred_download_peers -= state->fPreferredDownload;
16371640
m_peers_downloading_from -= (state->nBlocksInFlight != 0);
16381641
assert(m_peers_downloading_from >= 0);
@@ -1935,6 +1938,11 @@ PeerManagerImpl::PeerManagerImpl(const CChainParams& chainparams, CConnman& conn
19351938
m_mn_activeman(mn_activeman),
19361939
m_ignore_incoming_txs(ignore_incoming_txs)
19371940
{
1941+
// While Erlay support is incomplete, it must be enabled explicitly via -txreconciliation.
1942+
// This argument can go away after Erlay support is complete.
1943+
if (gArgs.GetBoolArg("-txreconciliation", DEFAULT_TXRECONCILIATION_ENABLE)) {
1944+
m_txreconciliation = std::make_unique<TxReconciliationTracker>(TXRECONCILIATION_VERSION);
1945+
}
19381946
}
19391947

19401948
void PeerManagerImpl::StartScheduledTasks(CScheduler& scheduler)
@@ -3509,8 +3517,6 @@ void PeerManagerImpl::ProcessMessage(
35093517
m_connman.PushMessage(&pfrom, msg_maker.Make(NetMsgType::SENDADDRV2));
35103518
}
35113519

3512-
m_connman.PushMessage(&pfrom, msg_maker.Make(NetMsgType::VERACK));
3513-
35143520
pfrom.m_has_all_wanted_services = HasAllDesirableServiceFlags(nServices);
35153521
peer->m_their_services = nServices;
35163522
pfrom.SetAddrLocal(addrMe);
@@ -3536,6 +3542,25 @@ void PeerManagerImpl::ProcessMessage(
35363542
if (fRelay) pfrom.m_relays_txs = true;
35373543
}
35383544

3545+
if (greatest_common_version >= INCREASE_MAX_HEADERS2_VERSION && m_txreconciliation) {
3546+
// Per BIP-330, we announce txreconciliation support if:
3547+
// - protocol version per the VERSION message supports INCREASE_MAX_HEADERS2_VERSION;
3548+
// - we intended to exchange transactions over this connection while establishing it
3549+
// and the peer indicated support for transaction relay in the VERSION message;
3550+
// - we are not in -blocksonly mode.
3551+
if (pfrom.m_relays_txs && !m_ignore_incoming_txs) {
3552+
const uint64_t recon_salt = m_txreconciliation->PreRegisterPeer(pfrom.GetId());
3553+
// We suggest our txreconciliation role (initiator/responder) based on
3554+
// the connection direction.
3555+
m_connman.PushMessage(&pfrom, msg_maker.Make(NetMsgType::SENDTXRCNCL,
3556+
!pfrom.IsInboundConn(),
3557+
pfrom.IsInboundConn(),
3558+
TXRECONCILIATION_VERSION, recon_salt));
3559+
}
3560+
}
3561+
3562+
m_connman.PushMessage(&pfrom, msg_maker.Make(NetMsgType::VERACK));
3563+
35393564
// Potentially mark this peer as a preferred download peer.
35403565
{
35413566
LOCK(cs_main);
@@ -3677,6 +3702,15 @@ void PeerManagerImpl::ProcessMessage(
36773702
}
36783703
}
36793704

3705+
if (m_txreconciliation) {
3706+
if (pfrom.nVersion < INCREASE_MAX_HEADERS2_VERSION || !m_txreconciliation->IsPeerRegistered(pfrom.GetId())) {
3707+
// We could have optimistically pre-registered/registered the peer. In that case,
3708+
// we should forget about the reconciliation state here if the node version is below
3709+
// our minimum supported version.
3710+
m_txreconciliation->ForgetPeer(pfrom.GetId());
3711+
}
3712+
}
3713+
36803714
pfrom.fSuccessfullyConnected = true;
36813715
return;
36823716
}
@@ -3727,6 +3761,58 @@ void PeerManagerImpl::ProcessMessage(
37273761
return;
37283762
}
37293763

3764+
// Received from a peer demonstrating readiness to announce transactions via reconciliations.
3765+
// This feature negotiation must happen between VERSION and VERACK to avoid relay problems
3766+
// from switching announcement protocols after the connection is up.
3767+
if (msg_type == NetMsgType::SENDTXRCNCL) {
3768+
if (!m_txreconciliation) {
3769+
LogPrint(BCLog::NET, "sendtxrcncl from peer=%d ignored, as our node does not have txreconciliation enabled\n", pfrom.GetId());
3770+
return;
3771+
}
3772+
3773+
if (pfrom.fSuccessfullyConnected) {
3774+
// Disconnect peers that send a SENDTXRCNCL message after VERACK.
3775+
LogPrint(BCLog::NET, "sendtxrcncl received after verack from peer=%d; disconnecting\n", pfrom.GetId());
3776+
pfrom.fDisconnect = true;
3777+
return;
3778+
}
3779+
3780+
if (!peer->GetTxRelay()) {
3781+
// Disconnect peers that send a SENDTXRCNCL message even though we indicated we don't
3782+
// support transaction relay.
3783+
LogPrint(BCLog::NET, "sendtxrcncl received from peer=%d to which we indicated no tx relay; disconnecting\n", pfrom.GetId());
3784+
pfrom.fDisconnect = true;
3785+
return;
3786+
}
3787+
3788+
bool is_peer_initiator, is_peer_responder;
3789+
uint32_t peer_txreconcl_version;
3790+
uint64_t remote_salt;
3791+
vRecv >> is_peer_initiator >> is_peer_responder >> peer_txreconcl_version >> remote_salt;
3792+
3793+
if (m_txreconciliation->IsPeerRegistered(pfrom.GetId())) {
3794+
// A peer is already registered, meaning we already received SENDTXRCNCL from them.
3795+
LogPrint(BCLog::NET, "txreconciliation protocol violation from peer=%d (sendtxrcncl received from already registered peer); disconnecting\n", pfrom.GetId());
3796+
pfrom.fDisconnect = true;
3797+
return;
3798+
}
3799+
3800+
const ReconciliationRegisterResult result = m_txreconciliation->RegisterPeer(pfrom.GetId(), pfrom.IsInboundConn(),
3801+
is_peer_initiator, is_peer_responder,
3802+
peer_txreconcl_version,
3803+
remote_salt);
3804+
3805+
// If it's a protocol violation, disconnect.
3806+
// If the peer was not found (but something unexpected happened) or it was registered,
3807+
// nothing to be done.
3808+
if (result == ReconciliationRegisterResult::PROTOCOL_VIOLATION) {
3809+
LogPrint(BCLog::NET, "txreconciliation protocol violation from peer=%d; disconnecting\n", pfrom.GetId());
3810+
pfrom.fDisconnect = true;
3811+
return;
3812+
}
3813+
return;
3814+
}
3815+
37303816
if (!pfrom.fSuccessfullyConnected) {
37313817
LogPrint(BCLog::NET, "Unsupported message \"%s\" prior to verack from peer=%d\n", SanitizeString(msg_type), pfrom.GetId());
37323818
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 CHashWriter 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+
LogPrint(BCLog::TXRECONCILIATION, "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+
LogPrint(BCLog::TXRECONCILIATION, "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+
LogPrint(BCLog::TXRECONCILIATION, "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)