diff --git a/src/xrpld/app/consensus/ConsensusExtensions.cpp b/src/xrpld/app/consensus/ConsensusExtensions.cpp index bef5ac82b8..7eb849e26d 100644 --- a/src/xrpld/app/consensus/ConsensusExtensions.cpp +++ b/src/xrpld/app/consensus/ConsensusExtensions.cpp @@ -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{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 } diff --git a/src/xrpld/app/consensus/ExportIntent.md b/src/xrpld/app/consensus/ExportIntent.md index 3a5070940c..0111773890 100644 --- a/src/xrpld/app/consensus/ExportIntent.md +++ b/src/xrpld/app/consensus/ExportIntent.md @@ -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.