Skip to content

Commit 6d859cb

Browse files
committed
Merge bitcoin#24021: Rename and move PoissonNextSend functions
9b8dcb2 [net processing] Rename PoissonNextSendInbound to NextInvToInbounds (John Newbery) ea99f5d [net processing] Move PoissonNextSendInbound to PeerManager (John Newbery) bb06074 scripted-diff: replace PoissonNextSend with GetExponentialRand (John Newbery) 03cfa1b [refactor] Use uint64_t and std namespace in PoissonNextSend (John Newbery) 9e64d69 [move] Move PoissonNextSend to src/random and update comment (John Newbery) Pull request description: `PoissonNextSend` and `PoissonNextSendInbound` are used in the p2p code to obfuscate various regularly occurring processes, in order to make it harder for others to get timing-based information deterministically. The naming of these functions has been confusing to several people (including myself, see also bitcoin#23347) because the resulting random timestamps don't follow a Poisson distribution but an exponential distribution (related to events in a Poisson process, hence the name). This PR - moves `PoissonNextSend()` out of `net` to `random` and renames it to `GetExponentialRand()` - moves `PoissonNextSendInbound()` out of `CConnman` to `PeerManager` and renames it to `NextInvToInbounds()` - adds documentation for these functions This is work by jnewbery - due to him being less active currently, I opened the PR and will address feedback. ACKs for top commit: jnewbery: ACK 9b8dcb2 hebasto: ACK 9b8dcb2, I have reviewed the code and it looks OK, I agree it can be merged. theStack: ACK 9b8dcb2 📊 Tree-SHA512: 85c366c994e7147f9981fe863fb9838502643fa61ffd32d55a43feef96a38b79a5daa2c4d38ce01074897cc95fa40c76779816edad53f5265b81b05c3a1f4f50
2 parents e3ce019 + 9b8dcb2 commit 6d859cb

File tree

6 files changed

+53
-45
lines changed

6 files changed

+53
-45
lines changed

src/net.cpp

+5-22
Original file line numberDiff line numberDiff line change
@@ -1878,8 +1878,8 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
18781878
auto start = GetTime<std::chrono::microseconds>();
18791879

18801880
// Minimum time before next feeler connection (in microseconds).
1881-
auto next_feeler = PoissonNextSend(start, FEELER_INTERVAL);
1882-
auto next_extra_block_relay = PoissonNextSend(start, EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL);
1881+
auto next_feeler = GetExponentialRand(start, FEELER_INTERVAL);
1882+
auto next_extra_block_relay = GetExponentialRand(start, EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL);
18831883
const bool dnsseed = gArgs.GetBoolArg("-dnsseed", DEFAULT_DNSSEED);
18841884
bool add_fixed_seeds = gArgs.GetBoolArg("-fixedseeds", DEFAULT_FIXEDSEEDS);
18851885

@@ -1999,7 +1999,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
19991999
//
20002000
// This is similar to the logic for trying extra outbound (full-relay)
20012001
// peers, except:
2002-
// - we do this all the time on a poisson timer, rather than just when
2002+
// - we do this all the time on an exponential timer, rather than just when
20032003
// our tip is stale
20042004
// - we potentially disconnect our next-youngest block-relay-only peer, if our
20052005
// newest block-relay-only peer delivers a block more recently.
@@ -2008,10 +2008,10 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
20082008
// Because we can promote these connections to block-relay-only
20092009
// connections, they do not get their own ConnectionType enum
20102010
// (similar to how we deal with extra outbound peers).
2011-
next_extra_block_relay = PoissonNextSend(now, EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL);
2011+
next_extra_block_relay = GetExponentialRand(now, EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL);
20122012
conn_type = ConnectionType::BLOCK_RELAY;
20132013
} else if (now > next_feeler) {
2014-
next_feeler = PoissonNextSend(now, FEELER_INTERVAL);
2014+
next_feeler = GetExponentialRand(now, FEELER_INTERVAL);
20152015
conn_type = ConnectionType::FEELER;
20162016
fFeeler = true;
20172017
} else {
@@ -3058,23 +3058,6 @@ bool CConnman::ForNode(NodeId id, std::function<bool(CNode* pnode)> func)
30583058
return found != nullptr && NodeFullyConnected(found) && func(found);
30593059
}
30603060

3061-
std::chrono::microseconds CConnman::PoissonNextSendInbound(std::chrono::microseconds now, std::chrono::seconds average_interval)
3062-
{
3063-
if (m_next_send_inv_to_incoming.load() < now) {
3064-
// If this function were called from multiple threads simultaneously
3065-
// it would possible that both update the next send variable, and return a different result to their caller.
3066-
// This is not possible in practice as only the net processing thread invokes this function.
3067-
m_next_send_inv_to_incoming = PoissonNextSend(now, average_interval);
3068-
}
3069-
return m_next_send_inv_to_incoming;
3070-
}
3071-
3072-
std::chrono::microseconds PoissonNextSend(std::chrono::microseconds now, std::chrono::seconds average_interval)
3073-
{
3074-
double unscaled = -log1p(GetRand(1ULL << 48) * -0.0000000000000035527136788 /* -1/2^48 */);
3075-
return now + std::chrono::duration_cast<std::chrono::microseconds>(unscaled * average_interval + 0.5us);
3076-
}
3077-
30783061
CSipHasher CConnman::GetDeterministicRandomizer(uint64_t id) const
30793062
{
30803063
return CSipHasher(nSeed0, nSeed1).Write(id);

src/net.h

-11
Original file line numberDiff line numberDiff line change
@@ -936,12 +936,6 @@ class CConnman
936936

937937
void WakeMessageHandler();
938938

939-
/** Attempts to obfuscate tx time through exponentially distributed emitting.
940-
Works assuming that a single interval is used.
941-
Variable intervals will result in privacy decrease.
942-
*/
943-
std::chrono::microseconds PoissonNextSendInbound(std::chrono::microseconds now, std::chrono::seconds average_interval);
944-
945939
/** Return true if we should disconnect the peer for failing an inactivity check. */
946940
bool ShouldRunInactivityChecks(const CNode& node, std::chrono::seconds now) const;
947941

@@ -1221,8 +1215,6 @@ class CConnman
12211215
*/
12221216
std::atomic_bool m_start_extra_block_relay_peers{false};
12231217

1224-
std::atomic<std::chrono::microseconds> m_next_send_inv_to_incoming{0us};
1225-
12261218
/**
12271219
* A vector of -bind=<address>:<port>=onion arguments each of which is
12281220
* an address and port that are designated for incoming Tor connections.
@@ -1270,9 +1262,6 @@ class CConnman
12701262
friend struct ConnmanTestMsg;
12711263
};
12721264

1273-
/** Return a timestamp in the future (in microseconds) for exponentially distributed events. */
1274-
std::chrono::microseconds PoissonNextSend(std::chrono::microseconds now, std::chrono::seconds average_interval);
1275-
12761265
/** Dump binary message to file, with timestamp */
12771266
void CaptureMessage(const CAddress& addr, const std::string& msg_type, const Span<const unsigned char>& data, bool is_incoming);
12781267

src/net_processing.cpp

+28-5
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,8 @@ class PeerManagerImpl final : public PeerManager
450450
*/
451451
std::map<NodeId, PeerRef> m_peer_map GUARDED_BY(m_peer_mutex);
452452

453+
std::atomic<std::chrono::microseconds> m_next_inv_to_inbounds{0us};
454+
453455
/** Number of nodes with fSyncStarted. */
454456
int nSyncStarted GUARDED_BY(cs_main) = 0;
455457

@@ -524,6 +526,15 @@ class PeerManagerImpl final : public PeerManager
524526
Mutex m_recent_confirmed_transactions_mutex;
525527
CRollingBloomFilter m_recent_confirmed_transactions GUARDED_BY(m_recent_confirmed_transactions_mutex){48'000, 0.000'001};
526528

529+
/**
530+
* For sending `inv`s to inbound peers, we use a single (exponentially
531+
* distributed) timer for all peers. If we used a separate timer for each
532+
* peer, a spy node could make multiple inbound connections to us to
533+
* accurately determine when we received the transaction (and potentially
534+
* determine the transaction's origin). */
535+
std::chrono::microseconds NextInvToInbounds(std::chrono::microseconds now,
536+
std::chrono::seconds average_interval);
537+
527538
/** Have we requested this block from a peer */
528539
bool IsBlockRequested(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
529540

@@ -825,6 +836,18 @@ static void UpdatePreferredDownload(const CNode& node, CNodeState* state) EXCLUS
825836
nPreferredDownload += state->fPreferredDownload;
826837
}
827838

839+
std::chrono::microseconds PeerManagerImpl::NextInvToInbounds(std::chrono::microseconds now,
840+
std::chrono::seconds average_interval)
841+
{
842+
if (m_next_inv_to_inbounds.load() < now) {
843+
// If this function were called from multiple threads simultaneously
844+
// it would possible that both update the next send variable, and return a different result to their caller.
845+
// This is not possible in practice as only the net processing thread invokes this function.
846+
m_next_inv_to_inbounds = GetExponentialRand(now, average_interval);
847+
}
848+
return m_next_inv_to_inbounds;
849+
}
850+
828851
bool PeerManagerImpl::IsBlockRequested(const uint256& hash)
829852
{
830853
return mapBlocksInFlight.find(hash) != mapBlocksInFlight.end();
@@ -4434,13 +4457,13 @@ void PeerManagerImpl::MaybeSendAddr(CNode& node, Peer& peer, std::chrono::micros
44344457
FastRandomContext insecure_rand;
44354458
PushAddress(peer, *local_addr, insecure_rand);
44364459
}
4437-
peer.m_next_local_addr_send = PoissonNextSend(current_time, AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL);
4460+
peer.m_next_local_addr_send = GetExponentialRand(current_time, AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL);
44384461
}
44394462

44404463
// We sent an `addr` message to this peer recently. Nothing more to do.
44414464
if (current_time <= peer.m_next_addr_send) return;
44424465

4443-
peer.m_next_addr_send = PoissonNextSend(current_time, AVG_ADDRESS_BROADCAST_INTERVAL);
4466+
peer.m_next_addr_send = GetExponentialRand(current_time, AVG_ADDRESS_BROADCAST_INTERVAL);
44444467

44454468
if (!Assume(peer.m_addrs_to_send.size() <= MAX_ADDR_TO_SEND)) {
44464469
// Should be impossible since we always check size before adding to
@@ -4512,7 +4535,7 @@ void PeerManagerImpl::MaybeSendFeefilter(CNode& pto, std::chrono::microseconds c
45124535
m_connman.PushMessage(&pto, CNetMsgMaker(pto.GetCommonVersion()).Make(NetMsgType::FEEFILTER, filterToSend));
45134536
pto.m_tx_relay->lastSentFeeFilter = filterToSend;
45144537
}
4515-
pto.m_tx_relay->m_next_send_feefilter = PoissonNextSend(current_time, AVG_FEEFILTER_BROADCAST_INTERVAL);
4538+
pto.m_tx_relay->m_next_send_feefilter = GetExponentialRand(current_time, AVG_FEEFILTER_BROADCAST_INTERVAL);
45164539
}
45174540
// If the fee filter has changed substantially and it's still more than MAX_FEEFILTER_CHANGE_DELAY
45184541
// until scheduled broadcast, then move the broadcast to within MAX_FEEFILTER_CHANGE_DELAY.
@@ -4792,9 +4815,9 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
47924815
if (pto->m_tx_relay->nNextInvSend < current_time) {
47934816
fSendTrickle = true;
47944817
if (pto->IsInboundConn()) {
4795-
pto->m_tx_relay->nNextInvSend = m_connman.PoissonNextSendInbound(current_time, INBOUND_INVENTORY_BROADCAST_INTERVAL);
4818+
pto->m_tx_relay->nNextInvSend = NextInvToInbounds(current_time, INBOUND_INVENTORY_BROADCAST_INTERVAL);
47964819
} else {
4797-
pto->m_tx_relay->nNextInvSend = PoissonNextSend(current_time, OUTBOUND_INVENTORY_BROADCAST_INTERVAL);
4820+
pto->m_tx_relay->nNextInvSend = GetExponentialRand(current_time, OUTBOUND_INVENTORY_BROADCAST_INTERVAL);
47984821
}
47994822
}
48004823

src/random.cpp

+7
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <sync.h> // for Mutex
2020
#include <util/time.h> // for GetTimeMicros()
2121

22+
#include <cmath>
2223
#include <stdlib.h>
2324
#include <thread>
2425

@@ -714,3 +715,9 @@ void RandomInit()
714715

715716
ReportHardwareRand();
716717
}
718+
719+
std::chrono::microseconds GetExponentialRand(std::chrono::microseconds now, std::chrono::seconds average_interval)
720+
{
721+
double unscaled = -std::log1p(GetRand(uint64_t{1} << 48) * -0.0000000000000035527136788 /* -1/2^48 */);
722+
return now + std::chrono::duration_cast<std::chrono::microseconds>(unscaled * average_interval + 0.5us);
723+
}

src/random.h

+13-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
#include <crypto/common.h>
1111
#include <uint256.h>
1212

13-
#include <chrono> // For std::chrono::microseconds
13+
#include <chrono>
1414
#include <cstdint>
1515
#include <limits>
1616

@@ -82,6 +82,18 @@ D GetRandomDuration(typename std::common_type<D>::type max) noexcept
8282
};
8383
constexpr auto GetRandMicros = GetRandomDuration<std::chrono::microseconds>;
8484
constexpr auto GetRandMillis = GetRandomDuration<std::chrono::milliseconds>;
85+
86+
/**
87+
* Return a timestamp in the future sampled from an exponential distribution
88+
* (https://en.wikipedia.org/wiki/Exponential_distribution). This distribution
89+
* is memoryless and should be used for repeated network events (e.g. sending a
90+
* certain type of message) to minimize leaking information to observers.
91+
*
92+
* The probability of an event occuring before time x is 1 - e^-(x/a) where a
93+
* is the average interval between events.
94+
* */
95+
std::chrono::microseconds GetExponentialRand(std::chrono::microseconds now, std::chrono::seconds average_interval);
96+
8597
int GetRandInt(int nMax) noexcept;
8698
uint256 GetRandHash() noexcept;
8799

src/test/fuzz/connman.cpp

-6
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,6 @@ FUZZ_TARGET_INIT(connman, initialize_connman)
9797
[&] {
9898
(void)connman.OutboundTargetReached(fuzzed_data_provider.ConsumeBool());
9999
},
100-
[&] {
101-
// Limit now to int32_t to avoid signed integer overflow
102-
(void)connman.PoissonNextSendInbound(
103-
std::chrono::microseconds{fuzzed_data_provider.ConsumeIntegral<int32_t>()},
104-
std::chrono::seconds{fuzzed_data_provider.ConsumeIntegral<int>()});
105-
},
106100
[&] {
107101
CSerializedNetMsg serialized_net_msg;
108102
serialized_net_msg.m_type = fuzzed_data_provider.ConsumeRandomLengthString(CMessageHeader::COMMAND_SIZE);

0 commit comments

Comments
 (0)