Skip to content

Commit 88d326c

Browse files
committed
p2p: Finish negotiating reconciliation support
Once we received a reconciliation announcement support message from a peer and it doesn't violate our protocol, we store the negotiated parameters which will be used for future reconciliations.
1 parent 36cf6bf commit 88d326c

File tree

3 files changed

+159
-4
lines changed

3 files changed

+159
-4
lines changed

src/net_processing.cpp

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1782,7 +1782,7 @@ PeerManagerImpl::PeerManagerImpl(CConnman& connman, AddrMan& addrman,
17821782
// While Erlay support is incomplete, it must be enabled explicitly via -txreconciliation.
17831783
// This argument can go away after Erlay support is complete.
17841784
if (gArgs.GetBoolArg("-txreconciliation", DEFAULT_TXRECONCILIATION_ENABLE)) {
1785-
m_txreconciliation = std::make_unique<TxReconciliationTracker>();
1785+
m_txreconciliation = std::make_unique<TxReconciliationTracker>(TXRECONCILIATION_VERSION);
17861786
}
17871787
}
17881788

@@ -3477,6 +3477,58 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
34773477
return;
34783478
}
34793479

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

src/node/txreconciliation.cpp

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,46 @@
1313

1414
namespace {
1515

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+
1631
/**
1732
* Keeps track of txreconciliation-related per-peer state.
1833
*/
1934
class TxReconciliationState
2035
{
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) {}
2156
};
2257

2358
} // namespace
@@ -28,6 +63,9 @@ class TxReconciliationTracker::Impl
2863
private:
2964
mutable Mutex m_txreconciliation_mutex;
3065

66+
// Local protocol version
67+
uint32_t m_recon_version;
68+
3169
/**
3270
* Keeps track of txreconciliation states of eligible peers.
3371
* For pre-registered peers, the locally generated salt is stored.
@@ -37,7 +75,7 @@ class TxReconciliationTracker::Impl
3775
std::unordered_map<NodeId, std::variant<uint64_t, TxReconciliationState>> m_states GUARDED_BY(m_txreconciliation_mutex);
3876

3977
public:
40-
explicit Impl() {}
78+
explicit Impl(uint32_t recon_version) : m_recon_version(recon_version) {}
4179

4280
uint64_t PreRegisterPeer(NodeId peer_id) EXCLUSIVE_LOCKS_REQUIRED(!m_txreconciliation_mutex)
4381
{
@@ -55,6 +93,50 @@ class TxReconciliationTracker::Impl
5593
return local_salt;
5694
}
5795

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+
58140
void ForgetPeer(NodeId peer_id) EXCLUSIVE_LOCKS_REQUIRED(!m_txreconciliation_mutex)
59141
{
60142
AssertLockNotHeld(m_txreconciliation_mutex);
@@ -74,7 +156,7 @@ class TxReconciliationTracker::Impl
74156
}
75157
};
76158

77-
TxReconciliationTracker::TxReconciliationTracker() : m_impl{std::make_unique<TxReconciliationTracker::Impl>()} {}
159+
TxReconciliationTracker::TxReconciliationTracker(uint32_t recon_version) : m_impl{std::make_unique<TxReconciliationTracker::Impl>(recon_version)} {}
78160

79161
TxReconciliationTracker::~TxReconciliationTracker() = default;
80162

@@ -83,6 +165,14 @@ uint64_t TxReconciliationTracker::PreRegisterPeer(NodeId peer_id)
83165
return m_impl->PreRegisterPeer(peer_id);
84166
}
85167

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+
86176
void TxReconciliationTracker::ForgetPeer(NodeId peer_id)
87177
{
88178
m_impl->ForgetPeer(peer_id);

src/node/txreconciliation.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ static constexpr bool DEFAULT_TXRECONCILIATION_ENABLE{false};
1616
/** Supported transaction reconciliation protocol version */
1717
static constexpr uint32_t TXRECONCILIATION_VERSION{1};
1818

19+
enum ReconciliationRegisterResult {
20+
NOT_FOUND = 0,
21+
SUCCESS = 1,
22+
PROTOCOL_VIOLATION = 2,
23+
};
24+
1925
/**
2026
* Transaction reconciliation is a way for nodes to efficiently announce transactions.
2127
* This object keeps track of all txreconciliation-related communications with the peers.
@@ -50,7 +56,7 @@ class TxReconciliationTracker
5056
const std::unique_ptr<Impl> m_impl;
5157

5258
public:
53-
explicit TxReconciliationTracker();
59+
explicit TxReconciliationTracker(uint32_t recon_version);
5460
~TxReconciliationTracker();
5561

5662
/**
@@ -62,6 +68,13 @@ class TxReconciliationTracker
6268
*/
6369
uint64_t PreRegisterPeer(NodeId peer_id);
6470

71+
/**
72+
* Step 0. Once the peer agreed to reconcile txs with us, generate the state required to track
73+
* ongoing reconciliations. Must be called only after pre-registering the peer and only once.
74+
*/
75+
ReconciliationRegisterResult RegisterPeer(NodeId peer_id, bool is_peer_inbound, bool is_peer_recon_initiator,
76+
bool is_peer_recon_responder, uint32_t peer_recon_version, uint64_t remote_salt);
77+
6578
/**
6679
* Attempts to forget txreconciliation-related state of the peer (if we previously stored any).
6780
* After this, we won't be able to reconcile transactions with the peer.

0 commit comments

Comments
 (0)