https://github.com/bitcoin/bitcoin/pull/29415

Motivation

Transaction origin privacy in Bitcoin Core is not particularly good against specific types of attackers (such as state level, ISP, potentially even chain analytic companies, …). A node that has a global view of the network can figure out (or, at least, guess with high confidence) who is the originator of a transaction by simply listening to who broadcast that transaction first. Moreover, the Bitcoin Core wallet will eagerly re-broadcast transaction created by it on a fixed interval (24 hours IIRC). This is also a big tell, given it will not apply to transaction received over he wire.

This PR tries to obfuscate the origin of a transaction by, instead of broadcasting it to our current peers, creating a short-lived connection using one of our privacy network (e.g. Tor or I2P), sending the transaction and disconnecting. This way, the link between origin (IP/geolocation) and transaction will be broken. The approach of this PR is to limit this to sendrawtransaction, given this has a lot of ramifications with the wallet. Eventually, this should be fully integrated with the node, so wallet transactions can take advantage of it, but that’d be covered in follow-ups.

Thoughts/Questions

First pass (up to https://github.com/bitcoin/bitcoin/pull/29415/commits/28adad8eefd18a62232b5c77cbc9ca739a53308d) → review 1

A new flag privatebroadcast is defined to control this feature. If set (false by default), transactions send via sendrawtransaction will using this new type of one-shot privacy-oriented connection to distribute the transaction. However, the current approach is to restrict broadcasting wallet transactions if the new flag is enabled. Therefore, privatebroadcast cannot work with walletbroadcast. I find this counterintuitive, and I would have not guessed that by the flags (or PR description).

My guess for why this is approached in this way is that the wallet is unaware of these new transactions until they are announced to us by one of our peers. Therefore, if we allowed both types of broadcast, a transaction created by the wallet could try to use the same utxos that one created by sendrawtransaction, and those would conflict.

Checking ‣, there is a conversation between @mzumsande and @vasild regarding nMaxConnections (ref). This made @vasil look deeper into how the maximum amount of connections is limited by the number of available file descriptions of the system and find that there are some inconsistencies with it. I’m also scratching my head with this:

Extracted from https://github.com/bitcoin/bitcoin/blob/c8e3978114716bb8fb10695b9d187652f3ab4926/src/init.cpp#L976-L997

// Make sure enough file descriptors are available
int nBind = std::max(nUserBind, size_t(1));
nUserMaxConnections = args.GetIntArg("-maxconnections", DEFAULT_MAX_PEER_CONNECTIONS);
nMaxConnections = std::max(nUserMaxConnections, 0);

nFD = RaiseFileDescriptorLimit(nMaxConnections + MIN_CORE_FILEDESCRIPTORS + MAX_ADDNODE_CONNECTIONS + nBind + NUM_FDS_MESSAGE_CAPTURE);

#ifdef USE_POLL
int fd_max = nFD;
#else
int fd_max = FD_SETSIZE;
#endif
// Trim requested connection counts, to fit into system limitations
// <int> in std::min<int>(...) to work around FreeBSD compilation issue described in #2695
nMaxConnections = std::max(std::min<int>(nMaxConnections, fd_max - nBind - MIN_CORE_FILEDESCRIPTORS - MAX_ADDNODE_CONNECTIONS - NUM_FDS_MESSAGE_CAPTURE), 0);
if (nFD < MIN_CORE_FILEDESCRIPTORS)
    return InitError(_("Not enough file descriptors available."));
nMaxConnections = std::min(nFD - MIN_CORE_FILEDESCRIPTORS - MAX_ADDNODE_CONNECTIONS - NUM_FDS_MESSAGE_CAPTURE, nMaxConnections);

if (nMaxConnections < nUserMaxConnections)
    InitWarning(strprintf(_("Reducing -maxconnections from %d to %d, because of system limitations."), nUserMaxConnections, nMaxConnections));

There’s a lot going on here and little documentation 🫠