From fc23fa8535613eaff54987f4b84910625b416767 Mon Sep 17 00:00:00 2001 From: Nicholas Dudfield Date: Tue, 3 Mar 2026 07:42:27 +0700 Subject: [PATCH] consensus: reduce entropy-set proposal fanout Keep entropy-set recovery path but elect a deterministic single broadcaster (lowest NodeID among tx-converged participants) instead of every proposer broadcasting entropySetHash. This lowers steady-state proposal chatter while preserving liveness for lagging peers that need entropy-set fetch/merge. --- src/xrpld/consensus/Consensus.h | 44 ++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/src/xrpld/consensus/Consensus.h b/src/xrpld/consensus/Consensus.h index 8063b3293..24687b7b0 100644 --- a/src/xrpld/consensus/Consensus.h +++ b/src/xrpld/consensus/Consensus.h @@ -1853,22 +1853,38 @@ Consensus::phaseEstablish( asCloseTime(result_->position.closeTime()), now_); - // Publish entropySetHash before accepting so peers (and - // observers recovering from dropped reveal proposals) can - // fetch/merge entropy sets in ConvergingReveal. + // Publish entropySetHash before accepting so lagging peers + // can fetch/merge reveal sets in ConvergingReveal. // - // Tradeoff: - // - Adds one extra proposal broadcast in rounds that make - // it to ConvergingReveal (often a 4th proposal in quiet - // rounds: seq0, commitSet, reveal, entropySet). - // - In return, lagging nodes can deterministically recover - // reveal sets instead of building a divergent entropy - // transaction and falling into sync/jump loops. - // - // We intentionally bias toward consensus safety/liveness - // under packet loss or drop-injection, accepting small - // bandwidth/CPU overhead. + // Optimization: + // Avoid every proposer sending this final position update. + // Elect one deterministic broadcaster per tx-converged + // group (lowest NodeID among self + tx-converged peers). + // This keeps the recovery path while reducing steady-state + // proposal traffic in quiet rounds. + bool publishEntropySet = true; if (mode_.get() == ConsensusMode::proposing) + { + auto const ourPos = result_->position.position(); + auto const selfNode = result_->position.nodeID(); + auto electedNode = selfNode; + for (auto const& [nodeId, peerPos] : currPeerPositions_) + { + if (!(peerPos.proposal().position() == ourPos)) + continue; + if (nodeId < electedNode) + electedNode = nodeId; + } + publishEntropySet = (selfNode == electedNode); + JLOG(j_.debug()) + << "RNG: entropySet broadcaster election self=" + << selfNode << " elected=" << electedNode + << " publish=" + << (publishEntropySet ? "yes" : "no"); + } + + if (mode_.get() == ConsensusMode::proposing && + publishEntropySet) adaptor_.propose(result_->position); JLOG(j_.debug()) << "RNG: built entropySet";