feat(consensus): add ttCONSENSUS_ENTROPY pseudo-transaction protocol layer

Add protocol definitions for consensus-derived entropy pseudo-transaction:
- ttCONSENSUS_ENTROPY = 105 transaction type
- ltCONSENSUS_ENTROPY = 0x0058 ledger entry type
- keylet::consensusEntropy() singleton keylet (namespace 'X')
- applyConsensusEntropy() handler in Change.cpp
- Added to isPseudoTx() in STTx.cpp

The entropy value is stored in sfDigest field of the singleton ledger object.
This provides the protocol foundation for same-ledger entropy injection.
This commit is contained in:
Nicholas Dudfield
2026-02-05 17:26:31 +07:00
parent e7867c07a1
commit 960fffcf82
10 changed files with 99 additions and 3 deletions

View File

@@ -96,6 +96,22 @@ Change::preflight(PreflightContext const& ctx)
}
}
if (ctx.tx.getTxnType() == ttCONSENSUS_ENTROPY)
{
// TODO: Add amendment gating when the feature is finalized
// if (!ctx.rules.enabled(featureConsensusEntropy))
// {
// JLOG(ctx.j.warn()) << "Change: ConsensusEntropy is not enabled.";
// return temDISABLED;
// }
if (!ctx.tx.isFieldPresent(sfDigest))
{
JLOG(ctx.j.warn()) << "Change: ConsensusEntropy must have sfDigest";
return temMALFORMED;
}
}
return tesSUCCESS;
}
@@ -154,6 +170,7 @@ Change::preclaim(PreclaimContext const& ctx)
case ttAMENDMENT:
case ttUNL_MODIFY:
case ttEMIT_FAILURE:
case ttCONSENSUS_ENTROPY:
return tesSUCCESS;
case ttUNL_REPORT: {
if (!ctx.tx.isFieldPresent(sfImportVLKey) ||
@@ -209,12 +226,41 @@ Change::doApply()
return applyEmitFailure();
case ttUNL_REPORT:
return applyUNLReport();
case ttCONSENSUS_ENTROPY:
return applyConsensusEntropy();
default:
assert(0);
return tefFAILURE;
}
}
TER
Change::applyConsensusEntropy()
{
auto const entropy = ctx_.tx.getFieldH256(sfDigest);
auto const seq = view().info().seq;
auto sle = view().peek(keylet::consensusEntropy());
bool const created = !sle;
if (created)
sle = std::make_shared<SLE>(keylet::consensusEntropy());
sle->setFieldH256(sfDigest, entropy);
sle->setFieldH256(sfPreviousTxnID, ctx_.tx.getTransactionID());
sle->setFieldU32(sfPreviousTxnLgrSeq, seq);
if (created)
view().insert(sle);
else
view().update(sle);
JLOG(j_.info()) << "ConsensusEntropy: updated entropy to " << entropy
<< " at ledger " << seq;
return tesSUCCESS;
}
TER
Change::applyUNLReport()
{

View File

@@ -76,6 +76,9 @@ private:
TER
applyUNLReport();
TER
applyConsensusEntropy();
};
} // namespace ripple

View File

@@ -115,6 +115,10 @@ negativeUNL() noexcept;
Keylet const&
UNLReport() noexcept;
/** The (fixed) index of the object containing consensus-derived entropy. */
Keylet const&
consensusEntropy() noexcept;
/** The beginning of an order book */
struct book_t
{

View File

@@ -183,7 +183,15 @@ enum LedgerEntryType : std::uint16_t
*
* \sa keylet::UNLReport
*/
ltUNL_REPORT = 0x0052,
ltUNL_REPORT = 0x0052,
/** The ledger object which stores consensus-derived entropy.
\note This is a singleton: only one such object exists in the ledger.
\sa keylet::consensusEntropy
*/
ltCONSENSUS_ENTROPY = 0x0058,
//---------------------------------------------------------------------------
/** A special type, matching any ledger entry type.

View File

@@ -197,6 +197,13 @@ enum TxType : std::uint16_t
ttUNL_MODIFY = 102,
ttEMIT_FAILURE = 103,
ttUNL_REPORT = 104,
/** This system-generated transaction type is used to record consensus-derived entropy.
The entropy is computed from a commit-reveal scheme during consensus and
written to the ledger for use by hooks and other deterministic applications.
*/
ttCONSENSUS_ENTROPY = 105,
};
// clang-format on

View File

@@ -73,6 +73,7 @@ enum class LedgerNameSpace : std::uint16_t {
IMPORT_VLSEQ = 'I',
UNL_REPORT = 'R',
CRON = 'L',
CONSENSUS_ENTROPY = 'X',
// No longer used or supported. Left here to reserve the space
// to avoid accidental reuse.
@@ -496,6 +497,14 @@ cron(uint32_t timestamp, std::optional<AccountID> const& id)
return {ltCRON, uint256::fromVoid(h)};
}
Keylet const&
consensusEntropy() noexcept
{
static Keylet const ret{
ltCONSENSUS_ENTROPY, indexHash(LedgerNameSpace::CONSENSUS_ENTROPY)};
return ret;
}
} // namespace keylet
} // namespace ripple

View File

@@ -381,6 +381,15 @@ LedgerFormats::LedgerFormats()
},
commonFields);
add(jss::ConsensusEntropy,
ltCONSENSUS_ENTROPY,
{
{sfDigest, soeREQUIRED}, // The consensus-derived entropy
{sfPreviousTxnID, soeREQUIRED},
{sfPreviousTxnLgrSeq, soeREQUIRED},
},
commonFields);
// clang-format on
}

View File

@@ -615,7 +615,8 @@ isPseudoTx(STObject const& tx)
auto tt = safe_cast<TxType>(*t);
return tt == ttAMENDMENT || tt == ttFEE || tt == ttUNL_MODIFY ||
tt == ttEMIT_FAILURE || tt == ttUNL_REPORT || tt == ttCRON;
tt == ttEMIT_FAILURE || tt == ttUNL_REPORT || tt == ttCRON ||
tt == ttCONSENSUS_ENTROPY;
}
} // namespace ripple

View File

@@ -490,6 +490,14 @@ TxFormats::TxFormats()
{sfStartTime, soeOPTIONAL},
},
commonFields);
add(jss::ConsensusEntropy,
ttCONSENSUS_ENTROPY,
{
{sfLedgerSequence, soeREQUIRED},
{sfDigest, soeREQUIRED}, // The computed entropy value
},
commonFields);
}
TxFormats const&

View File

@@ -254,7 +254,8 @@ JSS(count); // in: AccountTx*, ValidatorList
JSS(counters); // in/out: retrieve counters
JSS(coins);
JSS(children);
JSS(ctid); // in/out: Tx RPC
JSS(ConsensusEntropy); // transaction and ledger type.
JSS(ctid); // in/out: Tx RPC
JSS(cres);
JSS(cron);
JSS(currency_a); // out: BookChanges