diff --git a/Builds/CMake/RippledCore.cmake b/Builds/CMake/RippledCore.cmake index 74ff58f60..4652c453e 100644 --- a/Builds/CMake/RippledCore.cmake +++ b/Builds/CMake/RippledCore.cmake @@ -461,6 +461,7 @@ target_sources (rippled PRIVATE src/ripple/app/tx/impl/CronSet.cpp src/ripple/app/tx/impl/DeleteAccount.cpp src/ripple/app/tx/impl/DepositPreauth.cpp + src/ripple/app/tx/impl/Entropy.cpp src/ripple/app/tx/impl/Escrow.cpp src/ripple/app/tx/impl/GenesisMint.cpp src/ripple/app/tx/impl/Import.cpp diff --git a/src/ripple/app/consensus/RCLConsensus.cpp b/src/ripple/app/consensus/RCLConsensus.cpp index 4fb0c5275..81230e8d8 100644 --- a/src/ripple/app/consensus/RCLConsensus.cpp +++ b/src/ripple/app/consensus/RCLConsensus.cpp @@ -44,6 +44,8 @@ #include #include #include +#include +#include #include #include @@ -225,6 +227,8 @@ RCLConsensus::Adaptor::propose(RCLCxPeerPos::Proposal const& proposal) prop.set_signature(sig.data(), sig.size()); + injectShuffleTxn(app_, sig); + auto const suppression = proposalUniqueId( proposal.position(), proposal.prevLedger(), @@ -652,6 +656,12 @@ RCLConsensus::Adaptor::doAccept( tapNONE, "consensus", [&](OpenView& view, beast::Journal j) { + if (rules->enabled(featureRNG)) + { + auto tx = makeEntropyTxn(view, app_, j_); + if (tx) + app_.getOPs().submitTransaction(tx); + } return app_.getTxQ().accept(app_, view); }); diff --git a/src/ripple/app/ledger/impl/BuildLedger.cpp b/src/ripple/app/ledger/impl/BuildLedger.cpp index 85ed72106..e21ec4b00 100644 --- a/src/ripple/app/ledger/impl/BuildLedger.cpp +++ b/src/ripple/app/ledger/impl/BuildLedger.cpp @@ -104,12 +104,15 @@ applyTransactions( bool certainRetry = true; std::size_t count = 0; + // apply the ttSHUFFLE txns first in the ledger to + // ensure no one can predict the outcome + // then apply ttENTROPY transactions if (view.rules().enabled(featureRNG)) + for (auto tt : {ttSHUFFLE, ttENTROPY}) { - // apply the ttRNG txns first in the ledger to ensure no one can predict the outcome for (auto it = txns.begin(); it != txns.end();) { - if (it->second->getFieldU16(sfTransactionType) != ttRNG) + if (tt != it->second->getFieldU16(sfTransactionType)) { ++it; continue; diff --git a/src/ripple/app/main/Application.cpp b/src/ripple/app/main/Application.cpp index 20427107b..0268ec626 100644 --- a/src/ripple/app/main/Application.cpp +++ b/src/ripple/app/main/Application.cpp @@ -599,6 +599,12 @@ public: return validatorKeys_.publicKey; } + SecretKey const& + getValidationSecretKey() const override + { + return validatorKeys_.secretKey; + } + NetworkOPs& getOPs() override { diff --git a/src/ripple/app/main/Application.h b/src/ripple/app/main/Application.h index a6e58b19e..3c45f67e6 100644 --- a/src/ripple/app/main/Application.h +++ b/src/ripple/app/main/Application.h @@ -240,6 +240,9 @@ public: virtual PublicKey const& getValidationPublicKey() const = 0; + + virtual SecretKey const& + getValidationSecretKey() const = 0; virtual Resource::Manager& getResourceManager() = 0; diff --git a/src/ripple/app/misc/NetworkOPs.cpp b/src/ripple/app/misc/NetworkOPs.cpp index 0e5b8ef5f..f27fe4970 100644 --- a/src/ripple/app/misc/NetworkOPs.cpp +++ b/src/ripple/app/misc/NetworkOPs.cpp @@ -228,7 +228,7 @@ public: doTransactionSync( std::shared_ptr transaction, bool bUnlimited, - FailHard failType); + FailHard failType) override; /** * For transactions not submitted by a locally connected client, fire and diff --git a/src/ripple/app/misc/NetworkOPs.h b/src/ripple/app/misc/NetworkOPs.h index 350542404..630808b85 100644 --- a/src/ripple/app/misc/NetworkOPs.h +++ b/src/ripple/app/misc/NetworkOPs.h @@ -112,6 +112,14 @@ public: bool bLocal, FailHard failType) = 0; + + // directly inject transaction, skipping checks + virtual void + doTransactionSync( + std::shared_ptr transaction, + bool bUnlimited, + FailHard failType) = 0; + //-------------------------------------------------------------------------- // // Owner functions diff --git a/src/ripple/app/misc/impl/TxQ.cpp b/src/ripple/app/misc/impl/TxQ.cpp index 6e02d9852..f1737fa8c 100644 --- a/src/ripple/app/misc/impl/TxQ.cpp +++ b/src/ripple/app/misc/impl/TxQ.cpp @@ -1478,117 +1478,6 @@ TxQ::accept(Application& app, OpenView& view) } } - // Inject an RNG psuedo if we're on the UNL - if (view.rules().enabled(featureRNG)) - { - JLOG(j_.debug()) - << "RNG processing: started"; - - auto const seq = view.info().seq; - - do - { - // if we're not a validator we do nothing here - if (app.getValidationPublicKey().empty()) - break; - - if (seq > 256) // only do the UNL check if its not a newly created test network - { - // and if we're not on the UNLReport we also do nothing - auto const unlRep = view.read(keylet::UNLReport()); - if (!unlRep || !unlRep->isFieldPresent(sfActiveValidators)) - { - JLOG(j_.debug()) - << "RNG processing: UNLReport misssing"; - // nothing to do without a unlreport object - break; - } - - bool found = false; - auto const& avs = unlRep->getFieldArray(sfActiveValidators); - for (auto const& av : avs) - { - if (PublicKey(av[sfPublicKey]) == app.getValidationPublicKey()) - { - found = true; - break; - } - } - - if (!found) - { - JLOG(j_.debug()) - << "RNG processing: UNLReport present but we're not on it"; - break; - } - } - - - AccountID acc = calcAccountID(app.getValidationPublicKey()); - - static auto getRnd = []() -> uint256 { - static std::ifstream rng("/dev/urandom", std::ios::binary); - uint256 out; - if (rng && rng.read(reinterpret_cast(out.data()), 32)) - return out; - std::random_device rd; - for (auto& word : out) - word = rd(); - return out; - }; - - static std::map rngMap; - - uint256 nextRnd = getRnd(); - - if (rngMap.find(seq) != rngMap.end()) - break; // should never happen - - 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 - STTx rngTx(ttRNG, [&](auto& obj) { - obj.setFieldU32(sfLedgerSequence, seq); - obj.setAccountID(sfValidator, acc); - if (prevRnd.has_value()) - obj.setFieldH256(sfRandomData, *prevRnd); - obj.setFieldH256(sfNextRandomDigest, sha512Half(nextRnd)); - // RH TODO: should we sign this?? - }); - - // submit to the ledger - { - uint256 txID = rngTx.getTransactionID(); - - JLOG(j_.debug()) - << "RNG processing: Submitting pseudo: " - << rngTx.getFullText() - << " txid: " << txID; - auto s = std::make_shared(); - rngTx.add(*s); - app.getHashRouter().setFlags(txID, SF_PRIVATE2); - app.getHashRouter().setFlags(txID, SF_EMITTED); - view.rawTxInsert(txID, std::move(s), nullptr); - ledgerChanged = true; - } - - } while (0); - } - // Inject cron transactions, if any if (view.rules().enabled(featureCron)) { diff --git a/src/ripple/app/tx/impl/Change.cpp b/src/ripple/app/tx/impl/Change.cpp index 38289c125..074d052ee 100644 --- a/src/ripple/app/tx/impl/Change.cpp +++ b/src/ripple/app/tx/impl/Change.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include namespace ripple { @@ -96,7 +97,7 @@ Change::preflight(PreflightContext const& ctx) } } - if (ctx.tx.getTxnType() == ttRNG && !ctx.rules.enabled(featureRNG)) + if (ctx.tx.getTxnType() == ttSHUFFLE && !ctx.rules.enabled(featureRNG)) { JLOG(ctx.j.warn()) << "Change: FeatureRNG is not enabled."; return temDISABLED; @@ -110,7 +111,7 @@ Change::preclaim(PreclaimContext const& ctx) { // If tapOPEN_LEDGER is resurrected into ApplyFlags, // this block can be moved to preflight. - if (ctx.view.open()) + if (ctx.view.open() && ctx.tx.getTxnType() != ttSHUFFLE) { JLOG(ctx.j.warn()) << "Change transaction against open ledger"; return temINVALID; @@ -160,7 +161,7 @@ Change::preclaim(PreclaimContext const& ctx) case ttAMENDMENT: case ttUNL_MODIFY: case ttEMIT_FAILURE: - case ttRNG: + case ttSHUFFLE: return tesSUCCESS; case ttUNL_REPORT: { if (!ctx.tx.isFieldPresent(sfImportVLKey) || @@ -216,8 +217,8 @@ Change::doApply() return applyEmitFailure(); case ttUNL_REPORT: return applyUNLReport(); - case ttRNG: - return applyRNG(); + case ttSHUFFLE: + return applyShuffle(); default: assert(0); return tefFAILURE; @@ -225,14 +226,15 @@ Change::doApply() } TER -Change::applyRNG() +Change::applyShuffle() { auto const seq = view().info().seq; + auto const txSeq = ctx_.tx.getFieldU32(sfLedgerSequence); - if (seq != ctx_.tx.getFieldU32(sfLedgerSequence)) + if (seq != txSeq) { - JLOG(j_.warn()) << "Change: ttRNG, wrong ledger seq=" << seq; + JLOG(j_.warn()) << "Change: ttSHUFFLE, wrong ledger seq. lgr=" << seq << " tx=" << txSeq; return tefFAILURE; } @@ -249,9 +251,6 @@ Change::applyRNG() 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 @@ -259,82 +258,25 @@ Change::applyRNG() // 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); + + // update the ledger sequence of the object + sle->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)); + // increment entropy count + sle->setFieldU16(sfEntropyCount, sle->getFieldU16(sfEntropyCount) + 1); } + + // contribute the new entropy to the random data field + sle->setFieldH256(sfRandomData, + sha512Half( + seq, + sle->getFieldU16(sfEntropyCount), + sle->getFieldH256(sfRandomData), + ctx_.tx.getFieldH256(sfRandomData))); - // 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 @@ -1327,4 +1269,49 @@ Change::applyUNLModify() return tesSUCCESS; } +void +injectShuffleTxn(Application& app, Slice const& sig) +{ + // in featureRNG we use trusted proposal signatures as shuffling entropy + // so inject a psuedo to do that here + auto ol = app.openLedger().current(); + if (ol && ol->rules().enabled(featureRNG)) + { + uint256 rnd = sha512Half(std::string("shuffler"), sig); + // create txn + STTx shuffleTx (ttSHUFFLE, [&](auto& obj) { + obj.setFieldU32(sfLedgerSequence, ol->info().seq + 1); + obj.setFieldH256(sfRandomData, rnd); + obj.setAccountID(sfAccount, AccountID()); + }); + + // inject it into the propose set and into the open ledger + uint256 txID = shuffleTx.getTransactionID(); + + JLOG(app.journal("Transaction").debug()) + << "SHUFFLE processing: Submitting pseudo: " + << shuffleTx.getFullText() + << " txid: " << txID; + app.getHashRouter().setFlags(txID, SF_PRIVATE2); + app.getHashRouter().setFlags(txID, SF_EMITTED); + + { + auto s = std::make_shared(); + shuffleTx.add(*s); + + std::unique_lock masterLock{app.getMasterMutex(), std::defer_lock}; + + std::unique_lock ledgerLock{ + app.getLedgerMaster().peekMutex(), std::defer_lock}; + std::lock(masterLock, ledgerLock); + + app.openLedger().modify([&](OpenView& view, beast::Journal j) { + view.rawTxInsert(txID, std::move(s), nullptr); + return true; + }); + + } + } +} + } // namespace ripple diff --git a/src/ripple/app/tx/impl/Change.h b/src/ripple/app/tx/impl/Change.h index 4df511ca6..748e7aa32 100644 --- a/src/ripple/app/tx/impl/Change.h +++ b/src/ripple/app/tx/impl/Change.h @@ -78,9 +78,12 @@ private: applyUNLReport(); TER - applyRNG(); + applyShuffle(); }; +void +injectShuffleTxn(Application& app, Slice const& sig); + } // namespace ripple #endif diff --git a/src/ripple/app/tx/impl/Transactor.cpp b/src/ripple/app/tx/impl/Transactor.cpp index 80646172a..ba76ed2ca 100644 --- a/src/ripple/app/tx/impl/Transactor.cpp +++ b/src/ripple/app/tx/impl/Transactor.cpp @@ -442,9 +442,24 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee) // Only check fee is sufficient when the ledger is open. if (ctx.view.open()) { - auto const feeDue = + + auto feeDue = minimumFee(ctx.app, baseFee, ctx.view.fees(), ctx.flags); + if (ctx.view.rules().enabled(featureRNG)) + { + auto const pkSignerField = ctx.tx.getSigningPubKey(); + if (publicKeyType(makeSlice(pkSignerField))) + { + PublicKey pkSigner {makeSlice(pkSignerField)}; + if (inUNLReport(ctx.view, ctx.app, pkSigner, ctx.j)) + { + // UVTxns don't have to pay a fee + feeDue = beast::zero; + } + } + } + if (feePaid < feeDue) { JLOG(ctx.j.trace()) @@ -541,6 +556,16 @@ Transactor::checkSeqProxy( return tesSUCCESS; } + if (view.rules().enabled(featureRNG) && + tx.getTxnType() == ttENTROPY && t_seqProx.isSeq() && + tx[sfSequence] == 0) + { + JLOG(j.trace()) + << "applyTransaction: allowing ttENTROPY with seq=0 " + << toBase58(id); + return tesSUCCESS; + } + JLOG(j.trace()) << "applyTransaction: delay: source account does not exist " << toBase58(id); @@ -848,19 +873,32 @@ NotTEC Transactor::checkSingleSign(PreclaimContext const& ctx) { // Check that the value in the signing key slot is a public key. - auto const pkSigner = ctx.tx.getSigningPubKey(); - if (!publicKeyType(makeSlice(pkSigner))) + auto const pkSignerField = ctx.tx.getSigningPubKey(); + if (!publicKeyType(makeSlice(pkSignerField))) { JLOG(ctx.j.trace()) << "checkSingleSign: signing public key type is unknown"; return tefBAD_AUTH; // FIXME: should be better error! } + PublicKey pkSigner {makeSlice(pkSignerField)}; + // Look up the account. - auto const idSigner = calcAccountID(PublicKey(makeSlice(pkSigner))); + auto const idSigner = calcAccountID(pkSigner); auto const idAccount = ctx.tx.getAccountID(sfAccount); auto const sleAccount = ctx.view.read(keylet::account(idAccount)); + // check if this is a UVTxn (UNL Validator transaction) + // these are signed by the UNL validators + if (ctx.view.rules().enabled(featureRNG)) + { + // UVTxns of the approved type don't need an underlying account + // and can be signed with the manifest ephemeral key + if (inUNLReport(ctx.view, ctx.app, pkSigner, ctx.j) && + ctx.tx.getTxnType() == ttENTROPY) + return tesSUCCESS; + } + if (!sleAccount) return terNO_ACCOUNT; diff --git a/src/ripple/app/tx/impl/applySteps.cpp b/src/ripple/app/tx/impl/applySteps.cpp index 50818caf8..57b6e6d68 100644 --- a/src/ripple/app/tx/impl/applySteps.cpp +++ b/src/ripple/app/tx/impl/applySteps.cpp @@ -52,6 +52,7 @@ #include #include #include +#include namespace ripple { @@ -152,7 +153,7 @@ invoke_preflight(PreflightContext const& ctx) case ttUNL_MODIFY: case ttUNL_REPORT: case ttEMIT_FAILURE: - case ttRNG: + case ttSHUFFLE: return invoke_preflight_helper(ctx); case ttHOOK_SET: return invoke_preflight_helper(ctx); @@ -188,6 +189,8 @@ invoke_preflight(PreflightContext const& ctx) return invoke_preflight_helper(ctx); case ttCRON: return invoke_preflight_helper(ctx); + case ttENTROPY: + return invoke_preflight_helper(ctx); default: assert(false); return {temUNKNOWN, TxConsequences{temUNKNOWN}}; @@ -284,7 +287,7 @@ invoke_preclaim(PreclaimContext const& ctx) case ttUNL_MODIFY: case ttUNL_REPORT: case ttEMIT_FAILURE: - case ttRNG: + case ttSHUFFLE: return invoke_preclaim(ctx); case ttNFTOKEN_MINT: return invoke_preclaim(ctx); @@ -318,6 +321,8 @@ invoke_preclaim(PreclaimContext const& ctx) return invoke_preclaim(ctx); case ttCRON: return invoke_preclaim(ctx); + case ttENTROPY: + return invoke_preclaim(ctx); default: assert(false); return temUNKNOWN; @@ -376,7 +381,7 @@ invoke_calculateBaseFee(ReadView const& view, STTx const& tx) case ttUNL_MODIFY: case ttUNL_REPORT: case ttEMIT_FAILURE: - case ttRNG: + case ttSHUFFLE: return Change::calculateBaseFee(view, tx); case ttNFTOKEN_MINT: return NFTokenMint::calculateBaseFee(view, tx); @@ -410,6 +415,8 @@ invoke_calculateBaseFee(ReadView const& view, STTx const& tx) return CronSet::calculateBaseFee(view, tx); case ttCRON: return Cron::calculateBaseFee(view, tx); + case ttENTROPY: + return Entropy::calculateBaseFee(view, tx); default: return XRPAmount{0}; } @@ -547,7 +554,7 @@ invoke_apply(ApplyContext& ctx) case ttFEE: case ttUNL_MODIFY: case ttUNL_REPORT: - case ttRNG: + case ttSHUFFLE: case ttEMIT_FAILURE: { Change p(ctx); return p(); @@ -612,6 +619,10 @@ invoke_apply(ApplyContext& ctx) Cron p(ctx); return p(); } + case ttENTROPY: { + Entropy p(ctx); + return p(); + } default: assert(false); return {temUNKNOWN, false}; diff --git a/src/ripple/consensus/Consensus.h b/src/ripple/consensus/Consensus.h index df5ec01ce..e7581cf18 100644 --- a/src/ripple/consensus/Consensus.h +++ b/src/ripple/consensus/Consensus.h @@ -33,6 +33,10 @@ #include #include #include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/ledger/View.h b/src/ripple/ledger/View.h index 27659ad8d..a6990a47b 100644 --- a/src/ripple/ledger/View.h +++ b/src/ripple/ledger/View.h @@ -36,6 +36,8 @@ #include #include #include +#include +#include #include #include #include @@ -1094,6 +1096,70 @@ trustTransferLockedBalance( } return tesSUCCESS; } + +template +bool inUNLReport( + V const& view, + AccountID const& id, + beast::Journal const& j) +{ + + auto const seq = view.info().seq; + static uint32_t lastLgrSeq = 0; + static std::map cache; + + // for the first 256 ledgers we're just saying everyone is in the UNLReport + // because otherwise testing is very difficult. + if (seq < 256) + return true; + + if (lastLgrSeq != seq) + { + cache.clear(); + lastLgrSeq = seq; + } + else + { + if (cache.find(id) != cache.end()) + return cache[id]; + } + + // Check if UVAcc is on UNLReport we also do nothing + auto const unlRep = view.read(keylet::UNLReport()); + if (!unlRep || !unlRep->isFieldPresent(sfActiveValidators)) + { + JLOG(j.debug()) + << "UNLReport misssing"; + + // ensure we keep the cache invalid when in this state + lastLgrSeq = 0; + return false; + } + + auto const& avs = unlRep->getFieldArray(sfActiveValidators); + for (auto const& av : avs) + { + if (av.getAccountID(sfAccount) == id) + return cache[id] = true; + } + + return cache[id] = false; +} + + +template +bool inUNLReport( + V const& view, + Application& app, + PublicKey const& pk, + beast::Journal const& j) +{ + PublicKey uvPk = app.validatorManifests().getMasterKey(pk); + + return inUNLReport(view, calcAccountID(pk), j) || + (uvPk != pk && inUNLReport(view, calcAccountID(uvPk), j)); +} + } // namespace ripple #endif diff --git a/src/ripple/overlay/impl/PeerImp.cpp b/src/ripple/overlay/impl/PeerImp.cpp index adb05ddf6..3f4ef03b6 100644 --- a/src/ripple/overlay/impl/PeerImp.cpp +++ b/src/ripple/overlay/impl/PeerImp.cpp @@ -40,6 +40,8 @@ #include #include #include +#include +#include #include #include @@ -1955,6 +1957,9 @@ PeerImp::onMessage(std::shared_ptr const& m) if (!isTrusted && app_.config().RELAY_UNTRUSTED_PROPOSALS == -1) return; + // ttSHUFFLE is injected as part of featureRNG, based on the proposal signature + injectShuffleTxn(app_, makeSlice(set.signature())); + uint256 const proposeHash{set.currenttxhash()}; uint256 const prevLedger{set.previousledger()}; diff --git a/src/ripple/protocol/LedgerFormats.h b/src/ripple/protocol/LedgerFormats.h index 1e6e24848..7135dbfe4 100644 --- a/src/ripple/protocol/LedgerFormats.h +++ b/src/ripple/protocol/LedgerFormats.h @@ -262,7 +262,7 @@ enum LedgerEntryType : std::uint16_t ltEMITTED_TXN = 'E', - /** A ledger object containing a consensus-generated random number, operated on by ttRNG + /** A ledger object containing a consensus-generated random number, operated on by ttENTROPY \sa keylet::rng */ diff --git a/src/ripple/protocol/TxFormats.h b/src/ripple/protocol/TxFormats.h index 54ec86e6d..e62fdbba7 100644 --- a/src/ripple/protocol/TxFormats.h +++ b/src/ripple/protocol/TxFormats.h @@ -149,9 +149,13 @@ enum TxType : std::uint16_t ttURITOKEN_CREATE_SELL_OFFER = 48, ttURITOKEN_CANCEL_SELL_OFFER = 49, - /* A pseudo-txn used by featureRNG which allows validators to submit blinded entropy + /* A pseudo-txn type used by featureRNG to shuffle and randomise the transaction set based on proposal + * signatures */ + ttSHUFFLE = 88, + + /* A UNLReport-validator only txn by featureRNG which allows validators to submit blinded entropy * to a consensus based random number system */ - ttRNG = 89, + ttENTROPY = 89, /* A pseudo-txn alarm signal for invoking a hook, emitted by validators after alarm set conditions are met */ ttCRON = 92, diff --git a/src/ripple/protocol/impl/STTx.cpp b/src/ripple/protocol/impl/STTx.cpp index 4bdc1413b..b16fe8e0d 100644 --- a/src/ripple/protocol/impl/STTx.cpp +++ b/src/ripple/protocol/impl/STTx.cpp @@ -615,8 +615,7 @@ 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 == ttRNG; + tt == ttEMIT_FAILURE || tt == ttUNL_REPORT || tt == ttCRON || tt == ttSHUFFLE; } } // namespace ripple diff --git a/src/ripple/protocol/impl/TxFormats.cpp b/src/ripple/protocol/impl/TxFormats.cpp index ceeffd182..c1a483811 100644 --- a/src/ripple/protocol/impl/TxFormats.cpp +++ b/src/ripple/protocol/impl/TxFormats.cpp @@ -491,15 +491,24 @@ TxFormats::TxFormats() }, commonFields); - add(jss::Rng, - ttRNG, + add(jss::Entropy, + ttENTROPY, { - {sfValidator, soeREQUIRED}, {sfRandomData, soeREQUIRED}, {sfNextRandomDigest, soeREQUIRED}, {sfLedgerSequence, soeREQUIRED}, + {sfParentHash, soeREQUIRED}, }, commonFields); + + add(jss::Shuffle, + ttSHUFFLE, + { + {sfLedgerSequence, soeREQUIRED}, + {sfRandomData, soeREQUIRED}, + }, + commonFields); + } TxFormats const& diff --git a/src/ripple/protocol/jss.h b/src/ripple/protocol/jss.h index b12c0025a..1ebc658d5 100644 --- a/src/ripple/protocol/jss.h +++ b/src/ripple/protocol/jss.h @@ -142,6 +142,8 @@ JSS(HookState); // ledger type. JSS(HookStateData); // field. JSS(HookStateKey); // field. JSS(EmittedTxn); // ledger type. +JSS(Entropy); +JSS(Shuffle); JSS(SignerList); // ledger type. JSS(SignerListSet); // transaction type. JSS(SigningPubKey); // field.