fix(consensus): rebuild accepted export witnesses against the round parent

This commit is contained in:
Nicholas Dudfield
2026-09-17 14:16:47 +07:00
parent 459196778b
commit d0ec00473c
2 changed files with 39 additions and 22 deletions

View File

@@ -2569,19 +2569,34 @@ ConsensusExtensions::onPreBuild(
auto const parent =
app_.getLedgerMaster().getLedgerByHash(roundPrevLedgerHash_);
auto const validated = app_.getLedgerMaster().getValidatedLedger();
bool parentExtendsValidated =
parent && validated && validated->info().seq <= parent->info().seq;
if (parentExtendsValidated &&
// Rebuild only from this round's exact parent and accepted evidence.
// Local validation can lag that parent or already have passed it.
// Preserve the competing-ancestry guard in either direction without
// making cursor progress itself change the synthetic transaction set.
// Live share admission/signing/release retain their validated-ledger
// checks; this boundary neither admits nor publishes a new share.
bool parentCompatibleWithValidated =
parent && validated && seq > 0 && parent->info().seq == seq - 1;
if (parentCompatibleWithValidated &&
validated->info().seq < parent->info().seq)
{
auto const validatedHash =
hashOfSeq(*parent, validated->info().seq, j_);
parentExtendsValidated =
parentCompatibleWithValidated =
validatedHash && *validatedHash == validated->info().hash;
}
else if (parentExtendsValidated)
else if (
parentCompatibleWithValidated &&
validated->info().seq > parent->info().seq)
{
parentExtendsValidated =
auto const parentHash =
hashOfSeq(*validated, parent->info().seq, j_);
parentCompatibleWithValidated =
parentHash && *parentHash == parent->info().hash;
}
else if (parentCompatibleWithValidated)
{
parentCompatibleWithValidated =
parent->info().hash == validated->info().hash;
}
@@ -2593,8 +2608,8 @@ ConsensusExtensions::onPreBuild(
<< " validatedHash="
<< (validated ? to_string(validated->info().hash)
: std::string{"none"})
<< " parentExtendsValidated=" << parentExtendsValidated
<< " acceptedRoot="
<< " parentCompatibleWithValidated="
<< parentCompatibleWithValidated << " acceptedRoot="
<< (acceptedExportSigSetHash_
? to_string(*acceptedExportSigSetHash_)
: std::string{"none"})
@@ -2604,7 +2619,7 @@ ConsensusExtensions::onPreBuild(
: std::string{"none"})
<< " convergenceFailed=" << exportSigConvergenceFailed_;
if (parentExtendsValidated)
if (parentCompatibleWithValidated)
{
auto const pending = pendingExportLatches(*parent, seq);
std::size_t materialized = 0;
@@ -2626,14 +2641,9 @@ ConsensusExtensions::onPreBuild(
auto const originSeq = latch->getFieldU32(sfLedgerSequence);
auto const originHash = hashOfSeq(*parent, originSeq, j_);
auto const validatedOriginHash =
originSeq == validated->info().seq
? std::optional<uint256>{validated->info().hash}
: hashOfSeq(*validated, originSeq, j_);
if (!originHash || !validatedOriginHash ||
*originHash != *validatedOriginHash)
if (!originHash)
{
skip("origin-ancestry-unavailable-or-mismatched");
skip("origin-not-in-round-parent-ancestry");
continue;
}
@@ -2752,7 +2762,7 @@ ConsensusExtensions::onPreBuild(
JLOG(j_.debug())
<< "Export: preBuild witnesses skipped"
<< " buildSeq=" << seq
<< " reason=parent-not-descendant-of-current-validation";
<< " reason=parent-unavailable-or-incompatible-with-validation";
}
//@@end export-later-ledger-witness-materialization
}

View File

@@ -131,11 +131,18 @@ Standalone test execution substitutes exact possession of its locally verified
map; intent admission still requires the UNLReport-backed parent state. Late
collector arrivals cannot mutate the accepted root.
Materialization also requires the consensus parent to descend from the node's
validated ledger, the origin ledger hash to agree through both ancestry views,
and the exact origin ledger and `ttEXPORT` transaction to be locally available.
If any of that validated possession is absent, the latch remains pending; a
build cursor ahead of validation never forces witness production.
Witness rebuilding resolves the origin through the exact consensus parent's
ancestry and requires the exact origin ledger and `ttEXPORT` transaction to be
locally available. The local validated ledger must be on compatible ancestry:
it may be an ancestor of that parent or already a descendant of it. Known
competing ancestry still prevents materialization, but progress of this local
cursor alone must not change the synthetic transaction set for a fixed parent
and accepted root. Missing accepted-root material or origin-ledger possession
still prevents witness construction.
This rebuild rule does not authorize share admission, signing or release from
a merely closed parent. Those live paths retain the source-validation checks
in INV-4 and INV-5; an origin ahead of local validation remains deferred there.
*Anti-pattern:* assembling from the current collector at apply time or treating
peer root support as remote payload availability.