Skip to content

Commit 3a5e885

Browse files
committed
Add 2 outbound block-relay-only connections
Transaction relay is primarily optimized for balancing redundancy/robustness with bandwidth minimization -- as a result transaction relay leaks information that adversaries can use to infer the network topology. Network topology is better kept private for (at least) two reasons: (a) Knowledge of the network graph can make it easier to find the source IP of a given transaction. (b) Knowledge of the network graph could be used to split a target node or nodes from the honest network (eg by knowing which peers to attack in order to achieve a network split). We can eliminate the risks of (b) by separating block relay from transaction relay; inferring network connectivity from the relay of blocks/block headers is much more expensive for an adversary. After this commit, bitcoind will make 2 additional outbound connections that are only used for block relay. (In the future, we might consider rotating our transaction-relay peers to help limit the effects of (a).)
1 parent b83f51a commit 3a5e885

File tree

5 files changed

+75
-40
lines changed

5 files changed

+75
-40
lines changed

src/init.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1753,7 +1753,8 @@ bool AppInitMain(InitInterfaces& interfaces)
17531753
CConnman::Options connOptions;
17541754
connOptions.nLocalServices = nLocalServices;
17551755
connOptions.nMaxConnections = nMaxConnections;
1756-
connOptions.nMaxOutbound = std::min(MAX_OUTBOUND_CONNECTIONS, connOptions.nMaxConnections);
1756+
connOptions.m_max_outbound_full_relay = std::min(MAX_OUTBOUND_FULL_RELAY_CONNECTIONS, connOptions.nMaxConnections);
1757+
connOptions.m_max_outbound_block_relay = std::min(MAX_BLOCKS_ONLY_CONNECTIONS, connOptions.nMaxConnections-connOptions.m_max_outbound_full_relay);
17571758
connOptions.nMaxAddnode = MAX_ADDNODE_CONNECTIONS;
17581759
connOptions.nMaxFeeler = 1;
17591760
connOptions.nBestHeight = chain_active_height;

src/net.cpp

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ static CAddress GetBindAddress(SOCKET sock)
352352
return addr_bind;
353353
}
354354

355-
CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCountFailure, bool manual_connection)
355+
CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCountFailure, bool manual_connection, bool block_relay_only)
356356
{
357357
if (pszDest == nullptr) {
358358
if (IsLocal(addrConnect))
@@ -442,7 +442,7 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo
442442
NodeId id = GetNewNodeId();
443443
uint64_t nonce = GetDeterministicRandomizer(RANDOMIZER_ID_LOCALHOSTNONCE).Write(id).Finalize();
444444
CAddress addr_bind = GetBindAddress(hSocket);
445-
CNode* pnode = new CNode(id, nLocalServices, GetBestHeight(), hSocket, addrConnect, CalculateKeyedNetGroup(addrConnect), nonce, addr_bind, pszDest ? pszDest : "", false);
445+
CNode* pnode = new CNode(id, nLocalServices, GetBestHeight(), hSocket, addrConnect, CalculateKeyedNetGroup(addrConnect), nonce, addr_bind, pszDest ? pszDest : "", false, block_relay_only);
446446
pnode->AddRef();
447447

448448
return pnode;
@@ -905,7 +905,7 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) {
905905
SOCKET hSocket = accept(hListenSocket.socket, (struct sockaddr*)&sockaddr, &len);
906906
CAddress addr;
907907
int nInbound = 0;
908-
int nMaxInbound = nMaxConnections - (nMaxOutbound + nMaxFeeler);
908+
int nMaxInbound = nMaxConnections - m_max_outbound;
909909

910910
if (hSocket != INVALID_SOCKET) {
911911
if (!addr.SetSockAddr((const struct sockaddr*)&sockaddr)) {
@@ -1666,7 +1666,7 @@ int CConnman::GetExtraOutboundCount()
16661666
}
16671667
}
16681668
}
1669-
return std::max(nOutbound - nMaxOutbound, 0);
1669+
return std::max(nOutbound - m_max_outbound_full_relay - m_max_outbound_block_relay, 0);
16701670
}
16711671

16721672
void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
@@ -1726,7 +1726,8 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
17261726
CAddress addrConnect;
17271727

17281728
// Only connect out to one peer per network group (/16 for IPv4).
1729-
int nOutbound = 0;
1729+
int nOutboundFullRelay = 0;
1730+
int nOutboundBlockRelay = 0;
17301731
std::set<std::vector<unsigned char> > setConnected;
17311732
{
17321733
LOCK(cs_vNodes);
@@ -1738,7 +1739,11 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
17381739
// also have the added issue that they're attacker controlled and could be used
17391740
// to prevent us from connecting to particular hosts if we used them here.
17401741
setConnected.insert(pnode->addr.GetGroup());
1741-
nOutbound++;
1742+
if (pnode->m_tx_relay == nullptr) {
1743+
nOutboundBlockRelay++;
1744+
} else if (!pnode->fFeeler) {
1745+
nOutboundFullRelay++;
1746+
}
17421747
}
17431748
}
17441749
}
@@ -1757,7 +1762,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
17571762
//
17581763
bool fFeeler = false;
17591764

1760-
if (nOutbound >= nMaxOutbound && !GetTryNewOutboundPeer()) {
1765+
if (nOutboundFullRelay >= m_max_outbound_full_relay && nOutboundBlockRelay >= m_max_outbound_block_relay && !GetTryNewOutboundPeer()) {
17611766
int64_t nTime = GetTimeMicros(); // The current time right now (in microseconds).
17621767
if (nTime > nNextFeeler) {
17631768
nNextFeeler = PoissonNextSend(nTime, FEELER_INTERVAL);
@@ -1831,7 +1836,14 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
18311836
LogPrint(BCLog::NET, "Making feeler connection to %s\n", addrConnect.ToString());
18321837
}
18331838

1834-
OpenNetworkConnection(addrConnect, (int)setConnected.size() >= std::min(nMaxConnections - 1, 2), &grant, nullptr, false, fFeeler);
1839+
// Open this connection as block-relay-only if we're already at our
1840+
// full-relay capacity, but not yet at our block-relay peer limit.
1841+
// (It should not be possible for fFeeler to be set if we're not
1842+
// also at our block-relay peer limit, but check against that as
1843+
// well for sanity.)
1844+
bool block_relay_only = nOutboundBlockRelay < m_max_outbound_block_relay && !fFeeler && nOutboundFullRelay >= m_max_outbound_full_relay;
1845+
1846+
OpenNetworkConnection(addrConnect, (int)setConnected.size() >= std::min(nMaxConnections - 1, 2), &grant, nullptr, false, fFeeler, false, block_relay_only);
18351847
}
18361848
}
18371849
}
@@ -1918,7 +1930,7 @@ void CConnman::ThreadOpenAddedConnections()
19181930
}
19191931

19201932
// if successful, this moves the passed grant to the constructed node
1921-
void CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFailure, CSemaphoreGrant *grantOutbound, const char *pszDest, bool fOneShot, bool fFeeler, bool manual_connection)
1933+
void CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFailure, CSemaphoreGrant *grantOutbound, const char *pszDest, bool fOneShot, bool fFeeler, bool manual_connection, bool block_relay_only)
19221934
{
19231935
//
19241936
// Initiate outbound network connection
@@ -1937,7 +1949,7 @@ void CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFai
19371949
} else if (FindNode(std::string(pszDest)))
19381950
return;
19391951

1940-
CNode* pnode = ConnectNode(addrConnect, pszDest, fCountFailure, manual_connection);
1952+
CNode* pnode = ConnectNode(addrConnect, pszDest, fCountFailure, manual_connection, block_relay_only);
19411953

19421954
if (!pnode)
19431955
return;
@@ -2240,7 +2252,7 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions)
22402252

22412253
if (semOutbound == nullptr) {
22422254
// initialize semaphore
2243-
semOutbound = MakeUnique<CSemaphore>(std::min((nMaxOutbound + nMaxFeeler), nMaxConnections));
2255+
semOutbound = MakeUnique<CSemaphore>(std::min(m_max_outbound, nMaxConnections));
22442256
}
22452257
if (semAddnode == nullptr) {
22462258
// initialize semaphore
@@ -2318,7 +2330,7 @@ void CConnman::Interrupt()
23182330
InterruptSocks5(true);
23192331

23202332
if (semOutbound) {
2321-
for (int i=0; i<(nMaxOutbound + nMaxFeeler); i++) {
2333+
for (int i=0; i<m_max_outbound; i++) {
23222334
semOutbound->post();
23232335
}
23242336
}
@@ -2628,7 +2640,7 @@ int CConnman::GetBestHeight() const
26282640

26292641
unsigned int CConnman::GetReceiveFloodSize() const { return nReceiveFloodSize; }
26302642

2631-
CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress& addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const CAddress& addrBindIn, const std::string& addrNameIn, bool fInboundIn)
2643+
CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress& addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const CAddress& addrBindIn, const std::string& addrNameIn, bool fInboundIn, bool block_relay_only)
26322644
: nTimeConnected(GetSystemTimeInSeconds()),
26332645
addr(addrIn),
26342646
addrBind(addrBindIn),
@@ -2643,7 +2655,9 @@ CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn
26432655
hSocket = hSocketIn;
26442656
addrName = addrNameIn == "" ? addr.ToStringIPPort() : addrNameIn;
26452657
hashContinue = uint256();
2646-
m_tx_relay = MakeUnique<TxRelay>();
2658+
if (!block_relay_only) {
2659+
m_tx_relay = MakeUnique<TxRelay>();
2660+
}
26472661

26482662
for (const std::string &msg : getAllNetMessageTypes())
26492663
mapRecvBytesPerMsgCmd[msg] = 0;

src/net.h

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,12 @@ static const unsigned int MAX_ADDR_TO_SEND = 1000;
5656
static const unsigned int MAX_PROTOCOL_MESSAGE_LENGTH = 4 * 1000 * 1000;
5757
/** Maximum length of the user agent string in `version` message */
5858
static const unsigned int MAX_SUBVERSION_LENGTH = 256;
59-
/** Maximum number of automatic outgoing nodes */
60-
static const int MAX_OUTBOUND_CONNECTIONS = 8;
59+
/** Maximum number of automatic outgoing nodes over which we'll relay everything (blocks, tx, addrs, etc) */
60+
static const int MAX_OUTBOUND_FULL_RELAY_CONNECTIONS = 8;
6161
/** Maximum number of addnode outgoing nodes */
6262
static const int MAX_ADDNODE_CONNECTIONS = 8;
63+
/** Maximum number of block-relay-only outgoing connections */
64+
static const int MAX_BLOCKS_ONLY_CONNECTIONS = 2;
6365
/** -listen default */
6466
static const bool DEFAULT_LISTEN = true;
6567
/** -upnp default */
@@ -126,7 +128,8 @@ class CConnman
126128
{
127129
ServiceFlags nLocalServices = NODE_NONE;
128130
int nMaxConnections = 0;
129-
int nMaxOutbound = 0;
131+
int m_max_outbound_full_relay = 0;
132+
int m_max_outbound_block_relay = 0;
130133
int nMaxAddnode = 0;
131134
int nMaxFeeler = 0;
132135
int nBestHeight = 0;
@@ -150,10 +153,12 @@ class CConnman
150153
void Init(const Options& connOptions) {
151154
nLocalServices = connOptions.nLocalServices;
152155
nMaxConnections = connOptions.nMaxConnections;
153-
nMaxOutbound = std::min(connOptions.nMaxOutbound, connOptions.nMaxConnections);
156+
m_max_outbound_full_relay = std::min(connOptions.m_max_outbound_full_relay, connOptions.nMaxConnections);
157+
m_max_outbound_block_relay = connOptions.m_max_outbound_block_relay;
154158
m_use_addrman_outgoing = connOptions.m_use_addrman_outgoing;
155159
nMaxAddnode = connOptions.nMaxAddnode;
156160
nMaxFeeler = connOptions.nMaxFeeler;
161+
m_max_outbound = m_max_outbound_full_relay + m_max_outbound_block_relay + nMaxFeeler;
157162
nBestHeight = connOptions.nBestHeight;
158163
clientInterface = connOptions.uiInterface;
159164
m_banman = connOptions.m_banman;
@@ -192,7 +197,7 @@ class CConnman
192197
bool GetNetworkActive() const { return fNetworkActive; };
193198
bool GetUseAddrmanOutgoing() const { return m_use_addrman_outgoing; };
194199
void SetNetworkActive(bool active);
195-
void OpenNetworkConnection(const CAddress& addrConnect, bool fCountFailure, CSemaphoreGrant *grantOutbound = nullptr, const char *strDest = nullptr, bool fOneShot = false, bool fFeeler = false, bool manual_connection = false);
200+
void OpenNetworkConnection(const CAddress& addrConnect, bool fCountFailure, CSemaphoreGrant *grantOutbound = nullptr, const char *strDest = nullptr, bool fOneShot = false, bool fFeeler = false, bool manual_connection = false, bool block_relay_only = false);
196201
bool CheckIncomingNonce(uint64_t nonce);
197202

198203
bool ForNode(NodeId id, std::function<bool(CNode* pnode)> func);
@@ -248,7 +253,7 @@ class CConnman
248253
void AddNewAddresses(const std::vector<CAddress>& vAddr, const CAddress& addrFrom, int64_t nTimePenalty = 0);
249254
std::vector<CAddress> GetAddresses();
250255

251-
// This allows temporarily exceeding nMaxOutbound, with the goal of finding
256+
// This allows temporarily exceeding m_max_outbound_full_relay, with the goal of finding
252257
// a peer that is better than all our current peers.
253258
void SetTryNewOutboundPeer(bool flag);
254259
bool GetTryNewOutboundPeer();
@@ -350,7 +355,7 @@ class CConnman
350355
CNode* FindNode(const CService& addr);
351356

352357
bool AttemptToEvictConnection();
353-
CNode* ConnectNode(CAddress addrConnect, const char *pszDest, bool fCountFailure, bool manual_connection);
358+
CNode* ConnectNode(CAddress addrConnect, const char *pszDest, bool fCountFailure, bool manual_connection, bool block_relay_only);
354359
void AddWhitelistPermissionFlags(NetPermissionFlags& flags, const CNetAddr &addr) const;
355360

356361
void DeleteNode(CNode* pnode);
@@ -409,9 +414,17 @@ class CConnman
409414
std::unique_ptr<CSemaphore> semOutbound;
410415
std::unique_ptr<CSemaphore> semAddnode;
411416
int nMaxConnections;
412-
int nMaxOutbound;
417+
418+
// How many full-relay (tx, block, addr) outbound peers we want
419+
int m_max_outbound_full_relay;
420+
421+
// How many block-relay only outbound peers we want
422+
// We do not relay tx or addr messages with these peers
423+
int m_max_outbound_block_relay;
424+
413425
int nMaxAddnode;
414426
int nMaxFeeler;
427+
int m_max_outbound;
415428
bool m_use_addrman_outgoing;
416429
std::atomic<int> nBestHeight;
417430
CClientUIInterface* clientInterface;
@@ -437,7 +450,7 @@ class CConnman
437450
std::thread threadMessageHandler;
438451

439452
/** flag for deciding to connect to an extra outbound peer,
440-
* in excess of nMaxOutbound
453+
* in excess of m_max_outbound_full_relay
441454
* This takes the place of a feeler connection */
442455
std::atomic_bool m_try_another_outbound_peer;
443456

@@ -756,7 +769,7 @@ class CNode
756769

757770
std::set<uint256> orphan_work_set;
758771

759-
CNode(NodeId id, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress &addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const CAddress &addrBindIn, const std::string &addrNameIn = "", bool fInboundIn = false);
772+
CNode(NodeId id, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress &addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const CAddress &addrBindIn, const std::string &addrNameIn = "", bool fInboundIn = false, bool block_relay_only = false);
760773
~CNode();
761774
CNode(const CNode&) = delete;
762775
CNode& operator=(const CNode&) = delete;

src/net_processing.cpp

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ struct CNodeState {
262262
bool fSupportsDesiredCmpctVersion;
263263

264264
/** State used to enforce CHAIN_SYNC_TIMEOUT
265-
* Only in effect for outbound, non-manual connections, with
265+
* Only in effect for outbound, non-manual, full-relay connections, with
266266
* m_protect == false
267267
* Algorithm: if a peer's best known block has less work than our tip,
268268
* set a timeout CHAIN_SYNC_TIMEOUT seconds in the future:
@@ -425,7 +425,7 @@ static void PushNodeVersion(CNode *pnode, CConnman* connman, int64_t nTime)
425425
CAddress addrMe = CAddress(CService(), nLocalNodeServices);
426426

427427
connman->PushMessage(pnode, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::VERSION, PROTOCOL_VERSION, (uint64_t)nLocalNodeServices, nTime, addrYou, addrMe,
428-
nonce, strSubVersion, nNodeStartingHeight, ::g_relay_txes));
428+
nonce, strSubVersion, nNodeStartingHeight, ::g_relay_txes && pnode->m_tx_relay != nullptr));
429429

430430
if (fLogIPs) {
431431
LogPrint(BCLog::NET, "send version message: version %d, blocks=%d, us=%s, them=%s, peer=%d\n", PROTOCOL_VERSION, nNodeStartingHeight, addrMe.ToString(), addrYou.ToString(), nodeid);
@@ -757,7 +757,7 @@ void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds)
757757
}
758758

759759
// Returns true for outbound peers, excluding manual connections, feelers, and
760-
// one-shots
760+
// one-shots.
761761
static bool IsOutboundDisconnectionCandidate(const CNode *node)
762762
{
763763
return !(node->fInbound || node->m_manual_connection || node->fFeeler || node->fOneShot);
@@ -1772,9 +1772,11 @@ bool static ProcessHeadersMessage(CNode *pfrom, CConnman *connman, const std::ve
17721772
}
17731773
}
17741774

1775-
if (!pfrom->fDisconnect && IsOutboundDisconnectionCandidate(pfrom) && nodestate->pindexBestKnownBlock != nullptr) {
1776-
// If this is an outbound peer, check to see if we should protect
1775+
if (!pfrom->fDisconnect && IsOutboundDisconnectionCandidate(pfrom) && nodestate->pindexBestKnownBlock != nullptr && pfrom->m_tx_relay != nullptr) {
1776+
// If this is an outbound full-relay peer, check to see if we should protect
17771777
// it from the bad/lagging chain logic.
1778+
// Note that block-relay-only peers are already implicitly protected, so we
1779+
// only consider setting m_protect for the full-relay peers.
17781780
if (g_outbound_peers_with_protect_from_disconnect < MAX_OUTBOUND_PEERS_TO_PROTECT_FROM_DISCONNECT && nodestate->pindexBestKnownBlock->nChainWork >= ::ChainActive().Tip()->nChainWork && !nodestate->m_chain_sync.m_protect) {
17791781
LogPrint(BCLog::NET, "Protecting outbound peer=%d from eviction\n", pfrom->GetId());
17801782
nodestate->m_chain_sync.m_protect = true;
@@ -2088,9 +2090,10 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
20882090
// Mark this node as currently connected, so we update its timestamp later.
20892091
LOCK(cs_main);
20902092
State(pfrom->GetId())->fCurrentlyConnected = true;
2091-
LogPrintf("New outbound peer connected: version: %d, blocks=%d, peer=%d%s\n",
2092-
pfrom->nVersion.load(), pfrom->nStartingHeight, pfrom->GetId(),
2093-
(fLogIPs ? strprintf(", peeraddr=%s", pfrom->addr.ToString()) : ""));
2093+
LogPrintf("New outbound peer connected: version: %d, blocks=%d, peer=%d%s (%s)\n",
2094+
pfrom->nVersion.load(), pfrom->nStartingHeight,
2095+
pfrom->GetId(), (fLogIPs ? strprintf(", peeraddr=%s", pfrom->addr.ToString()) : ""),
2096+
pfrom->m_tx_relay == nullptr ? "block-relay" : "full-relay");
20942097
}
20952098

20962099
if (pfrom->nVersion >= SENDHEADERS_VERSION) {
@@ -2214,7 +2217,9 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
22142217
return false;
22152218
}
22162219

2217-
bool fBlocksOnly = !g_relay_txes;
2220+
// We won't accept tx inv's if we're in blocks-only mode, or this is a
2221+
// block-relay-only peer
2222+
bool fBlocksOnly = !g_relay_txes || (pfrom->m_tx_relay == nullptr);
22182223

22192224
// Allow whitelisted peers to send data other than blocks in blocks only mode if whitelistrelay is true
22202225
if (pfrom->HasPermission(PF_RELAY))
@@ -3453,6 +3458,8 @@ void PeerLogicValidation::EvictExtraOutboundPeers(int64_t time_in_seconds)
34533458
if (state == nullptr) return; // shouldn't be possible, but just in case
34543459
// Don't evict our protected peers
34553460
if (state->m_chain_sync.m_protect) return;
3461+
// Don't evict our block-relay-only peers.
3462+
if (pnode->m_tx_relay == nullptr) return;
34563463
if (state->m_last_block_announcement < oldest_block_announcement || (state->m_last_block_announcement == oldest_block_announcement && pnode->GetId() > worst_peer)) {
34573464
worst_peer = pnode->GetId();
34583465
oldest_block_announcement = state->m_last_block_announcement;

0 commit comments

Comments
 (0)