From 960fffcf82e62f268694f693d15a75e3e8cc4239 Mon Sep 17 00:00:00 2001 From: Nicholas Dudfield Date: Thu, 5 Feb 2026 17:26:31 +0700 Subject: [PATCH] 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. --- src/ripple/app/tx/impl/Change.cpp | 46 ++++++++++++++++++++++ src/ripple/app/tx/impl/Change.h | 3 ++ src/ripple/protocol/Indexes.h | 4 ++ src/ripple/protocol/LedgerFormats.h | 10 ++++- src/ripple/protocol/TxFormats.h | 7 ++++ src/ripple/protocol/impl/Indexes.cpp | 9 +++++ src/ripple/protocol/impl/LedgerFormats.cpp | 9 +++++ src/ripple/protocol/impl/STTx.cpp | 3 +- src/ripple/protocol/impl/TxFormats.cpp | 8 ++++ src/ripple/protocol/jss.h | 3 +- 10 files changed, 99 insertions(+), 3 deletions(-) diff --git a/src/ripple/app/tx/impl/Change.cpp b/src/ripple/app/tx/impl/Change.cpp index 37a436fee..fb34221a7 100644 --- a/src/ripple/app/tx/impl/Change.cpp +++ b/src/ripple/app/tx/impl/Change.cpp @@ -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(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() { diff --git a/src/ripple/app/tx/impl/Change.h b/src/ripple/app/tx/impl/Change.h index 20c701f5c..53031ddb5 100644 --- a/src/ripple/app/tx/impl/Change.h +++ b/src/ripple/app/tx/impl/Change.h @@ -76,6 +76,9 @@ private: TER applyUNLReport(); + + TER + applyConsensusEntropy(); }; } // namespace ripple diff --git a/src/ripple/protocol/Indexes.h b/src/ripple/protocol/Indexes.h index 03f9dc2a4..ab8a7918d 100644 --- a/src/ripple/protocol/Indexes.h +++ b/src/ripple/protocol/Indexes.h @@ -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 { diff --git a/src/ripple/protocol/LedgerFormats.h b/src/ripple/protocol/LedgerFormats.h index f87f7ae3f..0d1012519 100644 --- a/src/ripple/protocol/LedgerFormats.h +++ b/src/ripple/protocol/LedgerFormats.h @@ -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. diff --git a/src/ripple/protocol/TxFormats.h b/src/ripple/protocol/TxFormats.h index bea5905cb..a3a021645 100644 --- a/src/ripple/protocol/TxFormats.h +++ b/src/ripple/protocol/TxFormats.h @@ -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 diff --git a/src/ripple/protocol/impl/Indexes.cpp b/src/ripple/protocol/impl/Indexes.cpp index f75faf165..228644b94 100644 --- a/src/ripple/protocol/impl/Indexes.cpp +++ b/src/ripple/protocol/impl/Indexes.cpp @@ -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 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 diff --git a/src/ripple/protocol/impl/LedgerFormats.cpp b/src/ripple/protocol/impl/LedgerFormats.cpp index 5f51976be..ed986f6a4 100644 --- a/src/ripple/protocol/impl/LedgerFormats.cpp +++ b/src/ripple/protocol/impl/LedgerFormats.cpp @@ -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 } diff --git a/src/ripple/protocol/impl/STTx.cpp b/src/ripple/protocol/impl/STTx.cpp index 2fbb19421..a56d1ce84 100644 --- a/src/ripple/protocol/impl/STTx.cpp +++ b/src/ripple/protocol/impl/STTx.cpp @@ -615,7 +615,8 @@ isPseudoTx(STObject const& tx) auto tt = safe_cast(*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 diff --git a/src/ripple/protocol/impl/TxFormats.cpp b/src/ripple/protocol/impl/TxFormats.cpp index 789de36f2..c5db25618 100644 --- a/src/ripple/protocol/impl/TxFormats.cpp +++ b/src/ripple/protocol/impl/TxFormats.cpp @@ -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& diff --git a/src/ripple/protocol/jss.h b/src/ripple/protocol/jss.h index 316037268..ec7f2b479 100644 --- a/src/ripple/protocol/jss.h +++ b/src/ripple/protocol/jss.h @@ -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