first part of featRNG

This commit is contained in:
Richard Holland
2025-12-19 22:54:35 +11:00
parent 5a118a4e2b
commit 64cfdae9e9
9 changed files with 154 additions and 1 deletions

View File

@@ -103,6 +103,46 @@ applyTransactions(
bool certainRetry = true;
std::size_t count = 0;
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();)
{
if (it->second->getFieldU16(sfTransactionType) != ttRNG)
{
++it;
continue;
}
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

@@ -1477,6 +1477,104 @@ TxQ::accept(Application& app, OpenView& view)
}
}
// Inject an RNG psuedo if we're on the UNL
if (view.rules().enabled(featureRNG))
{
do
{
// if we're not a validator we do nothing here
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))
{
// 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]) == keys.masterPublicKey)
{
found = true;
break;
}
}
if (!found)
break;
auto const seq = view.info().seq;
AccountID acc = calcAccountID(keys.masterPublicKey);
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))
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(sfLastSolution, *prevRnd);
obj.setFieldH256(sfDigest, sha512Half(nextRnd);
});
// submit to the ledger
{
uint256 txID = rngTx.getTransactionID();
auto s = std::make_shared<ripple::Serializer>();
exportTx.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

@@ -154,6 +154,7 @@ Change::preclaim(PreclaimContext const& ctx)
case ttAMENDMENT:
case ttUNL_MODIFY:
case ttEMIT_FAILURE:
case ttRNG:
return tesSUCCESS;
case ttUNL_REPORT: {
if (!ctx.tx.isFieldPresent(sfImportVLKey) ||
@@ -209,6 +210,8 @@ Change::doApply()
return applyEmitFailure();
case ttUNL_REPORT:
return applyUNLReport();
case ttRNG:
return applyRNG();
default:
assert(0);
return tefFAILURE;

View File

@@ -152,6 +152,7 @@ invoke_preflight(PreflightContext const& ctx)
case ttUNL_MODIFY:
case ttUNL_REPORT:
case ttEMIT_FAILURE:
case ttRNG:
return invoke_preflight_helper<Change>(ctx);
case ttHOOK_SET:
return invoke_preflight_helper<SetHook>(ctx);
@@ -283,6 +284,7 @@ invoke_preclaim(PreclaimContext const& ctx)
case ttUNL_MODIFY:
case ttUNL_REPORT:
case ttEMIT_FAILURE:
case ttRNG:
return invoke_preclaim<Change>(ctx);
case ttNFTOKEN_MINT:
return invoke_preclaim<NFTokenMint>(ctx);
@@ -374,6 +376,7 @@ invoke_calculateBaseFee(ReadView const& view, STTx const& tx)
case ttUNL_MODIFY:
case ttUNL_REPORT:
case ttEMIT_FAILURE:
case ttRNG:
return Change::calculateBaseFee(view, tx);
case ttNFTOKEN_MINT:
return NFTokenMint::calculateBaseFee(view, tx);
@@ -544,6 +547,7 @@ invoke_apply(ApplyContext& ctx)
case ttFEE:
case ttUNL_MODIFY:
case ttUNL_REPORT:
case ttRNG:
case ttEMIT_FAILURE: {
Change p(ctx);
return p();

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

@@ -563,6 +563,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;

View File

@@ -149,6 +149,10 @@ 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
* to a consensus based random number system */
ttRNG = 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

@@ -316,6 +316,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);