code complete, compiling untested

This commit is contained in:
Richard Holland
2025-12-21 14:41:16 +11:00
parent 64cfdae9e9
commit e0fe289b52
14 changed files with 207 additions and 15 deletions

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,10 +104,10 @@ applyTransactions(
bool certainRetry = true;
std::size_t count = 0;
if (view.rules.enabled(featureRNG))
if (view.rules().enabled(featureRNG))
{
// apply the ttRNG txns first in the ledger to ensure no one can predict the outcome
for (it = txns.begin(); it != txns.end();)
for (auto it = txns.begin(); it != txns.end();)
{
if (it->second->getFieldU16(sfTransactionType) != ttRNG)
{
@@ -114,6 +115,7 @@ applyTransactions(
continue;
}
auto const txid = it->first.getTXID();
try
{
switch (applyTransaction(

View File

@@ -30,6 +30,7 @@
#include <algorithm>
#include <limits>
#include <numeric>
#include <ripple/protocol/digest.h>
namespace ripple {
@@ -1486,13 +1487,7 @@ TxQ::accept(Application& app, OpenView& view)
if (app.getValidationPublicKey().empty())
break;
auto const& keys = app.getValidatorKeys();
if (keys.configInvalid())
break;
// and if we're not on the UNLReport we also do nothing
auto const unlRep = view.read(keylet::UNLReport());
if (!unlRep || !unlRep->isFieldPresent(sfActiveValidators))
{
@@ -1504,7 +1499,7 @@ TxQ::accept(Application& app, OpenView& view)
auto const& avs = unlRep->getFieldArray(sfActiveValidators);
for (auto const& av : avs)
{
if (PublicKey(av[sfPublicKey]) == keys.masterPublicKey)
if (PublicKey(av[sfPublicKey]) == app.getValidationPublicKey())
{
found = true;
break;
@@ -1516,7 +1511,7 @@ TxQ::accept(Application& app, OpenView& view)
auto const seq = view.info().seq;
AccountID acc = calcAccountID(keys.masterPublicKey);
AccountID acc = calcAccountID(app.getValidationPublicKey());
static auto getRnd = []() -> uint256 {
static std::ifstream rng("/dev/urandom", std::ios::binary);
@@ -1540,7 +1535,7 @@ TxQ::accept(Application& app, OpenView& view)
std::optional<uint256> prevRnd;
if (rngMap.find(seq - 1))
if (rngMap.find(seq - 1) != rngMap.end())
prevRnd = rngMap[seq - 1];
// amortized cleanup, for every ledger attempt to delete two old entries
@@ -1557,15 +1552,16 @@ TxQ::accept(Application& app, OpenView& view)
obj.setFieldU32(sfLedgerSequence, seq);
obj.setAccountID(sfValidator, acc);
if (prevRnd.has_value())
obj.setFieldH256(sfLastSolution, *prevRnd);
obj.setFieldH256(sfDigest, sha512Half(nextRnd);
obj.setFieldH256(sfRandomData, *prevRnd);
obj.setFieldH256(sfNextRandomDigest, sha512Half(nextRnd));
// RH TODO: should we sign this??
});
// submit to the ledger
{
uint256 txID = rngTx.getTransactionID();
auto s = std::make_shared<ripple::Serializer>();
exportTx.add(*s);
rngTx.add(*s);
app.getHashRouter().setFlags(txID, SF_PRIVATE2);
app.getHashRouter().setFlags(txID, SF_EMITTED);
view.rawTxInsert(txID, std::move(s), nullptr);

View File

@@ -96,6 +96,12 @@ Change::preflight(PreflightContext const& ctx)
}
}
if (ctx.tx.getTxnType() == ttRNG && !ctx.rules.enabled(featureRNG))
{
JLOG(ctx.j.warn()) << "Change: FeatureRNG is not enabled.";
return temDISABLED;
}
return tesSUCCESS;
}
@@ -218,6 +224,125 @@ Change::doApply()
}
}
TER
Change::applyRNG()
{
auto const seq = view().info().seq;
if (seq != ctx_.tx.getFieldU32(sfLedgerSequence))
{
JLOG(j_.warn()) << "Change: ttRNG, wrong ledger seq=" << seq;
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)
{
// 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(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);
}
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;
}
TER
Change::applyUNLReport()
{

View File

@@ -76,6 +76,9 @@ private:
TER
applyUNLReport();
TER
applyRNG();
};
} // namespace ripple

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 ttRNG
\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;
@@ -606,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
@@ -635,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

@@ -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,14 @@ 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,20 @@ 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);
@@ -362,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
@@ -388,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

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

View File

@@ -490,6 +490,16 @@ TxFormats::TxFormats()
{sfStartTime, soeOPTIONAL},
},
commonFields);
add(jss::Rng,
ttRNG,
{
{sfValidator, soeREQUIRED},
{sfRandomData, soeREQUIRED},
{sfNextRandomDigest, soeREQUIRED},
{sfLedgerSequence, soeREQUIRED},
},
commonFields);
}
TxFormats const&

View File

@@ -123,6 +123,8 @@ 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