From e0fe289b5240f96dcaadafaaed243bf661f24549 Mon Sep 17 00:00:00 2001 From: Richard Holland Date: Sun, 21 Dec 2025 14:41:16 +1100 Subject: [PATCH] code complete, compiling untested --- src/ripple/app/ledger/impl/BuildLedger.cpp | 6 +- src/ripple/app/misc/impl/TxQ.cpp | 20 ++- src/ripple/app/tx/impl/Change.cpp | 125 ++++++++++++++++++ src/ripple/app/tx/impl/Change.h | 3 + src/ripple/protocol/Indexes.h | 3 + src/ripple/protocol/LedgerFormats.h | 7 + src/ripple/protocol/SField.h | 6 + src/ripple/protocol/impl/Indexes.cpp | 9 ++ .../protocol/impl/InnerObjectFormats.cpp | 8 ++ src/ripple/protocol/impl/LedgerFormats.cpp | 14 ++ src/ripple/protocol/impl/SField.cpp | 6 + src/ripple/protocol/impl/STTx.cpp | 3 +- src/ripple/protocol/impl/TxFormats.cpp | 10 ++ src/ripple/protocol/jss.h | 2 + 14 files changed, 207 insertions(+), 15 deletions(-) diff --git a/src/ripple/app/ledger/impl/BuildLedger.cpp b/src/ripple/app/ledger/impl/BuildLedger.cpp index 203c450b4..85ed72106 100644 --- a/src/ripple/app/ledger/impl/BuildLedger.cpp +++ b/src/ripple/app/ledger/impl/BuildLedger.cpp @@ -25,6 +25,7 @@ #include #include #include +#include namespace ripple { @@ -103,10 +104,10 @@ applyTransactions( bool certainRetry = true; std::size_t count = 0; - if (view.rules.enabled(featureRNG)) + if (view.rules().enabled(featureRNG)) { // apply the ttRNG txns first in the ledger to ensure no one can predict the outcome - for (it = txns.begin(); it != txns.end();) + for (auto it = txns.begin(); it != txns.end();) { if (it->second->getFieldU16(sfTransactionType) != ttRNG) { @@ -114,6 +115,7 @@ applyTransactions( continue; } + auto const txid = it->first.getTXID(); try { switch (applyTransaction( diff --git a/src/ripple/app/misc/impl/TxQ.cpp b/src/ripple/app/misc/impl/TxQ.cpp index ee37c02c2..978b6c2bf 100644 --- a/src/ripple/app/misc/impl/TxQ.cpp +++ b/src/ripple/app/misc/impl/TxQ.cpp @@ -30,6 +30,7 @@ #include #include #include +#include namespace ripple { @@ -1486,13 +1487,7 @@ TxQ::accept(Application& app, OpenView& view) if (app.getValidationPublicKey().empty()) break; - auto const& keys = app.getValidatorKeys(); - - if (keys.configInvalid()) - break; - // and if we're not on the UNLReport we also do nothing - auto const unlRep = view.read(keylet::UNLReport()); if (!unlRep || !unlRep->isFieldPresent(sfActiveValidators)) { @@ -1504,7 +1499,7 @@ TxQ::accept(Application& app, OpenView& view) auto const& avs = unlRep->getFieldArray(sfActiveValidators); for (auto const& av : avs) { - if (PublicKey(av[sfPublicKey]) == keys.masterPublicKey) + if (PublicKey(av[sfPublicKey]) == app.getValidationPublicKey()) { found = true; break; @@ -1516,7 +1511,7 @@ TxQ::accept(Application& app, OpenView& view) auto const seq = view.info().seq; - AccountID acc = calcAccountID(keys.masterPublicKey); + AccountID acc = calcAccountID(app.getValidationPublicKey()); static auto getRnd = []() -> uint256 { static std::ifstream rng("/dev/urandom", std::ios::binary); @@ -1540,7 +1535,7 @@ TxQ::accept(Application& app, OpenView& view) std::optional prevRnd; - if (rngMap.find(seq - 1)) + if (rngMap.find(seq - 1) != rngMap.end()) prevRnd = rngMap[seq - 1]; // amortized cleanup, for every ledger attempt to delete two old entries @@ -1557,15 +1552,16 @@ TxQ::accept(Application& app, OpenView& view) obj.setFieldU32(sfLedgerSequence, seq); obj.setAccountID(sfValidator, acc); if (prevRnd.has_value()) - obj.setFieldH256(sfLastSolution, *prevRnd); - obj.setFieldH256(sfDigest, sha512Half(nextRnd); + obj.setFieldH256(sfRandomData, *prevRnd); + obj.setFieldH256(sfNextRandomDigest, sha512Half(nextRnd)); + // RH TODO: should we sign this?? }); // submit to the ledger { uint256 txID = rngTx.getTransactionID(); auto s = std::make_shared(); - exportTx.add(*s); + rngTx.add(*s); app.getHashRouter().setFlags(txID, SF_PRIVATE2); app.getHashRouter().setFlags(txID, SF_EMITTED); view.rawTxInsert(txID, std::move(s), nullptr); diff --git a/src/ripple/app/tx/impl/Change.cpp b/src/ripple/app/tx/impl/Change.cpp index 95d925313..38289c125 100644 --- a/src/ripple/app/tx/impl/Change.cpp +++ b/src/ripple/app/tx/impl/Change.cpp @@ -96,6 +96,12 @@ Change::preflight(PreflightContext const& ctx) } } + if (ctx.tx.getTxnType() == ttRNG && !ctx.rules.enabled(featureRNG)) + { + JLOG(ctx.j.warn()) << "Change: FeatureRNG is not enabled."; + return temDISABLED; + } + return tesSUCCESS; } @@ -218,6 +224,125 @@ Change::doApply() } } +TER +Change::applyRNG() +{ + + auto const seq = view().info().seq; + + if (seq != ctx_.tx.getFieldU32(sfLedgerSequence)) + { + JLOG(j_.warn()) << "Change: ttRNG, wrong ledger seq=" << seq; + return tefFAILURE; + } + + auto sle = view().peek(keylet::random()); + + bool const created = !sle; + + if (created) + { + sle = std::make_shared(keylet::random()); + } + + auto lastSeq = created ? 0 : sle->getFieldU32(sfLedgerSequence); + + if (lastSeq < seq) + { + // update the ledger sequence of the object + sle->setFieldU32(sfLedgerSequence, seq); + + // reset entropy count to zero... this will probably be + // one after the below executes but its possible the digest + // doesn't match and the entropy count isn't incremented + sle->setFieldU16(sfEntropyCount, 0); + + // swap the random data out ready for this round of entropy collection + sle->setFieldH256(sfLastRandomData, sle->getFieldH256(sfRandomData)); + sle->setFieldH256(sfRandomData, beast::zero); + } + + uint256 nextDigest = ctx_.tx.getFieldH256(sfNextRandomDigest); + uint256 currentEntropy = ctx_.tx.getFieldH256(sfRandomData); + uint256 currentDigest = sha512Half(currentEntropy); + + AccountID const validator = ctx_.tx.getAccountID(sfValidator); + + // RH TODO: check if they're on the UNLReport and ignore if not + + // iterate the digest array to find the entry if it exists + STArray digestEntries = sle->getFieldArray(sfRandomDigests); + std::map entries; + + for (auto& entry : digestEntries) + { + // we'll automatically clean up really old entries by just omitting them from + // the map here + if (entry.getFieldU32(sfLedgerSequence) < seq - 5) + continue; + + entries.emplace(entry.getAccountID(sfValidator), std::move(entry)); + } + + if (auto it = entries.find(validator); it != entries.end()) + { + auto& entry = it->second; + + // ensure the precommitted digest matches the provided entropy + if (entry.getFieldH256(sfNextRandomDigest) != currentDigest) + { + if (entry.getFieldU32(sfLedgerSequence) != seq - 1) + { + // this is a skip-ahead or missed last txn somehow, so ignore, but no warning. + } + else + { + // this is a clear violation so warn (and ignore the entropy) + JLOG(j_.warn()) << "!!! Validator " << validator << " supplied entropy that " + << "does not match precommitment value !!!"; + } + } + else + { + + // contribute the new entropy to the random data field + sle->setFieldH256(sfRandomData, sha512Half(validator, sle->getFieldH256(sfRandomData), currentEntropy)); + + // increment entropy count + sle->setFieldU16(sfEntropyCount, sle->getFieldU16(sfEntropyCount) + 1); + } + + // update the digest entry + entry.setFieldH256(sfNextRandomDigest, nextDigest); + entry.setFieldU32(sfLedgerSequence, seq); + } + else + { + // this validator doesn't have an entry so create one + STObject entry{sfRandomDigestEntry}; + entry.setAccountID(sfValidator, validator); + entry.setFieldH256(sfNextRandomDigest, nextDigest); + entry.setFieldU32(sfLedgerSequence, seq); + entries.emplace(validator, std::move(entry)); + } + + // update the array + STArray newEntries(sfRandomDigests); + newEntries.reserve(entries.size()); + for (auto& [_, entry] : entries) + newEntries.push_back(std::move(entry)); + + sle->setFieldArray(sfRandomDigests, std::move(newEntries)); + + // send it off to the ledger + if (!created) + view().update(sle); + else + view().insert(sle); + + 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..4df511ca6 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 + applyRNG(); }; } // namespace ripple diff --git a/src/ripple/protocol/Indexes.h b/src/ripple/protocol/Indexes.h index 03f9dc2a4..b52c8f3fa 100644 --- a/src/ripple/protocol/Indexes.h +++ b/src/ripple/protocol/Indexes.h @@ -51,6 +51,9 @@ class SeqProxy; */ namespace keylet { +Keylet const& +random() noexcept; + /** The (fixed) index of the object containing the emitted txns for the ledger. */ Keylet const& diff --git a/src/ripple/protocol/LedgerFormats.h b/src/ripple/protocol/LedgerFormats.h index f87f7ae3f..1e6e24848 100644 --- a/src/ripple/protocol/LedgerFormats.h +++ b/src/ripple/protocol/LedgerFormats.h @@ -260,6 +260,13 @@ enum LedgerEntryType : std::uint16_t \sa keylet::emitted */ ltEMITTED_TXN = 'E', + + + /** A ledger object containing a consensus-generated random number, operated on by ttRNG + + \sa keylet::rng + */ + ltRANDOM = 0x526EU, // Rn }; // clang-format off diff --git a/src/ripple/protocol/SField.h b/src/ripple/protocol/SField.h index fbf181c57..bff308f3a 100644 --- a/src/ripple/protocol/SField.h +++ b/src/ripple/protocol/SField.h @@ -355,6 +355,7 @@ extern SF_UINT16 const sfHookEmitCount; extern SF_UINT16 const sfHookExecutionIndex; extern SF_UINT16 const sfHookApiVersion; extern SF_UINT16 const sfHookStateScale; +extern SF_UINT16 const sfEntropyCount; // 32-bit integers (common) extern SF_UINT32 const sfNetworkID; @@ -491,6 +492,9 @@ extern SF_UINT256 const sfGovernanceFlags; extern SF_UINT256 const sfGovernanceMarks; extern SF_UINT256 const sfEmittedTxnID; extern SF_UINT256 const sfCron; +extern SF_UINT256 const sfRandomData; +extern SF_UINT256 const sfLastRandomData; +extern SF_UINT256 const sfNextRandomDigest; // currency amount (common) extern SF_AMOUNT const sfAmount; @@ -606,6 +610,7 @@ extern SField const sfHookEmission; extern SField const sfMintURIToken; extern SField const sfAmountEntry; extern SField const sfRemark; +extern SField const sfRandomDigestEntry; // array of objects (common) // ARRAY/1 is reserved for end of array @@ -635,6 +640,7 @@ extern SField const sfImportVLKeys; extern SField const sfHookEmissions; extern SField const sfAmounts; extern SField const sfRemarks; +extern SField const sfRandomDigests; //------------------------------------------------------------------------------ diff --git a/src/ripple/protocol/impl/Indexes.cpp b/src/ripple/protocol/impl/Indexes.cpp index f75faf165..eb00941bb 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', + RANDOM = 0x526E, // Rn // 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& +random() noexcept +{ + static Keylet const ret{ + ltRANDOM, indexHash(LedgerNameSpace::RANDOM)}; + return ret; +} + } // namespace keylet } // namespace ripple diff --git a/src/ripple/protocol/impl/InnerObjectFormats.cpp b/src/ripple/protocol/impl/InnerObjectFormats.cpp index 4c2500ff2..cf0aec770 100644 --- a/src/ripple/protocol/impl/InnerObjectFormats.cpp +++ b/src/ripple/protocol/impl/InnerObjectFormats.cpp @@ -167,6 +167,14 @@ InnerObjectFormats::InnerObjectFormats() {sfRemarkValue, soeOPTIONAL}, {sfFlags, soeOPTIONAL}, }); + + add(sfRandomDigestEntry.jsonName.c_str(), + sfRandomDigestEntry.getCode(), + { + {sfValidator, soeREQUIRED}, + {sfNextRandomDigest, soeREQUIRED}, + {sfLedgerSequence, soeREQUIRED}, + }); } InnerObjectFormats const& diff --git a/src/ripple/protocol/impl/LedgerFormats.cpp b/src/ripple/protocol/impl/LedgerFormats.cpp index 5f51976be..3dbae56a1 100644 --- a/src/ripple/protocol/impl/LedgerFormats.cpp +++ b/src/ripple/protocol/impl/LedgerFormats.cpp @@ -381,6 +381,20 @@ LedgerFormats::LedgerFormats() }, commonFields); + add(jss::Random, + ltRANDOM, + { + {sfRandomData, soeREQUIRED}, + {sfLastRandomData, soeREQUIRED}, + {sfEntropyCount, soeREQUIRED}, + {sfRandomDigests, soeREQUIRED}, + {sfLedgerSequence, soeREQUIRED}, + {sfPreviousTxnID, soeREQUIRED}, + {sfPreviousTxnLgrSeq, soeREQUIRED}, + }, + commonFields); + + // clang-format on } diff --git a/src/ripple/protocol/impl/SField.cpp b/src/ripple/protocol/impl/SField.cpp index 4c9c8023a..2b16d01e0 100644 --- a/src/ripple/protocol/impl/SField.cpp +++ b/src/ripple/protocol/impl/SField.cpp @@ -103,6 +103,7 @@ CONSTRUCT_TYPED_SFIELD(sfHookEmitCount, "HookEmitCount", UINT16, CONSTRUCT_TYPED_SFIELD(sfHookExecutionIndex, "HookExecutionIndex", UINT16, 19); CONSTRUCT_TYPED_SFIELD(sfHookApiVersion, "HookApiVersion", UINT16, 20); CONSTRUCT_TYPED_SFIELD(sfHookStateScale, "HookStateScale", UINT16, 21); +CONSTRUCT_TYPED_SFIELD(sfEntropyCount, "EntropyCount", UINT16, 99); // 32-bit integers (common) CONSTRUCT_TYPED_SFIELD(sfNetworkID, "NetworkID", UINT32, 1); @@ -244,6 +245,9 @@ CONSTRUCT_TYPED_SFIELD(sfGovernanceMarks, "GovernanceMarks", UINT256, CONSTRUCT_TYPED_SFIELD(sfEmittedTxnID, "EmittedTxnID", UINT256, 97); CONSTRUCT_TYPED_SFIELD(sfHookCanEmit, "HookCanEmit", UINT256, 96); CONSTRUCT_TYPED_SFIELD(sfCron, "Cron", UINT256, 95); +CONSTRUCT_TYPED_SFIELD(sfRandomData, "RandomData", UINT256, 94); +CONSTRUCT_TYPED_SFIELD(sfLastRandomData, "LastRandomData", UINT256, 93); +CONSTRUCT_TYPED_SFIELD(sfNextRandomDigest, "NextRandomDigest", UINT256, 92); // currency amount (common) CONSTRUCT_TYPED_SFIELD(sfAmount, "Amount", AMOUNT, 1); @@ -362,6 +366,7 @@ CONSTRUCT_UNTYPED_SFIELD(sfImportVLKey, "ImportVLKey", OBJECT, CONSTRUCT_UNTYPED_SFIELD(sfHookEmission, "HookEmission", OBJECT, 93); CONSTRUCT_UNTYPED_SFIELD(sfMintURIToken, "MintURIToken", OBJECT, 92); CONSTRUCT_UNTYPED_SFIELD(sfAmountEntry, "AmountEntry", OBJECT, 91); +CONSTRUCT_UNTYPED_SFIELD(sfRandomDigestEntry, "RandomDigestEntry", OBJECT, 89); // array of objects // ARRAY/1 is reserved for end of array @@ -388,6 +393,7 @@ CONSTRUCT_UNTYPED_SFIELD(sfActiveValidators, "ActiveValidators", ARRAY, CONSTRUCT_UNTYPED_SFIELD(sfImportVLKeys, "ImportVLKeys", ARRAY, 94); CONSTRUCT_UNTYPED_SFIELD(sfHookEmissions, "HookEmissions", ARRAY, 93); CONSTRUCT_UNTYPED_SFIELD(sfAmounts, "Amounts", ARRAY, 92); +CONSTRUCT_UNTYPED_SFIELD(sfRandomDigests, "RandomDigests", ARRAY, 91); // clang-format on diff --git a/src/ripple/protocol/impl/STTx.cpp b/src/ripple/protocol/impl/STTx.cpp index 2fbb19421..4bdc1413b 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 == ttRNG; } } // namespace ripple diff --git a/src/ripple/protocol/impl/TxFormats.cpp b/src/ripple/protocol/impl/TxFormats.cpp index 789de36f2..ceeffd182 100644 --- a/src/ripple/protocol/impl/TxFormats.cpp +++ b/src/ripple/protocol/impl/TxFormats.cpp @@ -490,6 +490,16 @@ TxFormats::TxFormats() {sfStartTime, soeOPTIONAL}, }, commonFields); + + add(jss::Rng, + ttRNG, + { + {sfValidator, soeREQUIRED}, + {sfRandomData, soeREQUIRED}, + {sfNextRandomDigest, soeREQUIRED}, + {sfLedgerSequence, soeREQUIRED}, + }, + commonFields); } TxFormats const& diff --git a/src/ripple/protocol/jss.h b/src/ripple/protocol/jss.h index 316037268..b12c0025a 100644 --- a/src/ripple/protocol/jss.h +++ b/src/ripple/protocol/jss.h @@ -123,6 +123,8 @@ JSS(PaymentChannelCreate); // transaction type. JSS(PaymentChannelFund); // transaction type. JSS(Remit); // transaction type. JSS(RippleState); // ledger type. +JSS(Rng); +JSS(Random); JSS(SLE_hit_rate); // out: GetCounts. JSS(SetFee); // transaction type. JSS(SetRemarks); // transaction type