diff --git a/src/xrpld/app/consensus/ConsensusExtensions.cpp b/src/xrpld/app/consensus/ConsensusExtensions.cpp index 6405b3942..717ab20cb 100644 --- a/src/xrpld/app/consensus/ConsensusExtensions.cpp +++ b/src/xrpld/app/consensus/ConsensusExtensions.cpp @@ -565,9 +565,10 @@ ConsensusExtensions::shouldSendExplicitFinalProposal() const // We only enable explicit-final when operators intentionally opt in via // runtime config/env for measurement/diagnostics. // - // TBD (2026-03-03): Keep collecting tx-bearing network data before - // revisiting whether explicit-final can be safely promoted beyond - // experimental use. + // TODO: remove the explicit-final proposal path. The implicit accept-time + // injection path is the consensus path; explicit-final never found a robust + // timing model and still carries separate experimental-only alignment + // hazards. Do not promote this by just widening the gates. auto const cfg = app_.getRuntimeConfig().getConfig("*"); if (cfg && cfg->explicitFinalProposal.has_value()) return *cfg->explicitFinalProposal; @@ -590,6 +591,9 @@ ConsensusExtensions::buildExplicitFinalProposalTxSet( // the agreed set at timeout boundaries. Routing explicit-final // (experimental, default-off) through it keeps this path byte-identical to // the implicit one. txns.id() is the BASE tx set hash for the fallback. + // + // TODO: delete this with the explicit-final proposal path; keep this helper + // only while the runtime-config experiment still exists. auto const selection = selectEntropy(txns.id(), seq); uint256 const finalEntropy = selection.digest; std::uint8_t const entropyTier = selection.tier; diff --git a/src/xrpld/app/consensus/ConsensusExtensionsDesign.md b/src/xrpld/app/consensus/ConsensusExtensionsDesign.md index 807436348..bf6e311f3 100644 --- a/src/xrpld/app/consensus/ConsensusExtensionsDesign.md +++ b/src/xrpld/app/consensus/ConsensusExtensionsDesign.md @@ -212,8 +212,9 @@ is the safe degradation path, not a consensus failure. > Known exception: the experimental, default-off explicit-final proposal path > counts alignment over the unfiltered proposer set (not the active view). It is -> flagged in-code as an outstanding F1 gap and must apply the same active-view -> filter before it is ever enabled. +> flagged in-code as an outstanding F1 gap, but the intended disposition is +> removal rather than repair/promotion; it must not be enabled as a production +> consensus path. Examples with six active validators on a UNLReport-anchored view (validator_quorum threshold five, participant_aligned threshold four; six is the smallest view with diff --git a/src/xrpld/consensus/ConsensusExtensionsTick.h b/src/xrpld/consensus/ConsensusExtensionsTick.h index b479ee44c..0a9faefb6 100644 --- a/src/xrpld/consensus/ConsensusExtensionsTick.h +++ b/src/xrpld/consensus/ConsensusExtensionsTick.h @@ -938,10 +938,10 @@ extensionsTick(Ext& ext, Ctx const& ctx) // the // normal implicit path (accept-time pseudo-tx injection). // - // TBD (2026-03-03): We did not find a robust timing model that - // folds this into a guaranteed-safe explicit final proposal - // across lossy/reordered links without increasing churn. Keep - // this path as opt-in for future evaluation. + // TODO: remove this explicit-final proposal path. We did not find + // a robust timing model that folds it into a guaranteed-safe + // explicit final proposal across lossy/reordered links without + // increasing churn. It remains opt-in only until deleted. { bool fullParticipantCoverage = false; bool entropyAligned = false; @@ -966,10 +966,9 @@ extensionsTick(Ext& ext, Ctx const& ctx) // main gate. Before this path is EVER enabled it must // filter peers through activeValidatorView()->containsNode // and gate the local +1 on local active-view membership - // (mirror inspectTxConvergedSidecarPeers); until then it - // stays default-off. Per the TBD above, no safe timing - // model was found for this path regardless, so it may never - // ship. + // (mirror inspectTxConvergedSidecarPeers) if this code + // survives long enough to be touched. Preferred disposition + // is deletion, not promotion. auto const participants = ctx.peerPositions.size() + 1; auto const expectedParticipants = ctx.prevProposers + 1; fullParticipantCoverage = @@ -1297,10 +1296,10 @@ extensionsTick(Ext& ext, Ctx const& ctx) if (exportState.conflict && quorumAligned()) { // Export sidecar roots are signed through ExtendedPosition - // whenever featureExport is active. A quorum-aligned hash is - // therefore enough to proceed; requiring every tx-converged - // active peer to publish an exportSigSetHash would let a - // missing minority sidecar force retry/expiry. + // whenever featureExport is active. A quorum-aligned hash + // is therefore enough to proceed; requiring every + // tx-converged active peer to publish an exportSigSetHash + // would let a missing minority sidecar force retry/expiry. JLOG(ext.j_.info()) << "Export: exportSigSetHash conflict ignored" << " reason=quorum-aligned"