mirror of
https://github.com/Xahau/xahaud.git
synced 2026-07-25 08:00:10 +00:00
feat(hooks): add dice() and random() hook APIs for consensus entropy
Port the Hook API surface from the tt-rng branch, adapted to use our commit-reveal consensus entropy (ltCONSENSUS_ENTROPY / sfDigest). Hook APIs: - dice(sides): returns random int [0, sides) from consensus entropy - random(write_ptr, write_len): fills buffer with 1-512 random bytes Internal fairRng() derives per-execution entropy by hashing: ledger seq + tx ID + hook hash + account + chain position + execution phase + consensus entropy + incrementing call counter. This ensures each call within a single hook execution returns different values. Quality gate: fairRng returns empty (TOO_LITTLE_ENTROPY) if fewer than 5 validators contributed, preventing weak entropy from being consumed by hooks. Also adds sfEntropyCount and sfLedgerSequence to the consensus entropy SLE and pseudo-tx, enabling the freshness and quality checks needed by the Hook API.
This commit is contained in:
@@ -1725,12 +1725,16 @@ RCLConsensus::Adaptor::injectEntropyPseudoTx(
|
||||
if (hasEntropy)
|
||||
{
|
||||
// Account Zero convention for pseudo-transactions (same as ttFEE, etc)
|
||||
auto const entropyCount = static_cast<std::uint16_t>(
|
||||
entropyFailed_ || pendingReveals_.empty() ? 0
|
||||
: pendingReveals_.size());
|
||||
STTx tx(ttCONSENSUS_ENTROPY, [&](auto& obj) {
|
||||
obj.setFieldU32(sfLedgerSequence, seq);
|
||||
obj.setAccountID(sfAccount, AccountID{});
|
||||
obj.setFieldU32(sfSequence, 0);
|
||||
obj.setFieldAmount(sfFee, STAmount{});
|
||||
obj.setFieldH256(sfDigest, finalEntropy);
|
||||
obj.setFieldU16(sfEntropyCount, entropyCount);
|
||||
});
|
||||
|
||||
retriableTxs.insert(std::make_shared<STTx>(std::move(tx)));
|
||||
|
||||
@@ -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 {
|
||||
@@ -469,6 +470,14 @@ static const APIWhitelist import_whitelist_1{
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
// featureConsensusEntropy
|
||||
static const APIWhitelist import_whitelist_entropy{
|
||||
// clang-format off
|
||||
HOOK_API_DEFINITION(I64, dice, (I32)),
|
||||
HOOK_API_DEFINITION(I64, random, (I32, I32)),
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
#undef HOOK_API_DEFINITION
|
||||
#undef I32
|
||||
#undef I64
|
||||
|
||||
@@ -1034,6 +1034,13 @@ validateGuards(
|
||||
{
|
||||
// PASS, this is a version 1 api
|
||||
}
|
||||
else if (
|
||||
(rulesVersion & 0x04U) &&
|
||||
hook_api::import_whitelist_entropy.find(import_name) !=
|
||||
hook_api::import_whitelist_entropy.end())
|
||||
{
|
||||
// PASS, this is a consensus entropy api
|
||||
}
|
||||
else
|
||||
{
|
||||
GUARDLOG(hook::log::IMPORT_ILLEGAL)
|
||||
@@ -1262,8 +1269,13 @@ validateGuards(
|
||||
hook_api::import_whitelist.find(api_name) !=
|
||||
hook_api::import_whitelist.end()
|
||||
? hook_api::import_whitelist.find(api_name)->second
|
||||
: hook_api::import_whitelist_1.find(api_name)
|
||||
->second;
|
||||
: hook_api::import_whitelist_1.find(api_name) !=
|
||||
hook_api::import_whitelist_1.end()
|
||||
? hook_api::import_whitelist_1.find(api_name)
|
||||
->second
|
||||
: hook_api::import_whitelist_entropy
|
||||
.find(api_name)
|
||||
->second;
|
||||
|
||||
if (!first_signature)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 sleEntropy = view.peek(ripple::keylet::consensusEntropy());
|
||||
auto const seq = view.info().seq;
|
||||
|
||||
if (!sleEntropy || sleEntropy->getFieldU32(sfLedgerSequence) != seq ||
|
||||
sleEntropy->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"),
|
||||
sleEntropy->getFieldH256(sfDigest),
|
||||
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(
|
||||
|
||||
@@ -245,6 +245,8 @@ Change::applyConsensusEntropy()
|
||||
sle = std::make_shared<SLE>(keylet::consensusEntropy());
|
||||
|
||||
sle->setFieldH256(sfDigest, entropy);
|
||||
sle->setFieldU16(sfEntropyCount, ctx_.tx.getFieldU16(sfEntropyCount));
|
||||
sle->setFieldU32(sfLedgerSequence, view().info().seq);
|
||||
// Note: sfPreviousTxnID and sfPreviousTxnLgrSeq are set automatically
|
||||
// by ApplyStateTable::threadItem() because isThreadedType() returns true
|
||||
// for ledger entries that have sfPreviousTxnID in their format.
|
||||
|
||||
@@ -491,7 +491,8 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
|
||||
logger,
|
||||
hsacc,
|
||||
(ctx.rules.enabled(featureHooksUpdate1) ? 1 : 0) +
|
||||
(ctx.rules.enabled(fix20250131) ? 2 : 0));
|
||||
(ctx.rules.enabled(fix20250131) ? 2 : 0) +
|
||||
(ctx.rules.enabled(featureConsensusEntropy) ? 4 : 0));
|
||||
|
||||
if (ctx.j.trace())
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -385,6 +385,8 @@ LedgerFormats::LedgerFormats()
|
||||
ltCONSENSUS_ENTROPY,
|
||||
{
|
||||
{sfDigest, soeREQUIRED}, // The consensus-derived entropy
|
||||
{sfEntropyCount, soeREQUIRED}, // Number of validators that contributed
|
||||
{sfLedgerSequence, soeREQUIRED}, // Ledger this entropy is for
|
||||
{sfPreviousTxnID, soeREQUIRED},
|
||||
{sfPreviousTxnLgrSeq, soeREQUIRED},
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -496,6 +496,7 @@ TxFormats::TxFormats()
|
||||
{
|
||||
{sfLedgerSequence, soeREQUIRED},
|
||||
{sfDigest, soeREQUIRED},
|
||||
{sfEntropyCount, soeREQUIRED},
|
||||
{sfBlob, soeOPTIONAL}, // Proposal proof for SHAMap entries
|
||||
},
|
||||
commonFields);
|
||||
|
||||
Reference in New Issue
Block a user