mirror of
https://github.com/Xahau/xahaud.git
synced 2026-06-16 23:26:36 +00:00
fix(rng): value-based entropy pseudo-tx dedup + tier-read hardening
Address review findings on the tier 3 commits: - onPreBuild dedup was type-based (skip if any ttCONSENSUS_ENTROPY present), which silently trusts a pre-present pseudo-tx. Injection is deterministic, so every honest node derives the identical pseudo-tx (identical txID) for the same agreed inputs. Switch to value-based dedup: skip only when the present pseudo-tx EQUALS the one we would produce; a present-but-different entropy pseudo-tx is a determinism violation (version skew / divergent peer) and is now logged at error rather than accepted blindly. (The earlier 'cannot reconstruct the txID locally' justification was wrong — determinism guarantees it can.) - fairRng: read sfEntropyTier defensively (missing => 0 => fail closed). The field is soeREQUIRED so any entry this code wrote carries it; this only guards a pre-tier-3 persisted entry on a long-lived testnet. - quorum_degradation_smoke: assert EntropyTier==consensus_fallback(1) and EntropyCount==0 and non-zero digest explicitly, not just is_fallback.
This commit is contained in:
@@ -41,18 +41,27 @@ async def scenario(ctx, log):
|
||||
for seq in range(val_before + 1, degraded_end + 1):
|
||||
ce, _ = get_entropy_tx(ctx, seq)
|
||||
digest, entropy_count, is_fallback = entropy_fields(ce)
|
||||
tier = ce.get("EntropyTier")
|
||||
|
||||
if not is_fallback:
|
||||
# Tier 3 = consensus_fallback (1): explicit tier, count 0,
|
||||
# deterministic NON-zero digest.
|
||||
if tier != 1:
|
||||
raise AssertionError(
|
||||
f"Ledger {seq}: expected fallback entropy during 3/5 "
|
||||
f"window, got Digest={digest[:16]}... "
|
||||
f"EntropyCount={entropy_count}"
|
||||
f"Ledger {seq}: expected EntropyTier==1 "
|
||||
f"(consensus_fallback) during 3/5 window, got {tier} "
|
||||
f"(EntropyCount={entropy_count})"
|
||||
)
|
||||
if digest == ZERO_DIGEST:
|
||||
if entropy_count != 0:
|
||||
raise AssertionError(
|
||||
f"Ledger {seq}: fallback digest should be non-zero "
|
||||
f"(Tier 3), got zero"
|
||||
f"Ledger {seq}: fallback EntropyCount must be 0, got "
|
||||
f"{entropy_count}"
|
||||
)
|
||||
if not digest or digest == ZERO_DIGEST:
|
||||
raise AssertionError(
|
||||
f"Ledger {seq}: fallback digest must be non-zero "
|
||||
f"(Tier 3), got {digest[:16]}..."
|
||||
)
|
||||
assert is_fallback # tier==1 implies fallback
|
||||
|
||||
degraded_fallback += 1
|
||||
log(
|
||||
|
||||
@@ -1779,19 +1779,46 @@ ConsensusExtensions::onPreBuild(
|
||||
});
|
||||
|
||||
auto const txID = tx.getTransactionID();
|
||||
// Dedup by type, not exact txID: with explicit-final proposals the
|
||||
// agreed set can already carry an entropy pseudo-tx whose fallback
|
||||
// digest was derived from a base tx set hash this node cannot
|
||||
// reconstruct. There must never be two entropy pseudo-txs.
|
||||
auto alreadyPresent = std::any_of(
|
||||
retriableTxs.begin(), retriableTxs.end(), [&](auto const& entry) {
|
||||
// Value-based dedup. There must never be two entropy pseudo-txs, but
|
||||
// when one is already present (explicit-final, or a peer's agreed
|
||||
// set) it must be VALIDATED as the exact pseudo-tx we would have
|
||||
// produced — not merely "same type". Injection is deterministic, so
|
||||
// every honest node derives the identical pseudo-tx (identical txID)
|
||||
// for the same agreed inputs. A present-but-different entropy pseudo-tx
|
||||
// is therefore a determinism violation (version skew or a divergent/
|
||||
// malicious peer) and must be surfaced, not silently trusted.
|
||||
auto const existing = std::find_if(
|
||||
retriableTxs.begin(), retriableTxs.end(), [](auto const& entry) {
|
||||
return entry.second->getTxnType() == ttCONSENSUS_ENTROPY;
|
||||
});
|
||||
if (alreadyPresent)
|
||||
if (existing != retriableTxs.end())
|
||||
{
|
||||
JLOG(j_.debug()) << "RNG: entropy pseudo-tx already present"
|
||||
<< " txHash=" << txID << " seq=" << seq
|
||||
<< " action=skip-duplicate";
|
||||
auto const existingID = existing->second->getTransactionID();
|
||||
if (existingID == txID)
|
||||
{
|
||||
JLOG(j_.debug()) << "RNG: entropy pseudo-tx already present"
|
||||
<< " txHash=" << txID << " seq=" << seq
|
||||
<< " action=skip-duplicate-verified";
|
||||
}
|
||||
else
|
||||
{
|
||||
// The agreed tx set's hash already commits to the existing
|
||||
// pseudo-tx, so we cannot replace it without forking off the
|
||||
// agreed ledger; keep it, but loudly flag the mismatch.
|
||||
JLOG(j_.error())
|
||||
<< "RNG: entropy pseudo-tx MISMATCH"
|
||||
<< " seq=" << seq << " reason=determinism-violation"
|
||||
<< " ourTxHash=" << txID << " ourDigest=" << finalEntropy
|
||||
<< " ourTier=" << static_cast<int>(entropyTier)
|
||||
<< " ourCount=" << entropyCount
|
||||
<< " presentTxHash=" << existingID << " presentDigest="
|
||||
<< existing->second->getFieldH256(sfDigest)
|
||||
<< " presentTier="
|
||||
<< static_cast<int>(
|
||||
existing->second->getFieldU8(sfEntropyTier))
|
||||
<< " presentCount="
|
||||
<< existing->second->getFieldU16(sfEntropyCount);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -4068,8 +4068,15 @@ fairRng(
|
||||
// TODO: open-ledger entropy uses previous ledger's entropy, so
|
||||
// dice/random results will differ between speculative and final
|
||||
// execution. This needs further thought re: UX implications.
|
||||
// Defensive: sfEntropyTier is soeREQUIRED, so any entry this code wrote
|
||||
// carries it. A missing field can only come from a pre-tier-3 persisted
|
||||
// entry; treat that as tier 0 (none) so the requirement check fails closed.
|
||||
auto const entropyTier =
|
||||
sleEntropy && sleEntropy->isFieldPresent(sfEntropyTier)
|
||||
? sleEntropy->getFieldU8(sfEntropyTier)
|
||||
: std::uint8_t{0};
|
||||
if (!sleEntropy || entropySeq > seq || (seq - entropySeq) > 1 ||
|
||||
sleEntropy->getFieldU8(sfEntropyTier) < minTier ||
|
||||
entropyTier < minTier ||
|
||||
sleEntropy->getFieldU16(sfEntropyCount) < minCount)
|
||||
return {};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user