fix(consensus): clear export sigs when export disabled

This commit is contained in:
Nicholas Dudfield
2026-04-28 09:17:17 +07:00
parent a956abb2d1
commit d87cfdc604
4 changed files with 70 additions and 0 deletions

View File

@@ -122,12 +122,37 @@ public:
BEAST_EXPECT(collector.signatureCount(tx) == 0);
}
void
testClearAll()
{
testcase("clear all signatures and round state");
ExportSigCollector collector;
auto const verifiedTx = makeHash("clear-all-verified");
auto const unverifiedTx = makeHash("clear-all-unverified");
auto const sig = makeSignature(12);
collector.addVerifiedSignature(verifiedTx, validator_, sig, 10);
collector.addUnverifiedSignature(unverifiedTx, validator_, sig, 10);
BEAST_EXPECT(collector.signatureCount(verifiedTx) == 1);
BEAST_EXPECT(collector.hasUnverifiedSignatures());
BEAST_EXPECT(collector.markSent(verifiedTx));
BEAST_EXPECT(!collector.markSent(verifiedTx));
collector.clearAll();
BEAST_EXPECT(collector.signatureCount(verifiedTx) == 0);
BEAST_EXPECT(!collector.hasUnverifiedSignatures());
BEAST_EXPECT(collector.markSent(verifiedTx));
}
void
run() override
{
testCleanupUsesFirstSeenSeq();
testUpgradeSetsFirstSeenSeq();
testRemoveInvalidUnverifiedSignature();
testClearAll();
}
};

View File

@@ -469,6 +469,29 @@ class ConsensusExtensions_test : public beast::unit_test::suite
BEAST_EXPECT(ext.fetchedExportSets.empty());
}
void
testExportDisabledRoundClearsCollector()
{
testcase("Export disabled round clears collector");
using namespace jtx;
Env env{*this, envconfig(), supported_amendments(), nullptr};
ConsensusExtensions ce{env.app(), env.journal};
auto const tx = makeHash("export-disabled-clears-collector");
auto const pk = makeValidatorKeys().front();
std::uint8_t const sigBytes[] = {1, 2, 3};
Buffer const sig{sigBytes, sizeof(sigBytes)};
ce.setExportEnabledThisRound(true);
ce.exportSigCollector().addVerifiedSignature(tx, pk, sig, 10);
ce.clearRngState();
BEAST_EXPECT(ce.exportSigCollector().signatureCount(tx) == 1);
ce.setExportEnabledThisRound(false);
ce.clearRngState();
BEAST_EXPECT(ce.exportSigCollector().signatureCount(tx) == 0);
}
public:
void
run() override
@@ -478,6 +501,7 @@ public:
testExportSigGateAllowsAlignedQuorumDespiteMinorityConflict();
testExportSigGateFetchesAdvertisedPeerSets();
testExportSigGateSkipsWhenExportDisabled();
testExportDisabledRoundClearsCollector();
}
};

View File

@@ -788,6 +788,13 @@ ConsensusExtensions::clearRngState()
exportSigCollector_.clearRound();
if (auto const closed = app_.getLedgerMaster().getClosedLedger())
exportSigCollector_.cleanupStale(closed->info().seq);
if (!exportEnabledThisRound_)
{
// Export disabled is an amendment boundary, not a retry boundary.
// Drop cached signatures so an emergency stop cannot leave old quorum
// material waiting for a later re-enable.
exportSigCollector_.clearAll();
}
//@@end round-stop-export-reset
//@@start round-stop-rng-reset
pendingCommits_.clear();
@@ -1873,6 +1880,9 @@ void
ConsensusExtensions::onTrustedPeerMessage(
::protocol::TMProposeSet const& wireMsg)
{
if (!exportEnabled())
return;
if (wireMsg.exportsignatures_size() == 0)
return;
@@ -2044,6 +2054,9 @@ ConsensusExtensions::attachExportSignatures(
{
auto const& valKeys = app_.getValidatorKeys();
if (!exportEnabled())
return;
// Attach export signatures for any ttEXPORT txns in the open ledger.
// Gated on featureExport amendment.
// RuntimeConfig no_export_sig disables sig attachment (testing sub-quorum).

View File

@@ -266,6 +266,14 @@ public:
sigs_.erase(txnHash);
}
void
clearAll()
{
std::lock_guard lock(mutex_);
sigs_.clear();
sentThisRound_.clear();
}
/// Get a snapshot of VERIFIED sigs (pubkeys only) for building
/// the SHAMap. Only verified sigs appear in convergence.
std::unordered_map<uint256, std::set<PublicKey>>