ttSHUFFLE injection appears to be working

This commit is contained in:
Richard Holland
2025-12-24 18:08:16 +11:00
parent 1690c045f2
commit d33a57f566
20 changed files with 261 additions and 213 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

@@ -44,6 +44,8 @@
#include <ripple/protocol/BuildInfo.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/digest.h>
#include <ripple/app/tx/impl/Entropy.h>
#include <ripple/app/tx/impl/Change.h>
#include <algorithm>
#include <mutex>
@@ -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

@@ -104,12 +104,15 @@ applyTransactions(
bool certainRetry = true;
std::size_t count = 0;
// apply the ttSHUFFLE txns first in the ledger to
// ensure no one can predict the outcome
// then apply ttENTROPY transactions
if (view.rules().enabled(featureRNG))
for (auto tt : {ttSHUFFLE, ttENTROPY})
{
// apply the ttRNG txns first in the ledger to ensure no one can predict the outcome
for (auto it = txns.begin(); it != txns.end();)
{
if (it->second->getFieldU16(sfTransactionType) != ttRNG)
if (tt != it->second->getFieldU16(sfTransactionType))
{
++it;
continue;

View File

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

View File

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

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

View File

@@ -112,6 +112,14 @@ 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

@@ -1478,117 +1478,6 @@ TxQ::accept(Application& app, OpenView& view)
}
}
// Inject an RNG psuedo if we're on the UNL
if (view.rules().enabled(featureRNG))
{
JLOG(j_.debug())
<< "RNG processing: started";
auto const seq = view.info().seq;
do
{
// if we're not a validator we do nothing here
if (app.getValidationPublicKey().empty())
break;
if (seq > 256) // only do the UNL check if its not a newly created test network
{
// and if we're not on the UNLReport we also do nothing
auto const unlRep = view.read(keylet::UNLReport());
if (!unlRep || !unlRep->isFieldPresent(sfActiveValidators))
{
JLOG(j_.debug())
<< "RNG processing: UNLReport misssing";
// nothing to do without a unlreport object
break;
}
bool found = false;
auto const& avs = unlRep->getFieldArray(sfActiveValidators);
for (auto const& av : avs)
{
if (PublicKey(av[sfPublicKey]) == app.getValidationPublicKey())
{
found = true;
break;
}
}
if (!found)
{
JLOG(j_.debug())
<< "RNG processing: UNLReport present but we're not on it";
break;
}
}
AccountID acc = calcAccountID(app.getValidationPublicKey());
static auto getRnd = []() -> uint256 {
static std::ifstream rng("/dev/urandom", std::ios::binary);
uint256 out;
if (rng && rng.read(reinterpret_cast<char*>(out.data()), 32))
return out;
std::random_device rd;
for (auto& word : out)
word = rd();
return out;
};
static std::map<uint32_t /* ledger seq */ , uint256 /* chosen rnd no */> rngMap;
uint256 nextRnd = getRnd();
if (rngMap.find(seq) != rngMap.end())
break; // should never happen
rngMap[seq] = nextRnd;
std::optional<uint256> prevRnd;
if (rngMap.find(seq - 1) != rngMap.end())
prevRnd = rngMap[seq - 1];
// amortized cleanup, for every ledger attempt to delete two old entries
// even in the most desynced ridiculous state this is guaranteed prevent map growth
for (int i = 0; i < 2; ++i)
{
auto it = rngMap.begin();
if (it != rngMap.end() && it->first < seq)
rngMap.erase(it->first);
}
// create txn
STTx rngTx(ttRNG, [&](auto& obj) {
obj.setFieldU32(sfLedgerSequence, seq);
obj.setAccountID(sfValidator, acc);
if (prevRnd.has_value())
obj.setFieldH256(sfRandomData, *prevRnd);
obj.setFieldH256(sfNextRandomDigest, sha512Half(nextRnd));
// RH TODO: should we sign this??
});
// submit to the ledger
{
uint256 txID = rngTx.getTransactionID();
JLOG(j_.debug())
<< "RNG processing: Submitting pseudo: "
<< rngTx.getFullText()
<< " txid: " << txID;
auto s = std::make_shared<ripple::Serializer>();
rngTx.add(*s);
app.getHashRouter().setFlags(txID, SF_PRIVATE2);
app.getHashRouter().setFlags(txID, SF_EMITTED);
view.rawTxInsert(txID, std::move(s), nullptr);
ledgerChanged = true;
}
} while (0);
}
// Inject cron transactions, if any
if (view.rules().enabled(featureCron))
{

View File

@@ -32,6 +32,7 @@
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/TxFlags.h>
#include <ripple/app/ledger/LedgerMaster.h>
#include <string_view>
namespace ripple {
@@ -96,7 +97,7 @@ Change::preflight(PreflightContext const& ctx)
}
}
if (ctx.tx.getTxnType() == ttRNG && !ctx.rules.enabled(featureRNG))
if (ctx.tx.getTxnType() == ttSHUFFLE && !ctx.rules.enabled(featureRNG))
{
JLOG(ctx.j.warn()) << "Change: FeatureRNG is not enabled.";
return temDISABLED;
@@ -110,7 +111,7 @@ Change::preclaim(PreclaimContext const& ctx)
{
// If tapOPEN_LEDGER is resurrected into ApplyFlags,
// this block can be moved to preflight.
if (ctx.view.open())
if (ctx.view.open() && ctx.tx.getTxnType() != ttSHUFFLE)
{
JLOG(ctx.j.warn()) << "Change transaction against open ledger";
return temINVALID;
@@ -160,7 +161,7 @@ Change::preclaim(PreclaimContext const& ctx)
case ttAMENDMENT:
case ttUNL_MODIFY:
case ttEMIT_FAILURE:
case ttRNG:
case ttSHUFFLE:
return tesSUCCESS;
case ttUNL_REPORT: {
if (!ctx.tx.isFieldPresent(sfImportVLKey) ||
@@ -216,8 +217,8 @@ Change::doApply()
return applyEmitFailure();
case ttUNL_REPORT:
return applyUNLReport();
case ttRNG:
return applyRNG();
case ttSHUFFLE:
return applyShuffle();
default:
assert(0);
return tefFAILURE;
@@ -225,14 +226,15 @@ Change::doApply()
}
TER
Change::applyRNG()
Change::applyShuffle()
{
auto const seq = view().info().seq;
auto const txSeq = ctx_.tx.getFieldU32(sfLedgerSequence);
if (seq != ctx_.tx.getFieldU32(sfLedgerSequence))
if (seq != txSeq)
{
JLOG(j_.warn()) << "Change: ttRNG, wrong ledger seq=" << seq;
JLOG(j_.warn()) << "Change: ttSHUFFLE, wrong ledger seq. lgr=" << seq << " tx=" << txSeq;
return tefFAILURE;
}
@@ -249,9 +251,6 @@ Change::applyRNG()
if (lastSeq < seq)
{
// update the ledger sequence of the object
sle->setFieldU32(sfLedgerSequence, seq);
// reset entropy count to zero... this will probably be
// one after the below executes but its possible the digest
// doesn't match and the entropy count isn't incremented
@@ -259,82 +258,25 @@ Change::applyRNG()
// swap the random data out ready for this round of entropy collection
sle->setFieldH256(sfLastRandomData, sle->getFieldH256(sfRandomData));
sle->setFieldH256(sfRandomData, beast::zero);
}
uint256 nextDigest = ctx_.tx.getFieldH256(sfNextRandomDigest);
uint256 currentEntropy = ctx_.tx.getFieldH256(sfRandomData);
uint256 currentDigest = sha512Half(currentEntropy);
AccountID const validator = ctx_.tx.getAccountID(sfValidator);
// RH TODO: check if they're on the UNLReport and ignore if not
// iterate the digest array to find the entry if it exists
STArray digestEntries = sle->getFieldArray(sfRandomDigests);
std::map<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);
// update the ledger sequence of the object
sle->setFieldU32(sfLedgerSequence, seq);
}
else
{
// this validator doesn't have an entry so create one
STObject entry{sfRandomDigestEntry};
entry.setAccountID(sfValidator, validator);
entry.setFieldH256(sfNextRandomDigest, nextDigest);
entry.setFieldU32(sfLedgerSequence, seq);
entries.emplace(validator, std::move(entry));
// increment entropy count
sle->setFieldU16(sfEntropyCount, sle->getFieldU16(sfEntropyCount) + 1);
}
// contribute the new entropy to the random data field
sle->setFieldH256(sfRandomData,
sha512Half(
seq,
sle->getFieldU16(sfEntropyCount),
sle->getFieldH256(sfRandomData),
ctx_.tx.getFieldH256(sfRandomData)));
// update the array
STArray newEntries(sfRandomDigests);
newEntries.reserve(entries.size());
for (auto& [_, entry] : entries)
newEntries.push_back(std::move(entry));
sle->setFieldArray(sfRandomDigests, std::move(newEntries));
// send it off to the ledger
if (!created)
view().update(sle);
else
@@ -1327,4 +1269,49 @@ Change::applyUNLModify()
return tesSUCCESS;
}
void
injectShuffleTxn(Application& app, Slice const& sig)
{
// in featureRNG we use trusted proposal signatures as shuffling entropy
// so inject a psuedo to do that here
auto ol = app.openLedger().current();
if (ol && ol->rules().enabled(featureRNG))
{
uint256 rnd = sha512Half(std::string("shuffler"), sig);
// create txn
STTx shuffleTx (ttSHUFFLE, [&](auto& obj) {
obj.setFieldU32(sfLedgerSequence, ol->info().seq + 1);
obj.setFieldH256(sfRandomData, rnd);
obj.setAccountID(sfAccount, AccountID());
});
// inject it into the propose set and into the open ledger
uint256 txID = shuffleTx.getTransactionID();
JLOG(app.journal("Transaction").debug())
<< "SHUFFLE processing: Submitting pseudo: "
<< shuffleTx.getFullText()
<< " txid: " << txID;
app.getHashRouter().setFlags(txID, SF_PRIVATE2);
app.getHashRouter().setFlags(txID, SF_EMITTED);
{
auto s = std::make_shared<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

@@ -78,9 +78,12 @@ private:
applyUNLReport();
TER
applyRNG();
applyShuffle();
};
void
injectShuffleTxn(Application& app, Slice const& sig);
} // namespace ripple
#endif

View File

@@ -442,9 +442,24 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee)
// Only check fee is sufficient when the ledger is open.
if (ctx.view.open())
{
auto const feeDue =
auto feeDue =
minimumFee(ctx.app, baseFee, ctx.view.fees(), ctx.flags);
if (ctx.view.rules().enabled(featureRNG))
{
auto const pkSignerField = ctx.tx.getSigningPubKey();
if (publicKeyType(makeSlice(pkSignerField)))
{
PublicKey pkSigner {makeSlice(pkSignerField)};
if (inUNLReport(ctx.view, ctx.app, pkSigner, ctx.j))
{
// UVTxns don't have to pay a fee
feeDue = beast::zero;
}
}
}
if (feePaid < feeDue)
{
JLOG(ctx.j.trace())
@@ -541,6 +556,16 @@ Transactor::checkSeqProxy(
return tesSUCCESS;
}
if (view.rules().enabled(featureRNG) &&
tx.getTxnType() == ttENTROPY && t_seqProx.isSeq() &&
tx[sfSequence] == 0)
{
JLOG(j.trace())
<< "applyTransaction: allowing ttENTROPY with seq=0 "
<< toBase58(id);
return tesSUCCESS;
}
JLOG(j.trace())
<< "applyTransaction: delay: source account does not exist "
<< toBase58(id);
@@ -848,19 +873,32 @@ NotTEC
Transactor::checkSingleSign(PreclaimContext const& ctx)
{
// Check that the value in the signing key slot is a public key.
auto const pkSigner = ctx.tx.getSigningPubKey();
if (!publicKeyType(makeSlice(pkSigner)))
auto const pkSignerField = ctx.tx.getSigningPubKey();
if (!publicKeyType(makeSlice(pkSignerField)))
{
JLOG(ctx.j.trace())
<< "checkSingleSign: signing public key type is unknown";
return tefBAD_AUTH; // FIXME: should be better error!
}
PublicKey pkSigner {makeSlice(pkSignerField)};
// Look up the account.
auto const idSigner = calcAccountID(PublicKey(makeSlice(pkSigner)));
auto const idSigner = calcAccountID(pkSigner);
auto const idAccount = ctx.tx.getAccountID(sfAccount);
auto const sleAccount = ctx.view.read(keylet::account(idAccount));
// check if this is a UVTxn (UNL Validator transaction)
// these are signed by the UNL validators
if (ctx.view.rules().enabled(featureRNG))
{
// UVTxns of the approved type don't need an underlying account
// and can be signed with the manifest ephemeral key
if (inUNLReport(ctx.view, ctx.app, pkSigner, ctx.j) &&
ctx.tx.getTxnType() == ttENTROPY)
return tesSUCCESS;
}
if (!sleAccount)
return terNO_ACCOUNT;

View File

@@ -52,6 +52,7 @@
#include <ripple/app/tx/impl/SetTrust.h>
#include <ripple/app/tx/impl/URIToken.h>
#include <ripple/app/tx/impl/XahauGenesis.h>
#include <ripple/app/tx/impl/Entropy.h>
namespace ripple {
@@ -152,7 +153,7 @@ invoke_preflight(PreflightContext const& ctx)
case ttUNL_MODIFY:
case ttUNL_REPORT:
case ttEMIT_FAILURE:
case ttRNG:
case ttSHUFFLE:
return invoke_preflight_helper<Change>(ctx);
case ttHOOK_SET:
return invoke_preflight_helper<SetHook>(ctx);
@@ -188,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}};
@@ -284,7 +287,7 @@ invoke_preclaim(PreclaimContext const& ctx)
case ttUNL_MODIFY:
case ttUNL_REPORT:
case ttEMIT_FAILURE:
case ttRNG:
case ttSHUFFLE:
return invoke_preclaim<Change>(ctx);
case ttNFTOKEN_MINT:
return invoke_preclaim<NFTokenMint>(ctx);
@@ -318,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;
@@ -376,7 +381,7 @@ invoke_calculateBaseFee(ReadView const& view, STTx const& tx)
case ttUNL_MODIFY:
case ttUNL_REPORT:
case ttEMIT_FAILURE:
case ttRNG:
case ttSHUFFLE:
return Change::calculateBaseFee(view, tx);
case ttNFTOKEN_MINT:
return NFTokenMint::calculateBaseFee(view, tx);
@@ -410,6 +415,8 @@ invoke_calculateBaseFee(ReadView const& view, STTx const& tx)
return CronSet::calculateBaseFee(view, tx);
case ttCRON:
return Cron::calculateBaseFee(view, tx);
case ttENTROPY:
return Entropy::calculateBaseFee(view, tx);
default:
return XRPAmount{0};
}
@@ -547,7 +554,7 @@ invoke_apply(ApplyContext& ctx)
case ttFEE:
case ttUNL_MODIFY:
case ttUNL_REPORT:
case ttRNG:
case ttSHUFFLE:
case ttEMIT_FAILURE: {
Change p(ctx);
return p();
@@ -612,6 +619,10 @@ invoke_apply(ApplyContext& ctx)
Cron p(ctx);
return p();
}
case ttENTROPY: {
Entropy p(ctx);
return p();
}
default:
assert(false);
return {temUNKNOWN, false};

View File

@@ -33,6 +33,10 @@
#include <deque>
#include <optional>
#include <sstream>
#include <ripple/app/main/Application.h>
#include <ripple/app/ledger/OpenLedger.h>
#include <ripple/protocol/digest.h>
#include <ripple/app/misc/HashRouter.h>
namespace ripple {

View File

@@ -36,6 +36,8 @@
#include <ripple/protocol/STTx.h>
#include <ripple/protocol/Serializer.h>
#include <ripple/protocol/TER.h>
#include <ripple/app/main/Application.h>
#include <ripple/app/misc/Manifest.h>
#include <functional>
#include <map>
#include <memory>
@@ -1094,6 +1096,70 @@ 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

@@ -40,6 +40,8 @@
#include <ripple/overlay/impl/Tuning.h>
#include <ripple/overlay/predicates.h>
#include <ripple/protocol/digest.h>
#include <ripple/app/misc/NetworkOPs.h>
#include <ripple/app/tx/impl/Change.h>
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/predicate.hpp>
@@ -1955,6 +1957,9 @@ 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

@@ -262,7 +262,7 @@ enum LedgerEntryType : std::uint16_t
ltEMITTED_TXN = 'E',
/** A ledger object containing a consensus-generated random number, operated on by ttRNG
/** A ledger object containing a consensus-generated random number, operated on by ttENTROPY
\sa keylet::rng
*/

View File

@@ -149,9 +149,13 @@ enum TxType : std::uint16_t
ttURITOKEN_CREATE_SELL_OFFER = 48,
ttURITOKEN_CANCEL_SELL_OFFER = 49,
/* A pseudo-txn used by featureRNG which allows validators to submit blinded entropy
/* A pseudo-txn type used by featureRNG to shuffle and randomise the transaction set based on proposal
* signatures */
ttSHUFFLE = 88,
/* A UNLReport-validator only txn by featureRNG which allows validators to submit blinded entropy
* to a consensus based random number system */
ttRNG = 89,
ttENTROPY = 89,
/* A pseudo-txn alarm signal for invoking a hook, emitted by validators after alarm set conditions are met */
ttCRON = 92,

View File

@@ -615,8 +615,7 @@ 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 == ttRNG;
tt == ttEMIT_FAILURE || tt == ttUNL_REPORT || tt == ttCRON || tt == ttSHUFFLE;
}
} // namespace ripple

View File

@@ -491,15 +491,24 @@ TxFormats::TxFormats()
},
commonFields);
add(jss::Rng,
ttRNG,
add(jss::Entropy,
ttENTROPY,
{
{sfValidator, soeREQUIRED},
{sfRandomData, soeREQUIRED},
{sfNextRandomDigest, soeREQUIRED},
{sfLedgerSequence, soeREQUIRED},
{sfParentHash, soeREQUIRED},
},
commonFields);
add(jss::Shuffle,
ttSHUFFLE,
{
{sfLedgerSequence, soeREQUIRED},
{sfRandomData, soeREQUIRED},
},
commonFields);
}
TxFormats const&

View File

@@ -142,6 +142,8 @@ JSS(HookState); // ledger type.
JSS(HookStateData); // field.
JSS(HookStateKey); // field.
JSS(EmittedTxn); // ledger type.
JSS(Entropy);
JSS(Shuffle);
JSS(SignerList); // ledger type.
JSS(SignerListSet); // transaction type.
JSS(SigningPubKey); // field.