From b6a23d6d3bf76df42e34c4ad86cd5e6c7e63090a Mon Sep 17 00:00:00 2001 From: Richard Holland Date: Sat, 3 Jan 2026 15:19:03 +1100 Subject: [PATCH] ttENTROPY working --- src/ripple/app/misc/impl/TxQ.cpp | 11 +++- .../app/rdb/backend/detail/impl/Node.cpp | 2 +- src/ripple/app/tx/impl/Entropy.cpp | 62 ++++++++----------- src/ripple/app/tx/impl/Transactor.cpp | 23 +++++-- src/ripple/protocol/STTx.h | 3 + src/ripple/protocol/impl/STTx.cpp | 18 +++++- src/ripple/protocol/impl/TxFormats.cpp | 1 - 7 files changed, 74 insertions(+), 46 deletions(-) diff --git a/src/ripple/app/misc/impl/TxQ.cpp b/src/ripple/app/misc/impl/TxQ.cpp index f1737fa8c..adcc8c9b4 100644 --- a/src/ripple/app/misc/impl/TxQ.cpp +++ b/src/ripple/app/misc/impl/TxQ.cpp @@ -1931,13 +1931,18 @@ TxQ::tryDirectApply( const bool isFirstImport = !sleAccount && view.rules().enabled(featureImport) && tx->getTxnType() == ttIMPORT; + const bool isUV = + view.rules().enabled(featureRNG) && tx->getTxnType() == ttENTROPY; + + const bool accRequired = !(isFirstImport || isUV); + // Don't attempt to direct apply if the account is not in the ledger. - if (!sleAccount && !isFirstImport) + if (!sleAccount && accRequired) return {}; std::optional txSeqProx; - if (!isFirstImport) + if (accRequired) { SeqProxy const acctSeqProx = SeqProxy::sequence((*sleAccount)[sfSequence]); @@ -1950,7 +1955,7 @@ TxQ::tryDirectApply( } FeeLevel64 const requiredFeeLevel = - isFirstImport ? FeeLevel64{0} : [this, &view, flags]() { + !accRequired ? FeeLevel64{0} : [this, &view, flags]() { std::lock_guard lock(mutex_); return getRequiredFeeLevel( view, flags, feeMetrics_.getSnapshot(), lock); diff --git a/src/ripple/app/rdb/backend/detail/impl/Node.cpp b/src/ripple/app/rdb/backend/detail/impl/Node.cpp index c80038ef7..401c86902 100644 --- a/src/ripple/app/rdb/backend/detail/impl/Node.cpp +++ b/src/ripple/app/rdb/backend/detail/impl/Node.cpp @@ -319,7 +319,7 @@ saveValidatedLedger( *db << sql; } else if (auto const& sleTxn = acceptedLedgerTx->getTxn(); - !isPseudoTx(*sleTxn)) + !isPseudoTx(*sleTxn) && !isUVTx(*sleTxn)) { // It's okay for pseudo transactions to not affect any // accounts. But otherwise... diff --git a/src/ripple/app/tx/impl/Entropy.cpp b/src/ripple/app/tx/impl/Entropy.cpp index c464fed3c..9367a5c78 100644 --- a/src/ripple/app/tx/impl/Entropy.cpp +++ b/src/ripple/app/tx/impl/Entropy.cpp @@ -23,6 +23,7 @@ #include #include #include +#include namespace ripple { @@ -47,17 +48,16 @@ Entropy::preclaim(PreclaimContext const& ctx) if (!ctx.view.rules().enabled(featureRNG)) return temDISABLED; - auto const seq = ctx.view.info().seq; +// auto const seq = ctx.view.info().seq; - auto const txLgrSeq = ctx.tx[sfLedgerSequence]; +// auto const txLgrSeq = ctx.tx[sfLedgerSequence]; - // we will not process and can never accept any txns that aren't introduced this ledger - // for this ledger. - if (seq != txLgrSeq) - { - JLOG(ctx.j.warn()) << "Entropy: wrong ledger seq=" << seq; - return tefFAILURE; - } + // due to circulation we'll accept up to one ledger old entropy txns +// if (seq != txLgrSeq) +// { +// JLOG(ctx.j.warn()) << "Entropy: wrong ledger txseq=" << txLgrSeq << " lgrseq=" << seq << " acc:" << ctx.tx.getAccountID(sfAccount); +// return tefFAILURE; +// } // account must be a valid UV if (!inUNLReport(ctx.view, ctx.tx.getAccountID(sfAccount), ctx.j)) @@ -76,10 +76,10 @@ Entropy::doApply() auto const seq = view().info().seq; - if (seq != ctx_.tx.getFieldU32(sfLedgerSequence)) - { - return tefFAILURE; - } +// if (seq != ctx_.tx.getFieldU32(sfLedgerSequence)) +// { +// return tefFAILURE; +// } auto sle = view().peek(keylet::random()); @@ -268,43 +268,33 @@ makeEntropyTxn(OpenView& view, Application& app, beast::Journal const& j_) return out; }; - static std::map rngMap; + + static std::optional prevRnd; uint256 nextRnd = getRnd(); - if (rngMap.find(seq) != rngMap.end()) - return {}; - - rngMap[seq] = nextRnd; - - std::optional prevRnd; - - if (rngMap.find(seq - 1) != rngMap.end()) - prevRnd = rngMap[seq - 1]; - - // amortized cleanup, for every ledger attempt to delete two old entries - // even in the most desynced ridiculous state this is guaranteed prevent map growth - for (int i = 0; i < 2; ++i) - { - auto it = rngMap.begin(); - if (it != rngMap.end() && it->first < seq) - rngMap.erase(it->first); - } - // create txn auto rngTx = std::make_shared(ttENTROPY, [&](auto& obj) { - obj.setFieldU32(sfLedgerSequence, seq); + obj.setFieldU32(sfLastLedgerSequence, seq); obj.setAccountID(sfAccount, acc); obj.setFieldU32(sfSequence, 0); if (prevRnd.has_value()) obj.setFieldH256(sfRandomData, *prevRnd); obj.setFieldH256(sfNextRandomDigest, sha512Half(nextRnd)); - obj.setFieldVL(sfSigningPubKey, pk.slice()); + obj.setFieldVL(sfSigningPubKey, pkSigning.slice()); obj.setFieldH256(sfParentHash, view.info().parentHash); + obj.setFieldU32(sfFlags, tfFullyCanonicalSig); + + if (app.config().NETWORK_ID > 1024) + obj.setFieldU32(sfNetworkID, app.config().NETWORK_ID); }); + prevRnd = nextRnd; + // sign the txn using our ephemeral key - rngTx->sign(pk, app.getValidationSecretKey()); + rngTx->sign(pkSigning, app.getValidationSecretKey()); + + JLOG(j_.debug()) << "ENTROPY txn: " << rngTx->getFullText(); return rngTx; } diff --git a/src/ripple/app/tx/impl/Transactor.cpp b/src/ripple/app/tx/impl/Transactor.cpp index ba76ed2ca..a50fa47ca 100644 --- a/src/ripple/app/tx/impl/Transactor.cpp +++ b/src/ripple/app/tx/impl/Transactor.cpp @@ -280,13 +280,18 @@ Transactor::calculateBaseFee(ReadView const& view, STTx const& tx) // * The additional cost of each multisignature on the transaction. XRPAmount baseFee = view.fees().base; - if (tx.getFieldU16(sfTransactionType) == ttIMPORT) + auto const tt = tx.getFieldU16(sfTransactionType); + + if (tt == ttIMPORT) { XRPAmount const importFee = baseFee * 10; if (importFee > baseFee) baseFee = importFee; } + if (tt == ttENTROPY) + return XRPAmount{0}; + // Each signer adds one more baseFee to the minimum required fee // for the transaction. std::size_t const signerCount = @@ -476,6 +481,9 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee) auto const sle = ctx.view.read(keylet::account(id)); if (!sle) { + if (ctx.tx.getTxnType() == ttENTROPY) + return tesSUCCESS; + if (ctx.tx.getTxnType() == ttIMPORT) { if (!ctx.tx.isFieldPresent(sfIssuer)) @@ -650,7 +658,13 @@ Transactor::checkPriorTxAndLastLedger(PreclaimContext const& ctx) ctx.view.rules().enabled(featureImport) && ctx.tx.getTxnType() == ttIMPORT && !ctx.tx.isFieldPresent(sfIssuer); - if (!sle && !isFirstImport) + bool const isUV = + ctx.view.rules().enabled(featureRNG) && + ctx.tx.getTxnType() == ttENTROPY; + + bool const accRequired = !(isFirstImport || isUV); + + if (!sle && accRequired) { JLOG(ctx.j.trace()) << "applyTransaction: delay: source account does not exist " @@ -806,12 +820,13 @@ Transactor::apply() auto const sle = view().peek(keylet::account(account_)); // sle must exist except for transactions - // that allow zero account. (and ttIMPORT) + // that allow zero account. (and ttIMPORT and UV) assert( sle != nullptr || account_ == beast::zero || view().rules().enabled(featureImport) && ctx_.tx.getTxnType() == ttIMPORT && - !ctx_.tx.isFieldPresent(sfIssuer)); + !ctx_.tx.isFieldPresent(sfIssuer) || + ctx_.tx.getTxnType() == ttENTROPY); if (sle) { diff --git a/src/ripple/protocol/STTx.h b/src/ripple/protocol/STTx.h index c6a9e053c..e8b7d80cb 100644 --- a/src/ripple/protocol/STTx.h +++ b/src/ripple/protocol/STTx.h @@ -171,6 +171,9 @@ sterilize(STTx const& stx); bool isPseudoTx(STObject const& tx); +bool +isUVTx(STObject const& tx); + inline STTx::STTx(SerialIter&& sit) : STTx(sit) { } diff --git a/src/ripple/protocol/impl/STTx.cpp b/src/ripple/protocol/impl/STTx.cpp index b16fe8e0d..06d14932a 100644 --- a/src/ripple/protocol/impl/STTx.cpp +++ b/src/ripple/protocol/impl/STTx.cpp @@ -324,13 +324,18 @@ STTx::checkSingleSign(RequireFullyCanonicalSig requireCanonicalSig) const fullyCanonical); } } - catch (std::exception const&) + catch (std::exception const & e) { // Assume it was a signature failure. validSig = false; + + std::cout << "Invalid cause: " << e.what() << "\n"; } if (validSig == false) + { + std::cout << "Invalid signature on tx: " << this->getFullText() << "\n"; return Unexpected("Invalid signature."); + } // Signature was verified. return {}; } @@ -618,4 +623,15 @@ isPseudoTx(STObject const& tx) tt == ttEMIT_FAILURE || tt == ttUNL_REPORT || tt == ttCRON || tt == ttSHUFFLE; } +bool +isUVTx(STObject const& tx) +{ + auto t = tx[~sfTransactionType]; + if (!t) + return false; + + auto tt = safe_cast(*t); + return tt == ttENTROPY; +} + } // namespace ripple diff --git a/src/ripple/protocol/impl/TxFormats.cpp b/src/ripple/protocol/impl/TxFormats.cpp index c1a483811..9691e4e29 100644 --- a/src/ripple/protocol/impl/TxFormats.cpp +++ b/src/ripple/protocol/impl/TxFormats.cpp @@ -496,7 +496,6 @@ TxFormats::TxFormats() { {sfRandomData, soeREQUIRED}, {sfNextRandomDigest, soeREQUIRED}, - {sfLedgerSequence, soeREQUIRED}, {sfParentHash, soeREQUIRED}, }, commonFields);