Merge bitcoin/bitcoin#28331: BIP324 integration

75a329103505736acb9036224da2dfa8ab038c43 doc: mention BIP324 support in bips.md (Pieter Wuille)
64ca7210f05c4003228f4cb0b160d869e15f47d2 test: enable v2 transport between nodes in some functional tests (Pieter Wuille)
05d19fbcc10f26c7f1e3a9afc660eb7fa71b1d8c test: Functional test for opportunistic encryption (dhruv)
b815cce50e4bfa0efea8ea02659b7042c8fb18be net: expose transport types/session IDs of connections in RPC and logs (Pieter Wuille)
432a62c4dce908729c62edcfaebc3da6387c3afe net: reconnect with V1Transport under certain conditions (Pieter Wuille)
4d265d0342ae7e92df07ba51e8355db57c44f811 sync: modernize CSemaphore / CSemaphoreGrant (Pieter Wuille)
c73cd423636e06df46742f573640ca773b281ffc rpc: addnode arg to use BIP324 v2 p2p (dhruv)
62d21ee0974b582a6a32aa97ee35ef51c977ea4b net: use V2Transport when NODE_P2P_V2 service flag is present (Pieter Wuille)
a4706bc877504057e8522c929cc0704d3eaa7302 rpc: don't report v2 handshake bytes in the per-type sent byte statistics (Sebastian Falbesoner)
abf343b32026c3f8246f98c416e2c6cf5b66aa38 net: advertise NODE_P2P_V2 if CLI arg -v2transport is on (Pieter Wuille)

Pull request description:

  Part of #27634.

  This makes BIP324 support feature complete, through a (default off) `-v2transport` option for enabling V2 connections. If it is enabled:
  * The `NODE_P2P_V2` service flag (*1 << 11*) is advertized.
  * Inbound connections can use V1 or V2 (automatically detected based on the protocol used by the peer)
  * V2 connections are used on outbound when the `NODE_P2P_V2` service is available (or the new `use_v2` parameter is set on the `addnode` RPC).
  * V2 outbound connections that instantly fail get retried as V1.

  There are two new RPC fields, `"transport_protocol_type"` and `"session_id"`, in `getpeerinfo`.

ACKs for top commit:
  mzumsande:
    re-ACK 75a329103505736acb9036224da2dfa8ab038c43
  theStack:
    re-ACK 75a329103505736acb9036224da2dfa8ab038c43

Tree-SHA512: 90ea1cd37f3dce410a59ff5de1c2405891e8aa62318d0e06dcb68b21603fb0c061631526633f3d4fb630e63d2b8db407eed48e246befcbef3503bea893a4ff15
This commit is contained in:
fanquake 2023-10-03 09:56:43 +01:00
commit 6f882e6f86
No known key found for this signature in database
GPG Key ID: 2EEB9F5CC09526C1
22 changed files with 541 additions and 84 deletions

View File

@ -49,6 +49,7 @@ BIPs that are implemented by Bitcoin Core:
* [`BIP 173`](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki): Bech32 addresses for native Segregated Witness outputs are supported as of **v0.16.0** ([PR 11167](https://github.com/bitcoin/bitcoin/pull/11167)). Bech32 addresses are generated by default as of **v0.20.0** ([PR 16884](https://github.com/bitcoin/bitcoin/pull/16884)). * [`BIP 173`](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki): Bech32 addresses for native Segregated Witness outputs are supported as of **v0.16.0** ([PR 11167](https://github.com/bitcoin/bitcoin/pull/11167)). Bech32 addresses are generated by default as of **v0.20.0** ([PR 16884](https://github.com/bitcoin/bitcoin/pull/16884)).
* [`BIP 174`](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki): RPCs to operate on Partially Signed Bitcoin Transactions (PSBT) are present as of **v0.17.0** ([PR 13557](https://github.com/bitcoin/bitcoin/pull/13557)). * [`BIP 174`](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki): RPCs to operate on Partially Signed Bitcoin Transactions (PSBT) are present as of **v0.17.0** ([PR 13557](https://github.com/bitcoin/bitcoin/pull/13557)).
* [`BIP 176`](https://github.com/bitcoin/bips/blob/master/bip-0176.mediawiki): Bits Denomination [QT only] is supported as of **v0.16.0** ([PR 12035](https://github.com/bitcoin/bitcoin/pull/12035)). * [`BIP 176`](https://github.com/bitcoin/bips/blob/master/bip-0176.mediawiki): Bits Denomination [QT only] is supported as of **v0.16.0** ([PR 12035](https://github.com/bitcoin/bitcoin/pull/12035)).
* [`BIP 324`](https://github.com/bitcoin/bips/blob/master/bip-0324.mediawiki): The v2 transport protocol specified by BIP324 and the associated `NODE_P2P_V2` service bit are supported as of **v26.0**, but off by default ([PR 28331](https://github.com/bitcoin/bitcoin/pull/28331)).
* [`BIP 325`](https://github.com/bitcoin/bips/blob/master/bip-0325.mediawiki): Signet test network is supported as of **v0.21.0** ([PR 18267](https://github.com/bitcoin/bitcoin/pull/18267)). * [`BIP 325`](https://github.com/bitcoin/bips/blob/master/bip-0325.mediawiki): Signet test network is supported as of **v0.21.0** ([PR 18267](https://github.com/bitcoin/bitcoin/pull/18267)).
* [`BIP 339`](https://github.com/bitcoin/bips/blob/master/bip-0339.mediawiki): Relay of transactions by wtxid is supported as of **v0.21.0** ([PR 18044](https://github.com/bitcoin/bitcoin/pull/18044)). * [`BIP 339`](https://github.com/bitcoin/bips/blob/master/bip-0339.mediawiki): Relay of transactions by wtxid is supported as of **v0.21.0** ([PR 18044](https://github.com/bitcoin/bitcoin/pull/18044)).
* [`BIP 340`](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki) * [`BIP 340`](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki)

View File

@ -498,6 +498,7 @@ void SetupServerArgs(ArgsManager& argsman)
argsman.AddArg("-i2psam=<ip:port>", "I2P SAM proxy to reach I2P peers and accept I2P connections (default: none)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-i2psam=<ip:port>", "I2P SAM proxy to reach I2P peers and accept I2P connections (default: none)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-i2pacceptincoming", strprintf("Whether to accept inbound I2P connections (default: %i). Ignored if -i2psam is not set. Listening for inbound I2P connections is done through the SAM proxy, not by binding to a local address and port.", DEFAULT_I2P_ACCEPT_INCOMING), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-i2pacceptincoming", strprintf("Whether to accept inbound I2P connections (default: %i). Ignored if -i2psam is not set. Listening for inbound I2P connections is done through the SAM proxy, not by binding to a local address and port.", DEFAULT_I2P_ACCEPT_INCOMING), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-onlynet=<net>", "Make automatic outbound connections only to network <net> (" + Join(GetNetworkNames(), ", ") + "). Inbound and manual connections are not affected by this option. It can be specified multiple times to allow multiple networks.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-onlynet=<net>", "Make automatic outbound connections only to network <net> (" + Join(GetNetworkNames(), ", ") + "). Inbound and manual connections are not affected by this option. It can be specified multiple times to allow multiple networks.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-v2transport", strprintf("Support v2 transport (default: %u)", DEFAULT_V2_TRANSPORT), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-peerbloomfilters", strprintf("Support filtering of blocks and transaction with bloom filters (default: %u)", DEFAULT_PEERBLOOMFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-peerbloomfilters", strprintf("Support filtering of blocks and transaction with bloom filters (default: %u)", DEFAULT_PEERBLOOMFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-peerblockfilters", strprintf("Serve compact block filters to peers per BIP 157 (default: %u)", DEFAULT_PEERBLOCKFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-peerblockfilters", strprintf("Serve compact block filters to peers per BIP 157 (default: %u)", DEFAULT_PEERBLOCKFILTERS), 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("-txreconciliation", strprintf("Enable transaction reconciliations per BIP 330 (default: %d)", DEFAULT_TXRECONCILIATION_ENABLE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CONNECTION);
@ -893,6 +894,11 @@ bool AppInitParameterInteraction(const ArgsManager& args)
} }
} }
// Signal NODE_P2P_V2 if BIP324 v2 transport is enabled.
if (args.GetBoolArg("-v2transport", DEFAULT_V2_TRANSPORT)) {
nLocalServices = ServiceFlags(nLocalServices | NODE_P2P_V2);
}
// Signal NODE_COMPACT_FILTERS if peerblockfilters and basic filters index are both enabled. // Signal NODE_COMPACT_FILTERS if peerblockfilters and basic filters index are both enabled.
if (args.GetBoolArg("-peerblockfilters", DEFAULT_PEERBLOCKFILTERS)) { if (args.GetBoolArg("-peerblockfilters", DEFAULT_PEERBLOCKFILTERS)) {
if (g_enabled_filter_types.count(BlockFilterType::BASIC) != 1) { if (g_enabled_filter_types.count(BlockFilterType::BASIC) != 1) {

View File

@ -439,7 +439,7 @@ static CAddress GetBindAddress(const Sock& sock)
return addr_bind; return addr_bind;
} }
CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCountFailure, ConnectionType conn_type) CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCountFailure, ConnectionType conn_type, bool use_v2transport)
{ {
AssertLockNotHeld(m_unused_i2p_sessions_mutex); AssertLockNotHeld(m_unused_i2p_sessions_mutex);
assert(conn_type != ConnectionType::INBOUND); assert(conn_type != ConnectionType::INBOUND);
@ -457,7 +457,8 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo
} }
} }
LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "trying connection %s lastseen=%.1fhrs\n", LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "trying %s connection %s lastseen=%.1fhrs\n",
use_v2transport ? "v2" : "v1",
pszDest ? pszDest : addrConnect.ToStringAddrPort(), pszDest ? pszDest : addrConnect.ToStringAddrPort(),
Ticks<HoursDouble>(pszDest ? 0h : Now<NodeSeconds>() - addrConnect.nTime)); Ticks<HoursDouble>(pszDest ? 0h : Now<NodeSeconds>() - addrConnect.nTime));
@ -580,6 +581,7 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo
CNodeOptions{ CNodeOptions{
.i2p_sam_session = std::move(i2p_transient_session), .i2p_sam_session = std::move(i2p_transient_session),
.recv_flood_size = nReceiveFloodSize, .recv_flood_size = nReceiveFloodSize,
.use_v2transport = use_v2transport,
}); });
pnode->AddRef(); pnode->AddRef();
@ -665,6 +667,9 @@ void CNode::CopyStats(CNodeStats& stats)
LOCK(cs_vRecv); LOCK(cs_vRecv);
X(mapRecvBytesPerMsgType); X(mapRecvBytesPerMsgType);
X(nRecvBytes); X(nRecvBytes);
Transport::Info info = m_transport->GetInfo();
stats.m_transport_type = info.transport_type;
if (info.session_id) stats.m_session_id = HexStr(*info.session_id);
} }
X(m_permission_flags); X(m_permission_flags);
@ -732,6 +737,11 @@ V1Transport::V1Transport(const NodeId node_id, int nTypeIn, int nVersionIn) noex
Reset(); Reset();
} }
Transport::Info V1Transport::GetInfo() const noexcept
{
return {.transport_type = TransportProtocolType::V1, .session_id = {}};
}
int V1Transport::readHeader(Span<const uint8_t> msg_bytes) int V1Transport::readHeader(Span<const uint8_t> msg_bytes)
{ {
AssertLockHeld(m_recv_mutex); AssertLockHeld(m_recv_mutex);
@ -1542,8 +1552,15 @@ void V2Transport::MarkBytesSent(size_t bytes_sent) noexcept
LOCK(m_send_mutex); LOCK(m_send_mutex);
if (m_send_state == SendState::V1) return m_v1_fallback.MarkBytesSent(bytes_sent); if (m_send_state == SendState::V1) return m_v1_fallback.MarkBytesSent(bytes_sent);
if (m_send_state == SendState::AWAITING_KEY && m_send_pos == 0 && bytes_sent > 0) {
LogPrint(BCLog::NET, "start sending v2 handshake to peer=%d\n", m_nodeid);
}
m_send_pos += bytes_sent; m_send_pos += bytes_sent;
Assume(m_send_pos <= m_send_buffer.size()); Assume(m_send_pos <= m_send_buffer.size());
if (m_send_pos >= CMessageHeader::HEADER_SIZE) {
m_sent_v1_header_worth = true;
}
// Wipe the buffer when everything is sent. // Wipe the buffer when everything is sent.
if (m_send_pos == m_send_buffer.size()) { if (m_send_pos == m_send_buffer.size()) {
m_send_pos = 0; m_send_pos = 0;
@ -1551,6 +1568,23 @@ void V2Transport::MarkBytesSent(size_t bytes_sent) noexcept
} }
} }
bool V2Transport::ShouldReconnectV1() const noexcept
{
AssertLockNotHeld(m_send_mutex);
AssertLockNotHeld(m_recv_mutex);
// Only outgoing connections need reconnection.
if (!m_initiating) return false;
LOCK(m_recv_mutex);
// We only reconnect in the very first state and when the receive buffer is empty. Together
// these conditions imply nothing has been received so far.
if (m_recv_state != RecvState::KEY) return false;
if (!m_recv_buffer.empty()) return false;
// Check if we've sent enough for the other side to disconnect us (if it was V1).
LOCK(m_send_mutex);
return m_sent_v1_header_worth;
}
size_t V2Transport::GetSendMemoryUsage() const noexcept size_t V2Transport::GetSendMemoryUsage() const noexcept
{ {
AssertLockNotHeld(m_send_mutex); AssertLockNotHeld(m_send_mutex);
@ -1560,6 +1594,27 @@ size_t V2Transport::GetSendMemoryUsage() const noexcept
return sizeof(m_send_buffer) + memusage::DynamicUsage(m_send_buffer); return sizeof(m_send_buffer) + memusage::DynamicUsage(m_send_buffer);
} }
Transport::Info V2Transport::GetInfo() const noexcept
{
AssertLockNotHeld(m_recv_mutex);
LOCK(m_recv_mutex);
if (m_recv_state == RecvState::V1) return m_v1_fallback.GetInfo();
Transport::Info info;
// Do not report v2 and session ID until the version packet has been received
// and verified (confirming that the other side very likely has the same keys as us).
if (m_recv_state != RecvState::KEY_MAYBE_V1 && m_recv_state != RecvState::KEY &&
m_recv_state != RecvState::GARB_GARBTERM && m_recv_state != RecvState::VERSION) {
info.transport_type = TransportProtocolType::V2;
info.session_id = uint256(MakeUCharSpan(m_cipher.GetSessionID()));
} else {
info.transport_type = TransportProtocolType::DETECTING;
}
return info;
}
std::pair<size_t, bool> CConnman::SocketSendData(CNode& node) const std::pair<size_t, bool> CConnman::SocketSendData(CNode& node) const
{ {
auto it = node.vSendMsg.begin(); auto it = node.vSendMsg.begin();
@ -1609,7 +1664,9 @@ std::pair<size_t, bool> CConnman::SocketSendData(CNode& node) const
// Notify transport that bytes have been processed. // Notify transport that bytes have been processed.
node.m_transport->MarkBytesSent(nBytes); node.m_transport->MarkBytesSent(nBytes);
// Update statistics per message type. // Update statistics per message type.
if (!msg_type.empty()) { // don't report v2 handshake bytes for now
node.AccountForSentBytes(msg_type, nBytes); node.AccountForSentBytes(msg_type, nBytes);
}
nSentSize += nBytes; nSentSize += nBytes;
if ((size_t)nBytes != data.size()) { if ((size_t)nBytes != data.size()) {
// could not send full message; stop sending more // could not send full message; stop sending more
@ -1792,6 +1849,10 @@ void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr<Sock>&& sock,
} }
const bool inbound_onion = std::find(m_onion_binds.begin(), m_onion_binds.end(), addr_bind) != m_onion_binds.end(); const bool inbound_onion = std::find(m_onion_binds.begin(), m_onion_binds.end(), addr_bind) != m_onion_binds.end();
// The V2Transport transparently falls back to V1 behavior when an incoming V1 connection is
// detected, so use it whenever we signal NODE_P2P_V2.
const bool use_v2transport(nodeServices & NODE_P2P_V2);
CNode* pnode = new CNode(id, CNode* pnode = new CNode(id,
std::move(sock), std::move(sock),
addr, addr,
@ -1805,6 +1866,7 @@ void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr<Sock>&& sock,
.permission_flags = permission_flags, .permission_flags = permission_flags,
.prefer_evict = discouraged, .prefer_evict = discouraged,
.recv_flood_size = nReceiveFloodSize, .recv_flood_size = nReceiveFloodSize,
.use_v2transport = use_v2transport,
}); });
pnode->AddRef(); pnode->AddRef();
m_msgproc->InitializeNode(*pnode, nodeServices); m_msgproc->InitializeNode(*pnode, nodeServices);
@ -1853,12 +1915,19 @@ bool CConnman::AddConnection(const std::string& address, ConnectionType conn_typ
CSemaphoreGrant grant(*semOutbound, true); CSemaphoreGrant grant(*semOutbound, true);
if (!grant) return false; if (!grant) return false;
OpenNetworkConnection(CAddress(), false, &grant, address.c_str(), conn_type); OpenNetworkConnection(CAddress(), false, std::move(grant), address.c_str(), conn_type, /*use_v2transport=*/false);
return true; return true;
} }
void CConnman::DisconnectNodes() void CConnman::DisconnectNodes()
{ {
AssertLockNotHeld(m_nodes_mutex);
AssertLockNotHeld(m_reconnections_mutex);
// Use a temporary variable to accumulate desired reconnections, so we don't need
// m_reconnections_mutex while holding m_nodes_mutex.
decltype(m_reconnections) reconnections_to_add;
{ {
LOCK(m_nodes_mutex); LOCK(m_nodes_mutex);
@ -1881,6 +1950,19 @@ void CConnman::DisconnectNodes()
// remove from m_nodes // remove from m_nodes
m_nodes.erase(remove(m_nodes.begin(), m_nodes.end(), pnode), m_nodes.end()); m_nodes.erase(remove(m_nodes.begin(), m_nodes.end(), pnode), m_nodes.end());
// Add to reconnection list if appropriate. We don't reconnect right here, because
// the creation of a connection is a blocking operation (up to several seconds),
// and we don't want to hold up the socket handler thread for that long.
if (pnode->m_transport->ShouldReconnectV1()) {
reconnections_to_add.push_back({
.addr_connect = pnode->addr,
.grant = std::move(pnode->grantOutbound),
.destination = pnode->m_dest,
.conn_type = pnode->m_conn_type,
.use_v2transport = false});
LogPrint(BCLog::NET, "retrying with v1 transport protocol for peer=%d\n", pnode->GetId());
}
// release outbound grant (if any) // release outbound grant (if any)
pnode->grantOutbound.Release(); pnode->grantOutbound.Release();
@ -1908,6 +1990,11 @@ void CConnman::DisconnectNodes()
} }
} }
} }
{
// Move entries from reconnections_to_add to m_reconnections.
LOCK(m_reconnections_mutex);
m_reconnections.splice(m_reconnections.end(), std::move(reconnections_to_add));
}
} }
void CConnman::NotifyNumConnectionsChanged() void CConnman::NotifyNumConnectionsChanged()
@ -2285,9 +2372,9 @@ void CConnman::ProcessAddrFetch()
m_addr_fetches.pop_front(); m_addr_fetches.pop_front();
} }
CAddress addr; CAddress addr;
CSemaphoreGrant grant(*semOutbound, true); CSemaphoreGrant grant(*semOutbound, /*fTry=*/true);
if (grant) { if (grant) {
OpenNetworkConnection(addr, false, &grant, strDest.c_str(), ConnectionType::ADDR_FETCH); OpenNetworkConnection(addr, false, std::move(grant), strDest.c_str(), ConnectionType::ADDR_FETCH, /*use_v2transport=*/false);
} }
} }
@ -2380,6 +2467,7 @@ bool CConnman::MaybePickPreferredNetwork(std::optional<Network>& network)
void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
{ {
AssertLockNotHeld(m_unused_i2p_sessions_mutex); AssertLockNotHeld(m_unused_i2p_sessions_mutex);
AssertLockNotHeld(m_reconnections_mutex);
FastRandomContext rng; FastRandomContext rng;
// Connect to specific addresses // Connect to specific addresses
if (!connect.empty()) if (!connect.empty())
@ -2389,7 +2477,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
for (const std::string& strAddr : connect) for (const std::string& strAddr : connect)
{ {
CAddress addr(CService(), NODE_NONE); CAddress addr(CService(), NODE_NONE);
OpenNetworkConnection(addr, false, nullptr, strAddr.c_str(), ConnectionType::MANUAL); OpenNetworkConnection(addr, false, {}, strAddr.c_str(), ConnectionType::MANUAL, /*use_v2transport=*/false);
for (int i = 0; i < 10 && i < nLoop; i++) for (int i = 0; i < 10 && i < nLoop; i++)
{ {
if (!interruptNet.sleep_for(std::chrono::milliseconds(500))) if (!interruptNet.sleep_for(std::chrono::milliseconds(500)))
@ -2423,6 +2511,8 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
if (!interruptNet.sleep_for(std::chrono::milliseconds(500))) if (!interruptNet.sleep_for(std::chrono::milliseconds(500)))
return; return;
PerformReconnections();
CSemaphoreGrant grant(*semOutbound); CSemaphoreGrant grant(*semOutbound);
if (interruptNet) if (interruptNet)
return; return;
@ -2443,7 +2533,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
// Perform cheap checks before locking a mutex. // Perform cheap checks before locking a mutex.
else if (!dnsseed && !use_seednodes) { else if (!dnsseed && !use_seednodes) {
LOCK(m_added_nodes_mutex); LOCK(m_added_nodes_mutex);
if (m_added_nodes.empty()) { if (m_added_node_params.empty()) {
add_fixed_seeds_now = true; add_fixed_seeds_now = true;
LogPrintf("Adding fixed seeds as -dnsseed=0 (or IPv4/IPv6 connections are disabled via -onlynet) and neither -addnode nor -seednode are provided\n"); LogPrintf("Adding fixed seeds as -dnsseed=0 (or IPv4/IPv6 connections are disabled via -onlynet) and neither -addnode nor -seednode are provided\n");
} }
@ -2692,7 +2782,9 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
// Don't record addrman failure attempts when node is offline. This can be identified since all local // Don't record addrman failure attempts when node is offline. This can be identified since all local
// network connections (if any) belong in the same netgroup, and the size of `outbound_ipv46_peer_netgroups` would only be 1. // network connections (if any) belong in the same netgroup, and the size of `outbound_ipv46_peer_netgroups` would only be 1.
const bool count_failures{((int)outbound_ipv46_peer_netgroups.size() + outbound_privacy_network_peers) >= std::min(nMaxConnections - 1, 2)}; const bool count_failures{((int)outbound_ipv46_peer_netgroups.size() + outbound_privacy_network_peers) >= std::min(nMaxConnections - 1, 2)};
OpenNetworkConnection(addrConnect, count_failures, &grant, /*strDest=*/nullptr, conn_type); // Use BIP324 transport when both us and them have NODE_V2_P2P set.
const bool use_v2transport(addrConnect.nServices & GetLocalServices() & NODE_P2P_V2);
OpenNetworkConnection(addrConnect, count_failures, std::move(grant), /*strDest=*/nullptr, conn_type, use_v2transport);
} }
} }
} }
@ -2714,11 +2806,11 @@ std::vector<AddedNodeInfo> CConnman::GetAddedNodeInfo() const
{ {
std::vector<AddedNodeInfo> ret; std::vector<AddedNodeInfo> ret;
std::list<std::string> lAddresses(0); std::list<AddedNodeParams> lAddresses(0);
{ {
LOCK(m_added_nodes_mutex); LOCK(m_added_nodes_mutex);
ret.reserve(m_added_nodes.size()); ret.reserve(m_added_node_params.size());
std::copy(m_added_nodes.cbegin(), m_added_nodes.cend(), std::back_inserter(lAddresses)); std::copy(m_added_node_params.cbegin(), m_added_node_params.cend(), std::back_inserter(lAddresses));
} }
@ -2738,9 +2830,9 @@ std::vector<AddedNodeInfo> CConnman::GetAddedNodeInfo() const
} }
} }
for (const std::string& strAddNode : lAddresses) { for (const auto& addr : lAddresses) {
CService service(LookupNumeric(strAddNode, GetDefaultPort(strAddNode))); CService service(LookupNumeric(addr.m_added_node, GetDefaultPort(addr.m_added_node)));
AddedNodeInfo addedNode{strAddNode, CService(), false, false}; AddedNodeInfo addedNode{addr, CService(), false, false};
if (service.IsValid()) { if (service.IsValid()) {
// strAddNode is an IP:port // strAddNode is an IP:port
auto it = mapConnected.find(service); auto it = mapConnected.find(service);
@ -2751,7 +2843,7 @@ std::vector<AddedNodeInfo> CConnman::GetAddedNodeInfo() const
} }
} else { } else {
// strAddNode is a name // strAddNode is a name
auto it = mapConnectedByName.find(strAddNode); auto it = mapConnectedByName.find(addr.m_added_node);
if (it != mapConnectedByName.end()) { if (it != mapConnectedByName.end()) {
addedNode.resolvedAddress = it->second.second; addedNode.resolvedAddress = it->second.second;
addedNode.fConnected = true; addedNode.fConnected = true;
@ -2767,6 +2859,7 @@ std::vector<AddedNodeInfo> CConnman::GetAddedNodeInfo() const
void CConnman::ThreadOpenAddedConnections() void CConnman::ThreadOpenAddedConnections()
{ {
AssertLockNotHeld(m_unused_i2p_sessions_mutex); AssertLockNotHeld(m_unused_i2p_sessions_mutex);
AssertLockNotHeld(m_reconnections_mutex);
while (true) while (true)
{ {
CSemaphoreGrant grant(*semAddnode); CSemaphoreGrant grant(*semAddnode);
@ -2774,26 +2867,28 @@ void CConnman::ThreadOpenAddedConnections()
bool tried = false; bool tried = false;
for (const AddedNodeInfo& info : vInfo) { for (const AddedNodeInfo& info : vInfo) {
if (!info.fConnected) { if (!info.fConnected) {
if (!grant.TryAcquire()) { if (!grant) {
// If we've used up our semaphore and need a new one, let's not wait here since while we are waiting // If we've used up our semaphore and need a new one, let's not wait here since while we are waiting
// the addednodeinfo state might change. // the addednodeinfo state might change.
break; break;
} }
tried = true; tried = true;
CAddress addr(CService(), NODE_NONE); CAddress addr(CService(), NODE_NONE);
OpenNetworkConnection(addr, false, &grant, info.strAddedNode.c_str(), ConnectionType::MANUAL); OpenNetworkConnection(addr, false, std::move(grant), info.m_params.m_added_node.c_str(), ConnectionType::MANUAL, info.m_params.m_use_v2transport);
if (!interruptNet.sleep_for(std::chrono::milliseconds(500))) if (!interruptNet.sleep_for(std::chrono::milliseconds(500))) return;
return; grant = CSemaphoreGrant(*semAddnode, /*fTry=*/true);
} }
} }
// Retry every 60 seconds if a connection was attempted, otherwise two seconds // Retry every 60 seconds if a connection was attempted, otherwise two seconds
if (!interruptNet.sleep_for(std::chrono::seconds(tried ? 60 : 2))) if (!interruptNet.sleep_for(std::chrono::seconds(tried ? 60 : 2)))
return; return;
// See if any reconnections are desired.
PerformReconnections();
} }
} }
// if successful, this moves the passed grant to the constructed node // if successful, this moves the passed grant to the constructed node
void CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFailure, CSemaphoreGrant *grantOutbound, const char *pszDest, ConnectionType conn_type) void CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFailure, CSemaphoreGrant&& grant_outbound, const char *pszDest, ConnectionType conn_type, bool use_v2transport)
{ {
AssertLockNotHeld(m_unused_i2p_sessions_mutex); AssertLockNotHeld(m_unused_i2p_sessions_mutex);
assert(conn_type != ConnectionType::INBOUND); assert(conn_type != ConnectionType::INBOUND);
@ -2815,12 +2910,11 @@ void CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFai
} else if (FindNode(std::string(pszDest))) } else if (FindNode(std::string(pszDest)))
return; return;
CNode* pnode = ConnectNode(addrConnect, pszDest, fCountFailure, conn_type); CNode* pnode = ConnectNode(addrConnect, pszDest, fCountFailure, conn_type, use_v2transport);
if (!pnode) if (!pnode)
return; return;
if (grantOutbound) pnode->grantOutbound = std::move(grant_outbound);
grantOutbound->MoveTo(pnode->grantOutbound);
m_msgproc->InitializeNode(*pnode, nLocalServices); m_msgproc->InitializeNode(*pnode, nLocalServices);
{ {
@ -3373,23 +3467,23 @@ std::vector<CAddress> CConnman::GetAddresses(CNode& requestor, size_t max_addres
return cache_entry.m_addrs_response_cache; return cache_entry.m_addrs_response_cache;
} }
bool CConnman::AddNode(const std::string& strNode) bool CConnman::AddNode(const AddedNodeParams& add)
{ {
LOCK(m_added_nodes_mutex); LOCK(m_added_nodes_mutex);
for (const std::string& it : m_added_nodes) { for (const auto& it : m_added_node_params) {
if (strNode == it) return false; if (add.m_added_node == it.m_added_node) return false;
} }
m_added_nodes.push_back(strNode); m_added_node_params.push_back(add);
return true; return true;
} }
bool CConnman::RemoveAddedNode(const std::string& strNode) bool CConnman::RemoveAddedNode(const std::string& strNode)
{ {
LOCK(m_added_nodes_mutex); LOCK(m_added_nodes_mutex);
for(std::vector<std::string>::iterator it = m_added_nodes.begin(); it != m_added_nodes.end(); ++it) { for (auto it = m_added_node_params.begin(); it != m_added_node_params.end(); ++it) {
if (strNode == *it) { if (strNode == it->m_added_node) {
m_added_nodes.erase(it); m_added_node_params.erase(it);
return true; return true;
} }
} }
@ -3577,6 +3671,15 @@ ServiceFlags CConnman::GetLocalServices() const
return nLocalServices; return nLocalServices;
} }
static std::unique_ptr<Transport> MakeTransport(NodeId id, bool use_v2transport, bool inbound) noexcept
{
if (use_v2transport) {
return std::make_unique<V2Transport>(id, /*initiating=*/!inbound, SER_NETWORK, INIT_PROTO_VERSION);
} else {
return std::make_unique<V1Transport>(id, SER_NETWORK, INIT_PROTO_VERSION);
}
}
CNode::CNode(NodeId idIn, CNode::CNode(NodeId idIn,
std::shared_ptr<Sock> sock, std::shared_ptr<Sock> sock,
const CAddress& addrIn, const CAddress& addrIn,
@ -3587,13 +3690,14 @@ CNode::CNode(NodeId idIn,
ConnectionType conn_type_in, ConnectionType conn_type_in,
bool inbound_onion, bool inbound_onion,
CNodeOptions&& node_opts) CNodeOptions&& node_opts)
: m_transport{std::make_unique<V1Transport>(idIn, SER_NETWORK, INIT_PROTO_VERSION)}, : m_transport{MakeTransport(idIn, node_opts.use_v2transport, conn_type_in == ConnectionType::INBOUND)},
m_permission_flags{node_opts.permission_flags}, m_permission_flags{node_opts.permission_flags},
m_sock{sock}, m_sock{sock},
m_connected{GetTime<std::chrono::seconds>()}, m_connected{GetTime<std::chrono::seconds>()},
addr{addrIn}, addr{addrIn},
addrBind{addrBindIn}, addrBind{addrBindIn},
m_addr_name{addrNameIn.empty() ? addr.ToStringAddrPort() : addrNameIn}, m_addr_name{addrNameIn.empty() ? addr.ToStringAddrPort() : addrNameIn},
m_dest(addrNameIn),
m_inbound_onion{inbound_onion}, m_inbound_onion{inbound_onion},
m_prefer_evict{node_opts.prefer_evict}, m_prefer_evict{node_opts.prefer_evict},
nKeyedNetGroup{nKeyedNetGroupIn}, nKeyedNetGroup{nKeyedNetGroupIn},
@ -3724,6 +3828,33 @@ uint64_t CConnman::CalculateKeyedNetGroup(const CAddress& address) const
return GetDeterministicRandomizer(RANDOMIZER_ID_NETGROUP).Write(vchNetGroup).Finalize(); return GetDeterministicRandomizer(RANDOMIZER_ID_NETGROUP).Write(vchNetGroup).Finalize();
} }
void CConnman::PerformReconnections()
{
AssertLockNotHeld(m_reconnections_mutex);
AssertLockNotHeld(m_unused_i2p_sessions_mutex);
while (true) {
// Move first element of m_reconnections to todo (avoiding an allocation inside the lock).
decltype(m_reconnections) todo;
{
LOCK(m_reconnections_mutex);
if (m_reconnections.empty()) break;
todo.splice(todo.end(), m_reconnections, m_reconnections.begin());
}
auto& item = *todo.begin();
OpenNetworkConnection(item.addr_connect,
// We only reconnect if the first attempt to connect succeeded at
// connection time, but then failed after the CNode object was
// created. Since we already know connecting is possible, do not
// count failure to reconnect.
/*fCountFailure=*/false,
std::move(item.grant),
item.destination.empty() ? nullptr : item.destination.c_str(),
item.conn_type,
item.use_v2transport);
}
}
// Dump binary message to file, with timestamp. // Dump binary message to file, with timestamp.
static void CaptureMessageToFile(const CAddress& addr, static void CaptureMessageToFile(const CAddress& addr,
const std::string& msg_type, const std::string& msg_type,

View File

@ -94,11 +94,17 @@ static constexpr bool DEFAULT_FIXEDSEEDS{true};
static const size_t DEFAULT_MAXRECEIVEBUFFER = 5 * 1000; static const size_t DEFAULT_MAXRECEIVEBUFFER = 5 * 1000;
static const size_t DEFAULT_MAXSENDBUFFER = 1 * 1000; static const size_t DEFAULT_MAXSENDBUFFER = 1 * 1000;
static constexpr bool DEFAULT_V2_TRANSPORT{false};
typedef int64_t NodeId; typedef int64_t NodeId;
struct AddedNodeInfo struct AddedNodeParams {
{ std::string m_added_node;
std::string strAddedNode; bool m_use_v2transport;
};
struct AddedNodeInfo {
AddedNodeParams m_params;
CService resolvedAddress; CService resolvedAddress;
bool fConnected; bool fConnected;
bool fInbound; bool fInbound;
@ -226,6 +232,10 @@ public:
Network m_network; Network m_network;
uint32_t m_mapped_as; uint32_t m_mapped_as;
ConnectionType m_conn_type; ConnectionType m_conn_type;
/** Transport protocol type. */
TransportProtocolType m_transport_type;
/** BIP324 session id string in hex, if any. */
std::string m_session_id;
}; };
@ -262,6 +272,15 @@ class Transport {
public: public:
virtual ~Transport() {} virtual ~Transport() {}
struct Info
{
TransportProtocolType transport_type;
std::optional<uint256> session_id;
};
/** Retrieve information about this transport. */
virtual Info GetInfo() const noexcept = 0;
// 1. Receiver side functions, for decoding bytes received on the wire into transport protocol // 1. Receiver side functions, for decoding bytes received on the wire into transport protocol
// agnostic CNetMessage (message type & payload) objects. // agnostic CNetMessage (message type & payload) objects.
@ -355,6 +374,11 @@ public:
/** Return the memory usage of this transport attributable to buffered data to send. */ /** Return the memory usage of this transport attributable to buffered data to send. */
virtual size_t GetSendMemoryUsage() const noexcept = 0; virtual size_t GetSendMemoryUsage() const noexcept = 0;
// 3. Miscellaneous functions.
/** Whether upon disconnections, a reconnect with V1 is warranted. */
virtual bool ShouldReconnectV1() const noexcept = 0;
}; };
class V1Transport final : public Transport class V1Transport final : public Transport
@ -415,6 +439,8 @@ public:
return WITH_LOCK(m_recv_mutex, return CompleteInternal()); return WITH_LOCK(m_recv_mutex, return CompleteInternal());
} }
Info GetInfo() const noexcept override;
bool ReceivedBytes(Span<const uint8_t>& msg_bytes) override EXCLUSIVE_LOCKS_REQUIRED(!m_recv_mutex) bool ReceivedBytes(Span<const uint8_t>& msg_bytes) override EXCLUSIVE_LOCKS_REQUIRED(!m_recv_mutex)
{ {
AssertLockNotHeld(m_recv_mutex); AssertLockNotHeld(m_recv_mutex);
@ -434,6 +460,7 @@ public:
BytesToSend GetBytesToSend(bool have_next_message) const noexcept override EXCLUSIVE_LOCKS_REQUIRED(!m_send_mutex); BytesToSend GetBytesToSend(bool have_next_message) const noexcept override EXCLUSIVE_LOCKS_REQUIRED(!m_send_mutex);
void MarkBytesSent(size_t bytes_sent) noexcept override EXCLUSIVE_LOCKS_REQUIRED(!m_send_mutex); void MarkBytesSent(size_t bytes_sent) noexcept override EXCLUSIVE_LOCKS_REQUIRED(!m_send_mutex);
size_t GetSendMemoryUsage() const noexcept override EXCLUSIVE_LOCKS_REQUIRED(!m_send_mutex); size_t GetSendMemoryUsage() const noexcept override EXCLUSIVE_LOCKS_REQUIRED(!m_send_mutex);
bool ShouldReconnectV1() const noexcept override { return false; }
}; };
class V2Transport final : public Transport class V2Transport final : public Transport
@ -602,6 +629,8 @@ private:
std::string m_send_type GUARDED_BY(m_send_mutex); std::string m_send_type GUARDED_BY(m_send_mutex);
/** Current sender state. */ /** Current sender state. */
SendState m_send_state GUARDED_BY(m_send_mutex); SendState m_send_state GUARDED_BY(m_send_mutex);
/** Whether we've sent at least 24 bytes (which would trigger disconnect for V1 peers). */
bool m_sent_v1_header_worth GUARDED_BY(m_send_mutex) {false};
/** Change the receive state. */ /** Change the receive state. */
void SetReceiveState(RecvState recv_state) noexcept EXCLUSIVE_LOCKS_REQUIRED(m_recv_mutex); void SetReceiveState(RecvState recv_state) noexcept EXCLUSIVE_LOCKS_REQUIRED(m_recv_mutex);
@ -647,6 +676,10 @@ public:
BytesToSend GetBytesToSend(bool have_next_message) const noexcept override EXCLUSIVE_LOCKS_REQUIRED(!m_send_mutex); BytesToSend GetBytesToSend(bool have_next_message) const noexcept override EXCLUSIVE_LOCKS_REQUIRED(!m_send_mutex);
void MarkBytesSent(size_t bytes_sent) noexcept override EXCLUSIVE_LOCKS_REQUIRED(!m_send_mutex); void MarkBytesSent(size_t bytes_sent) noexcept override EXCLUSIVE_LOCKS_REQUIRED(!m_send_mutex);
size_t GetSendMemoryUsage() const noexcept override EXCLUSIVE_LOCKS_REQUIRED(!m_send_mutex); size_t GetSendMemoryUsage() const noexcept override EXCLUSIVE_LOCKS_REQUIRED(!m_send_mutex);
// Miscellaneous functions.
bool ShouldReconnectV1() const noexcept override EXCLUSIVE_LOCKS_REQUIRED(!m_recv_mutex, !m_send_mutex);
Info GetInfo() const noexcept override EXCLUSIVE_LOCKS_REQUIRED(!m_recv_mutex);
}; };
struct CNodeOptions struct CNodeOptions
@ -655,6 +688,7 @@ struct CNodeOptions
std::unique_ptr<i2p::sam::Session> i2p_sam_session = nullptr; std::unique_ptr<i2p::sam::Session> i2p_sam_session = nullptr;
bool prefer_evict = false; bool prefer_evict = false;
size_t recv_flood_size{DEFAULT_MAXRECEIVEBUFFER * 1000}; size_t recv_flood_size{DEFAULT_MAXRECEIVEBUFFER * 1000};
bool use_v2transport = false;
}; };
/** Information about a peer */ /** Information about a peer */
@ -699,6 +733,8 @@ public:
// Bind address of our side of the connection // Bind address of our side of the connection
const CAddress addrBind; const CAddress addrBind;
const std::string m_addr_name; const std::string m_addr_name;
/** The pszDest argument provided to ConnectNode(). Only used for reconnections. */
const std::string m_dest;
//! Whether this peer is an inbound onion, i.e. connected via our Tor onion service. //! Whether this peer is an inbound onion, i.e. connected via our Tor onion service.
const bool m_inbound_onion; const bool m_inbound_onion;
std::atomic<int> nVersion{0}; std::atomic<int> nVersion{0};
@ -1072,7 +1108,11 @@ public:
vWhitelistedRange = connOptions.vWhitelistedRange; vWhitelistedRange = connOptions.vWhitelistedRange;
{ {
LOCK(m_added_nodes_mutex); LOCK(m_added_nodes_mutex);
m_added_nodes = connOptions.m_added_nodes;
for (const std::string& added_node : connOptions.m_added_nodes) {
// -addnode cli arg does not currently have a way to signal BIP324 support
m_added_node_params.push_back({added_node, false});
}
} }
m_onion_binds = connOptions.onion_binds; m_onion_binds = connOptions.onion_binds;
} }
@ -1096,7 +1136,7 @@ public:
bool GetNetworkActive() const { return fNetworkActive; }; bool GetNetworkActive() const { return fNetworkActive; };
bool GetUseAddrmanOutgoing() const { return m_use_addrman_outgoing; }; bool GetUseAddrmanOutgoing() const { return m_use_addrman_outgoing; };
void SetNetworkActive(bool active); void SetNetworkActive(bool active);
void OpenNetworkConnection(const CAddress& addrConnect, bool fCountFailure, CSemaphoreGrant* grantOutbound, const char* strDest, ConnectionType conn_type) EXCLUSIVE_LOCKS_REQUIRED(!m_unused_i2p_sessions_mutex); void OpenNetworkConnection(const CAddress& addrConnect, bool fCountFailure, CSemaphoreGrant&& grant_outbound, const char* strDest, ConnectionType conn_type, bool use_v2transport) EXCLUSIVE_LOCKS_REQUIRED(!m_unused_i2p_sessions_mutex);
bool CheckIncomingNonce(uint64_t nonce); bool CheckIncomingNonce(uint64_t nonce);
// alias for thread safety annotations only, not defined // alias for thread safety annotations only, not defined
@ -1159,7 +1199,7 @@ public:
// Count the number of block-relay-only peers we have over our limit. // Count the number of block-relay-only peers we have over our limit.
int GetExtraBlockRelayCount() const; int GetExtraBlockRelayCount() const;
bool AddNode(const std::string& node) EXCLUSIVE_LOCKS_REQUIRED(!m_added_nodes_mutex); bool AddNode(const AddedNodeParams& add) EXCLUSIVE_LOCKS_REQUIRED(!m_added_nodes_mutex);
bool RemoveAddedNode(const std::string& node) EXCLUSIVE_LOCKS_REQUIRED(!m_added_nodes_mutex); bool RemoveAddedNode(const std::string& node) EXCLUSIVE_LOCKS_REQUIRED(!m_added_nodes_mutex);
std::vector<AddedNodeInfo> GetAddedNodeInfo() const EXCLUSIVE_LOCKS_REQUIRED(!m_added_nodes_mutex); std::vector<AddedNodeInfo> GetAddedNodeInfo() const EXCLUSIVE_LOCKS_REQUIRED(!m_added_nodes_mutex);
@ -1242,10 +1282,10 @@ private:
bool Bind(const CService& addr, unsigned int flags, NetPermissionFlags permissions); bool Bind(const CService& addr, unsigned int flags, NetPermissionFlags permissions);
bool InitBinds(const Options& options); bool InitBinds(const Options& options);
void ThreadOpenAddedConnections() EXCLUSIVE_LOCKS_REQUIRED(!m_added_nodes_mutex, !m_unused_i2p_sessions_mutex); void ThreadOpenAddedConnections() EXCLUSIVE_LOCKS_REQUIRED(!m_added_nodes_mutex, !m_unused_i2p_sessions_mutex, !m_reconnections_mutex);
void AddAddrFetch(const std::string& strDest) EXCLUSIVE_LOCKS_REQUIRED(!m_addr_fetches_mutex); void AddAddrFetch(const std::string& strDest) EXCLUSIVE_LOCKS_REQUIRED(!m_addr_fetches_mutex);
void ProcessAddrFetch() EXCLUSIVE_LOCKS_REQUIRED(!m_addr_fetches_mutex, !m_unused_i2p_sessions_mutex); void ProcessAddrFetch() EXCLUSIVE_LOCKS_REQUIRED(!m_addr_fetches_mutex, !m_unused_i2p_sessions_mutex);
void ThreadOpenConnections(std::vector<std::string> connect) EXCLUSIVE_LOCKS_REQUIRED(!m_addr_fetches_mutex, !m_added_nodes_mutex, !m_nodes_mutex, !m_unused_i2p_sessions_mutex); void ThreadOpenConnections(std::vector<std::string> connect) EXCLUSIVE_LOCKS_REQUIRED(!m_addr_fetches_mutex, !m_added_nodes_mutex, !m_nodes_mutex, !m_unused_i2p_sessions_mutex, !m_reconnections_mutex);
void ThreadMessageHandler() EXCLUSIVE_LOCKS_REQUIRED(!mutexMsgProc); void ThreadMessageHandler() EXCLUSIVE_LOCKS_REQUIRED(!mutexMsgProc);
void ThreadI2PAcceptIncoming(); void ThreadI2PAcceptIncoming();
void AcceptConnection(const ListenSocket& hListenSocket); void AcceptConnection(const ListenSocket& hListenSocket);
@ -1263,7 +1303,7 @@ private:
const CAddress& addr_bind, const CAddress& addr_bind,
const CAddress& addr); const CAddress& addr);
void DisconnectNodes(); void DisconnectNodes() EXCLUSIVE_LOCKS_REQUIRED(!m_reconnections_mutex, !m_nodes_mutex);
void NotifyNumConnectionsChanged(); void NotifyNumConnectionsChanged();
/** Return true if the peer is inactive and should be disconnected. */ /** Return true if the peer is inactive and should be disconnected. */
bool InactivityCheck(const CNode& node) const; bool InactivityCheck(const CNode& node) const;
@ -1295,7 +1335,7 @@ private:
*/ */
void SocketHandlerListening(const Sock::EventsPerSock& events_per_sock); void SocketHandlerListening(const Sock::EventsPerSock& events_per_sock);
void ThreadSocketHandler() EXCLUSIVE_LOCKS_REQUIRED(!m_total_bytes_sent_mutex, !mutexMsgProc); void ThreadSocketHandler() EXCLUSIVE_LOCKS_REQUIRED(!m_total_bytes_sent_mutex, !mutexMsgProc, !m_nodes_mutex, !m_reconnections_mutex);
void ThreadDNSAddressSeed() EXCLUSIVE_LOCKS_REQUIRED(!m_addr_fetches_mutex, !m_nodes_mutex); void ThreadDNSAddressSeed() EXCLUSIVE_LOCKS_REQUIRED(!m_addr_fetches_mutex, !m_nodes_mutex);
uint64_t CalculateKeyedNetGroup(const CAddress& ad) const; uint64_t CalculateKeyedNetGroup(const CAddress& ad) const;
@ -1312,7 +1352,7 @@ private:
bool AlreadyConnectedToAddress(const CAddress& addr); bool AlreadyConnectedToAddress(const CAddress& addr);
bool AttemptToEvictConnection(); bool AttemptToEvictConnection();
CNode* ConnectNode(CAddress addrConnect, const char *pszDest, bool fCountFailure, ConnectionType conn_type) EXCLUSIVE_LOCKS_REQUIRED(!m_unused_i2p_sessions_mutex); CNode* ConnectNode(CAddress addrConnect, const char *pszDest, bool fCountFailure, ConnectionType conn_type, bool use_v2transport) EXCLUSIVE_LOCKS_REQUIRED(!m_unused_i2p_sessions_mutex);
void AddWhitelistPermissionFlags(NetPermissionFlags& flags, const CNetAddr &addr) const; void AddWhitelistPermissionFlags(NetPermissionFlags& flags, const CNetAddr &addr) const;
void DeleteNode(CNode* pnode); void DeleteNode(CNode* pnode);
@ -1384,7 +1424,10 @@ private:
const NetGroupManager& m_netgroupman; const NetGroupManager& m_netgroupman;
std::deque<std::string> m_addr_fetches GUARDED_BY(m_addr_fetches_mutex); std::deque<std::string> m_addr_fetches GUARDED_BY(m_addr_fetches_mutex);
Mutex m_addr_fetches_mutex; Mutex m_addr_fetches_mutex;
std::vector<std::string> m_added_nodes GUARDED_BY(m_added_nodes_mutex);
// connection string and whether to use v2 p2p
std::vector<AddedNodeParams> m_added_node_params GUARDED_BY(m_added_nodes_mutex);
mutable Mutex m_added_nodes_mutex; mutable Mutex m_added_nodes_mutex;
std::vector<CNode*> m_nodes GUARDED_BY(m_nodes_mutex); std::vector<CNode*> m_nodes GUARDED_BY(m_nodes_mutex);
std::list<CNode*> m_nodes_disconnected; std::list<CNode*> m_nodes_disconnected;
@ -1523,6 +1566,29 @@ private:
*/ */
std::queue<std::unique_ptr<i2p::sam::Session>> m_unused_i2p_sessions GUARDED_BY(m_unused_i2p_sessions_mutex); std::queue<std::unique_ptr<i2p::sam::Session>> m_unused_i2p_sessions GUARDED_BY(m_unused_i2p_sessions_mutex);
/**
* Mutex protecting m_reconnections.
*/
Mutex m_reconnections_mutex;
/** Struct for entries in m_reconnections. */
struct ReconnectionInfo
{
CAddress addr_connect;
CSemaphoreGrant grant;
std::string destination;
ConnectionType conn_type;
bool use_v2transport;
};
/**
* List of reconnections we have to make.
*/
std::list<ReconnectionInfo> m_reconnections GUARDED_BY(m_reconnections_mutex);
/** Attempt reconnections, if m_reconnections non-empty. */
void PerformReconnections() EXCLUSIVE_LOCKS_REQUIRED(!m_reconnections_mutex, !m_unused_i2p_sessions_mutex);
/** /**
* Cap on the size of `m_unused_i2p_sessions`, to ensure it does not * Cap on the size of `m_unused_i2p_sessions`, to ensure it does not
* unexpectedly use too much memory. * unexpectedly use too much memory.

View File

@ -3585,13 +3585,16 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
return; return;
} }
if (!pfrom.IsInboundConn()) { // Log succesful connections unconditionally for outbound, but not for inbound as those
// can be triggered by an attacker at high rate.
if (!pfrom.IsInboundConn() || LogAcceptCategory(BCLog::NET, BCLog::Level::Debug)) {
const auto mapped_as{m_connman.GetMappedAS(pfrom.addr)}; const auto mapped_as{m_connman.GetMappedAS(pfrom.addr)};
LogPrintf("New outbound peer connected: version: %d, blocks=%d, peer=%d%s%s (%s)\n", LogPrintf("New %s %s peer connected: version: %d, blocks=%d, peer=%d%s%s\n",
pfrom.ConnectionTypeAsString(),
TransportTypeAsString(pfrom.m_transport->GetInfo().transport_type),
pfrom.nVersion.load(), peer->m_starting_height, pfrom.nVersion.load(), peer->m_starting_height,
pfrom.GetId(), (fLogIPs ? strprintf(", peeraddr=%s", pfrom.addr.ToStringAddrPort()) : ""), pfrom.GetId(), (fLogIPs ? strprintf(", peeraddr=%s", pfrom.addr.ToStringAddrPort()) : ""),
(mapped_as ? strprintf(", mapped_as=%d", mapped_as) : ""), (mapped_as ? strprintf(", mapped_as=%d", mapped_as) : ""));
pfrom.ConnectionTypeAsString());
} }
if (pfrom.GetCommonVersion() >= SHORT_IDS_BLOCKS_VERSION) { if (pfrom.GetCommonVersion() >= SHORT_IDS_BLOCKS_VERSION) {

View File

@ -24,3 +24,17 @@ std::string ConnectionTypeAsString(ConnectionType conn_type)
assert(false); assert(false);
} }
std::string TransportTypeAsString(TransportProtocolType transport_type)
{
switch (transport_type) {
case TransportProtocolType::DETECTING:
return "detecting";
case TransportProtocolType::V1:
return "v1";
case TransportProtocolType::V2:
return "v2";
} // no default case, so the compiler can warn about missing cases
assert(false);
}

View File

@ -6,6 +6,7 @@
#define BITCOIN_NODE_CONNECTION_TYPES_H #define BITCOIN_NODE_CONNECTION_TYPES_H
#include <string> #include <string>
#include <stdint.h>
/** Different types of connections to a peer. This enum encapsulates the /** Different types of connections to a peer. This enum encapsulates the
* information we have available at the time of opening or accepting the * information we have available at the time of opening or accepting the
@ -79,4 +80,14 @@ enum class ConnectionType {
/** Convert ConnectionType enum to a string value */ /** Convert ConnectionType enum to a string value */
std::string ConnectionTypeAsString(ConnectionType conn_type); std::string ConnectionTypeAsString(ConnectionType conn_type);
/** Transport layer version */
enum class TransportProtocolType : uint8_t {
DETECTING, //!< Peer could be v1 or v2
V1, //!< Unencrypted, plaintext protocol
V2, //!< BIP324 protocol
};
/** Convert TransportProtocolType enum to a string value */
std::string TransportTypeAsString(TransportProtocolType transport_type);
#endif // BITCOIN_NODE_CONNECTION_TYPES_H #endif // BITCOIN_NODE_CONNECTION_TYPES_H

View File

@ -199,6 +199,7 @@ static std::string serviceFlagToStr(size_t bit)
case NODE_WITNESS: return "WITNESS"; case NODE_WITNESS: return "WITNESS";
case NODE_COMPACT_FILTERS: return "COMPACT_FILTERS"; case NODE_COMPACT_FILTERS: return "COMPACT_FILTERS";
case NODE_NETWORK_LIMITED: return "NETWORK_LIMITED"; case NODE_NETWORK_LIMITED: return "NETWORK_LIMITED";
case NODE_P2P_V2: return "P2P_V2";
// Not using default, so we get warned when a case is missing // Not using default, so we get warned when a case is missing
} }

View File

@ -291,6 +291,9 @@ enum ServiceFlags : uint64_t {
// See BIP159 for details on how this is implemented. // See BIP159 for details on how this is implemented.
NODE_NETWORK_LIMITED = (1 << 10), NODE_NETWORK_LIMITED = (1 << 10),
// NODE_P2P_V2 means the node supports BIP324 transport
NODE_P2P_V2 = (1 << 11),
// Bits 24-31 are reserved for temporary experiments. Just pick a bit that // Bits 24-31 are reserved for temporary experiments. Just pick a bit that
// isn't getting used, or one not being used much, and notify the // isn't getting used, or one not being used much, and notify the
// bitcoin-development mailing list. Remember that service bits are just // bitcoin-development mailing list. Remember that service bits are just

View File

@ -301,6 +301,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "addpeeraddress", 2, "tried"}, { "addpeeraddress", 2, "tried"},
{ "sendmsgtopeer", 0, "peer_id" }, { "sendmsgtopeer", 0, "peer_id" },
{ "stop", 0, "wait" }, { "stop", 0, "wait" },
{ "addnode", 2, "v2transport" },
}; };
// clang-format on // clang-format on

View File

@ -45,6 +45,12 @@ const std::vector<std::string> CONNECTION_TYPE_DOC{
"feeler (short-lived automatic connection for testing addresses)" "feeler (short-lived automatic connection for testing addresses)"
}; };
const std::vector<std::string> TRANSPORT_TYPE_DOC{
"detecting (peer could be v1 or v2)",
"v1 (plaintext transport protocol)",
"v2 (BIP324 encrypted transport protocol)"
};
static RPCHelpMan getconnectioncount() static RPCHelpMan getconnectioncount()
{ {
return RPCHelpMan{"getconnectioncount", return RPCHelpMan{"getconnectioncount",
@ -164,6 +170,8 @@ static RPCHelpMan getpeerinfo()
{RPCResult::Type::STR, "connection_type", "Type of connection: \n" + Join(CONNECTION_TYPE_DOC, ",\n") + ".\n" {RPCResult::Type::STR, "connection_type", "Type of connection: \n" + Join(CONNECTION_TYPE_DOC, ",\n") + ".\n"
"Please note this output is unlikely to be stable in upcoming releases as we iterate to\n" "Please note this output is unlikely to be stable in upcoming releases as we iterate to\n"
"best capture connection behaviors."}, "best capture connection behaviors."},
{RPCResult::Type::STR, "transport_protocol_type", "Type of transport protocol: \n" + Join(TRANSPORT_TYPE_DOC, ",\n") + ".\n"},
{RPCResult::Type::STR, "session_id", "The session ID for this connection, or \"\" if there is none (\"v2\" transport protocol only).\n"},
}}, }},
}}, }},
}, },
@ -268,6 +276,8 @@ static RPCHelpMan getpeerinfo()
} }
obj.pushKV("bytesrecv_per_msg", recvPerMsgType); obj.pushKV("bytesrecv_per_msg", recvPerMsgType);
obj.pushKV("connection_type", ConnectionTypeAsString(stats.m_conn_type)); obj.pushKV("connection_type", ConnectionTypeAsString(stats.m_conn_type));
obj.pushKV("transport_protocol_type", TransportTypeAsString(stats.m_transport_type));
obj.pushKV("session_id", stats.m_session_id);
ret.push_back(obj); ret.push_back(obj);
} }
@ -289,11 +299,12 @@ static RPCHelpMan addnode()
{ {
{"node", RPCArg::Type::STR, RPCArg::Optional::NO, "The address of the peer to connect to"}, {"node", RPCArg::Type::STR, RPCArg::Optional::NO, "The address of the peer to connect to"},
{"command", RPCArg::Type::STR, RPCArg::Optional::NO, "'add' to add a node to the list, 'remove' to remove a node from the list, 'onetry' to try a connection to the node once"}, {"command", RPCArg::Type::STR, RPCArg::Optional::NO, "'add' to add a node to the list, 'remove' to remove a node from the list, 'onetry' to try a connection to the node once"},
{"v2transport", RPCArg::Type::BOOL, RPCArg::Default{false}, "Attempt to connect using BIP324 v2 transport protocol (ignored for 'remove' command)"},
}, },
RPCResult{RPCResult::Type::NONE, "", ""}, RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{ RPCExamples{
HelpExampleCli("addnode", "\"192.168.0.6:8333\" \"onetry\"") HelpExampleCli("addnode", "\"192.168.0.6:8333\" \"onetry\" true")
+ HelpExampleRpc("addnode", "\"192.168.0.6:8333\", \"onetry\"") + HelpExampleRpc("addnode", "\"192.168.0.6:8333\", \"onetry\" true")
}, },
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{ {
@ -307,17 +318,22 @@ static RPCHelpMan addnode()
CConnman& connman = EnsureConnman(node); CConnman& connman = EnsureConnman(node);
const std::string node_arg{request.params[0].get_str()}; const std::string node_arg{request.params[0].get_str()};
bool use_v2transport = self.Arg<bool>(2);
if (use_v2transport && !(node.connman->GetLocalServices() & NODE_P2P_V2)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Error: v2transport requested but not enabled (see -v2transport)");
}
if (command == "onetry") if (command == "onetry")
{ {
CAddress addr; CAddress addr;
connman.OpenNetworkConnection(addr, /*fCountFailure=*/false, /*grantOutbound=*/nullptr, node_arg.c_str(), ConnectionType::MANUAL); connman.OpenNetworkConnection(addr, /*fCountFailure=*/false, /*grant_outbound=*/{}, node_arg.c_str(), ConnectionType::MANUAL, use_v2transport);
return UniValue::VNULL; return UniValue::VNULL;
} }
if (command == "add") if (command == "add")
{ {
if (!connman.AddNode(node_arg)) { if (!connman.AddNode({node_arg, use_v2transport})) {
throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED, "Error: Node already added"); throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED, "Error: Node already added");
} }
} }
@ -475,7 +491,7 @@ static RPCHelpMan getaddednodeinfo()
if (!request.params[0].isNull()) { if (!request.params[0].isNull()) {
bool found = false; bool found = false;
for (const AddedNodeInfo& info : vInfo) { for (const AddedNodeInfo& info : vInfo) {
if (info.strAddedNode == request.params[0].get_str()) { if (info.m_params.m_added_node == request.params[0].get_str()) {
vInfo.assign(1, info); vInfo.assign(1, info);
found = true; found = true;
break; break;
@ -490,7 +506,7 @@ static RPCHelpMan getaddednodeinfo()
for (const AddedNodeInfo& info : vInfo) { for (const AddedNodeInfo& info : vInfo) {
UniValue obj(UniValue::VOBJ); UniValue obj(UniValue::VOBJ);
obj.pushKV("addednode", info.strAddedNode); obj.pushKV("addednode", info.m_params.m_added_node);
obj.pushKV("connected", info.fConnected); obj.pushKV("connected", info.fConnected);
UniValue addresses(UniValue::VARR); UniValue addresses(UniValue::VARR);
if (info.fConnected) { if (info.fConnected) {

View File

@ -682,6 +682,7 @@ TMPL_INST(nullptr, std::optional<bool>, maybe_arg ? std::optional{maybe_arg->get
TMPL_INST(nullptr, const std::string*, maybe_arg ? &maybe_arg->get_str() : nullptr;); TMPL_INST(nullptr, const std::string*, maybe_arg ? &maybe_arg->get_str() : nullptr;);
// Required arg or optional arg with default value. // Required arg or optional arg with default value.
TMPL_INST(CheckRequiredOrDefault, bool, CHECK_NONFATAL(maybe_arg)->get_bool(););
TMPL_INST(CheckRequiredOrDefault, int, CHECK_NONFATAL(maybe_arg)->getInt<int>();); TMPL_INST(CheckRequiredOrDefault, int, CHECK_NONFATAL(maybe_arg)->getInt<int>(););
TMPL_INST(CheckRequiredOrDefault, uint64_t, CHECK_NONFATAL(maybe_arg)->getInt<uint64_t>();); TMPL_INST(CheckRequiredOrDefault, uint64_t, CHECK_NONFATAL(maybe_arg)->getInt<uint64_t>(););
TMPL_INST(CheckRequiredOrDefault, const std::string&, CHECK_NONFATAL(maybe_arg)->get_str();); TMPL_INST(CheckRequiredOrDefault, const std::string&, CHECK_NONFATAL(maybe_arg)->get_str(););

View File

@ -301,6 +301,10 @@ inline MutexType* MaybeCheckNotHeld(MutexType* m) LOCKS_EXCLUDED(m) LOCK_RETURNE
//! gcc and the -Wreturn-stack-address flag in clang, both enabled by default. //! gcc and the -Wreturn-stack-address flag in clang, both enabled by default.
#define WITH_LOCK(cs, code) (MaybeCheckNotHeld(cs), [&]() -> decltype(auto) { LOCK(cs); code; }()) #define WITH_LOCK(cs, code) (MaybeCheckNotHeld(cs), [&]() -> decltype(auto) { LOCK(cs); code; }())
/** An implementation of a semaphore.
*
* See https://en.wikipedia.org/wiki/Semaphore_(programming)
*/
class CSemaphore class CSemaphore
{ {
private: private:
@ -309,25 +313,33 @@ private:
int value; int value;
public: public:
explicit CSemaphore(int init) : value(init) {} explicit CSemaphore(int init) noexcept : value(init) {}
void wait() // Disallow default construct, copy, move.
CSemaphore() = delete;
CSemaphore(const CSemaphore&) = delete;
CSemaphore(CSemaphore&&) = delete;
CSemaphore& operator=(const CSemaphore&) = delete;
CSemaphore& operator=(CSemaphore&&) = delete;
void wait() noexcept
{ {
std::unique_lock<std::mutex> lock(mutex); std::unique_lock<std::mutex> lock(mutex);
condition.wait(lock, [&]() { return value >= 1; }); condition.wait(lock, [&]() { return value >= 1; });
value--; value--;
} }
bool try_wait() bool try_wait() noexcept
{ {
std::lock_guard<std::mutex> lock(mutex); std::lock_guard<std::mutex> lock(mutex);
if (value < 1) if (value < 1) {
return false; return false;
}
value--; value--;
return true; return true;
} }
void post() void post() noexcept
{ {
{ {
std::lock_guard<std::mutex> lock(mutex); std::lock_guard<std::mutex> lock(mutex);
@ -345,53 +357,72 @@ private:
bool fHaveGrant; bool fHaveGrant;
public: public:
void Acquire() void Acquire() noexcept
{ {
if (fHaveGrant) if (fHaveGrant) {
return; return;
}
sem->wait(); sem->wait();
fHaveGrant = true; fHaveGrant = true;
} }
void Release() void Release() noexcept
{ {
if (!fHaveGrant) if (!fHaveGrant) {
return; return;
}
sem->post(); sem->post();
fHaveGrant = false; fHaveGrant = false;
} }
bool TryAcquire() bool TryAcquire() noexcept
{ {
if (!fHaveGrant && sem->try_wait()) if (!fHaveGrant && sem->try_wait()) {
fHaveGrant = true; fHaveGrant = true;
}
return fHaveGrant; return fHaveGrant;
} }
void MoveTo(CSemaphoreGrant& grant) // Disallow copy.
CSemaphoreGrant(const CSemaphoreGrant&) = delete;
CSemaphoreGrant& operator=(const CSemaphoreGrant&) = delete;
// Allow move.
CSemaphoreGrant(CSemaphoreGrant&& other) noexcept
{ {
grant.Release(); sem = other.sem;
grant.sem = sem; fHaveGrant = other.fHaveGrant;
grant.fHaveGrant = fHaveGrant; other.fHaveGrant = false;
fHaveGrant = false; other.sem = nullptr;
} }
CSemaphoreGrant() : sem(nullptr), fHaveGrant(false) {} CSemaphoreGrant& operator=(CSemaphoreGrant&& other) noexcept
explicit CSemaphoreGrant(CSemaphore& sema, bool fTry = false) : sem(&sema), fHaveGrant(false)
{ {
if (fTry) Release();
sem = other.sem;
fHaveGrant = other.fHaveGrant;
other.fHaveGrant = false;
other.sem = nullptr;
return *this;
}
CSemaphoreGrant() noexcept : sem(nullptr), fHaveGrant(false) {}
explicit CSemaphoreGrant(CSemaphore& sema, bool fTry = false) noexcept : sem(&sema), fHaveGrant(false)
{
if (fTry) {
TryAcquire(); TryAcquire();
else } else {
Acquire(); Acquire();
} }
}
~CSemaphoreGrant() ~CSemaphoreGrant()
{ {
Release(); Release();
} }
operator bool() const explicit operator bool() const noexcept
{ {
return fHaveGrant; return fHaveGrant;
} }

View File

@ -61,7 +61,7 @@ FUZZ_TARGET(connman, .init = initialize_connman)
random_string = fuzzed_data_provider.ConsumeRandomLengthString(64); random_string = fuzzed_data_provider.ConsumeRandomLengthString(64);
}, },
[&] { [&] {
connman.AddNode(random_string); connman.AddNode({random_string, fuzzed_data_provider.ConsumeBool()});
}, },
[&] { [&] {
connman.CheckIncomingNonce(fuzzed_data_provider.ConsumeIntegral<uint64_t>()); connman.CheckIncomingNonce(fuzzed_data_provider.ConsumeIntegral<uint64_t>());

View File

@ -328,6 +328,9 @@ void SimulationTest(Transport& initiator, Transport& responder, R& rng, FuzzedDa
// Make sure all expected messages were received. // Make sure all expected messages were received.
assert(expected[0].empty()); assert(expected[0].empty());
assert(expected[1].empty()); assert(expected[1].empty());
// Compare session IDs.
assert(transports[0]->GetInfo().session_id == transports[1]->GetInfo().session_id);
} }
std::unique_ptr<Transport> MakeV1Transport(NodeId nodeid) noexcept std::unique_ptr<Transport> MakeV1Transport(NodeId nodeid) noexcept

View File

@ -1321,6 +1321,14 @@ public:
SendPacket(contents); SendPacket(contents);
} }
/** Test whether the transport's session ID matches the session ID we expect. */
void CompareSessionIDs() const
{
auto info = m_transport.GetInfo();
BOOST_CHECK(info.session_id);
BOOST_CHECK(uint256(MakeUCharSpan(m_cipher.GetSessionID())) == *info.session_id);
}
/** Introduce a bit error in the data scheduled to be sent. */ /** Introduce a bit error in the data scheduled to be sent. */
void Damage() void Damage()
{ {
@ -1346,6 +1354,7 @@ BOOST_AUTO_TEST_CASE(v2transport_test)
BOOST_REQUIRE(ret && ret->empty()); BOOST_REQUIRE(ret && ret->empty());
tester.ReceiveGarbage(); tester.ReceiveGarbage();
tester.ReceiveVersion(); tester.ReceiveVersion();
tester.CompareSessionIDs();
auto msg_data_1 = g_insecure_rand_ctx.randbytes<uint8_t>(InsecureRandRange(100000)); auto msg_data_1 = g_insecure_rand_ctx.randbytes<uint8_t>(InsecureRandRange(100000));
auto msg_data_2 = g_insecure_rand_ctx.randbytes<uint8_t>(InsecureRandRange(1000)); auto msg_data_2 = g_insecure_rand_ctx.randbytes<uint8_t>(InsecureRandRange(1000));
tester.SendMessage(uint8_t(4), msg_data_1); // cmpctblock short id tester.SendMessage(uint8_t(4), msg_data_1); // cmpctblock short id
@ -1386,6 +1395,7 @@ BOOST_AUTO_TEST_CASE(v2transport_test)
BOOST_REQUIRE(ret && ret->empty()); BOOST_REQUIRE(ret && ret->empty());
tester.ReceiveGarbage(); tester.ReceiveGarbage();
tester.ReceiveVersion(); tester.ReceiveVersion();
tester.CompareSessionIDs();
auto msg_data_1 = g_insecure_rand_ctx.randbytes<uint8_t>(InsecureRandRange(100000)); auto msg_data_1 = g_insecure_rand_ctx.randbytes<uint8_t>(InsecureRandRange(100000));
auto msg_data_2 = g_insecure_rand_ctx.randbytes<uint8_t>(InsecureRandRange(1000)); auto msg_data_2 = g_insecure_rand_ctx.randbytes<uint8_t>(InsecureRandRange(1000));
tester.SendMessage(uint8_t(14), msg_data_1); // inv short id tester.SendMessage(uint8_t(14), msg_data_1); // inv short id
@ -1439,6 +1449,7 @@ BOOST_AUTO_TEST_CASE(v2transport_test)
BOOST_REQUIRE(ret && ret->empty()); BOOST_REQUIRE(ret && ret->empty());
tester.ReceiveGarbage(); tester.ReceiveGarbage();
tester.ReceiveVersion(); tester.ReceiveVersion();
tester.CompareSessionIDs();
for (unsigned d = 0; d < num_decoys_1; ++d) { for (unsigned d = 0; d < num_decoys_1; ++d) {
auto decoy_data = g_insecure_rand_ctx.randbytes<uint8_t>(InsecureRandRange(1000)); auto decoy_data = g_insecure_rand_ctx.randbytes<uint8_t>(InsecureRandRange(1000));
tester.SendPacket(/*content=*/decoy_data, /*aad=*/{}, /*ignore=*/true); tester.SendPacket(/*content=*/decoy_data, /*aad=*/{}, /*ignore=*/true);
@ -1516,6 +1527,7 @@ BOOST_AUTO_TEST_CASE(v2transport_test)
BOOST_REQUIRE(ret && ret->empty()); BOOST_REQUIRE(ret && ret->empty());
tester.ReceiveGarbage(); tester.ReceiveGarbage();
tester.ReceiveVersion(); tester.ReceiveVersion();
tester.CompareSessionIDs();
auto msg_data_1 = g_insecure_rand_ctx.randbytes<uint8_t>(4000000); // test that receiving 4M payload works auto msg_data_1 = g_insecure_rand_ctx.randbytes<uint8_t>(4000000); // test that receiving 4M payload works
auto msg_data_2 = g_insecure_rand_ctx.randbytes<uint8_t>(4000000); // test that sending 4M payload works auto msg_data_2 = g_insecure_rand_ctx.randbytes<uint8_t>(4000000); // test that sending 4M payload works
tester.SendMessage(uint8_t(InsecureRandRange(223) + 33), {}); // unknown short id tester.SendMessage(uint8_t(InsecureRandRange(223) + 33), {}); // unknown short id

View File

@ -65,6 +65,7 @@ constexpr ServiceFlags ALL_SERVICE_FLAGS[]{
NODE_WITNESS, NODE_WITNESS,
NODE_COMPACT_FILTERS, NODE_COMPACT_FILTERS,
NODE_NETWORK_LIMITED, NODE_NETWORK_LIMITED,
NODE_P2P_V2,
}; };
constexpr NetPermissionFlags ALL_NET_PERMISSION_FLAGS[]{ constexpr NetPermissionFlags ALL_NET_PERMISSION_FLAGS[]{

View File

@ -0,0 +1,127 @@
#!/usr/bin/env python3
# Copyright (c) 2021-present 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 v2 transport
"""
from test_framework.messages import NODE_P2P_V2
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal
class V2TransportTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 5
self.extra_args = [["-v2transport=1"], ["-v2transport=1"], ["-v2transport=0"], ["-v2transport=0"], ["-v2transport=0"]]
def run_test(self):
sending_handshake = "start sending v2 handshake to peer"
downgrading_to_v1 = "retrying with v1 transport protocol for peer"
self.disconnect_nodes(0, 1)
self.disconnect_nodes(1, 2)
self.disconnect_nodes(2, 3)
self.disconnect_nodes(3, 4)
# verify local services
network_info = self.nodes[2].getnetworkinfo()
assert_equal(int(network_info["localservices"], 16) & NODE_P2P_V2, 0)
assert "P2P_V2" not in network_info["localservicesnames"]
network_info = self.nodes[1].getnetworkinfo()
assert_equal(int(network_info["localservices"], 16) & NODE_P2P_V2, NODE_P2P_V2)
assert "P2P_V2" in network_info["localservicesnames"]
# V2 nodes can sync with V2 nodes
assert_equal(self.nodes[0].getblockcount(), 0)
assert_equal(self.nodes[1].getblockcount(), 0)
with self.nodes[0].assert_debug_log(expected_msgs=[sending_handshake],
unexpected_msgs=[downgrading_to_v1]):
self.connect_nodes(0, 1, peer_advertises_v2=True)
self.generate(self.nodes[0], 5, sync_fun=lambda: self.sync_all(self.nodes[0:2]))
assert_equal(self.nodes[1].getblockcount(), 5)
# verify there is a v2 connection between node 0 and 1
node_0_info = self.nodes[0].getpeerinfo()
node_1_info = self.nodes[0].getpeerinfo()
assert_equal(len(node_0_info), 1)
assert_equal(len(node_1_info), 1)
assert_equal(node_0_info[0]["transport_protocol_type"], "v2")
assert_equal(node_1_info[0]["transport_protocol_type"], "v2")
assert_equal(len(node_0_info[0]["session_id"]), 64)
assert_equal(len(node_1_info[0]["session_id"]), 64)
assert_equal(node_0_info[0]["session_id"], node_1_info[0]["session_id"])
# V1 nodes can sync with each other
assert_equal(self.nodes[2].getblockcount(), 0)
assert_equal(self.nodes[3].getblockcount(), 0)
with self.nodes[2].assert_debug_log(expected_msgs=[],
unexpected_msgs=[sending_handshake, downgrading_to_v1]):
self.connect_nodes(2, 3, peer_advertises_v2=False)
self.generate(self.nodes[2], 8, sync_fun=lambda: self.sync_all(self.nodes[2:4]))
assert_equal(self.nodes[3].getblockcount(), 8)
assert self.nodes[0].getbestblockhash() != self.nodes[2].getbestblockhash()
# verify there is a v1 connection between node 2 and 3
node_2_info = self.nodes[2].getpeerinfo()
node_3_info = self.nodes[3].getpeerinfo()
assert_equal(len(node_2_info), 1)
assert_equal(len(node_3_info), 1)
assert_equal(node_2_info[0]["transport_protocol_type"], "v1")
assert_equal(node_3_info[0]["transport_protocol_type"], "v1")
assert_equal(len(node_2_info[0]["session_id"]), 0)
assert_equal(len(node_3_info[0]["session_id"]), 0)
# V1 nodes can sync with V2 nodes
self.disconnect_nodes(0, 1)
self.disconnect_nodes(2, 3)
with self.nodes[2].assert_debug_log(expected_msgs=[],
unexpected_msgs=[sending_handshake, downgrading_to_v1]):
self.connect_nodes(2, 1, peer_advertises_v2=False) # cannot enable v2 on v1 node
self.sync_all(self.nodes[1:3])
assert_equal(self.nodes[1].getblockcount(), 8)
assert self.nodes[0].getbestblockhash() != self.nodes[1].getbestblockhash()
# verify there is a v1 connection between node 1 and 2
node_1_info = self.nodes[1].getpeerinfo()
node_2_info = self.nodes[2].getpeerinfo()
assert_equal(len(node_1_info), 1)
assert_equal(len(node_2_info), 1)
assert_equal(node_1_info[0]["transport_protocol_type"], "v1")
assert_equal(node_2_info[0]["transport_protocol_type"], "v1")
assert_equal(len(node_1_info[0]["session_id"]), 0)
assert_equal(len(node_2_info[0]["session_id"]), 0)
# V2 nodes can sync with V1 nodes
self.disconnect_nodes(1, 2)
with self.nodes[0].assert_debug_log(expected_msgs=[],
unexpected_msgs=[sending_handshake, downgrading_to_v1]):
self.connect_nodes(0, 3, peer_advertises_v2=False)
self.sync_all([self.nodes[0], self.nodes[3]])
assert_equal(self.nodes[0].getblockcount(), 8)
# verify there is a v1 connection between node 0 and 3
node_0_info = self.nodes[0].getpeerinfo()
node_3_info = self.nodes[3].getpeerinfo()
assert_equal(len(node_0_info), 1)
assert_equal(len(node_3_info), 1)
assert_equal(node_0_info[0]["transport_protocol_type"], "v1")
assert_equal(node_3_info[0]["transport_protocol_type"], "v1")
assert_equal(len(node_0_info[0]["session_id"]), 0)
assert_equal(len(node_3_info[0]["session_id"]), 0)
# V2 node mines another block and everyone gets it
self.connect_nodes(0, 1, peer_advertises_v2=True)
self.connect_nodes(1, 2, peer_advertises_v2=False)
self.generate(self.nodes[1], 1, sync_fun=lambda: self.sync_all(self.nodes[0:4]))
assert_equal(self.nodes[0].getblockcount(), 9) # sync_all() verifies tip hashes match
# V1 node mines another block and everyone gets it
self.generate(self.nodes[3], 2, sync_fun=lambda: self.sync_all(self.nodes[0:4]))
assert_equal(self.nodes[2].getblockcount(), 11) # sync_all() verifies tip hashes match
assert_equal(self.nodes[4].getblockcount(), 0)
# Peer 4 is v1 p2p, but is falsely advertised as v2.
with self.nodes[1].assert_debug_log(expected_msgs=[sending_handshake, downgrading_to_v1]):
self.connect_nodes(1, 4, peer_advertises_v2=True)
self.sync_all()
assert_equal(self.nodes[4].getblockcount(), 11)
if __name__ == '__main__':
V2TransportTest().main()

View File

@ -142,11 +142,13 @@ class NetTest(BitcoinTestFramework):
"relaytxes": False, "relaytxes": False,
"services": "0000000000000000", "services": "0000000000000000",
"servicesnames": [], "servicesnames": [],
"session_id": "",
"startingheight": -1, "startingheight": -1,
"subver": "", "subver": "",
"synced_blocks": -1, "synced_blocks": -1,
"synced_headers": -1, "synced_headers": -1,
"timeoffset": 0, "timeoffset": 0,
"transport_protocol_type": "v1",
"version": 0, "version": 0,
}, },
) )

View File

@ -52,6 +52,7 @@ NODE_BLOOM = (1 << 2)
NODE_WITNESS = (1 << 3) NODE_WITNESS = (1 << 3)
NODE_COMPACT_FILTERS = (1 << 6) NODE_COMPACT_FILTERS = (1 << 6)
NODE_NETWORK_LIMITED = (1 << 10) NODE_NETWORK_LIMITED = (1 << 10)
NODE_P2P_V2 = (1 << 11)
MSG_TX = 1 MSG_TX = 1
MSG_BLOCK = 2 MSG_BLOCK = 2

View File

@ -189,6 +189,8 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
parser.add_argument("--randomseed", type=int, parser.add_argument("--randomseed", type=int,
help="set a random seed for deterministically reproducing a previous test run") help="set a random seed for deterministically reproducing a previous test run")
parser.add_argument("--timeout-factor", dest="timeout_factor", type=float, help="adjust test timeouts by a factor. Setting it to 0 disables all timeouts") parser.add_argument("--timeout-factor", dest="timeout_factor", type=float, help="adjust test timeouts by a factor. Setting it to 0 disables all timeouts")
parser.add_argument("--v2transport", dest="v2transport", default=False, action="store_true",
help="use BIP324 v2 connections between all nodes by default")
self.add_options(parser) self.add_options(parser)
# Running TestShell in a Jupyter notebook causes an additional -f argument # Running TestShell in a Jupyter notebook causes an additional -f argument
@ -504,6 +506,9 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
assert_equal(len(binary), num_nodes) assert_equal(len(binary), num_nodes)
assert_equal(len(binary_cli), num_nodes) assert_equal(len(binary_cli), num_nodes)
for i in range(num_nodes): for i in range(num_nodes):
args = list(extra_args[i])
if self.options.v2transport and ("-v2transport=0" not in args):
args.append("-v2transport=1")
test_node_i = TestNode( test_node_i = TestNode(
i, i,
get_datadir_path(self.options.tmpdir, i), get_datadir_path(self.options.tmpdir, i),
@ -517,7 +522,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
coverage_dir=self.options.coveragedir, coverage_dir=self.options.coveragedir,
cwd=self.options.tmpdir, cwd=self.options.tmpdir,
extra_conf=extra_confs[i], extra_conf=extra_confs[i],
extra_args=extra_args[i], extra_args=args,
use_cli=self.options.usecli, use_cli=self.options.usecli,
start_perf=self.options.perf, start_perf=self.options.perf,
use_valgrind=self.options.valgrind, use_valgrind=self.options.valgrind,
@ -581,13 +586,23 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
def wait_for_node_exit(self, i, timeout): def wait_for_node_exit(self, i, timeout):
self.nodes[i].process.wait(timeout) self.nodes[i].process.wait(timeout)
def connect_nodes(self, a, b): def connect_nodes(self, a, b, *, peer_advertises_v2=None):
from_connection = self.nodes[a] from_connection = self.nodes[a]
to_connection = self.nodes[b] to_connection = self.nodes[b]
from_num_peers = 1 + len(from_connection.getpeerinfo()) from_num_peers = 1 + len(from_connection.getpeerinfo())
to_num_peers = 1 + len(to_connection.getpeerinfo()) to_num_peers = 1 + len(to_connection.getpeerinfo())
ip_port = "127.0.0.1:" + str(p2p_port(b)) ip_port = "127.0.0.1:" + str(p2p_port(b))
if peer_advertises_v2 is None:
peer_advertises_v2 = self.options.v2transport
if peer_advertises_v2:
from_connection.addnode(node=ip_port, command="onetry", v2transport=True)
else:
# skip the optional third argument (default false) for
# compatibility with older clients
from_connection.addnode(ip_port, "onetry") from_connection.addnode(ip_port, "onetry")
# poll until version handshake complete to avoid race conditions # poll until version handshake complete to avoid race conditions
# with transaction relaying # with transaction relaying
# See comments in net_processing: # See comments in net_processing:
@ -595,12 +610,12 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
# * Must have a verack message before anything else # * Must have a verack message before anything else
self.wait_until(lambda: sum(peer['version'] != 0 for peer in from_connection.getpeerinfo()) == from_num_peers) self.wait_until(lambda: sum(peer['version'] != 0 for peer in from_connection.getpeerinfo()) == from_num_peers)
self.wait_until(lambda: sum(peer['version'] != 0 for peer in to_connection.getpeerinfo()) == to_num_peers) self.wait_until(lambda: sum(peer['version'] != 0 for peer in to_connection.getpeerinfo()) == to_num_peers)
self.wait_until(lambda: sum(peer['bytesrecv_per_msg'].pop('verack', 0) == 24 for peer in from_connection.getpeerinfo()) == from_num_peers) self.wait_until(lambda: sum(peer['bytesrecv_per_msg'].pop('verack', 0) >= 21 for peer in from_connection.getpeerinfo()) == from_num_peers)
self.wait_until(lambda: sum(peer['bytesrecv_per_msg'].pop('verack', 0) == 24 for peer in to_connection.getpeerinfo()) == to_num_peers) self.wait_until(lambda: sum(peer['bytesrecv_per_msg'].pop('verack', 0) >= 21 for peer in to_connection.getpeerinfo()) == to_num_peers)
# The message bytes are counted before processing the message, so make # The message bytes are counted before processing the message, so make
# sure it was fully processed by waiting for a ping. # sure it was fully processed by waiting for a ping.
self.wait_until(lambda: sum(peer["bytesrecv_per_msg"].pop("pong", 0) >= 32 for peer in from_connection.getpeerinfo()) == from_num_peers) self.wait_until(lambda: sum(peer["bytesrecv_per_msg"].pop("pong", 0) >= 29 for peer in from_connection.getpeerinfo()) == from_num_peers)
self.wait_until(lambda: sum(peer["bytesrecv_per_msg"].pop("pong", 0) >= 32 for peer in to_connection.getpeerinfo()) == to_num_peers) self.wait_until(lambda: sum(peer["bytesrecv_per_msg"].pop("pong", 0) >= 29 for peer in to_connection.getpeerinfo()) == to_num_peers)
def disconnect_nodes(self, a, b): def disconnect_nodes(self, a, b):
def disconnect_nodes_helper(node_a, node_b): def disconnect_nodes_helper(node_a, node_b):

View File

@ -117,6 +117,7 @@ BASE_SCRIPTS = [
'wallet_backup.py --descriptors', 'wallet_backup.py --descriptors',
'feature_segwit.py --legacy-wallet', 'feature_segwit.py --legacy-wallet',
'feature_segwit.py --descriptors', 'feature_segwit.py --descriptors',
'feature_segwit.py --descriptors --v2transport',
'p2p_tx_download.py', 'p2p_tx_download.py',
'wallet_avoidreuse.py --legacy-wallet', 'wallet_avoidreuse.py --legacy-wallet',
'wallet_avoidreuse.py --descriptors', 'wallet_avoidreuse.py --descriptors',
@ -195,6 +196,7 @@ BASE_SCRIPTS = [
'wallet_avoid_mixing_output_types.py --descriptors', 'wallet_avoid_mixing_output_types.py --descriptors',
'mempool_reorg.py', 'mempool_reorg.py',
'p2p_block_sync.py', 'p2p_block_sync.py',
'p2p_block_sync.py --v2transport',
'wallet_createwallet.py --legacy-wallet', 'wallet_createwallet.py --legacy-wallet',
'wallet_createwallet.py --usecli', 'wallet_createwallet.py --usecli',
'wallet_createwallet.py --descriptors', 'wallet_createwallet.py --descriptors',
@ -221,10 +223,13 @@ BASE_SCRIPTS = [
'wallet_transactiontime_rescan.py --legacy-wallet', 'wallet_transactiontime_rescan.py --legacy-wallet',
'p2p_addrv2_relay.py', 'p2p_addrv2_relay.py',
'p2p_compactblocks_hb.py', 'p2p_compactblocks_hb.py',
'p2p_compactblocks_hb.py --v2transport',
'p2p_disconnect_ban.py', 'p2p_disconnect_ban.py',
'p2p_disconnect_ban.py --v2transport',
'feature_posix_fs_permissions.py', 'feature_posix_fs_permissions.py',
'rpc_decodescript.py', 'rpc_decodescript.py',
'rpc_blockchain.py', 'rpc_blockchain.py',
'rpc_blockchain.py --v2transport',
'rpc_deprecated.py', 'rpc_deprecated.py',
'wallet_disable.py', 'wallet_disable.py',
'wallet_change_address.py --legacy-wallet', 'wallet_change_address.py --legacy-wallet',
@ -245,7 +250,10 @@ BASE_SCRIPTS = [
'mining_prioritisetransaction.py', 'mining_prioritisetransaction.py',
'p2p_invalid_locator.py', 'p2p_invalid_locator.py',
'p2p_invalid_block.py', 'p2p_invalid_block.py',
'p2p_invalid_block.py --v2transport',
'p2p_invalid_tx.py', 'p2p_invalid_tx.py',
'p2p_invalid_tx.py --v2transport',
'p2p_v2_transport.py',
'example_test.py', 'example_test.py',
'wallet_txn_doublespend.py --legacy-wallet', 'wallet_txn_doublespend.py --legacy-wallet',
'wallet_multisig_descriptor_psbt.py --descriptors', 'wallet_multisig_descriptor_psbt.py --descriptors',
@ -267,9 +275,12 @@ BASE_SCRIPTS = [
'wallet_importprunedfunds.py --legacy-wallet', 'wallet_importprunedfunds.py --legacy-wallet',
'wallet_importprunedfunds.py --descriptors', 'wallet_importprunedfunds.py --descriptors',
'p2p_leak_tx.py', 'p2p_leak_tx.py',
'p2p_leak_tx.py --v2transport',
'p2p_eviction.py', 'p2p_eviction.py',
'p2p_ibd_stalling.py', 'p2p_ibd_stalling.py',
'p2p_ibd_stalling.py --v2transport',
'p2p_net_deadlock.py', 'p2p_net_deadlock.py',
'p2p_net_deadlock.py --v2transport',
'wallet_signmessagewithaddress.py', 'wallet_signmessagewithaddress.py',
'rpc_signmessagewithprivkey.py', 'rpc_signmessagewithprivkey.py',
'rpc_generate.py', 'rpc_generate.py',