From ac659faec7297a48909116f7cf3c0f1cd2c0d0bb Mon Sep 17 00:00:00 2001 From: Nicholas Dudfield Date: Thu, 2 Jul 2026 19:26:32 +0700 Subject: [PATCH] fix(consensus): refresh extension gates before round cleanup --- src/test/consensus/ConsensusExtensions_test.cpp | 10 ++++++++++ src/xrpld/app/consensus/ConsensusExtensions.cpp | 8 ++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/test/consensus/ConsensusExtensions_test.cpp b/src/test/consensus/ConsensusExtensions_test.cpp index 41dc3ecc9..4d9aaa011 100644 --- a/src/test/consensus/ConsensusExtensions_test.cpp +++ b/src/test/consensus/ConsensusExtensions_test.cpp @@ -1202,6 +1202,10 @@ class ConsensusExtensions_test : public beast::unit_test::suite disabledEnv.app().getLedgerMaster().getClosedLedger(); ConsensusExtensions ce{enabledEnv.app(), activeNoopJournal()}; + auto const tx = makeHash("on-round-start-export-latch"); + auto const pk = makeValidatorKeys().front(); + std::uint8_t const sigBytes[] = {1, 2, 3}; + Buffer const sig{sigBytes, sizeof(sigBytes)}; ce.setRngEnabledThisRound(false); ce.setExportEnabledThisRound(false); @@ -1209,11 +1213,17 @@ class ConsensusExtensions_test : public beast::unit_test::suite BEAST_EXPECT(ce.rngEnabled()); BEAST_EXPECT(ce.exportEnabled()); + ce.exportSigCollector().addVerifiedSignature(tx, pk, sig, 10); + BEAST_EXPECT(ce.exportSigCollector().signatureCount(tx) == 1); + ce.onRoundStart(RCLCxLedger{enabledLedger}, {}); + BEAST_EXPECT(ce.exportSigCollector().signatureCount(tx) == 1); + ce.setRngEnabledThisRound(true); ce.setExportEnabledThisRound(true); ce.onRoundStart(RCLCxLedger{disabledLedger}, {}); BEAST_EXPECT(!ce.rngEnabled()); BEAST_EXPECT(!ce.exportEnabled()); + BEAST_EXPECT(ce.exportSigCollector().signatureCount(tx) == 0); } void diff --git a/src/xrpld/app/consensus/ConsensusExtensions.cpp b/src/xrpld/app/consensus/ConsensusExtensions.cpp index acdbf9391..531665de7 100644 --- a/src/xrpld/app/consensus/ConsensusExtensions.cpp +++ b/src/xrpld/app/consensus/ConsensusExtensions.cpp @@ -1414,9 +1414,9 @@ ConsensusExtensions::clearRngStatePreservingExport() likelyParticipants_.clear(); commitProofs_.clear(); //@@end round-stop-rng-reset - // Keep the round-level enable latches intact here. Callers either already - // hold a valid snapshot, or onRoundStart() refreshes it from the consensus - // parent ledger after clearing per-round working state. + // Keep the round-level enable latches intact here. onRoundStart() refreshes + // them from the consensus parent before clearing so boundary cleanup, such + // as the export-disabled signature wipe, observes the new round's rules. } void @@ -2234,10 +2234,10 @@ ConsensusExtensions::onRoundStart( RCLCxLedger const& prevLedger, hash_set lastProposers) { - clearRngState(); auto const& rules = prevLedger.ledger_->rules(); setRngEnabledThisRound(rules.enabled(featureConsensusEntropy)); setExportEnabledThisRound(rules.enabled(featureExport)); + clearRngState(); roundPrevLedgerHash_ = prevLedger.ledger_->info().hash; rngRoundSeq_ = prevLedger.ledger_->info().seq + 1;