Compare commits

...

12 Commits

Author SHA1 Message Date
Richard Holland
2a8383c6a9 hook apis for rng 2026-01-05 16:54:02 +11:00
Richard Holland
b6518f2b65 clang 2026-01-05 15:28:58 +11:00
RichardAH
41779dfd23 Merge branch 'dev' into featRNG 2026-01-05 14:10:45 +10:00
Richard Holland
889cda2a56 condition clean up ahead of ttEXPORT 2026-01-05 15:10:23 +11:00
Richard Holland
f738b8fd7c fix entropy count 2026-01-03 15:36:38 +11:00
Richard Holland
b6a23d6d3b ttENTROPY working 2026-01-03 15:19:03 +11:00
Richard Holland
55438ffc45 add msg suppression for shuffle 2025-12-27 11:43:50 +11:00
Richard Holland
d33a57f566 ttSHUFFLE injection appears to be working 2025-12-24 18:08:16 +11:00
Richard Holland
1690c045f2 scrap ttRNG and introduce ttENTROPY and ttSHUFFLE, dual rng scheme 2025-12-24 13:30:52 +11:00
Richard Holland
9efcd45212 debugging, invariant check, txns need to be injected earlier if possible 2025-12-22 11:57:35 +11:00
Richard Holland
e0fe289b52 code complete, compiling untested 2025-12-21 14:41:16 +11:00
Richard Holland
64cfdae9e9 first part of featRNG 2025-12-19 22:54:35 +11:00
36 changed files with 934 additions and 62 deletions

View File

@@ -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

View File

@@ -35,6 +35,8 @@
#include <ripple/app/misc/TxQ.h>
#include <ripple/app/misc/ValidatorKeys.h>
#include <ripple/app/misc/ValidatorList.h>
#include <ripple/app/tx/impl/Change.h>
#include <ripple/app/tx/impl/Entropy.h>
#include <ripple/basics/random.h>
#include <ripple/beast/core/LexicalCast.h>
#include <ripple/consensus/LedgerTiming.h>
@@ -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);
});

View File

@@ -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
};

View File

@@ -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<STObject const> provisionalMeta;
uint64_t rngCallCounter{
0}; // used to ensure conseq. rng calls don't return same data
};
class HookExecutor;
@@ -877,6 +882,9 @@ public:
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);

View File

@@ -6156,6 +6156,117 @@ DEFINE_HOOK_FUNCTION(
HOOK_TEARDOWN();
}
// byteCount must be a multiple of 32
inline std::vector<uint8_t>
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<uint8_t> 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(

View File

@@ -25,6 +25,7 @@
#include <ripple/app/misc/CanonicalTXSet.h>
#include <ripple/app/tx/apply.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/digest.h>
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)
{

View File

@@ -599,6 +599,12 @@ public:
return validatorKeys_.publicKey;
}
SecretKey const&
getValidationSecretKey() const override
{
return validatorKeys_.secretKey;
}
NetworkOPs&
getOPs() override
{

View File

@@ -241,6 +241,9 @@ public:
virtual PublicKey const&
getValidationPublicKey() const = 0;
virtual SecretKey const&
getValidationSecretKey() const = 0;
virtual Resource::Manager&
getResourceManager() = 0;
virtual PathRequests&

View File

@@ -228,7 +228,7 @@ public:
doTransactionSync(
std::shared_ptr<Transaction> 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<STTx const> const& iTrans)
return;
}
if (view->rules().enabled(featureRNG) && iTrans->getTxnType() == ttSHUFFLE)
{
// as above
return;
}
// this is an asynchronous interface
auto const trans = sterilize(*iTrans);

View File

@@ -112,6 +112,13 @@ public:
bool bLocal,
FailHard failType) = 0;
// directly inject transaction, skipping checks
virtual void
doTransactionSync(
std::shared_ptr<Transaction> transaction,
bool bUnlimited,
FailHard failType) = 0;
//--------------------------------------------------------------------------
//
// Owner functions

View File

@@ -25,6 +25,7 @@
#include <ripple/app/tx/apply.h>
#include <ripple/basics/mulDiv.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/digest.h>
#include <ripple/protocol/jss.h>
#include <ripple/protocol/st.h>
#include <algorithm>
@@ -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<SeqProxy> 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);

View File

@@ -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...

View File

@@ -20,6 +20,7 @@
#include <ripple/app/hook/Guard.h>
#include <ripple/app/hook/applyHook.h>
#include <ripple/app/ledger/Ledger.h>
#include <ripple/app/ledger/LedgerMaster.h>
#include <ripple/app/main/Application.h>
#include <ripple/app/misc/AmendmentTable.h>
#include <ripple/app/misc/NetworkOPs.h>
@@ -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<SLE>(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<ripple::Serializer>();
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

View File

@@ -76,8 +76,14 @@ private:
TER
applyUNLReport();
TER
applyShuffle();
};
void
injectShuffleTxn(Application& app, Slice const& sig);
} // namespace ripple
#endif

View File

@@ -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 <ripple/app/tx/impl/Entropy.h>
#include <ripple/basics/Log.h>
#include <ripple/ledger/View.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/TxFlags.h>
#include <ripple/protocol/st.h>
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<SLE>(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<AccountID, STObject> 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<STTx const>
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<char*>(out.data()), 32))
return out;
std::random_device rd;
for (auto& word : out)
word = rd();
return out;
};
static std::optional<uint256> prevRnd;
uint256 nextRnd = getRnd();
// create txn
auto rngTx = std::make_shared<STTx>(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

View File

@@ -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 <ripple/app/misc/Manifest.h>
#include <ripple/app/tx/impl/Transactor.h>
#include <ripple/basics/Log.h>
#include <ripple/core/Config.h>
#include <ripple/ledger/View.h>
#include <ripple/protocol/Indexes.h>
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<STTx const>
makeEntropyTxn(OpenView& view, Application& app, beast::Journal const& j_);
} // namespace ripple
#endif

View File

@@ -494,6 +494,7 @@ LedgerEntryTypesMatch::visitEntry(
case ltCRON:
case ltIMPORT_VLSEQ:
case ltUNL_REPORT:
case ltRANDOM:
break;
default:
invalidTypeAdded_ = true;

View File

@@ -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;

View File

@@ -32,6 +32,7 @@
#include <ripple/app/tx/impl/CronSet.h>
#include <ripple/app/tx/impl/DeleteAccount.h>
#include <ripple/app/tx/impl/DepositPreauth.h>
#include <ripple/app/tx/impl/Entropy.h>
#include <ripple/app/tx/impl/Escrow.h>
#include <ripple/app/tx/impl/GenesisMint.h>
#include <ripple/app/tx/impl/Import.h>
@@ -152,6 +153,7 @@ invoke_preflight(PreflightContext const& ctx)
case ttUNL_MODIFY:
case ttUNL_REPORT:
case ttEMIT_FAILURE:
case ttSHUFFLE:
return invoke_preflight_helper<Change>(ctx);
case ttHOOK_SET:
return invoke_preflight_helper<SetHook>(ctx);
@@ -187,6 +189,8 @@ invoke_preflight(PreflightContext const& ctx)
return invoke_preflight_helper<CronSet>(ctx);
case ttCRON:
return invoke_preflight_helper<Cron>(ctx);
case ttENTROPY:
return invoke_preflight_helper<Entropy>(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<Change>(ctx);
case ttNFTOKEN_MINT:
return invoke_preclaim<NFTokenMint>(ctx);
@@ -316,6 +321,8 @@ invoke_preclaim(PreclaimContext const& ctx)
return invoke_preclaim<CronSet>(ctx);
case ttCRON:
return invoke_preclaim<Cron>(ctx);
case ttENTROPY:
return invoke_preclaim<Entropy>(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};

View File

@@ -20,6 +20,9 @@
#ifndef RIPPLE_CONSENSUS_CONSENSUS_H_INCLUDED
#define RIPPLE_CONSENSUS_CONSENSUS_H_INCLUDED
#include <ripple/app/ledger/OpenLedger.h>
#include <ripple/app/main/Application.h>
#include <ripple/app/misc/HashRouter.h>
#include <ripple/basics/Log.h>
#include <ripple/basics/chrono.h>
#include <ripple/beast/utility/Journal.h>
@@ -29,6 +32,7 @@
#include <ripple/consensus/DisputedTx.h>
#include <ripple/consensus/LedgerTiming.h>
#include <ripple/json/json_writer.h>
#include <ripple/protocol/digest.h>
#include <boost/logic/tribool.hpp>
#include <deque>
#include <optional>

View File

@@ -20,6 +20,8 @@
#ifndef RIPPLE_LEDGER_VIEW_H_INCLUDED
#define RIPPLE_LEDGER_VIEW_H_INCLUDED
#include <ripple/app/main/Application.h>
#include <ripple/app/misc/Manifest.h>
#include <ripple/basics/Log.h>
#include <ripple/beast/utility/Journal.h>
#include <ripple/core/Config.h>
@@ -1094,6 +1096,66 @@ trustTransferLockedBalance(
}
return tesSUCCESS;
}
template <class V>
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<AccountID, bool> 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 <class V>
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

View File

@@ -28,6 +28,7 @@
#include <ripple/app/misc/Transaction.h>
#include <ripple/app/misc/ValidatorList.h>
#include <ripple/app/tx/apply.h>
#include <ripple/app/tx/impl/Change.h>
#include <ripple/basics/UptimeClock.h>
#include <ripple/basics/base64.h>
#include <ripple/basics/random.h>
@@ -1955,6 +1956,10 @@ PeerImp::onMessage(std::shared_ptr<protocol::TMProposeSet> 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()};

View File

@@ -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

View File

@@ -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&

View File

@@ -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

View File

@@ -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;
//------------------------------------------------------------------------------

View File

@@ -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)
{
}

View File

@@ -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,

View File

@@ -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.

View File

@@ -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<AccountID> 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

View File

@@ -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&

View File

@@ -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
}

View File

@@ -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

View File

@@ -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<TxType>(*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<TxType>(*t);
return tt == ttENTROPY;
}
} // namespace ripple

View File

@@ -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&

View File

@@ -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.