diff --git a/src/test/app/ConsensusEntropy_test.cpp b/src/test/app/ConsensusEntropy_test.cpp index 860416cce..8b54f9003 100644 --- a/src/test/app/ConsensusEntropy_test.cpp +++ b/src/test/app/ConsensusEntropy_test.cpp @@ -30,6 +30,7 @@ #include #include #include +#include namespace ripple { namespace test { @@ -57,6 +58,30 @@ standaloneContributorMask(std::uint16_t denominator, std::uint16_t count) return mask; } +std::uint32_t +expectedDice(uint256 block, std::uint32_t sides) +{ + auto const sampleRange = + std::uint64_t{std::numeric_limits::max()} + 1; + auto const acceptLimit = sampleRange - (sampleRange % sides); + + for (;;) + { + for (std::size_t i = 0; i < block.size(); i += sizeof(std::uint32_t)) + { + auto const* candidate = block.data() + i; + std::uint32_t const value = + (std::uint32_t{candidate[0]} << 24U) | + (std::uint32_t{candidate[1]} << 16U) | + (std::uint32_t{candidate[2]} << 8U) | + std::uint32_t{candidate[3]}; + if (value < acceptLimit) + return value % sides; + } + block = sha512Half(Slice{block.data(), block.size()}); + } +} + } // namespace class ConsensusEntropy_test : public beast::unit_test::suite @@ -208,6 +233,11 @@ class ConsensusEntropy_test : public beast::unit_test::suite HSFEE); env.close(); + auto const entropy = env.le(keylet::consensusEntropy()); + BEAST_REQUIRE(entropy); + auto const entropyDigest = entropy->getFieldH256(sfDigest); + auto const drawLedgerSeq = env.current()->info().seq; + // Invoke the hook Json::Value invoke; invoke[jss::TransactionType] = "Invoke"; @@ -222,10 +252,21 @@ class ConsensusEntropy_test : public beast::unit_test::suite BEAST_REQUIRE(hookExecutions.size() == 1); auto const returnCode = hookExecutions[0].getFieldU64(sfHookReturnCode); + auto const firstBlock = sha512Half( + drawLedgerSeq, + env.tx()->getTransactionID(), + alice.id(), + hookExecutions[0].getFieldH256(sfHookHash), + alice.id(), + std::uint8_t{0}, + std::string{"strong"}, + std::string{"direct"}, + entropyDigest, + std::uint64_t{0}); + auto const expected = expectedDice(firstBlock, 6); std::cerr << " dice(6) returnCode = " << returnCode << " (hex 0x" << std::hex << returnCode << std::dec << ")\n"; - // dice(6) returns 0..5 - BEAST_EXPECT(returnCode <= 5); + BEAST_EXPECT(returnCode == expected); // Result should be 3 (accept) BEAST_EXPECT(hookExecutions[0].getFieldU8(sfHookResult) == 3); diff --git a/src/xrpld/app/consensus/ConsensusEntropyIntent.md b/src/xrpld/app/consensus/ConsensusEntropyIntent.md index 9f60db07f..7473042a9 100644 --- a/src/xrpld/app/consensus/ConsensusEntropyIntent.md +++ b/src/xrpld/app/consensus/ConsensusEntropyIntent.md @@ -230,7 +230,8 @@ entropyDigest, i)`. The counter is local to one Hook execution role and is post-incremented once when a draw stream passes snapshot admission; rejected arguments or entropy do not consume it. Further blocks are `sha512Half(previousBlock)`. `dice` rejects zero sides and uses deterministic -32-bit rejection sampling rather than biased modulo reduction. `random` accepts +unsigned big-endian 32-bit rejection sampling rather than biased modulo +reduction. `random` accepts one through 512 requested bytes, rounds its internal generation length to a 32-byte boundary, and writes only the requested prefix. Missing, malformed, future, older-than-one-ledger, or below-tier entropy makes either draw return diff --git a/src/xrpld/app/hook/detail/applyHook.cpp b/src/xrpld/app/hook/detail/applyHook.cpp index d5310a114..ea830bad4 100644 --- a/src/xrpld/app/hook/detail/applyHook.cpp +++ b/src/xrpld/app/hook/detail/applyHook.cpp @@ -4204,8 +4204,12 @@ DEFINE_HOOK_FUNCTION(int64_t, dice, uint32_t sides, uint32_t min_tier) for (std::size_t i = 0; i + sizeof(std::uint32_t) <= bytes.size(); i += sizeof(std::uint32_t)) { - std::uint32_t value; - std::memcpy(&value, bytes.data() + i, sizeof(std::uint32_t)); + auto const* candidate = bytes.data() + i; + std::uint32_t const value = + (std::uint32_t{candidate[0]} << 24U) | + (std::uint32_t{candidate[1]} << 16U) | + (std::uint32_t{candidate[2]} << 8U) | + std::uint32_t{candidate[3]}; if (value < acceptLimit) return value % sides; }