diff --git a/Builds/CMake/RippledCore.cmake b/Builds/CMake/RippledCore.cmake index 74ff58f601..4652c453e1 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 4fb0c5275f..b93b96dea0 100644 --- a/src/ripple/app/consensus/RCLConsensus.cpp +++ b/src/ripple/app/consensus/RCLConsensus.cpp @@ -35,6 +35,8 @@ #include #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/hook/Enum.h b/src/ripple/app/hook/Enum.h index 9728b10401..504af0c5aa 100644 --- a/src/ripple/app/hook/Enum.h +++ b/src/ripple/app/hook/Enum.h @@ -350,7 +350,8 @@ enum hook_return_code : int64_t { MEM_OVERLAP = -43, // one or more specified buffers are the same memory TOO_MANY_STATE_MODIFICATIONS = -44, // more than 5000 modified state // entires in the combined hook chains - TOO_MANY_NAMESPACES = -45 + TOO_MANY_NAMESPACES = -45, + TOO_LITTLE_ENTROPY = -46, }; enum ExitType : uint8_t { @@ -459,6 +460,8 @@ static const APIWhitelist import_whitelist{ HOOK_API_DEFINITION(I64, otxn_slot, (I32)), HOOK_API_DEFINITION(I64, otxn_param, (I32, I32, I32, I32)), HOOK_API_DEFINITION(I64, meta_slot, (I32)), + HOOK_API_DEFINITION(I64, dice, (I32)), + HOOK_API_DEFINITION(I64, random, (I32, I32)), // clang-format on }; diff --git a/src/ripple/app/hook/applyHook.h b/src/ripple/app/hook/applyHook.h index 2c1f250bbf..cffb15c294 100644 --- a/src/ripple/app/hook/applyHook.h +++ b/src/ripple/app/hook/applyHook.h @@ -406,6 +406,9 @@ DECLARE_HOOK_FUNCTION( uint32_t slot_no_tx, uint32_t slot_no_meta); +DECLARE_HOOK_FUNCTION(int64_t, dice, uint32_t sides); +DECLARE_HOOK_FUNCTION(int64_t, random, uint32_t write_ptr, uint32_t write_len); + /* DECLARE_HOOK_FUNCTION(int64_t, str_find, uint32_t hread_ptr, uint32_t hread_len, uint32_t nread_ptr, uint32_t nread_len, uint32_t mode, @@ -513,6 +516,8 @@ struct HookResult false; // hook_again allows strong pre-apply to nominate // additional weak post-apply execution std::shared_ptr provisionalMeta; + uint64_t rngCallCounter{ + 0}; // used to ensure conseq. rng calls don't return same data }; class HookExecutor; @@ -877,6 +882,9 @@ class HookExecutor ADD_HOOK_FUNCTION(meta_slot, ctx); ADD_HOOK_FUNCTION(xpop_slot, ctx); + ADD_HOOK_FUNCTION(dice, ctx); + ADD_HOOK_FUNCTION(random, ctx); + /* ADD_HOOK_FUNCTION(str_find, ctx); ADD_HOOK_FUNCTION(str_replace, ctx); diff --git a/src/ripple/app/hook/impl/applyHook.cpp b/src/ripple/app/hook/impl/applyHook.cpp index f27b7d23b3..c06addedfa 100644 --- a/src/ripple/app/hook/impl/applyHook.cpp +++ b/src/ripple/app/hook/impl/applyHook.cpp @@ -6156,6 +6156,117 @@ DEFINE_HOOK_FUNCTION( HOOK_TEARDOWN(); } + +// byteCount must be a multiple of 32 +inline std::vector +fairRng(ApplyContext& applyCtx, hook::HookResult& hr, uint32_t byteCount) +{ + if (byteCount > 512) + byteCount = 512; + + // force the byte count to be a multiple of 32 + byteCount &= ~0b11111; + + if (byteCount == 0) + return {}; + + auto& view = applyCtx.view(); + + auto const sleRandom = view.peek(ripple::keylet::random()); + auto const seq = view.info().seq; + + if (!sleRandom || sleRandom->getFieldU32(sfLedgerSequence) != seq || + sleRandom->getFieldU16(sfEntropyCount) < 5) + return {}; + + // we'll generate bytes in lots of 32 + + uint256 rndData = sha512Half( + view.info().seq, + applyCtx.tx.getTransactionID(), + hr.otxnAccount, + hr.hookHash, + hr.account, + hr.hookChainPosition, + hr.executeAgainAsWeak ? std::string("weak") : std::string("strong"), + sleRandom->getFieldH256(sfRandomData), + hr.rngCallCounter++); + + std::vector bytesOut; + bytesOut.resize(byteCount); + + uint8_t* ptr = bytesOut.data(); + while (1) + { + std::memcpy(ptr, rndData.data(), 32); + ptr += 32; + + if (ptr - bytesOut.data() >= byteCount) + break; + + rndData = sha512Half(rndData); + } + + return bytesOut; +} + +DEFINE_HOOK_FUNCTION(int64_t, dice, uint32_t sides) +{ + HOOK_SETUP(); + + auto vec = fairRng(applyCtx, hookCtx.result, 32); + + if (vec.empty()) + return TOO_LITTLE_ENTROPY; + + if (vec.size() != 32) + return INTERNAL_ERROR; + + uint32_t value; + std::memcpy(&value, vec.data(), sizeof(uint32_t)); + + return value % sides; + + HOOK_TEARDOWN(); +} + +DEFINE_HOOK_FUNCTION(int64_t, random, uint32_t write_ptr, uint32_t write_len) +{ + HOOK_SETUP(); + + if (write_len == 0) + return TOO_SMALL; + + if (write_len > 512) + return TOO_BIG; + + uint32_t required = write_len; + + if (required & ~0b11111 == required) + { + // already a multiple of 32 bytes + } + else + { + // round up + required &= ~0b11111; + required += 32; + } + + if (NOT_IN_BOUNDS(write_ptr, write_len, memory_length)) + return OUT_OF_BOUNDS; + + auto vec = fairRng(applyCtx, hookCtx.result, required); + + if (vec.empty()) + return TOO_LITTLE_ENTROPY; + + WRITE_WASM_MEMORY_AND_RETURN( + write_ptr, write_len, vec.data(), vec.size(), memory, memory_length); + + HOOK_TEARDOWN(); +} + /* DEFINE_HOOK_FUNCTION( diff --git a/src/ripple/app/ledger/impl/BuildLedger.cpp b/src/ripple/app/ledger/impl/BuildLedger.cpp index 56feda0667..c319c6e006 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,6 +104,50 @@ 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}) + { + for (auto it = txns.begin(); it != txns.end();) + { + if (tt != it->second->getFieldU16(sfTransactionType)) + { + ++it; + continue; + } + + auto const txid = it->first.getTXID(); + try + { + switch (applyTransaction( + app, view, *it->second, certainRetry, tapNONE, j)) + { + case ApplyResult::Success: + it = txns.erase(it); + ++count; + break; + + case ApplyResult::Fail: + failed.insert(txid); + it = txns.erase(it); + break; + + case ApplyResult::Retry: + ++it; + } + } + catch (std::exception const& ex) + { + JLOG(j.warn()) + << "Transaction " << txid << " throws: " << ex.what(); + failed.insert(txid); + it = txns.erase(it); + } + } + } + // Attempt to apply all of the retriable transactions for (int pass = 0; pass < LEDGER_TOTAL_PASSES; ++pass) { diff --git a/src/ripple/app/main/Application.cpp b/src/ripple/app/main/Application.cpp index 20427107b1..0268ec626f 100644 --- a/src/ripple/app/main/Application.cpp +++ b/src/ripple/app/main/Application.cpp @@ -599,6 +599,12 @@ class ApplicationImp : public Application, public BasicApp 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 a6e58b19e9..ea0cc79fec 100644 --- a/src/ripple/app/main/Application.h +++ b/src/ripple/app/main/Application.h @@ -241,6 +241,9 @@ class Application : public beast::PropertyStream::Source virtual PublicKey const& getValidationPublicKey() const = 0; + virtual SecretKey const& + getValidationSecretKey() const = 0; + virtual Resource::Manager& getResourceManager() = 0; virtual PathRequests& diff --git a/src/ripple/app/misc/NetworkOPs.cpp b/src/ripple/app/misc/NetworkOPs.cpp index 0e5b8ef5f5..0399bc297a 100644 --- a/src/ripple/app/misc/NetworkOPs.cpp +++ b/src/ripple/app/misc/NetworkOPs.cpp @@ -228,7 +228,7 @@ class NetworkOPsImp final : public NetworkOPs doTransactionSync( std::shared_ptr transaction, bool bUnlimited, - FailHard failType); + FailHard failType) override; /** * For transactions not submitted by a locally connected client, fire and @@ -1078,6 +1078,12 @@ NetworkOPsImp::submitTransaction(std::shared_ptr const& iTrans) return; } + if (view->rules().enabled(featureRNG) && iTrans->getTxnType() == ttSHUFFLE) + { + // as above + return; + } + // this is an asynchronous interface auto const trans = sterilize(*iTrans); diff --git a/src/ripple/app/misc/NetworkOPs.h b/src/ripple/app/misc/NetworkOPs.h index 350542404c..1f0eb1d4df 100644 --- a/src/ripple/app/misc/NetworkOPs.h +++ b/src/ripple/app/misc/NetworkOPs.h @@ -112,6 +112,13 @@ class NetworkOPs : public InfoSub::Source 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 5c4af9f8bc..34172bea6b 100644 --- a/src/ripple/app/misc/impl/TxQ.cpp +++ b/src/ripple/app/misc/impl/TxQ.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -1930,13 +1931,15 @@ TxQ::tryDirectApply( const bool isFirstImport = !sleAccount && view.rules().enabled(featureImport) && tx->getTxnType() == ttIMPORT; + const bool accRequired = !(isFirstImport || isUVTx(*tx)); + // 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]); @@ -1949,7 +1952,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 c80038ef74..401c869025 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/Change.cpp b/src/ripple/app/tx/impl/Change.cpp index 37a436feea..23248e91de 100644 --- a/src/ripple/app/tx/impl/Change.cpp +++ b/src/ripple/app/tx/impl/Change.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -96,6 +97,12 @@ Change::preflight(PreflightContext const& ctx) } } + if (ctx.tx.getTxnType() == ttSHUFFLE && !ctx.rules.enabled(featureRNG)) + { + JLOG(ctx.j.warn()) << "Change: FeatureRNG is not enabled."; + return temDISABLED; + } + return tesSUCCESS; } @@ -104,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; @@ -154,6 +161,7 @@ Change::preclaim(PreclaimContext const& ctx) case ttAMENDMENT: case ttUNL_MODIFY: case ttEMIT_FAILURE: + case ttSHUFFLE: return tesSUCCESS; case ttUNL_REPORT: { if (!ctx.tx.isFieldPresent(sfImportVLKey) || @@ -209,12 +217,72 @@ Change::doApply() return applyEmitFailure(); case ttUNL_REPORT: return applyUNLReport(); + case ttSHUFFLE: + return applyShuffle(); default: assert(0); return tefFAILURE; } } +TER +Change::applyShuffle() +{ + auto const seq = view().info().seq; + auto const txSeq = ctx_.tx.getFieldU32(sfLedgerSequence); + + if (seq != txSeq) + { + JLOG(j_.warn()) << "Change: ttSHUFFLE, wrong ledger seq. lgr=" << seq + << " tx=" << txSeq; + 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) + { + // 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)); + + // update the ledger sequence of the object + sle->setFieldU32(sfLedgerSequence, seq); + } + + // 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))); + + if (!created) + view().update(sle); + else + view().insert(sle); + + return tesSUCCESS; +} + TER Change::applyUNLReport() { @@ -1199,4 +1267,47 @@ 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 20c701f5ce..748e7aa32a 100644 --- a/src/ripple/app/tx/impl/Change.h +++ b/src/ripple/app/tx/impl/Change.h @@ -76,8 +76,14 @@ class Change : public Transactor TER applyUNLReport(); + + TER + applyShuffle(); }; +void +injectShuffleTxn(Application& app, Slice const& sig); + } // namespace ripple #endif diff --git a/src/ripple/app/tx/impl/Entropy.cpp b/src/ripple/app/tx/impl/Entropy.cpp new file mode 100644 index 0000000000..5fe704c954 --- /dev/null +++ b/src/ripple/app/tx/impl/Entropy.cpp @@ -0,0 +1,256 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include +#include +#include + +namespace ripple { + +TxConsequences +Entropy::makeTxConsequences(PreflightContext const& ctx) +{ + return TxConsequences{ctx.tx, TxConsequences::normal}; +} + +NotTEC +Entropy::preflight(PreflightContext const& ctx) +{ + if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) + return ret; + + if (!ctx.rules.enabled(featureRNG)) + return temDISABLED; + + return preflight2(ctx); +} + +TER +Entropy::preclaim(PreclaimContext const& ctx) +{ + if (!ctx.view.rules().enabled(featureRNG)) + return temDISABLED; + + // account must be a valid UV + if (!inUNLReport(ctx.view, ctx.tx.getAccountID(sfAccount), ctx.j)) + { + JLOG(ctx.j.warn()) << "Entropy: Txn Account isn't in the UNLReport."; + return tefFAILURE; + } + + return tesSUCCESS; +} + +TER +Entropy::doApply() +{ + auto const seq = view().info().seq; + + 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(sfAccount); + + // 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; +} + +// if this validator is on the UNLReport then return a signed ttENTROPY +// transaction to be added to the txq. +std::shared_ptr +makeEntropyTxn(OpenView& view, Application& app, beast::Journal const& j_) +{ + if (!view.rules().enabled(featureRNG)) + return {}; + + JLOG(j_.debug()) << "ENTROPY processing: started"; + + auto const seq = view.info().seq; + + static uint32_t lastSeq = 0; + + // we only generate once per ledger + if (lastSeq == seq) + return {}; + + lastSeq = seq; + + // if we're not a validator we do nothing here + if (app.getValidationPublicKey().empty()) + return {}; + + PublicKey pkSigning = app.getValidationPublicKey(); + + auto const pk = app.validatorManifests().getMasterKey(pkSigning); + + // Only continue if we're on the UNL + if (!inUNLReport(view, app, pk, j_)) + return {}; + + // build and sign a txn + + AccountID acc = calcAccountID(pk); + + 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::optional prevRnd; + + uint256 nextRnd = getRnd(); + + // create txn + auto rngTx = std::make_shared(ttENTROPY, [&](auto& obj) { + 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, 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(pkSigning, app.getValidationSecretKey()); + + JLOG(j_.debug()) << "ENTROPY txn: " << rngTx->getFullText(); + + return rngTx; +} + +} // namespace ripple diff --git a/src/ripple/app/tx/impl/Entropy.h b/src/ripple/app/tx/impl/Entropy.h new file mode 100644 index 0000000000..c9ecbbcf30 --- /dev/null +++ b/src/ripple/app/tx/impl/Entropy.h @@ -0,0 +1,67 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_TX_ENTROPY_H_INCLUDED +#define RIPPLE_TX_ENTROPY_H_INCLUDED + +#include +#include +#include +#include +#include +#include + +namespace ripple { + +class Entropy : public Transactor +{ +public: + static constexpr ConsequencesFactoryType ConsequencesFactory{Custom}; + + explicit Entropy(ApplyContext& ctx) : Transactor(ctx) + { + } + + static XRPAmount + calculateBaseFee(ReadView const& view, STTx const& tx) + { + return XRPAmount{0}; + } + + static TxConsequences + makeTxConsequences(PreflightContext const& ctx); + + static NotTEC + preflight(PreflightContext const& ctx); + + static TER + preclaim(PreclaimContext const& ctx); + + TER + doApply() override; +}; + +// if this validator is on the UNLReport then return a signed ttENTROPY +// transaction to be added to the txq. +std::shared_ptr +makeEntropyTxn(OpenView& view, Application& app, beast::Journal const& j_); + +} // namespace ripple + +#endif diff --git a/src/ripple/app/tx/impl/InvariantCheck.cpp b/src/ripple/app/tx/impl/InvariantCheck.cpp index 5679a2b47c..acb0553972 100644 --- a/src/ripple/app/tx/impl/InvariantCheck.cpp +++ b/src/ripple/app/tx/impl/InvariantCheck.cpp @@ -494,6 +494,7 @@ LedgerEntryTypesMatch::visitEntry( case ltCRON: case ltIMPORT_VLSEQ: case ltUNL_REPORT: + case ltRANDOM: break; default: invalidTypeAdded_ = true; diff --git a/src/ripple/app/tx/impl/Transactor.cpp b/src/ripple/app/tx/impl/Transactor.cpp index 80646172a0..afb93b1e7f 100644 --- a/src/ripple/app/tx/impl/Transactor.cpp +++ b/src/ripple/app/tx/impl/Transactor.cpp @@ -273,6 +273,9 @@ Transactor::calculateHookChainFee( XRPAmount Transactor::calculateBaseFee(ReadView const& view, STTx const& tx) { + if (isUVTx(tx)) + return XRPAmount{0}; + // Returns the fee in fee units. // The computation has two parts: @@ -280,7 +283,9 @@ 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) @@ -442,8 +447,21 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee) // Only check fee is sufficient when the ledger is open. if (ctx.view.open()) { - auto const feeDue = - minimumFee(ctx.app, baseFee, ctx.view.fees(), ctx.flags); + 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) { @@ -461,6 +479,9 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee) auto const sle = ctx.view.read(keylet::account(id)); if (!sle) { + if (isUVTx(ctx.tx)) + return tesSUCCESS; + if (ctx.tx.getTxnType() == ttIMPORT) { if (!ctx.tx.isFieldPresent(sfIssuer)) @@ -541,6 +562,13 @@ Transactor::checkSeqProxy( return tesSUCCESS; } + if (isUVTx(tx) && t_seqProx.isSeq() && tx[sfSequence] == 0) + { + JLOG(j.trace()) << "applyTransaction: allowing UVTx with seq=0 " + << toBase58(id); + return tesSUCCESS; + } + JLOG(j.trace()) << "applyTransaction: delay: source account does not exist " << toBase58(id); @@ -625,7 +653,9 @@ Transactor::checkPriorTxAndLastLedger(PreclaimContext const& ctx) ctx.view.rules().enabled(featureImport) && ctx.tx.getTxnType() == ttIMPORT && !ctx.tx.isFieldPresent(sfIssuer); - if (!sle && !isFirstImport) + bool const accRequired = !(isFirstImport || isUVTx(ctx.tx)); + + if (!sle && accRequired) { JLOG(ctx.j.trace()) << "applyTransaction: delay: source account does not exist " @@ -781,12 +811,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) || + isUVTx(ctx_.tx)); if (sle) { @@ -848,19 +879,26 @@ 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)); + // UVTxns of the approved type don't need an underlying account + // and can be signed with the manifest ephemeral key + if (isUVTx(ctx.tx) && inUNLReport(ctx.view, ctx.app, pkSigner, ctx.j)) + 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 a18f78e1c2..12cb323f19 100644 --- a/src/ripple/app/tx/impl/applySteps.cpp +++ b/src/ripple/app/tx/impl/applySteps.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -152,6 +153,7 @@ invoke_preflight(PreflightContext const& ctx) case ttUNL_MODIFY: case ttUNL_REPORT: case ttEMIT_FAILURE: + case ttSHUFFLE: return invoke_preflight_helper(ctx); case ttHOOK_SET: return invoke_preflight_helper(ctx); @@ -187,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}}; @@ -283,6 +287,7 @@ invoke_preclaim(PreclaimContext const& ctx) case ttUNL_MODIFY: case ttUNL_REPORT: case ttEMIT_FAILURE: + case ttSHUFFLE: return invoke_preclaim(ctx); case ttNFTOKEN_MINT: return invoke_preclaim(ctx); @@ -316,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; @@ -374,6 +381,7 @@ invoke_calculateBaseFee(ReadView const& view, STTx const& tx) case ttUNL_MODIFY: case ttUNL_REPORT: case ttEMIT_FAILURE: + case ttSHUFFLE: return Change::calculateBaseFee(view, tx); case ttNFTOKEN_MINT: return NFTokenMint::calculateBaseFee(view, tx); @@ -407,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}; } @@ -544,6 +554,7 @@ invoke_apply(ApplyContext& ctx) case ttFEE: case ttUNL_MODIFY: case ttUNL_REPORT: + case ttSHUFFLE: case ttEMIT_FAILURE: { Change p(ctx); return p(); @@ -608,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 df5ec01cee..d1a2f462f2 100644 --- a/src/ripple/consensus/Consensus.h +++ b/src/ripple/consensus/Consensus.h @@ -20,6 +20,9 @@ #ifndef RIPPLE_CONSENSUS_CONSENSUS_H_INCLUDED #define RIPPLE_CONSENSUS_CONSENSUS_H_INCLUDED +#include +#include +#include #include #include #include @@ -29,6 +32,7 @@ #include #include #include +#include #include #include #include diff --git a/src/ripple/ledger/View.h b/src/ripple/ledger/View.h index 27659ad8d0..a541177bd8 100644 --- a/src/ripple/ledger/View.h +++ b/src/ripple/ledger/View.h @@ -20,6 +20,8 @@ #ifndef RIPPLE_LEDGER_VIEW_H_INCLUDED #define RIPPLE_LEDGER_VIEW_H_INCLUDED +#include +#include #include #include #include @@ -1094,6 +1096,66 @@ 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 adb05ddf62..1ad736910a 100644 --- a/src/ripple/overlay/impl/PeerImp.cpp +++ b/src/ripple/overlay/impl/PeerImp.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -1955,6 +1956,10 @@ 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/Feature.h b/src/ripple/protocol/Feature.h index 2766859dc7..24436d1c32 100644 --- a/src/ripple/protocol/Feature.h +++ b/src/ripple/protocol/Feature.h @@ -74,7 +74,7 @@ namespace detail { // Feature.cpp. Because it's only used to reserve storage, and determine how // large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than // the actual number of amendments. A LogicError on startup will verify this. -static constexpr std::size_t numFeatures = 90; +static constexpr std::size_t numFeatures = 91; /** Amendments that this server supports and the default voting behavior. Whether they are enabled depends on the Rules defined in the validated @@ -378,6 +378,7 @@ extern uint256 const fixInvalidTxFlags; extern uint256 const featureExtendedHookState; extern uint256 const fixCronStacking; extern uint256 const fixHookAPI20251128; +extern uint256 const featureRNG; } // namespace ripple #endif diff --git a/src/ripple/protocol/Indexes.h b/src/ripple/protocol/Indexes.h index 03f9dc2a42..b52c8f3fad 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 f87f7ae3f0..7135dbfe42 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 ttENTROPY + + \sa keylet::rng + */ + ltRANDOM = 0x526EU, // Rn }; // clang-format off diff --git a/src/ripple/protocol/SField.h b/src/ripple/protocol/SField.h index f507b22326..bff308f3a7 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; @@ -563,6 +567,7 @@ extern SF_ACCOUNT const sfEmitCallback; extern SF_ACCOUNT const sfHookAccount; extern SF_ACCOUNT const sfNFTokenMinter; extern SF_ACCOUNT const sfInform; +extern SF_ACCOUNT const sfValidator; // path set extern SField const sfPaths; @@ -605,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 @@ -634,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/STTx.h b/src/ripple/protocol/STTx.h index c6a9e053c3..e8b7d80cb6 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/TxFormats.h b/src/ripple/protocol/TxFormats.h index bea5905cbf..e62fdbba78 100644 --- a/src/ripple/protocol/TxFormats.h +++ b/src/ripple/protocol/TxFormats.h @@ -149,6 +149,14 @@ enum TxType : std::uint16_t ttURITOKEN_CREATE_SELL_OFFER = 48, ttURITOKEN_CANCEL_SELL_OFFER = 49, + /* 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 */ + 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/Feature.cpp b/src/ripple/protocol/impl/Feature.cpp index 24383f896c..ca4236c9d1 100644 --- a/src/ripple/protocol/impl/Feature.cpp +++ b/src/ripple/protocol/impl/Feature.cpp @@ -484,6 +484,7 @@ REGISTER_FIX (fixInvalidTxFlags, Supported::yes, VoteBehavior::De REGISTER_FEATURE(ExtendedHookState, Supported::yes, VoteBehavior::DefaultNo); REGISTER_FIX (fixCronStacking, Supported::yes, VoteBehavior::DefaultYes); REGISTER_FIX (fixHookAPI20251128, Supported::yes, VoteBehavior::DefaultYes); +REGISTER_FEATURE(RNG, Supported::yes, VoteBehavior::DefaultNo); // The following amendments are obsolete, but must remain supported // because they could potentially get enabled. diff --git a/src/ripple/protocol/impl/Indexes.cpp b/src/ripple/protocol/impl/Indexes.cpp index f75faf1653..9fae6786e4 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,13 @@ 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 4c2500ff21..cf0aec7703 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 5f51976be3..5ff6da1a7f 100644 --- a/src/ripple/protocol/impl/LedgerFormats.cpp +++ b/src/ripple/protocol/impl/LedgerFormats.cpp @@ -381,6 +381,19 @@ 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 c4f2ef85a5..2b16d01e02 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); @@ -316,6 +320,7 @@ CONSTRUCT_TYPED_SFIELD(sfEmitCallback, "EmitCallback", ACCOUNT, // account (uncommon) CONSTRUCT_TYPED_SFIELD(sfHookAccount, "HookAccount", ACCOUNT, 16); CONSTRUCT_TYPED_SFIELD(sfInform, "Inform", ACCOUNT, 99); +CONSTRUCT_TYPED_SFIELD(sfValidator, "Validator", ACCOUNT, 98); // vector of 256-bit CONSTRUCT_TYPED_SFIELD(sfIndexes, "Indexes", VECTOR256, 1, SField::sMD_Never); @@ -361,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 @@ -387,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 2fbb194218..773758579b 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 {}; } @@ -615,7 +620,19 @@ 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 == 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 789de36f2d..b8d19a2800 100644 --- a/src/ripple/protocol/impl/TxFormats.cpp +++ b/src/ripple/protocol/impl/TxFormats.cpp @@ -490,6 +490,23 @@ TxFormats::TxFormats() {sfStartTime, soeOPTIONAL}, }, commonFields); + + add(jss::Entropy, + ttENTROPY, + { + {sfRandomData, soeREQUIRED}, + {sfNextRandomDigest, 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 3160372689..8cc9292fee 100644 --- a/src/ripple/protocol/jss.h +++ b/src/ripple/protocol/jss.h @@ -97,49 +97,53 @@ JSS(isSigningField); // out: RPC server_definitions JSS(isVLEncoded); // out: RPC server_definitions JSS(Import); JSS(ImportVLSequence); -JSS(Invalid); // -JSS(Invoke); // transaction type -JSS(InvoiceID); // field -JSS(LastLedgerSequence); // in: TransactionSign; field -JSS(LedgerHashes); // ledger type. -JSS(LimitAmount); // field. -JSS(NetworkID); // field. -JSS(NFTokenBurn); // transaction type. -JSS(NFTokenMint); // transaction type. -JSS(NFTokenOffer); // ledger type. -JSS(NFTokenAcceptOffer); // transaction type. -JSS(NFTokenCancelOffer); // transaction type. -JSS(NFTokenCreateOffer); // transaction type. -JSS(NFTokenPage); // ledger type. -JSS(Offer); // ledger type. -JSS(OfferCancel); // transaction type. -JSS(OfferCreate); // transaction type. -JSS(OfferSequence); // field. -JSS(Paths); // in/out: TransactionSign -JSS(PayChannel); // ledger type. -JSS(Payment); // transaction type. -JSS(PaymentChannelClaim); // transaction type. -JSS(PaymentChannelCreate); // transaction type. -JSS(PaymentChannelFund); // transaction type. -JSS(Remit); // transaction type. -JSS(RippleState); // ledger type. -JSS(SLE_hit_rate); // out: GetCounts. -JSS(SetFee); // transaction type. -JSS(SetRemarks); // transaction type -JSS(UNLModify); // transaction type. -JSS(UNLReport); // transaction type. -JSS(SettleDelay); // in: TransactionSign -JSS(SendMax); // in: TransactionSign -JSS(Sequence); // in/out: TransactionSign; field. -JSS(SetFlag); // field. -JSS(SetRegularKey); // transaction type. -JSS(SetHook); // transaction type. -JSS(Hook); // ledger type. -JSS(HookDefinition); // ledger type. -JSS(HookState); // ledger type. -JSS(HookStateData); // field. -JSS(HookStateKey); // field. -JSS(EmittedTxn); // ledger type. +JSS(Invalid); // +JSS(Invoke); // transaction type +JSS(InvoiceID); // field +JSS(LastLedgerSequence); // in: TransactionSign; field +JSS(LedgerHashes); // ledger type. +JSS(LimitAmount); // field. +JSS(NetworkID); // field. +JSS(NFTokenBurn); // transaction type. +JSS(NFTokenMint); // transaction type. +JSS(NFTokenOffer); // ledger type. +JSS(NFTokenAcceptOffer); // transaction type. +JSS(NFTokenCancelOffer); // transaction type. +JSS(NFTokenCreateOffer); // transaction type. +JSS(NFTokenPage); // ledger type. +JSS(Offer); // ledger type. +JSS(OfferCancel); // transaction type. +JSS(OfferCreate); // transaction type. +JSS(OfferSequence); // field. +JSS(Paths); // in/out: TransactionSign +JSS(PayChannel); // ledger type. +JSS(Payment); // transaction type. +JSS(PaymentChannelClaim); // transaction type. +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 +JSS(UNLModify); // transaction type. +JSS(UNLReport); // transaction type. +JSS(SettleDelay); // in: TransactionSign +JSS(SendMax); // in: TransactionSign +JSS(Sequence); // in/out: TransactionSign; field. +JSS(SetFlag); // field. +JSS(SetRegularKey); // transaction type. +JSS(SetHook); // transaction type. +JSS(Hook); // ledger type. +JSS(HookDefinition); // ledger type. +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.