From cfb0dfe2cf0b46f3ea9e62992ade989860f086c8 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Mon, 11 Mar 2024 10:44:35 -0400 Subject: [PATCH] random: convert GetExponentialRand into rand_exp_duration --- src/net.cpp | 12 ++++++------ src/net_processing.cpp | 10 +++++----- src/random.cpp | 5 ++--- src/random.h | 31 ++++++++++++++++++++----------- 4 files changed, 33 insertions(+), 25 deletions(-) diff --git a/src/net.cpp b/src/net.cpp index 9e969718b7d..034e290dc5a 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -2481,9 +2481,9 @@ void CConnman::ThreadOpenConnections(const std::vector connect) auto start = GetTime(); // Minimum time before next feeler connection (in microseconds). - auto next_feeler = GetExponentialRand(start, FEELER_INTERVAL); - auto next_extra_block_relay = GetExponentialRand(start, EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL); - auto next_extra_network_peer{GetExponentialRand(start, EXTRA_NETWORK_PEER_INTERVAL)}; + auto next_feeler = start + FastRandomContext().rand_exp_duration(FEELER_INTERVAL); + auto next_extra_block_relay = start + FastRandomContext().rand_exp_duration(EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL); + auto next_extra_network_peer{start + FastRandomContext().rand_exp_duration(EXTRA_NETWORK_PEER_INTERVAL)}; const bool dnsseed = gArgs.GetBoolArg("-dnsseed", DEFAULT_DNSSEED); bool add_fixed_seeds = gArgs.GetBoolArg("-fixedseeds", DEFAULT_FIXEDSEEDS); const bool use_seednodes{gArgs.IsArgSet("-seednode")}; @@ -2642,10 +2642,10 @@ void CConnman::ThreadOpenConnections(const std::vector connect) // Because we can promote these connections to block-relay-only // connections, they do not get their own ConnectionType enum // (similar to how we deal with extra outbound peers). - next_extra_block_relay = GetExponentialRand(now, EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL); + next_extra_block_relay = now + FastRandomContext().rand_exp_duration(EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL); conn_type = ConnectionType::BLOCK_RELAY; } else if (now > next_feeler) { - next_feeler = GetExponentialRand(now, FEELER_INTERVAL); + next_feeler = now + FastRandomContext().rand_exp_duration(FEELER_INTERVAL); conn_type = ConnectionType::FEELER; fFeeler = true; } else if (nOutboundFullRelay == m_max_outbound_full_relay && @@ -2658,7 +2658,7 @@ void CConnman::ThreadOpenConnections(const std::vector connect) // This is not attempted if the user changed -maxconnections to a value // so low that less than MAX_OUTBOUND_FULL_RELAY_CONNECTIONS are made, // to prevent interactions with otherwise protected outbound peers. - next_extra_network_peer = GetExponentialRand(now, EXTRA_NETWORK_PEER_INTERVAL); + next_extra_network_peer = now + FastRandomContext().rand_exp_duration(EXTRA_NETWORK_PEER_INTERVAL); } else { // skip to next iteration of while loop continue; diff --git a/src/net_processing.cpp b/src/net_processing.cpp index ce3ff71df15..dad81997839 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -1244,7 +1244,7 @@ std::chrono::microseconds PeerManagerImpl::NextInvToInbounds(std::chrono::micros // If this function were called from multiple threads simultaneously // it would possible that both update the next send variable, and return a different result to their caller. // This is not possible in practice as only the net processing thread invokes this function. - m_next_inv_to_inbounds = GetExponentialRand(now, average_interval); + m_next_inv_to_inbounds = now + FastRandomContext().rand_exp_duration(average_interval); } return m_next_inv_to_inbounds; } @@ -5654,13 +5654,13 @@ void PeerManagerImpl::MaybeSendAddr(CNode& node, Peer& peer, std::chrono::micros CAddress local_addr{*local_service, peer.m_our_services, Now()}; PushAddress(peer, local_addr); } - peer.m_next_local_addr_send = GetExponentialRand(current_time, AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL); + peer.m_next_local_addr_send = current_time + FastRandomContext().rand_exp_duration(AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL); } // We sent an `addr` message to this peer recently. Nothing more to do. if (current_time <= peer.m_next_addr_send) return; - peer.m_next_addr_send = GetExponentialRand(current_time, AVG_ADDRESS_BROADCAST_INTERVAL); + peer.m_next_addr_send = current_time + FastRandomContext().rand_exp_duration(AVG_ADDRESS_BROADCAST_INTERVAL); if (!Assume(peer.m_addrs_to_send.size() <= MAX_ADDR_TO_SEND)) { // Should be impossible since we always check size before adding to @@ -5747,7 +5747,7 @@ void PeerManagerImpl::MaybeSendFeefilter(CNode& pto, Peer& peer, std::chrono::mi MakeAndPushMessage(pto, NetMsgType::FEEFILTER, filterToSend); peer.m_fee_filter_sent = filterToSend; } - peer.m_next_send_feefilter = GetExponentialRand(current_time, AVG_FEEFILTER_BROADCAST_INTERVAL); + peer.m_next_send_feefilter = current_time + FastRandomContext().rand_exp_duration(AVG_FEEFILTER_BROADCAST_INTERVAL); } // If the fee filter has changed substantially and it's still more than MAX_FEEFILTER_CHANGE_DELAY // until scheduled broadcast, then move the broadcast to within MAX_FEEFILTER_CHANGE_DELAY. @@ -6059,7 +6059,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto) if (pto->IsInboundConn()) { tx_relay->m_next_inv_send_time = NextInvToInbounds(current_time, INBOUND_INVENTORY_BROADCAST_INTERVAL); } else { - tx_relay->m_next_inv_send_time = GetExponentialRand(current_time, OUTBOUND_INVENTORY_BROADCAST_INTERVAL); + tx_relay->m_next_inv_send_time = current_time + FastRandomContext().rand_exp_duration(OUTBOUND_INVENTORY_BROADCAST_INTERVAL); } } diff --git a/src/random.cpp b/src/random.cpp index 5067bf259c3..530dfff1ed7 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -773,8 +773,7 @@ void RandomInit() ReportHardwareRand(); } -std::chrono::microseconds GetExponentialRand(std::chrono::microseconds now, std::chrono::seconds average_interval) +double MakeExponentiallyDistributed(uint64_t uniform) noexcept { - double unscaled = -std::log1p(FastRandomContext().randbits<48>() * -0.0000000000000035527136788 /* -1/2^48 */); - return now + std::chrono::duration_cast(unscaled * average_interval + 0.5us); + return -std::log1p((uniform >> 16) * -0.0000000000000035527136788 /* -1/2^48 */); } diff --git a/src/random.h b/src/random.h index b9cba1d6029..821c84fce3a 100644 --- a/src/random.h +++ b/src/random.h @@ -81,17 +81,6 @@ */ void GetRandBytes(Span bytes) noexcept; -/** - * Return a timestamp in the future sampled from an exponential distribution - * (https://en.wikipedia.org/wiki/Exponential_distribution). This distribution - * is memoryless and should be used for repeated network events (e.g. sending a - * certain type of message) to minimize leaking information to observers. - * - * The probability of an event occurring before time x is 1 - e^-(x/a) where a - * is the average interval between events. - * */ -std::chrono::microseconds GetExponentialRand(std::chrono::microseconds now, std::chrono::seconds average_interval); - uint256 GetRandHash() noexcept; /** @@ -139,6 +128,9 @@ concept StdChronoDuration = requires { std::type_identity()); }; +/** Given a uniformly random uint64_t, return an exponentially distributed double with mean 1. */ +double MakeExponentiallyDistributed(uint64_t uniform) noexcept; + /** Mixin class that provides helper randomness functions. * * Intended to be used through CRTP: https://en.cppreference.com/w/cpp/language/crtp. @@ -327,6 +319,23 @@ public: return Dur{Impl().randrange(range.count())}; } + /** + * Return a duration sampled from an exponential distribution + * (https://en.wikipedia.org/wiki/Exponential_distribution). Successive events + * whose intervals are distributed according to this form a memoryless Poisson + * process. This should be used for repeated network events (e.g. sending a + * certain type of message) to minimize leaking information to observers. + * + * The probability of an event occurring before time x is 1 - e^-(x/a) where a + * is the average interval between events. + * */ + std::chrono::microseconds rand_exp_duration(std::chrono::microseconds mean) noexcept + { + using namespace std::chrono_literals; + auto unscaled = MakeExponentiallyDistributed(Impl().rand64()); + return std::chrono::duration_cast(unscaled * mean + 0.5us); + } + // Compatibility with the UniformRandomBitGenerator concept typedef uint64_t result_type; static constexpr uint64_t min() noexcept { return 0; }