Skip to content

Commit 23c9f3e

Browse files
committed
Eliminate TX trickle bypass, sort TX invs for privacy and priority.
Previously Bitcoin would send 1/4 of transactions out to all peers instantly. This causes high overhead because it makes >80% of INVs size 1. Doing so harms privacy, because it limits the amount of source obscurity a transaction can receive. These randomized broadcasts also disobeyed transaction dependencies and required use of the orphan pool. Because the orphan pool is so small this leads to poor propagation for dependent transactions. When the bypass wasn't in effect, transactions were sent in the order they were received. This avoided creating orphans but undermines privacy fairly significantly. This commit: Eliminates the bypass. The bypass is replaced by halving the average delay for outbound peers. Sorts candidate transactions for INV by their topological depth then by their feerate (then hash); removing the information leakage and providing priority service to higher fee transactions. Limits the amount of transactions sent in a single INV to 7tx/sec (and twice that for outbound); this limits the harm of low fee transaction floods, gives faster relay service to higher fee transactions. The 7 sounds lower than it really is because received advertisements need not be sent, and because the aggregate rate is multipled by the number of peers. Coming from btc@f2d3ba73860e875972738d1da1507124d0971ae5
1 parent 6ebfd17 commit 23c9f3e

File tree

5 files changed

+59
-23
lines changed

5 files changed

+59
-23
lines changed

src/net_processing.cpp

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1940,6 +1940,29 @@ bool ProcessMessages(CNode* pfrom, CConnman& connman, std::atomic<bool>& interru
19401940
return fMoreWork;
19411941
}
19421942

1943+
class CompareInvMempoolOrder
1944+
{
1945+
CTxMemPool *mp;
1946+
public:
1947+
CompareInvMempoolOrder(CTxMemPool *mempool)
1948+
{
1949+
mp = mempool;
1950+
}
1951+
1952+
bool operator()(const CInv &a, const CInv &b)
1953+
{
1954+
if (a.type != MSG_TX && b.type != MSG_TX) {
1955+
return false;
1956+
} else {
1957+
if (a.type != MSG_TX) {
1958+
return true;
1959+
} else if (b.type != MSG_TX) {
1960+
return false;
1961+
}
1962+
return mp->CompareDepthAndScore(a.hash, b.hash);
1963+
}
1964+
}
1965+
};
19431966

19441967
bool SendMessages(CNode* pto, CConnman& connman, std::atomic<bool>& interruptMsgProc)
19451968
{
@@ -2067,38 +2090,31 @@ bool SendMessages(CNode* pto, CConnman& connman, std::atomic<bool>& interruptMsg
20672090
bool fSendTrickle = pto->fWhitelisted;
20682091
if (pto->nNextInvSend < nNow) {
20692092
fSendTrickle = true;
2070-
pto->nNextInvSend = PoissonNextSend(nNow, AVG_INVENTORY_BROADCAST_INTERVAL);
2093+
// Use half the delay for outbound peers, as their is less privacy concern for them.
2094+
pto->nNextInvSend = PoissonNextSend(nNow, INVENTORY_BROADCAST_INTERVAL >> !pto->fInbound);
20712095
}
20722096
LOCK(pto->cs_inventory);
2073-
vInv.reserve(pto->vInventoryToSend.size());
2097+
if (fSendTrickle && pto->vInventoryToSend.size() > 1) {
2098+
// Topologically and fee-rate sort the inventory we send for privacy and priority reasons.
2099+
CompareInvMempoolOrder compareInvMempoolOrder(&mempool);
2100+
std::stable_sort(pto->vInventoryToSend.begin(), pto->vInventoryToSend.end(), compareInvMempoolOrder);
2101+
}
2102+
vInv.reserve(std::min<size_t>(INVENTORY_BROADCAST_MAX, pto->vInventoryToSend.size()));
20742103
vInvWait.reserve(pto->vInventoryToSend.size());
20752104
for (const CInv& inv : pto->vInventoryToSend) {
20762105
if (inv.type == MSG_TX && pto->filterInventoryKnown.contains(inv.hash))
20772106
continue;
20782107

2079-
// trickle out tx inv to protect privacy
2080-
if (inv.type == MSG_TX && !fSendTrickle) {
2081-
// 1/4 of tx invs blast to all immediately
2082-
static uint256 hashSalt;
2083-
if (hashSalt.IsNull())
2084-
hashSalt = GetRandHash();
2085-
uint256 hashRand = inv.hash ^ hashSalt;
2086-
hashRand = Hash(BEGIN(hashRand), END(hashRand));
2087-
bool fTrickleWait = ((hashRand & 3) != 0);
2088-
2089-
if (fTrickleWait) {
2090-
vInvWait.push_back(inv);
2091-
continue;
2092-
}
2108+
// No reason to drain out at many times the network's capacity,
2109+
// especially since we have many peers and some will draw much shorter delays.
2110+
if (vInv.size() >= INVENTORY_BROADCAST_MAX || (inv.type == MSG_TX && !fSendTrickle)) {
2111+
vInvWait.push_back(inv);
2112+
continue;
20932113
}
20942114

20952115
pto->filterInventoryKnown.insert(inv.hash);
20962116

20972117
vInv.push_back(inv);
2098-
if (vInv.size() >= 1000) {
2099-
connman.PushMessage(pto, msgMaker.Make(NetMsgType::INV, vInv));
2100-
vInv.clear();
2101-
}
21022118
}
21032119
pto->vInventoryToSend = vInvWait;
21042120
}

src/net_processing.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@ static const unsigned int DEFAULT_BLOCK_SPAM_FILTER_MAX_SIZE = 100;
2121
/** Default for -blockspamfiltermaxavg, maximum average size of an index occurrence in the block spam filter */
2222
static const unsigned int DEFAULT_BLOCK_SPAM_FILTER_MAX_AVG = 10;
2323

24+
/** Average delay between trickled inventory transmissions in seconds.
25+
* Blocks and whitelisted receivers bypass this, outbound peers get half this delay. */
26+
static const unsigned int INVENTORY_BROADCAST_INTERVAL = 5;
27+
/** Maximum number of inventory items to send per transmission.
28+
* Limits the impact of low-fee transaction floods. */
29+
static const unsigned int INVENTORY_BROADCAST_MAX = 7 * INVENTORY_BROADCAST_INTERVAL;
30+
2431
/** Register with a network node to receive its signals */
2532
void RegisterNodeSignals(CNodeSignals& nodeSignals);
2633
/** Unregister a network node */

src/txmempool.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -815,6 +815,21 @@ void CTxMemPool::checkNullifiers() const
815815
}
816816
}
817817

818+
bool CTxMemPool::CompareDepthAndScore(const uint256& hasha, const uint256& hashb)
819+
{
820+
LOCK(cs);
821+
indexed_transaction_set::const_iterator i = mapTx.find(hasha);
822+
if (i == mapTx.end()) return false;
823+
indexed_transaction_set::const_iterator j = mapTx.find(hashb);
824+
if (j == mapTx.end()) return true;
825+
uint64_t counta = i->GetCountWithAncestors();
826+
uint64_t countb = j->GetCountWithAncestors();
827+
if (counta == countb) {
828+
return CompareTxMemPoolEntryByScore()(*i, *j);
829+
}
830+
return counta < countb;
831+
}
832+
818833
void CTxMemPool::queryHashes(std::vector<uint256>& vtxid)
819834
{
820835
vtxid.clear();

src/txmempool.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,7 @@ class CTxMemPool
531531
void removeForBlock(const std::vector<CTransactionRef>& vtx, unsigned int nBlockHeight, std::list<CTransactionRef>& conflicts, bool fCurrentEstimate = true);
532532
void clear();
533533
void _clear(); // lock-free
534+
bool CompareDepthAndScore(const uint256& hasha, const uint256& hashb);
534535
void queryHashes(std::vector<uint256>& vtxid);
535536
void getTransactions(std::set<uint256>& setTxid);
536537
bool isSpent(const COutPoint& outpoint);

src/validation.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,6 @@ static const unsigned int MAX_REJECT_MESSAGE_LENGTH = 111;
107107
static const unsigned int AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL = 24 * 24 * 60;
108108
/** Average delay between peer address broadcasts in seconds. */
109109
static const unsigned int AVG_ADDRESS_BROADCAST_INTERVAL = 30;
110-
/** Average delay between trickled inventory broadcasts in seconds.
111-
* Blocks, whitelisted receivers, and a random 25% of transactions bypass this. */
112-
static const unsigned int AVG_INVENTORY_BROADCAST_INTERVAL = 5;
113110
/** Default multiplier used in the computation for shielded txes min fee */
114111
static const unsigned int DEFAULT_SHIELDEDTXFEE_K = 100;
115112

0 commit comments

Comments
 (0)