This continues the work of moving application layer data into net_processing, by moving all addr data into the new Peer object added in #19607.
For motivation, see #19398.
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.
Reviewers, this pull request conflicts with the following ones:
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.
236@@ -216,7 +237,8 @@ struct Peer {
237 /** Work queue of items requested by this peer **/
238 std::deque<CInv> m_getdata_requests GUARDED_BY(m_getdata_requests_mutex);
239
240- explicit Peer(NodeId id) : m_id(id) {}
241+ explicit Peer(NodeId id, bool addr_relay)
242+ : m_id(id), m_addr_known{addr_relay ? nullptr : MakeUnique<CRollingBloomFilter>(5000, 0.001)} {}
std::make_unique
in new code.
This makes the following commit easier.
651@@ -617,6 +652,39 @@ static CNodeState *State(NodeId pnode) EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
652 return &it->second;
653 }
654
655+static bool RelayAddrsWithPeer(const Peer& peer) { return peer.m_addr_known != nullptr; }
0static bool RelayAddrsWithPeer(const Peer& peer)
1{
2 return peer.m_addr_known != nullptr;
3}
240@@ -218,7 +241,10 @@ struct Peer {
241 /** Work queue of items requested by this peer **/
242 std::deque<CInv> m_getdata_requests GUARDED_BY(m_getdata_requests_mutex);
243
244- explicit Peer(NodeId id) : m_id(id) {}
245+ explicit Peer(NodeId id, bool addr_relay)
246+ : m_id(id)
247+ , m_addr_known{addr_relay ? nullptr : std::make_unique<CRollingBloomFilter>(5000, 0.001)}
248+ {}
clang-format-diff.py
to support such formatting, i.e., both a leading comma and {}
on the the same line?
BCIS_BeforeComma
? (https://clang.llvm.org/docs/ClangFormatStyleOptions.html)
1590
1591 // Relay reachable addresses to 2 peers. Unreachable addresses are relayed randomly to 1 or 2 peers.
1592 unsigned int nRelayNodes = (fReachable || (hasher.Finalize() & 1)) ? 2 : 1;
1593
1594- std::array<std::pair<uint64_t, CNode*>,2> best{{{0, nullptr}, {0, nullptr}}};
1595+ std::array<std::pair<uint64_t, Peer*>,2> best{{{0, nullptr}, {0, nullptr}}};
style nit:
0 std::array<std::pair<uint64_t, Peer*>, 2> best{{{0, nullptr}, {0, nullptr}}};
2739 continue;
2740 }
2741 bool fReachable = IsReachable(addr);
2742- if (addr.nTime > nSince && !pfrom.fGetAddr && vAddr.size() <= 10 && addr.IsRoutable())
2743+ if (addr.nTime > nSince && !peer->m_getaddr_sent && vAddr.size() <= 10 && addr.IsRoutable())
2744 {
style nit:
0 if (addr.nTime > nSince && !peer->m_getaddr_sent && vAddr.size() <= 10 && addr.IsRoutable()) {
Approach ACK 468db1469382ee11380f0667454b86ec1903eca8.
In "[net processing] Move addr relay data and logic into net processing" commit (6fdf4b62e3161ce6fdd27b778de53933dccde65e) four functions are moved from CNode
members to the net_processing.cpp
as free static
functions. I agree that there’s no need to make them members of Peer
, but why not place them in a namespace? It could be unnamed, or we could choose a pretty good name for it :)
Also CNode::AddAddressKnown
and CNode::PushAddress
are subjects for fuzzing in the master branch. Could new free functions also be fuzzed?
Thanks for the review, @hebasto! I’ve addressed both of your style comments.
four functions are moved from CNode members to the net_processing.cpp as free static functions. I agree that there’s no need to make them members of Peer, but why not place them in a namespace? It could be unnamed, or we could choose a pretty good name for it :)
There’s almost no difference between declaring a free function static
and placing it in the unnamed namespace. Both will prevent the name from being accessible outside the translation unit. See https://stackoverflow.com/a/154482/933705 for example.
Also CNode::AddAddressKnown and CNode::PushAddress are subjects for fuzzing in the master branch. Could new free functions also be fuzzed?
Both of these functions are called from ProcessMessage()
, so are indirectly tested by the fuzz/process_messages.py
fuzzer.
ACK c538369123b6c69358649255815cf331b2c39f52, I have reviewed the code and it looks OK, I agree it can be merged.
There’s almost no difference between declaring a free function
static
and placing it in the unnamed namespace. Both will prevent the name from being accessible outside the translation unit. See https://stackoverflow.com/a/154482/933705 for example.
#19176 (comment)
I don’t see a hugely compelling reason to prefer unnamed namespaces over static declaration in any of those links, but equally wouldn’t be opposed to updating the style guide to prefer unnnamed namespaces just for consistency.
One advantage of using static
that isn’t mentioned is that it’s instantly obvious to the reader that the symbol has internal linkage. Unnamed namespaces often span many hundreds of lines, so it’s not obvious when something is in the namespace. For example, I’ve just realized that in this PR, all the new functions are in fact inside an unnamed namespace that spans lines 542-942.
For example, I’ve just realized that in this PR, all the new functions are in fact inside an unnamed namespace that spans lines 542-942.
There are three adjacent unnamed namespaces…
Unnamed namespaces often span many hundreds of lines…
I think it is a code organizing problem, no?
There are three adjacent unnamed namespaces…
Yes. This is part of the unfinished work in #20758. Once that’s finished, these unnamed namespaces should be fixed up.
Unnamed namespaces often span many hundreds of lines…
I think it is a code organizing problem, no?
I’m not sure. I think if we use unnamed namespaces for all the internal functions that we don’t want exposed outside the translation unit then they’ll usually be hundreds of lines.
I’m not sure. I think if we use unnamed namespaces for all the internal functions that we don’t want exposed outside the translation unit then they’ll usually be hundreds of lines.
Correct. I mean that our code base have some really huge translation units :)
240@@ -218,7 +241,10 @@ struct Peer {
241 /** Work queue of items requested by this peer **/
242 std::deque<CInv> m_getdata_requests GUARDED_BY(m_getdata_requests_mutex);
243
244- explicit Peer(NodeId id) : m_id(id) {}
245+ explicit Peer(NodeId id, bool addr_relay)
addr_relay
is true - I would have expected the opposite, or a different name like disable_addr_relay
.
3621@@ -3566,20 +3622,20 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
3622 // to users' AddrMan and later request them by sending getaddr messages.
3623 // Making nodes which are behind NAT and can only make outgoing connections ignore
3624 // the getaddr message mitigates the attack.
3625- if (!pfrom.IsInboundConn()) {
3626+ if (!RelayAddrsWithPeer(*peer)) {
oh wow. That’s terrible. Thank you for catching this.
This was a bad rebase on top of 49c10a9ca40, which combined the two error conditions:
0diff --git a/src/net_processing.cpp b/src/net_processing.cpp
1index 690b59476b..859b67755d 100644
2--- a/src/net_processing.cpp
3+++ b/src/net_processing.cpp
4@@ -3521,11 +3521,7 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat
5 // Making nodes which are behind NAT and can only make outgoing connections ignore
6 // the getaddr message mitigates the attack.
7 if (!pfrom.IsInboundConn()) {
8- LogPrint(BCLog::NET, "Ignoring \"getaddr\" from outbound connection. peer=%d\n", pfrom.GetId());
9- return;
10- }
11- if (!pfrom.RelayAddrsWithConn()) {
12- LogPrint(BCLog::NET, "Ignoring \"getaddr\" from block-relay-only connection. peer=%d\n", pfrom.GetId());
13+ LogPrint(BCLog::NET, "Ignoring \"getaddr\" from %s connection. peer=%d\n", pfrom.ConnectionTypeAsString(), pfrom.GetId());
14 return;
15 }
652- {
653- // Known checking here is only to save space from duplicates.
654- // SendMessages will filter it again for knowns that were added
655- // after addresses were pushed.
656- assert(m_addr_known);
657- if (_addr.IsValid() && !m_addr_known->contains(_addr.GetKey()) && IsAddrCompatible(_addr)) {
IsAddrCompatible()
in the move? Since PushAddress()
is called from several places (not only RelayAddress()
where this is checked seperately) this looks like more than a pure refactor to me.
Thank you for the careful review @mzumsande. You caught two rebase errors :flushed:
I’ve fixed those and addressed your other comment. I’ve also made some minor edits to comments to clarify concepts, and also re-reviewed everything to make sure no other errors have crept in.
4208 // Nothing to do for non-address-relay peers
4209- if (!node.RelayAddrsWithConn()) return;
4210+ if (!RelayAddrsWithPeer(peer)) return;
4211
4212- assert(node.m_addr_known);
4213+ assert(peer.m_addr_known);
m_addr_known
is not present.
149@@ -150,6 +150,8 @@ static constexpr uint32_t MAX_GETCFILTERS_SIZE = 1000;
150 static constexpr uint32_t MAX_GETCFHEADERS_SIZE = 2000;
151 /** the maximum percentage of addresses from our addrman to return in response to a getaddr message. */
152 static constexpr size_t MAX_PCT_ADDR_TO_SEND = 23;
153+/** The maximum number of new addresses to relay to a peer in an ADDR message. */
224+ /** Time point to send the next ADDR message to this peer. */
225+ std::chrono::microseconds m_next_addr_send GUARDED_BY(m_addr_send_times_mutex){0};
226+ /** Time point to possibly re-announce our local address to this peer. */
227+ std::chrono::microseconds m_next_local_addr_send GUARDED_BY(m_addr_send_times_mutex){0};
228+ /** Whether the peer has signaled support for receiving ADDRv2 (BIP155)
229+ * messages, implying a preference to receive ADDRv2 instead of ADDR ones. */
666+{
667+ assert(peer.m_addr_known);
668+ peer.m_addr_known->insert(addr.GetKey());
669+}
670+
671+static void PushAddress(Peer& peer, const CAddress& addr, FastRandomContext& insecure_rand)
Because Peer
isn’t a class. It’s a data structure containing information about a Peer, which PeerManager acts on.
I know the distinction is blurry in C++, but I think it’s generally better to treat things as purely a data structure (which carries data that is acted on by external logic) or purely a class (where the data is private and internal logic exists behind well-defined public interface functions). Mixing the two, where some logic is contained within the object but external logic can also reach into the object and manipulate the data, is confusing.
-BEGIN VERIFY SCRIPT-
ren() { sed -i "s:\<$1\>:$2:g" $(git grep -l "\<$1\>" ./src ./test); }
ren fGetAddr m_getaddr_sent
ren fSentAddr m_getaddr_recvd
ren vAddrToSend m_addrs_to_send
-END VERIFY SCRIPT-
Thanks for the reviews @ajtowns and @amitiuttarwar. I’ve addressed all of your comments.
The “Move addr relay data and logic into net processing” commit is doing a lot of things, and I think that makes it a bit hard to review (and leads to rebase errors?). Might be worth putting a bit of effort into splitting it up further if some of the changes can be isolated?
I did try that at one point, but it seemed more confusing/difficult than just moving across the addr data/logic in one commit.
1013@@ -954,7 +1014,9 @@ void PeerManagerImpl::InitializeNode(CNode *pnode)
1014 assert(m_txrequest.Count(nodeid) == 0);
1015 }
1016 {
1017- PeerRef peer = std::make_shared<Peer>(nodeid);
1018+ // Addr relay is disabled for outbound block-relay-only peers to
1019+ // prevent adversaries from inferring these links from addr traffic.
1020+ PeerRef peer = std::make_shared<Peer>(nodeid, /* addr_relay = */ !pnode->IsBlockOnlyConn());
Using !IsBlockOnlyConn()
as proxy for “should we relay addresses” goes a bit in the opposite direction as the earlier connection types refactors went in. Any reason not just keep the function name (I realize there’s a derived one with that name added, but it’s ultimately still relying on this test.
I don’t have a particularly strong opinion here; I’m just noticing some flipflopping.
IsBlockOnlyConn()
is used only when calling the Peer
ctor. After that, we use the RelayAddrsWithPeer()
, which replaces the old RelayAddrsWithConn()
function. That’s a clearer separation between net and net_processing since we only check the connection type when initializing the Peer object.