mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-04-07 11:39:01 +02:00
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:
commit
6f882e6f86
@ -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 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 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 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)
|
||||
|
@ -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("-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("-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("-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);
|
||||
@ -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.
|
||||
if (args.GetBoolArg("-peerblockfilters", DEFAULT_PEERBLOCKFILTERS)) {
|
||||
if (g_enabled_filter_types.count(BlockFilterType::BASIC) != 1) {
|
||||
|
195
src/net.cpp
195
src/net.cpp
@ -439,7 +439,7 @@ static CAddress GetBindAddress(const Sock& sock)
|
||||
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);
|
||||
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(),
|
||||
Ticks<HoursDouble>(pszDest ? 0h : Now<NodeSeconds>() - addrConnect.nTime));
|
||||
|
||||
@ -580,6 +581,7 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo
|
||||
CNodeOptions{
|
||||
.i2p_sam_session = std::move(i2p_transient_session),
|
||||
.recv_flood_size = nReceiveFloodSize,
|
||||
.use_v2transport = use_v2transport,
|
||||
});
|
||||
pnode->AddRef();
|
||||
|
||||
@ -665,6 +667,9 @@ void CNode::CopyStats(CNodeStats& stats)
|
||||
LOCK(cs_vRecv);
|
||||
X(mapRecvBytesPerMsgType);
|
||||
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);
|
||||
|
||||
@ -732,6 +737,11 @@ V1Transport::V1Transport(const NodeId node_id, int nTypeIn, int nVersionIn) noex
|
||||
Reset();
|
||||
}
|
||||
|
||||
Transport::Info V1Transport::GetInfo() const noexcept
|
||||
{
|
||||
return {.transport_type = TransportProtocolType::V1, .session_id = {}};
|
||||
}
|
||||
|
||||
int V1Transport::readHeader(Span<const uint8_t> msg_bytes)
|
||||
{
|
||||
AssertLockHeld(m_recv_mutex);
|
||||
@ -1542,8 +1552,15 @@ void V2Transport::MarkBytesSent(size_t bytes_sent) noexcept
|
||||
LOCK(m_send_mutex);
|
||||
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;
|
||||
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.
|
||||
if (m_send_pos == m_send_buffer.size()) {
|
||||
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
|
||||
{
|
||||
AssertLockNotHeld(m_send_mutex);
|
||||
@ -1560,6 +1594,27 @@ size_t V2Transport::GetSendMemoryUsage() const noexcept
|
||||
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
|
||||
{
|
||||
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.
|
||||
node.m_transport->MarkBytesSent(nBytes);
|
||||
// Update statistics per message type.
|
||||
node.AccountForSentBytes(msg_type, nBytes);
|
||||
if (!msg_type.empty()) { // don't report v2 handshake bytes for now
|
||||
node.AccountForSentBytes(msg_type, nBytes);
|
||||
}
|
||||
nSentSize += nBytes;
|
||||
if ((size_t)nBytes != data.size()) {
|
||||
// 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();
|
||||
// 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,
|
||||
std::move(sock),
|
||||
addr,
|
||||
@ -1805,6 +1866,7 @@ void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr<Sock>&& sock,
|
||||
.permission_flags = permission_flags,
|
||||
.prefer_evict = discouraged,
|
||||
.recv_flood_size = nReceiveFloodSize,
|
||||
.use_v2transport = use_v2transport,
|
||||
});
|
||||
pnode->AddRef();
|
||||
m_msgproc->InitializeNode(*pnode, nodeServices);
|
||||
@ -1853,12 +1915,19 @@ bool CConnman::AddConnection(const std::string& address, ConnectionType conn_typ
|
||||
CSemaphoreGrant grant(*semOutbound, true);
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@ -1881,6 +1950,19 @@ void CConnman::DisconnectNodes()
|
||||
// remove from m_nodes
|
||||
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)
|
||||
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()
|
||||
@ -2285,9 +2372,9 @@ void CConnman::ProcessAddrFetch()
|
||||
m_addr_fetches.pop_front();
|
||||
}
|
||||
CAddress addr;
|
||||
CSemaphoreGrant grant(*semOutbound, true);
|
||||
CSemaphoreGrant grant(*semOutbound, /*fTry=*/true);
|
||||
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)
|
||||
{
|
||||
AssertLockNotHeld(m_unused_i2p_sessions_mutex);
|
||||
AssertLockNotHeld(m_reconnections_mutex);
|
||||
FastRandomContext rng;
|
||||
// Connect to specific addresses
|
||||
if (!connect.empty())
|
||||
@ -2389,7 +2477,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
|
||||
for (const std::string& strAddr : connect)
|
||||
{
|
||||
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++)
|
||||
{
|
||||
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)))
|
||||
return;
|
||||
|
||||
PerformReconnections();
|
||||
|
||||
CSemaphoreGrant grant(*semOutbound);
|
||||
if (interruptNet)
|
||||
return;
|
||||
@ -2443,7 +2533,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
|
||||
// Perform cheap checks before locking a mutex.
|
||||
else if (!dnsseed && !use_seednodes) {
|
||||
LOCK(m_added_nodes_mutex);
|
||||
if (m_added_nodes.empty()) {
|
||||
if (m_added_node_params.empty()) {
|
||||
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");
|
||||
}
|
||||
@ -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
|
||||
// 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)};
|
||||
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::list<std::string> lAddresses(0);
|
||||
std::list<AddedNodeParams> lAddresses(0);
|
||||
{
|
||||
LOCK(m_added_nodes_mutex);
|
||||
ret.reserve(m_added_nodes.size());
|
||||
std::copy(m_added_nodes.cbegin(), m_added_nodes.cend(), std::back_inserter(lAddresses));
|
||||
ret.reserve(m_added_node_params.size());
|
||||
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) {
|
||||
CService service(LookupNumeric(strAddNode, GetDefaultPort(strAddNode)));
|
||||
AddedNodeInfo addedNode{strAddNode, CService(), false, false};
|
||||
for (const auto& addr : lAddresses) {
|
||||
CService service(LookupNumeric(addr.m_added_node, GetDefaultPort(addr.m_added_node)));
|
||||
AddedNodeInfo addedNode{addr, CService(), false, false};
|
||||
if (service.IsValid()) {
|
||||
// strAddNode is an IP:port
|
||||
auto it = mapConnected.find(service);
|
||||
@ -2751,7 +2843,7 @@ std::vector<AddedNodeInfo> CConnman::GetAddedNodeInfo() const
|
||||
}
|
||||
} else {
|
||||
// strAddNode is a name
|
||||
auto it = mapConnectedByName.find(strAddNode);
|
||||
auto it = mapConnectedByName.find(addr.m_added_node);
|
||||
if (it != mapConnectedByName.end()) {
|
||||
addedNode.resolvedAddress = it->second.second;
|
||||
addedNode.fConnected = true;
|
||||
@ -2767,6 +2859,7 @@ std::vector<AddedNodeInfo> CConnman::GetAddedNodeInfo() const
|
||||
void CConnman::ThreadOpenAddedConnections()
|
||||
{
|
||||
AssertLockNotHeld(m_unused_i2p_sessions_mutex);
|
||||
AssertLockNotHeld(m_reconnections_mutex);
|
||||
while (true)
|
||||
{
|
||||
CSemaphoreGrant grant(*semAddnode);
|
||||
@ -2774,26 +2867,28 @@ void CConnman::ThreadOpenAddedConnections()
|
||||
bool tried = false;
|
||||
for (const AddedNodeInfo& info : vInfo) {
|
||||
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
|
||||
// the addednodeinfo state might change.
|
||||
break;
|
||||
}
|
||||
tried = true;
|
||||
CAddress addr(CService(), NODE_NONE);
|
||||
OpenNetworkConnection(addr, false, &grant, info.strAddedNode.c_str(), ConnectionType::MANUAL);
|
||||
if (!interruptNet.sleep_for(std::chrono::milliseconds(500)))
|
||||
return;
|
||||
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))) return;
|
||||
grant = CSemaphoreGrant(*semAddnode, /*fTry=*/true);
|
||||
}
|
||||
}
|
||||
// Retry every 60 seconds if a connection was attempted, otherwise two seconds
|
||||
if (!interruptNet.sleep_for(std::chrono::seconds(tried ? 60 : 2)))
|
||||
return;
|
||||
// See if any reconnections are desired.
|
||||
PerformReconnections();
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
assert(conn_type != ConnectionType::INBOUND);
|
||||
@ -2815,12 +2910,11 @@ void CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFai
|
||||
} else if (FindNode(std::string(pszDest)))
|
||||
return;
|
||||
|
||||
CNode* pnode = ConnectNode(addrConnect, pszDest, fCountFailure, conn_type);
|
||||
CNode* pnode = ConnectNode(addrConnect, pszDest, fCountFailure, conn_type, use_v2transport);
|
||||
|
||||
if (!pnode)
|
||||
return;
|
||||
if (grantOutbound)
|
||||
grantOutbound->MoveTo(pnode->grantOutbound);
|
||||
pnode->grantOutbound = std::move(grant_outbound);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
bool CConnman::AddNode(const std::string& strNode)
|
||||
bool CConnman::AddNode(const AddedNodeParams& add)
|
||||
{
|
||||
LOCK(m_added_nodes_mutex);
|
||||
for (const std::string& it : m_added_nodes) {
|
||||
if (strNode == it) return false;
|
||||
for (const auto& it : m_added_node_params) {
|
||||
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;
|
||||
}
|
||||
|
||||
bool CConnman::RemoveAddedNode(const std::string& strNode)
|
||||
{
|
||||
LOCK(m_added_nodes_mutex);
|
||||
for(std::vector<std::string>::iterator it = m_added_nodes.begin(); it != m_added_nodes.end(); ++it) {
|
||||
if (strNode == *it) {
|
||||
m_added_nodes.erase(it);
|
||||
for (auto it = m_added_node_params.begin(); it != m_added_node_params.end(); ++it) {
|
||||
if (strNode == it->m_added_node) {
|
||||
m_added_node_params.erase(it);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -3577,6 +3671,15 @@ ServiceFlags CConnman::GetLocalServices() const
|
||||
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,
|
||||
std::shared_ptr<Sock> sock,
|
||||
const CAddress& addrIn,
|
||||
@ -3587,13 +3690,14 @@ CNode::CNode(NodeId idIn,
|
||||
ConnectionType conn_type_in,
|
||||
bool inbound_onion,
|
||||
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_sock{sock},
|
||||
m_connected{GetTime<std::chrono::seconds>()},
|
||||
addr{addrIn},
|
||||
addrBind{addrBindIn},
|
||||
m_addr_name{addrNameIn.empty() ? addr.ToStringAddrPort() : addrNameIn},
|
||||
m_dest(addrNameIn),
|
||||
m_inbound_onion{inbound_onion},
|
||||
m_prefer_evict{node_opts.prefer_evict},
|
||||
nKeyedNetGroup{nKeyedNetGroupIn},
|
||||
@ -3724,6 +3828,33 @@ uint64_t CConnman::CalculateKeyedNetGroup(const CAddress& address) const
|
||||
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.
|
||||
static void CaptureMessageToFile(const CAddress& addr,
|
||||
const std::string& msg_type,
|
||||
|
90
src/net.h
90
src/net.h
@ -94,11 +94,17 @@ static constexpr bool DEFAULT_FIXEDSEEDS{true};
|
||||
static const size_t DEFAULT_MAXRECEIVEBUFFER = 5 * 1000;
|
||||
static const size_t DEFAULT_MAXSENDBUFFER = 1 * 1000;
|
||||
|
||||
static constexpr bool DEFAULT_V2_TRANSPORT{false};
|
||||
|
||||
typedef int64_t NodeId;
|
||||
|
||||
struct AddedNodeInfo
|
||||
{
|
||||
std::string strAddedNode;
|
||||
struct AddedNodeParams {
|
||||
std::string m_added_node;
|
||||
bool m_use_v2transport;
|
||||
};
|
||||
|
||||
struct AddedNodeInfo {
|
||||
AddedNodeParams m_params;
|
||||
CService resolvedAddress;
|
||||
bool fConnected;
|
||||
bool fInbound;
|
||||
@ -226,6 +232,10 @@ public:
|
||||
Network m_network;
|
||||
uint32_t m_mapped_as;
|
||||
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:
|
||||
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
|
||||
// agnostic CNetMessage (message type & payload) objects.
|
||||
|
||||
@ -355,6 +374,11 @@ public:
|
||||
|
||||
/** Return the memory usage of this transport attributable to buffered data to send. */
|
||||
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
|
||||
@ -415,6 +439,8 @@ public:
|
||||
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)
|
||||
{
|
||||
AssertLockNotHeld(m_recv_mutex);
|
||||
@ -434,6 +460,7 @@ public:
|
||||
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);
|
||||
size_t GetSendMemoryUsage() const noexcept override EXCLUSIVE_LOCKS_REQUIRED(!m_send_mutex);
|
||||
bool ShouldReconnectV1() const noexcept override { return false; }
|
||||
};
|
||||
|
||||
class V2Transport final : public Transport
|
||||
@ -602,6 +629,8 @@ private:
|
||||
std::string m_send_type GUARDED_BY(m_send_mutex);
|
||||
/** Current sender state. */
|
||||
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. */
|
||||
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);
|
||||
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);
|
||||
|
||||
// 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
|
||||
@ -655,6 +688,7 @@ struct CNodeOptions
|
||||
std::unique_ptr<i2p::sam::Session> i2p_sam_session = nullptr;
|
||||
bool prefer_evict = false;
|
||||
size_t recv_flood_size{DEFAULT_MAXRECEIVEBUFFER * 1000};
|
||||
bool use_v2transport = false;
|
||||
};
|
||||
|
||||
/** Information about a peer */
|
||||
@ -699,6 +733,8 @@ public:
|
||||
// Bind address of our side of the connection
|
||||
const CAddress addrBind;
|
||||
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.
|
||||
const bool m_inbound_onion;
|
||||
std::atomic<int> nVersion{0};
|
||||
@ -1072,7 +1108,11 @@ public:
|
||||
vWhitelistedRange = connOptions.vWhitelistedRange;
|
||||
{
|
||||
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;
|
||||
}
|
||||
@ -1096,7 +1136,7 @@ public:
|
||||
bool GetNetworkActive() const { return fNetworkActive; };
|
||||
bool GetUseAddrmanOutgoing() const { return m_use_addrman_outgoing; };
|
||||
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);
|
||||
|
||||
// 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.
|
||||
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);
|
||||
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 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 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 ThreadI2PAcceptIncoming();
|
||||
void AcceptConnection(const ListenSocket& hListenSocket);
|
||||
@ -1263,7 +1303,7 @@ private:
|
||||
const CAddress& addr_bind,
|
||||
const CAddress& addr);
|
||||
|
||||
void DisconnectNodes();
|
||||
void DisconnectNodes() EXCLUSIVE_LOCKS_REQUIRED(!m_reconnections_mutex, !m_nodes_mutex);
|
||||
void NotifyNumConnectionsChanged();
|
||||
/** Return true if the peer is inactive and should be disconnected. */
|
||||
bool InactivityCheck(const CNode& node) const;
|
||||
@ -1295,7 +1335,7 @@ private:
|
||||
*/
|
||||
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);
|
||||
|
||||
uint64_t CalculateKeyedNetGroup(const CAddress& ad) const;
|
||||
@ -1312,7 +1352,7 @@ private:
|
||||
bool AlreadyConnectedToAddress(const CAddress& addr);
|
||||
|
||||
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 DeleteNode(CNode* pnode);
|
||||
@ -1384,7 +1424,10 @@ private:
|
||||
const NetGroupManager& m_netgroupman;
|
||||
std::deque<std::string> m_addr_fetches GUARDED_BY(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;
|
||||
std::vector<CNode*> m_nodes GUARDED_BY(m_nodes_mutex);
|
||||
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);
|
||||
|
||||
/**
|
||||
* 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
|
||||
* unexpectedly use too much memory.
|
||||
|
@ -3585,13 +3585,16 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
|
||||
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)};
|
||||
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.GetId(), (fLogIPs ? strprintf(", peeraddr=%s", pfrom.addr.ToStringAddrPort()) : ""),
|
||||
(mapped_as ? strprintf(", mapped_as=%d", mapped_as) : ""),
|
||||
pfrom.ConnectionTypeAsString());
|
||||
(mapped_as ? strprintf(", mapped_as=%d", mapped_as) : ""));
|
||||
}
|
||||
|
||||
if (pfrom.GetCommonVersion() >= SHORT_IDS_BLOCKS_VERSION) {
|
||||
|
@ -24,3 +24,17 @@ std::string ConnectionTypeAsString(ConnectionType conn_type)
|
||||
|
||||
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);
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
#define BITCOIN_NODE_CONNECTION_TYPES_H
|
||||
|
||||
#include <string>
|
||||
#include <stdint.h>
|
||||
|
||||
/** Different types of connections to a peer. This enum encapsulates 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 */
|
||||
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
|
||||
|
@ -199,6 +199,7 @@ static std::string serviceFlagToStr(size_t bit)
|
||||
case NODE_WITNESS: return "WITNESS";
|
||||
case NODE_COMPACT_FILTERS: return "COMPACT_FILTERS";
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -291,6 +291,9 @@ enum ServiceFlags : uint64_t {
|
||||
// See BIP159 for details on how this is implemented.
|
||||
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
|
||||
// isn't getting used, or one not being used much, and notify the
|
||||
// bitcoin-development mailing list. Remember that service bits are just
|
||||
|
@ -301,6 +301,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
||||
{ "addpeeraddress", 2, "tried"},
|
||||
{ "sendmsgtopeer", 0, "peer_id" },
|
||||
{ "stop", 0, "wait" },
|
||||
{ "addnode", 2, "v2transport" },
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
|
@ -45,6 +45,12 @@ const std::vector<std::string> CONNECTION_TYPE_DOC{
|
||||
"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()
|
||||
{
|
||||
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"
|
||||
"Please note this output is unlikely to be stable in upcoming releases as we iterate to\n"
|
||||
"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("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);
|
||||
}
|
||||
@ -289,11 +299,12 @@ static RPCHelpMan addnode()
|
||||
{
|
||||
{"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"},
|
||||
{"v2transport", RPCArg::Type::BOOL, RPCArg::Default{false}, "Attempt to connect using BIP324 v2 transport protocol (ignored for 'remove' command)"},
|
||||
},
|
||||
RPCResult{RPCResult::Type::NONE, "", ""},
|
||||
RPCExamples{
|
||||
HelpExampleCli("addnode", "\"192.168.0.6:8333\" \"onetry\"")
|
||||
+ HelpExampleRpc("addnode", "\"192.168.0.6:8333\", \"onetry\"")
|
||||
HelpExampleCli("addnode", "\"192.168.0.6:8333\" \"onetry\" true")
|
||||
+ HelpExampleRpc("addnode", "\"192.168.0.6:8333\", \"onetry\" true")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
@ -307,17 +318,22 @@ static RPCHelpMan addnode()
|
||||
CConnman& connman = EnsureConnman(node);
|
||||
|
||||
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")
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
@ -475,7 +491,7 @@ static RPCHelpMan getaddednodeinfo()
|
||||
if (!request.params[0].isNull()) {
|
||||
bool found = false;
|
||||
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);
|
||||
found = true;
|
||||
break;
|
||||
@ -490,7 +506,7 @@ static RPCHelpMan getaddednodeinfo()
|
||||
|
||||
for (const AddedNodeInfo& info : vInfo) {
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
obj.pushKV("addednode", info.strAddedNode);
|
||||
obj.pushKV("addednode", info.m_params.m_added_node);
|
||||
obj.pushKV("connected", info.fConnected);
|
||||
UniValue addresses(UniValue::VARR);
|
||||
if (info.fConnected) {
|
||||
|
@ -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;);
|
||||
|
||||
// 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, uint64_t, CHECK_NONFATAL(maybe_arg)->getInt<uint64_t>(););
|
||||
TMPL_INST(CheckRequiredOrDefault, const std::string&, CHECK_NONFATAL(maybe_arg)->get_str(););
|
||||
|
75
src/sync.h
75
src/sync.h
@ -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.
|
||||
#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
|
||||
{
|
||||
private:
|
||||
@ -309,25 +313,33 @@ private:
|
||||
int value;
|
||||
|
||||
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);
|
||||
condition.wait(lock, [&]() { return value >= 1; });
|
||||
value--;
|
||||
}
|
||||
|
||||
bool try_wait()
|
||||
bool try_wait() noexcept
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
if (value < 1)
|
||||
if (value < 1) {
|
||||
return false;
|
||||
}
|
||||
value--;
|
||||
return true;
|
||||
}
|
||||
|
||||
void post()
|
||||
void post() noexcept
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
@ -345,45 +357,64 @@ private:
|
||||
bool fHaveGrant;
|
||||
|
||||
public:
|
||||
void Acquire()
|
||||
void Acquire() noexcept
|
||||
{
|
||||
if (fHaveGrant)
|
||||
if (fHaveGrant) {
|
||||
return;
|
||||
}
|
||||
sem->wait();
|
||||
fHaveGrant = true;
|
||||
}
|
||||
|
||||
void Release()
|
||||
void Release() noexcept
|
||||
{
|
||||
if (!fHaveGrant)
|
||||
if (!fHaveGrant) {
|
||||
return;
|
||||
}
|
||||
sem->post();
|
||||
fHaveGrant = false;
|
||||
}
|
||||
|
||||
bool TryAcquire()
|
||||
bool TryAcquire() noexcept
|
||||
{
|
||||
if (!fHaveGrant && sem->try_wait())
|
||||
if (!fHaveGrant && sem->try_wait()) {
|
||||
fHaveGrant = true;
|
||||
}
|
||||
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();
|
||||
grant.sem = sem;
|
||||
grant.fHaveGrant = fHaveGrant;
|
||||
fHaveGrant = false;
|
||||
sem = other.sem;
|
||||
fHaveGrant = other.fHaveGrant;
|
||||
other.fHaveGrant = false;
|
||||
other.sem = nullptr;
|
||||
}
|
||||
|
||||
CSemaphoreGrant() : sem(nullptr), fHaveGrant(false) {}
|
||||
|
||||
explicit CSemaphoreGrant(CSemaphore& sema, bool fTry = false) : sem(&sema), fHaveGrant(false)
|
||||
CSemaphoreGrant& operator=(CSemaphoreGrant&& other) noexcept
|
||||
{
|
||||
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();
|
||||
else
|
||||
} else {
|
||||
Acquire();
|
||||
}
|
||||
}
|
||||
|
||||
~CSemaphoreGrant()
|
||||
@ -391,7 +422,7 @@ public:
|
||||
Release();
|
||||
}
|
||||
|
||||
operator bool() const
|
||||
explicit operator bool() const noexcept
|
||||
{
|
||||
return fHaveGrant;
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ FUZZ_TARGET(connman, .init = initialize_connman)
|
||||
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>());
|
||||
|
@ -328,6 +328,9 @@ void SimulationTest(Transport& initiator, Transport& responder, R& rng, FuzzedDa
|
||||
// Make sure all expected messages were received.
|
||||
assert(expected[0].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
|
||||
|
@ -1321,6 +1321,14 @@ public:
|
||||
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. */
|
||||
void Damage()
|
||||
{
|
||||
@ -1346,6 +1354,7 @@ BOOST_AUTO_TEST_CASE(v2transport_test)
|
||||
BOOST_REQUIRE(ret && ret->empty());
|
||||
tester.ReceiveGarbage();
|
||||
tester.ReceiveVersion();
|
||||
tester.CompareSessionIDs();
|
||||
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));
|
||||
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());
|
||||
tester.ReceiveGarbage();
|
||||
tester.ReceiveVersion();
|
||||
tester.CompareSessionIDs();
|
||||
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));
|
||||
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());
|
||||
tester.ReceiveGarbage();
|
||||
tester.ReceiveVersion();
|
||||
tester.CompareSessionIDs();
|
||||
for (unsigned d = 0; d < num_decoys_1; ++d) {
|
||||
auto decoy_data = g_insecure_rand_ctx.randbytes<uint8_t>(InsecureRandRange(1000));
|
||||
tester.SendPacket(/*content=*/decoy_data, /*aad=*/{}, /*ignore=*/true);
|
||||
@ -1516,6 +1527,7 @@ BOOST_AUTO_TEST_CASE(v2transport_test)
|
||||
BOOST_REQUIRE(ret && ret->empty());
|
||||
tester.ReceiveGarbage();
|
||||
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_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
|
||||
|
@ -65,6 +65,7 @@ constexpr ServiceFlags ALL_SERVICE_FLAGS[]{
|
||||
NODE_WITNESS,
|
||||
NODE_COMPACT_FILTERS,
|
||||
NODE_NETWORK_LIMITED,
|
||||
NODE_P2P_V2,
|
||||
};
|
||||
|
||||
constexpr NetPermissionFlags ALL_NET_PERMISSION_FLAGS[]{
|
||||
|
127
test/functional/p2p_v2_transport.py
Executable file
127
test/functional/p2p_v2_transport.py
Executable 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()
|
@ -142,11 +142,13 @@ class NetTest(BitcoinTestFramework):
|
||||
"relaytxes": False,
|
||||
"services": "0000000000000000",
|
||||
"servicesnames": [],
|
||||
"session_id": "",
|
||||
"startingheight": -1,
|
||||
"subver": "",
|
||||
"synced_blocks": -1,
|
||||
"synced_headers": -1,
|
||||
"timeoffset": 0,
|
||||
"transport_protocol_type": "v1",
|
||||
"version": 0,
|
||||
},
|
||||
)
|
||||
|
@ -52,6 +52,7 @@ NODE_BLOOM = (1 << 2)
|
||||
NODE_WITNESS = (1 << 3)
|
||||
NODE_COMPACT_FILTERS = (1 << 6)
|
||||
NODE_NETWORK_LIMITED = (1 << 10)
|
||||
NODE_P2P_V2 = (1 << 11)
|
||||
|
||||
MSG_TX = 1
|
||||
MSG_BLOCK = 2
|
||||
|
@ -189,6 +189,8 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
|
||||
parser.add_argument("--randomseed", type=int,
|
||||
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("--v2transport", dest="v2transport", default=False, action="store_true",
|
||||
help="use BIP324 v2 connections between all nodes by default")
|
||||
|
||||
self.add_options(parser)
|
||||
# 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_cli), 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(
|
||||
i,
|
||||
get_datadir_path(self.options.tmpdir, i),
|
||||
@ -517,7 +522,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
|
||||
coverage_dir=self.options.coveragedir,
|
||||
cwd=self.options.tmpdir,
|
||||
extra_conf=extra_confs[i],
|
||||
extra_args=extra_args[i],
|
||||
extra_args=args,
|
||||
use_cli=self.options.usecli,
|
||||
start_perf=self.options.perf,
|
||||
use_valgrind=self.options.valgrind,
|
||||
@ -581,13 +586,23 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
|
||||
def wait_for_node_exit(self, i, 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]
|
||||
to_connection = self.nodes[b]
|
||||
from_num_peers = 1 + len(from_connection.getpeerinfo())
|
||||
to_num_peers = 1 + len(to_connection.getpeerinfo())
|
||||
ip_port = "127.0.0.1:" + str(p2p_port(b))
|
||||
from_connection.addnode(ip_port, "onetry")
|
||||
|
||||
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")
|
||||
|
||||
# poll until version handshake complete to avoid race conditions
|
||||
# with transaction relaying
|
||||
# See comments in net_processing:
|
||||
@ -595,12 +610,12 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
|
||||
# * 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 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) == 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 from_connection.getpeerinfo()) == from_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
|
||||
# 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) >= 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 from_connection.getpeerinfo()) == from_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_helper(node_a, node_b):
|
||||
|
@ -117,6 +117,7 @@ BASE_SCRIPTS = [
|
||||
'wallet_backup.py --descriptors',
|
||||
'feature_segwit.py --legacy-wallet',
|
||||
'feature_segwit.py --descriptors',
|
||||
'feature_segwit.py --descriptors --v2transport',
|
||||
'p2p_tx_download.py',
|
||||
'wallet_avoidreuse.py --legacy-wallet',
|
||||
'wallet_avoidreuse.py --descriptors',
|
||||
@ -195,6 +196,7 @@ BASE_SCRIPTS = [
|
||||
'wallet_avoid_mixing_output_types.py --descriptors',
|
||||
'mempool_reorg.py',
|
||||
'p2p_block_sync.py',
|
||||
'p2p_block_sync.py --v2transport',
|
||||
'wallet_createwallet.py --legacy-wallet',
|
||||
'wallet_createwallet.py --usecli',
|
||||
'wallet_createwallet.py --descriptors',
|
||||
@ -221,10 +223,13 @@ BASE_SCRIPTS = [
|
||||
'wallet_transactiontime_rescan.py --legacy-wallet',
|
||||
'p2p_addrv2_relay.py',
|
||||
'p2p_compactblocks_hb.py',
|
||||
'p2p_compactblocks_hb.py --v2transport',
|
||||
'p2p_disconnect_ban.py',
|
||||
'p2p_disconnect_ban.py --v2transport',
|
||||
'feature_posix_fs_permissions.py',
|
||||
'rpc_decodescript.py',
|
||||
'rpc_blockchain.py',
|
||||
'rpc_blockchain.py --v2transport',
|
||||
'rpc_deprecated.py',
|
||||
'wallet_disable.py',
|
||||
'wallet_change_address.py --legacy-wallet',
|
||||
@ -245,7 +250,10 @@ BASE_SCRIPTS = [
|
||||
'mining_prioritisetransaction.py',
|
||||
'p2p_invalid_locator.py',
|
||||
'p2p_invalid_block.py',
|
||||
'p2p_invalid_block.py --v2transport',
|
||||
'p2p_invalid_tx.py',
|
||||
'p2p_invalid_tx.py --v2transport',
|
||||
'p2p_v2_transport.py',
|
||||
'example_test.py',
|
||||
'wallet_txn_doublespend.py --legacy-wallet',
|
||||
'wallet_multisig_descriptor_psbt.py --descriptors',
|
||||
@ -267,9 +275,12 @@ BASE_SCRIPTS = [
|
||||
'wallet_importprunedfunds.py --legacy-wallet',
|
||||
'wallet_importprunedfunds.py --descriptors',
|
||||
'p2p_leak_tx.py',
|
||||
'p2p_leak_tx.py --v2transport',
|
||||
'p2p_eviction.py',
|
||||
'p2p_ibd_stalling.py',
|
||||
'p2p_ibd_stalling.py --v2transport',
|
||||
'p2p_net_deadlock.py',
|
||||
'p2p_net_deadlock.py --v2transport',
|
||||
'wallet_signmessagewithaddress.py',
|
||||
'rpc_signmessagewithprivkey.py',
|
||||
'rpc_generate.py',
|
||||
|
Loading…
x
Reference in New Issue
Block a user