fix(export): allow quorum-aligned sidecar despite missing observation

This commit is contained in:
Nicholas Dudfield
2026-06-22 09:00:46 +07:00
parent 35e981e509
commit e55c2c6dc8
3 changed files with 22 additions and 36 deletions

View File

@@ -2648,9 +2648,9 @@ class ConsensusExtensions_test : public beast::unit_test::suite
}
void
testExportSigGateRequiresFullObservation()
testExportSigGateAllowsQuorumDespiteMissingObservation()
{
testcase("Export sig gate requires full sidecar observation");
testcase("Export sig gate allows quorum despite missing sidecar observation");
FakeExtensions ext;
ExportTickHarness harness;
@@ -2666,17 +2666,11 @@ class ConsensusExtensions_test : public beast::unit_test::suite
BEAST_EXPECT(harness.position.exportSigSetHash == localHash);
BEAST_EXPECT(ext.exportSigGateStarted_);
// Local quorum alignment is not enough if a tx-converged peer has
// not advertised any exportSigSetHash yet.
// A quorum-aligned signed exportSigSetHash is enough even if a
// tx-converged minority peer has not advertised any exportSigSetHash.
result = harness.tick(ext, std::chrono::milliseconds{100});
BEAST_EXPECT(!result.readyForAccept);
BEAST_EXPECT(!ext.exportSigConvergenceFailed_);
result = harness.tick(
ext,
harness.parms.rngREVEAL_TIMEOUT * 2 + std::chrono::milliseconds{1});
BEAST_EXPECT(result.readyForAccept);
BEAST_EXPECT(ext.exportSigConvergenceFailed_);
BEAST_EXPECT(!ext.exportSigConvergenceFailed_);
}
void
@@ -3120,7 +3114,7 @@ public:
testRngEntropyConflictIgnoredWithQuorumAlignment();
testRngExplicitFinalProposalPublishesSyntheticTxSet();
testExportSigGateAllowsAlignedQuorumDespiteMinorityConflict();
testExportSigGateRequiresFullObservation();
testExportSigGateAllowsQuorumDespiteMissingObservation();
testExportSigGateFetchesAdvertisedPeerSets();
testExportSigGateObservingModeDoesNotPropose();
testExportSigGateRefreshesHashBeforeWaiting();

View File

@@ -313,18 +313,15 @@ closing a minority ledger while sidecar convergence is already reachable. If no
advertised sidecar appears by the deadline, the gate stops waiting and the
export retries or expires through normal transaction rules.
Export success requires quorum alignment on `exportSigSetHash` AND full
observation of the tx-converged active set — not merely a local collector quorum.
Quorum alignment is necessary but not sufficient: even when a quorum of
tx-converged participants advertises the same export signature sidecar hash, that
hash is only treated as aligned (and below-quorum conflicts only ignored, and the
round only allowed to succeed) once the node has also observed an `exportSigSetHash`
from every tx-converged active validator (`peersSeen == txConverged`), because
export success changes ledger effects. While quorum is aligned but some
tx-converged peer's hash is unobserved, the node keeps waiting within the bounded
window. If quorum-aligned full observation is not reached by the bounded deadline,
do not choose the largest non-quorum set; the export retries or expires according
to normal transaction rules.
Export success requires quorum alignment on `exportSigSetHash`, not merely a
local collector quorum. Since `featureExport` enables signed extended proposal
fields, a quorum-aligned `exportSigSetHash` is enough to proceed even if a
tx-converged minority peer has not advertised an export sidecar hash. Do not let
one active validator with a missing sidecar force an otherwise quorum-aligned
export round to retry or expire. Full observation remains useful diagnostics; it
is not an Export success precondition. If no export signature hash reaches quorum
alignment by the bounded deadline, do not choose the largest non-quorum set; the
export retries or expires according to normal transaction rules.
Closed-ledger apply must not promote unverified proposal-carried signatures into
current-round quorum material. It may verify and retain them for a future retry,

View File

@@ -1269,13 +1269,6 @@ extensionsTick(Ext& ext, Ctx const& ctx)
auto quorumAligned = [&] {
return exportState.quorumAligned(exportQuorum);
};
auto fullObservation = [&] {
// Export success changes ledger effects too. Require a
// full view of tx-converged peers before treating a local
// quorum as safe enough to succeed in this ledger.
return exportState.fullObservation();
};
if (exportState.conflict && !quorumAligned())
{
auto const refreshedHash =
@@ -1301,9 +1294,13 @@ extensionsTick(Ext& ext, Ctx const& ctx)
exportState = inspectExportPeers(ctx.getPosition(), true);
}
if (exportState.conflict && quorumAligned() &&
fullObservation())
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.
JLOG(ext.j_.info())
<< "Export: exportSigSetHash conflict ignored"
<< " reason=quorum-aligned"
@@ -1314,9 +1311,7 @@ extensionsTick(Ext& ext, Ctx const& ctx)
<< " peersSeen=" << exportState.peersSeen
<< " txConverged=" << exportState.txConverged;
}
else if (
exportState.conflict || !quorumAligned() ||
!fullObservation())
else if (exportState.conflict || !quorumAligned())
{
auto const elapsed =
ctx.nowSteady - ext.exportSigGateStart_;