diff --git a/hook/extern.h b/hook/extern.h index b2e386bc0..a618635e7 100644 --- a/hook/extern.h +++ b/hook/extern.h @@ -357,24 +357,30 @@ xport_cancel(uint32_t read_ptr, uint32_t read_len, uint32_t flags); /* Consensus entropy APIs. - min_tier is a fail-closed floor: - 1 = consensus_fallback, 2 = participant_aligned, 3 = validator_quorum. - min_count is the minimum validator/reveal count the caller accepts. + min_tier is a required fail-closed floor: + 1 = consensus_fallback, 2 = participant_aligned, + 3 = validator_quorum, 4 = validator_full. - If the most recent finalized entropy object does not satisfy both floors, - these APIs return TOO_LITTLE_ENTROPY. Open-ledger and simulate execution - are provisional previews over the entropy currently visible to the node; - final ordered ledger execution may see a different entropy object. + entropy_status writes five big-endian bytes: + tier:u8, count:u16, denominator:u16 + and returns ledger age (0 current, 1 previous, >1 stale). + + Classify tier before count/denominator arithmetic: fallback is tier 1 + with count=denominator=0. Common policies are denominator-count <= 1, + 5*count >= 4*denominator (use widened arithmetic), or count >= floor. + + dice/random return TOO_LITTLE_ENTROPY if fresh visible entropy is below + min_tier. Open-ledger and simulate execution are provisional previews; + final ordered execution may see a different entropy object. */ extern int64_t -dice(uint32_t sides, uint32_t min_tier, uint32_t min_count); +dice(uint32_t sides, uint32_t min_tier); extern int64_t -random( - uint32_t write_ptr, - uint32_t write_len, - uint32_t min_tier, - uint32_t min_count); +random(uint32_t write_ptr, uint32_t write_len, uint32_t min_tier); + +extern int64_t +entropy_status(uint32_t write_ptr, uint32_t write_len); #ifdef __cplusplus } diff --git a/hook/generate_extern.sh b/hook/generate_extern.sh index b70be2d09..6987ddad2 100755 --- a/hook/generate_extern.sh +++ b/hook/generate_extern.sh @@ -46,14 +46,21 @@ APPLY_HOOK="$SCRIPT_DIR/../include/xrpl/hook/hook_api.macro" print "/*"; print " Consensus entropy APIs."; print ""; - print " min_tier is a fail-closed floor:"; - print " 1 = consensus_fallback, 2 = participant_aligned, 3 = validator_quorum."; - print " min_count is the minimum validator/reveal count the caller accepts."; + print " min_tier is a required fail-closed floor:"; + print " 1 = consensus_fallback, 2 = participant_aligned,"; + print " 3 = validator_quorum, 4 = validator_full."; print ""; - print " If the most recent finalized entropy object does not satisfy both floors,"; - print " these APIs return TOO_LITTLE_ENTROPY. Open-ledger and simulate execution"; - print " are provisional previews over the entropy currently visible to the node;"; - print " final ordered ledger execution may see a different entropy object."; + print " entropy_status writes five big-endian bytes:"; + print " tier:u8, count:u16, denominator:u16"; + print " and returns ledger age (0 current, 1 previous, >1 stale)."; + print ""; + print " Classify tier before count/denominator arithmetic: fallback is tier 1"; + print " with count=denominator=0. Common policies are denominator-count <= 1,"; + print " 5*count >= 4*denominator (use widened arithmetic), or count >= floor."; + print ""; + print " dice/random return TOO_LITTLE_ENTROPY if fresh visible entropy is below"; + print " min_tier. Open-ledger and simulate execution are provisional previews;"; + print " final ordered execution may see a different entropy object."; print "*/"; } diff --git a/include/xrpl/hook/hook_api.macro b/include/xrpl/hook/hook_api.macro index dfaa2145d..cae5e063c 100644 --- a/include/xrpl/hook/hook_api.macro +++ b/include/xrpl/hook/hook_api.macro @@ -388,12 +388,17 @@ HOOK_API_DEFINITION( int64_t, xport_cancel, (uint32_t, uint32_t, uint32_t), featureExport) -// int64_t dice(uint32_t sides, uint32_t min_tier, uint32_t min_count); +// int64_t dice(uint32_t sides, uint32_t min_tier); HOOK_API_DEFINITION( - int64_t, dice, (uint32_t, uint32_t, uint32_t), + int64_t, dice, (uint32_t, uint32_t), featureConsensusEntropy) -// int64_t random(uint32_t write_ptr, uint32_t write_len, uint32_t min_tier, uint32_t min_count); +// int64_t random(uint32_t write_ptr, uint32_t write_len, uint32_t min_tier); HOOK_API_DEFINITION( - int64_t, random, (uint32_t, uint32_t, uint32_t, uint32_t), + int64_t, random, (uint32_t, uint32_t, uint32_t), + featureConsensusEntropy) + +// int64_t entropy_status(uint32_t write_ptr, uint32_t write_len); +HOOK_API_DEFINITION( + int64_t, entropy_status, (uint32_t, uint32_t), featureConsensusEntropy) diff --git a/src/test/app/ConsensusEntropy_test.cpp b/src/test/app/ConsensusEntropy_test.cpp index 22f8ae71e..f2a875a97 100644 --- a/src/test/app/ConsensusEntropy_test.cpp +++ b/src/test/app/ConsensusEntropy_test.cpp @@ -181,7 +181,7 @@ class ConsensusEntropy_test : public beast::unit_test::suite extern int32_t _g(uint32_t, uint32_t); extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback(uint32_t read_ptr, uint32_t read_len, int64_t error_code); - extern int64_t dice(uint32_t sides, uint32_t min_tier, uint32_t min_count); + extern int64_t dice(uint32_t sides, uint32_t min_tier); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) int64_t hook(uint32_t r) @@ -189,7 +189,7 @@ class ConsensusEntropy_test : public beast::unit_test::suite _g(1,1); // dice(6) should return 0..5 - int64_t result = dice(6, 3, 5); + int64_t result = dice(6, 3); // negative means error if (result < 0) @@ -256,7 +256,7 @@ class ConsensusEntropy_test : public beast::unit_test::suite extern int32_t _g(uint32_t, uint32_t); extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback(uint32_t read_ptr, uint32_t read_len, int64_t error_code); - extern int64_t random(uint32_t write_ptr, uint32_t write_len, uint32_t min_tier, uint32_t min_count); + extern int64_t random(uint32_t write_ptr, uint32_t write_len, uint32_t min_tier); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) int64_t hook(uint32_t r) @@ -267,7 +267,7 @@ class ConsensusEntropy_test : public beast::unit_test::suite for (int i = 0; GUARD(32), i < 32; ++i) buf[i] = 0; - int64_t result = random((uint32_t)buf, 32, 3, 5); + int64_t result = random((uint32_t)buf, 32, 3); // Should return 32 (bytes written) if (result != 32) @@ -332,16 +332,16 @@ class ConsensusEntropy_test : public beast::unit_test::suite extern int32_t _g(uint32_t, uint32_t); extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback(uint32_t read_ptr, uint32_t read_len, int64_t error_code); - extern int64_t dice(uint32_t sides, uint32_t min_tier, uint32_t min_count); + extern int64_t dice(uint32_t sides, uint32_t min_tier); int64_t hook(uint32_t r) { _g(1,1); - int64_t r1 = dice(1000000, 3, 5); + int64_t r1 = dice(1000000, 3); if (r1 < 0) rollback(0, 0, r1); - int64_t r2 = dice(1000000, 3, 5); + int64_t r2 = dice(1000000, 3); if (r2 < 0) rollback(0, 0, r2); @@ -409,12 +409,12 @@ class ConsensusEntropy_test : public beast::unit_test::suite #include extern int32_t _g(uint32_t, uint32_t); extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code); - extern int64_t dice(uint32_t sides, uint32_t min_tier, uint32_t min_count); + extern int64_t dice(uint32_t sides, uint32_t min_tier); int64_t hook(uint32_t r) { _g(1,1); - int64_t result = dice(0, 3, 5); + int64_t result = dice(0, 3); // dice(0) should return negative error code, pass it through return accept(0, 0, result); } @@ -450,9 +450,9 @@ class ConsensusEntropy_test : public beast::unit_test::suite } void - testDiceRequirementNotMet() + testEntropyStatus() { - testcase("Hook dice() returns TOO_LITTLE_ENTROPY below requirement"); + testcase("Hook entropy_status() metadata and policy recipes"); using namespace jtx; Env env{ @@ -461,41 +461,78 @@ class ConsensusEntropy_test : public beast::unit_test::suite supported_amendments() | featureConsensusEntropy, nullptr}; + ConsensusTestConfig cfg; + cfg.standaloneEntropyTier = entropyTierValidatorQuorum; + cfg.standaloneEntropyCount = 19; + cfg.standaloneEntropyDenominator = 20; + env.app().getRuntimeConfig().setGlobalConfig(cfg); + auto const alice = Account{"alice"}; env.fund(XRP(10000), alice); env.close(); - BEAST_REQUIRE(env.le(keylet::consensusEntropy())); + auto const sle = env.le(keylet::consensusEntropy()); + BEAST_REQUIRE(sle); + BEAST_EXPECT( + sle->getFieldU8(sfEntropyTier) == entropyTierValidatorQuorum); + BEAST_EXPECT(sle->getFieldU16(sfEntropyCount) == 19); + BEAST_EXPECT(sle->getFieldU16(sfEntropyDenominator) == 20); - // Standalone entropy carries EntropyCount=20, - // EntropyDenominator=20, and tier validator_full. - // A hook demanding min_count=21 states a requirement this ledger - // cannot meet, so dice must fail closed with TOO_LITTLE_ENTROPY (-48) - // rather than silently serving weaker entropy. TestHook hook = consensusentropy_test_wasm[R"[test.hook]( #include extern int32_t _g(uint32_t, uint32_t); extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code); - extern int64_t dice(uint32_t sides, uint32_t min_tier, uint32_t min_count); + extern int64_t entropy_status(uint32_t write_ptr, uint32_t write_len); + #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) + #define OUT_OF_BOUNDS (-1) + #define TOO_SMALL (-4) int64_t hook(uint32_t r) { _g(1,1); - int64_t result = dice(6, 3, 21); - // requirement unmet: pass the error code through - return accept(0, 0, result); + uint8_t status[5] = {0xA5, 0xA5, 0xA5, 0xA5, 0xA5}; + + if (entropy_status((uint32_t)status, 4) != TOO_SMALL) + return accept(0, 0, 10); + for (int i = 0; GUARD(5), i < 5; ++i) + if (status[i] != 0xA5) + return accept(0, 0, 11); + + if (entropy_status(0xFFFFFFFEU, 5) != OUT_OF_BOUNDS) + return accept(0, 0, 12); + + int64_t age = entropy_status((uint32_t)status, 5); + if (age < 0 || age > 1) + return accept(0, 0, 13); + + uint32_t count = ((uint32_t)status[1] << 8) | status[2]; + uint32_t denominator = + ((uint32_t)status[3] << 8) | status[4]; + if (status[0] != 3 || count != 19 || denominator != 20) + return accept(0, 0, 14); + + // Common caller-side policies: tolerate one absent, require + // 4/5 participation, and require an absolute floor of 19. + if (status[0] < 2 || denominator - count > 1) + return accept(0, 0, 15); + if ((uint64_t)5 * count < (uint64_t)4 * denominator) + return accept(0, 0, 16); + if (count < 19) + return accept(0, 0, 17); + + return accept(0, 0, age); } )[test.hook]"]; env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), - M("set dice-requirement hook"), + M("set entropy-status hook"), HSFEE); env.close(); Json::Value invoke; invoke[jss::TransactionType] = "Invoke"; invoke[jss::Account] = alice.human(); - env(invoke, M("test dice min_count unmet"), fee(XRP(1))); + env(invoke, M("test entropy status"), fee(XRP(1))); auto meta = env.meta(); BEAST_REQUIRE(meta); @@ -504,13 +541,86 @@ class ConsensusEntropy_test : public beast::unit_test::suite auto const hookExecutions = meta->getFieldArray(sfHookExecutions); BEAST_REQUIRE(hookExecutions.size() == 1); - auto const rawCode = hookExecutions[0].getFieldU64(sfHookReturnCode); - int64_t returnCode = (rawCode & 0x8000000000000000ULL) - ? -static_cast(rawCode & 0x7FFFFFFFFFFFFFFFULL) - : static_cast(rawCode); - std::cerr << " dice(6,3,21) returnCode = " << returnCode << " (raw 0x" - << std::hex << rawCode << std::dec << ")\n"; - BEAST_EXPECT(returnCode == -48); // TOO_LITTLE_ENTROPY + // Open-ledger preview accepts age 1; final ordered execution sees the + // current-ledger pseudo first and records age 0 in final metadata. + BEAST_EXPECT(hookReturnCode(hookExecutions[0]) == 0); + BEAST_EXPECT(hookExecutions[0].getFieldU8(sfHookResult) == 3); + } + + void + testEntropyStatusFallback() + { + testcase("Hook entropy_status() classifies fallback before arithmetic"); + using namespace jtx; + + Env env{ + *this, + envconfig(), + supported_amendments() | featureConsensusEntropy, + nullptr}; + + ConsensusTestConfig cfg; + cfg.standaloneEntropyTier = entropyTierConsensusFallback; + cfg.standaloneEntropyCount = 0; + cfg.standaloneEntropyDenominator = 0; + env.app().getRuntimeConfig().setGlobalConfig(cfg); + + auto const alice = Account{"alice"}; + env.fund(XRP(10000), alice); + env.close(); + + auto const sle = env.le(keylet::consensusEntropy()); + BEAST_REQUIRE(sle); + BEAST_EXPECT( + sle->getFieldU8(sfEntropyTier) == entropyTierConsensusFallback); + BEAST_EXPECT(sle->getFieldU16(sfEntropyCount) == 0); + BEAST_EXPECT(sle->getFieldU16(sfEntropyDenominator) == 0); + + TestHook hook = consensusentropy_test_wasm[R"[test.hook]( + #include + extern int32_t _g(uint32_t, uint32_t); + extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code); + extern int64_t dice(uint32_t sides, uint32_t min_tier); + extern int64_t entropy_status(uint32_t write_ptr, uint32_t write_len); + #define TOO_LITTLE_ENTROPY (-48) + + int64_t hook(uint32_t r) + { + _g(1,1); + uint8_t status[5]; + int64_t age = entropy_status((uint32_t)status, 5); + if (age < 0 || age > 1) + return accept(0, 0, 20); + if (status[0] != 1 || status[1] != 0 || status[2] != 0 || + status[3] != 0 || status[4] != 0) + return accept(0, 0, 21); + + int64_t allowed = dice(6, 1); + if (allowed < 0 || allowed > 5) + return accept(0, 0, 22); + if (dice(6, 2) != TOO_LITTLE_ENTROPY) + return accept(0, 0, 23); + + return accept(0, 0, age); + } + )[test.hook]"]; + + env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + M("set fallback entropy-status hook"), + HSFEE); + env.close(); + + Json::Value invoke; + invoke[jss::TransactionType] = "Invoke"; + invoke[jss::Account] = alice.human(); + env(invoke, M("test fallback entropy status"), fee(XRP(1))); + + auto meta = env.meta(); + BEAST_REQUIRE(meta); + BEAST_REQUIRE(meta->isFieldPresent(sfHookExecutions)); + auto const hookExecutions = meta->getFieldArray(sfHookExecutions); + BEAST_REQUIRE(hookExecutions.size() == 1); + BEAST_EXPECT(hookReturnCode(hookExecutions[0]) == 0); BEAST_EXPECT(hookExecutions[0].getFieldU8(sfHookResult) == 3); } @@ -550,12 +660,12 @@ class ConsensusEntropy_test : public beast::unit_test::suite #include extern int32_t _g(uint32_t, uint32_t); extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code); - extern int64_t dice(uint32_t sides, uint32_t min_tier, uint32_t min_count); + extern int64_t dice(uint32_t sides, uint32_t min_tier); int64_t hook(uint32_t r) { _g(1,1); - int64_t result = dice(6, 4, 0); + int64_t result = dice(6, 4); return accept(0, 0, result); } )[test.hook]"]; @@ -602,12 +712,16 @@ class ConsensusEntropy_test : public beast::unit_test::suite #include extern int32_t _g(uint32_t, uint32_t); extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code); - extern int64_t dice(uint32_t sides, uint32_t min_tier, uint32_t min_count); + extern int64_t dice(uint32_t sides, uint32_t min_tier); + extern int64_t entropy_status(uint32_t write_ptr, uint32_t write_len); int64_t hook(uint32_t r) { _g(1,1); - int64_t result = dice(6, 3, 0); + uint8_t status[5]; + int64_t result = entropy_status((uint32_t)status, 5); + if (result >= 0) + result = dice(6, 3); return accept(0, 0, result); } )[test.hook]"]; @@ -640,8 +754,8 @@ class ConsensusEntropy_test : public beast::unit_test::suite #include extern int32_t _g(uint32_t, uint32_t); extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code); - extern int64_t dice(uint32_t sides, uint32_t min_tier, uint32_t min_count); - extern int64_t random(uint32_t write_ptr, uint32_t write_len, uint32_t min_tier, uint32_t min_count); + extern int64_t dice(uint32_t sides, uint32_t min_tier); + extern int64_t random(uint32_t write_ptr, uint32_t write_len, uint32_t min_tier); #define INVALID_ARGUMENT (-7) int64_t hook(uint32_t r) @@ -649,25 +763,25 @@ class ConsensusEntropy_test : public beast::unit_test::suite _g(1,1); uint8_t buf[32]; - int64_t bad_min_tier = dice(6, 0, 0); + int64_t bad_min_tier = dice(6, 0); if (bad_min_tier != INVALID_ARGUMENT) return accept(0, 0, bad_min_tier); - int64_t ok_full_tier = dice(6, 4, 0); + int64_t ok_full_tier = dice(6, 4); if (ok_full_tier < 0 || ok_full_tier > 5) return accept(0, 0, ok_full_tier); - int64_t bad_high_tier = dice(6, 5, 0); + int64_t bad_high_tier = dice(6, 5); if (bad_high_tier != INVALID_ARGUMENT) return accept(0, 0, bad_high_tier); - int64_t bad_min_count = dice(6, 3, 70000); - if (bad_min_count != INVALID_ARGUMENT) - return accept(0, 0, bad_min_count); + int64_t bad_random_low = random((uint32_t)buf, 32, 0); + if (bad_random_low != INVALID_ARGUMENT) + return accept(0, 0, bad_random_low); - int64_t bad_random_tier = random((uint32_t)buf, 32, 5, 0); - if (bad_random_tier != INVALID_ARGUMENT) - return accept(0, 0, bad_random_tier); + int64_t bad_random_high = random((uint32_t)buf, 32, 5); + if (bad_random_high != INVALID_ARGUMENT) + return accept(0, 0, bad_random_high); // Sentinel distinct from any dice (0..5) / random result and // from INVALID_ARGUMENT, so a regression that lets a bad @@ -693,7 +807,7 @@ class ConsensusEntropy_test : public beast::unit_test::suite auto const hookExecutions = meta->getFieldArray(sfHookExecutions); BEAST_REQUIRE(hookExecutions.size() == 1); - // 42 only if all four invalid requirements were rejected; any bad + // 42 only if all invalid requirements were rejected; any bad // requirement leaking through returns its own (non-42) code. BEAST_EXPECT(hookReturnCode(hookExecutions[0]) == 42); BEAST_EXPECT(hookExecutions[0].getFieldU8(sfHookResult) == 3); @@ -707,7 +821,8 @@ class ConsensusEntropy_test : public beast::unit_test::suite testNoSLEWithoutAmendment(); testDice(); testDiceZeroSides(); - testDiceRequirementNotMet(); + testEntropyStatus(); + testEntropyStatusFallback(); testDiceTierRequirementNotMet(); testDiceWithoutAmendment(); testInvalidEntropyRequirements(); diff --git a/src/test/app/ConsensusEntropy_test_hooks.h b/src/test/app/ConsensusEntropy_test_hooks.h index 94121d2a6..6b6585145 100644 --- a/src/test/app/ConsensusEntropy_test_hooks.h +++ b/src/test/app/ConsensusEntropy_test_hooks.h @@ -16,7 +16,7 @@ inline std::map> consensusentropy_test_wasm = extern int32_t _g(uint32_t, uint32_t); extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback(uint32_t read_ptr, uint32_t read_len, int64_t error_code); - extern int64_t dice(uint32_t sides, uint32_t min_tier, uint32_t min_count); + extern int64_t dice(uint32_t sides, uint32_t min_tier); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) int64_t hook(uint32_t r) @@ -24,7 +24,7 @@ inline std::map> consensusentropy_test_wasm = _g(1,1); // dice(6) should return 0..5 - int64_t result = dice(6, 3, 5); + int64_t result = dice(6, 3); // negative means error if (result < 0) @@ -39,25 +39,24 @@ inline std::map> consensusentropy_test_wasm = )[test.hook]", { 0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U, - 0x1AU, 0x04U, 0x60U, 0x03U, 0x7FU, 0x7FU, 0x7EU, 0x01U, 0x7EU, - 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U, 0x03U, 0x7FU, - 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU, - 0x02U, 0x31U, 0x04U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU, - 0x67U, 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x04U, 0x64U, - 0x69U, 0x63U, 0x65U, 0x00U, 0x02U, 0x03U, 0x65U, 0x6EU, 0x76U, - 0x08U, 0x72U, 0x6FU, 0x6CU, 0x6CU, 0x62U, 0x61U, 0x63U, 0x6BU, - 0x00U, 0x00U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x06U, 0x61U, 0x63U, - 0x63U, 0x65U, 0x70U, 0x74U, 0x00U, 0x00U, 0x03U, 0x02U, 0x01U, - 0x03U, 0x05U, 0x03U, 0x01U, 0x00U, 0x01U, 0x07U, 0x08U, 0x01U, - 0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x00U, 0x04U, 0x0AU, 0x40U, - 0x01U, 0x3EU, 0x01U, 0x02U, 0x7EU, 0x41U, 0x01U, 0x41U, 0x01U, - 0x10U, 0x00U, 0x1AU, 0x41U, 0x06U, 0x41U, 0x03U, 0x41U, 0x05U, - 0x10U, 0x01U, 0x22U, 0x01U, 0x21U, 0x02U, 0x02U, 0x40U, 0x20U, - 0x01U, 0x42U, 0x00U, 0x59U, 0x04U, 0x40U, 0x42U, 0x7FU, 0x21U, - 0x02U, 0x20U, 0x01U, 0x42U, 0x06U, 0x54U, 0x0DU, 0x01U, 0x0BU, - 0x41U, 0x00U, 0x41U, 0x00U, 0x20U, 0x02U, 0x10U, 0x02U, 0x1AU, - 0x0BU, 0x41U, 0x00U, 0x41U, 0x00U, 0x20U, 0x01U, 0x10U, 0x03U, - 0x0BU, + 0x19U, 0x04U, 0x60U, 0x03U, 0x7FU, 0x7FU, 0x7EU, 0x01U, 0x7EU, + 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U, 0x02U, 0x7FU, + 0x7FU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU, 0x02U, + 0x31U, 0x04U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU, 0x67U, + 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x04U, 0x64U, 0x69U, + 0x63U, 0x65U, 0x00U, 0x02U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x08U, + 0x72U, 0x6FU, 0x6CU, 0x6CU, 0x62U, 0x61U, 0x63U, 0x6BU, 0x00U, + 0x00U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x06U, 0x61U, 0x63U, 0x63U, + 0x65U, 0x70U, 0x74U, 0x00U, 0x00U, 0x03U, 0x02U, 0x01U, 0x03U, + 0x05U, 0x03U, 0x01U, 0x00U, 0x01U, 0x07U, 0x08U, 0x01U, 0x04U, + 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x00U, 0x04U, 0x0AU, 0x3EU, 0x01U, + 0x3CU, 0x01U, 0x02U, 0x7EU, 0x41U, 0x01U, 0x41U, 0x01U, 0x10U, + 0x00U, 0x1AU, 0x41U, 0x06U, 0x41U, 0x03U, 0x10U, 0x01U, 0x22U, + 0x01U, 0x21U, 0x02U, 0x02U, 0x40U, 0x20U, 0x01U, 0x42U, 0x00U, + 0x59U, 0x04U, 0x40U, 0x42U, 0x7FU, 0x21U, 0x02U, 0x20U, 0x01U, + 0x42U, 0x06U, 0x54U, 0x0DU, 0x01U, 0x0BU, 0x41U, 0x00U, 0x41U, + 0x00U, 0x20U, 0x02U, 0x10U, 0x02U, 0x1AU, 0x0BU, 0x41U, 0x00U, + 0x41U, 0x00U, 0x20U, 0x01U, 0x10U, 0x03U, 0x0BU, }}, /* ==== WASM: 1 ==== */ @@ -66,7 +65,7 @@ inline std::map> consensusentropy_test_wasm = extern int32_t _g(uint32_t, uint32_t); extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback(uint32_t read_ptr, uint32_t read_len, int64_t error_code); - extern int64_t random(uint32_t write_ptr, uint32_t write_len, uint32_t min_tier, uint32_t min_count); + extern int64_t random(uint32_t write_ptr, uint32_t write_len, uint32_t min_tier); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) int64_t hook(uint32_t r) @@ -77,7 +76,7 @@ inline std::map> consensusentropy_test_wasm = for (int i = 0; GUARD(32), i < 32; ++i) buf[i] = 0; - int64_t result = random((uint32_t)buf, 32, 3, 5); + int64_t result = random((uint32_t)buf, 32, 3); // Should return 32 (bytes written) if (result != 32) @@ -96,42 +95,42 @@ inline std::map> consensusentropy_test_wasm = )[test.hook]", { 0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U, - 0x1BU, 0x04U, 0x60U, 0x03U, 0x7FU, 0x7FU, 0x7EU, 0x01U, 0x7EU, - 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U, 0x04U, 0x7FU, - 0x7FU, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, - 0x7EU, 0x02U, 0x33U, 0x04U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, - 0x5FU, 0x67U, 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x06U, - 0x72U, 0x61U, 0x6EU, 0x64U, 0x6FU, 0x6DU, 0x00U, 0x02U, 0x03U, - 0x65U, 0x6EU, 0x76U, 0x08U, 0x72U, 0x6FU, 0x6CU, 0x6CU, 0x62U, - 0x61U, 0x63U, 0x6BU, 0x00U, 0x00U, 0x03U, 0x65U, 0x6EU, 0x76U, - 0x06U, 0x61U, 0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x00U, 0x00U, - 0x03U, 0x02U, 0x01U, 0x03U, 0x05U, 0x03U, 0x01U, 0x00U, 0x01U, - 0x06U, 0x08U, 0x01U, 0x7FU, 0x01U, 0x41U, 0x80U, 0x80U, 0x04U, - 0x0BU, 0x07U, 0x08U, 0x01U, 0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU, - 0x00U, 0x04U, 0x0AU, 0xD0U, 0x01U, 0x01U, 0xCDU, 0x01U, 0x02U, - 0x03U, 0x7FU, 0x01U, 0x7EU, 0x23U, 0x00U, 0x41U, 0x20U, 0x6BU, - 0x22U, 0x01U, 0x24U, 0x00U, 0x41U, 0x01U, 0x41U, 0x01U, 0x10U, - 0x00U, 0x1AU, 0x41U, 0x8EU, 0x80U, 0x80U, 0x80U, 0x78U, 0x41U, - 0x21U, 0x10U, 0x00U, 0x1AU, 0x41U, 0x00U, 0x21U, 0x00U, 0x03U, - 0x40U, 0x41U, 0x8EU, 0x80U, 0x80U, 0x80U, 0x78U, 0x41U, 0x21U, - 0x10U, 0x00U, 0x1AU, 0x20U, 0x00U, 0x20U, 0x01U, 0x6AU, 0x41U, - 0x00U, 0x3AU, 0x00U, 0x00U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, - 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x20U, 0x00U, 0x41U, - 0x01U, 0x6AU, 0x22U, 0x00U, 0x41U, 0x20U, 0x47U, 0x0DU, 0x00U, - 0x0BU, 0x20U, 0x01U, 0x41U, 0x20U, 0x41U, 0x03U, 0x41U, 0x05U, - 0x10U, 0x01U, 0x22U, 0x04U, 0x42U, 0x20U, 0x52U, 0x04U, 0x40U, - 0x41U, 0x00U, 0x41U, 0x00U, 0x20U, 0x04U, 0x10U, 0x02U, 0x1AU, - 0x0BU, 0x41U, 0x99U, 0x80U, 0x80U, 0x80U, 0x78U, 0x41U, 0x21U, + 0x1AU, 0x04U, 0x60U, 0x03U, 0x7FU, 0x7FU, 0x7EU, 0x01U, 0x7EU, + 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U, 0x03U, 0x7FU, + 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU, + 0x02U, 0x33U, 0x04U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU, + 0x67U, 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x06U, 0x72U, + 0x61U, 0x6EU, 0x64U, 0x6FU, 0x6DU, 0x00U, 0x02U, 0x03U, 0x65U, + 0x6EU, 0x76U, 0x08U, 0x72U, 0x6FU, 0x6CU, 0x6CU, 0x62U, 0x61U, + 0x63U, 0x6BU, 0x00U, 0x00U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x06U, + 0x61U, 0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x00U, 0x00U, 0x03U, + 0x02U, 0x01U, 0x03U, 0x05U, 0x03U, 0x01U, 0x00U, 0x01U, 0x06U, + 0x08U, 0x01U, 0x7FU, 0x01U, 0x41U, 0x80U, 0x80U, 0x04U, 0x0BU, + 0x07U, 0x08U, 0x01U, 0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x00U, + 0x04U, 0x0AU, 0xCEU, 0x01U, 0x01U, 0xCBU, 0x01U, 0x02U, 0x03U, + 0x7FU, 0x01U, 0x7EU, 0x23U, 0x00U, 0x41U, 0x20U, 0x6BU, 0x22U, + 0x01U, 0x24U, 0x00U, 0x41U, 0x01U, 0x41U, 0x01U, 0x10U, 0x00U, + 0x1AU, 0x41U, 0x8EU, 0x80U, 0x80U, 0x80U, 0x78U, 0x41U, 0x21U, 0x10U, 0x00U, 0x1AU, 0x41U, 0x00U, 0x21U, 0x00U, 0x03U, 0x40U, - 0x41U, 0x99U, 0x80U, 0x80U, 0x80U, 0x78U, 0x41U, 0x21U, 0x10U, - 0x00U, 0x1AU, 0x20U, 0x00U, 0x20U, 0x01U, 0x6AU, 0x2DU, 0x00U, - 0x00U, 0x21U, 0x03U, 0x41U, 0x01U, 0x20U, 0x02U, 0x20U, 0x03U, - 0x1BU, 0x21U, 0x02U, 0x20U, 0x00U, 0x41U, 0x01U, 0x6AU, 0x22U, - 0x00U, 0x41U, 0x20U, 0x47U, 0x0DU, 0x00U, 0x0BU, 0x20U, 0x02U, - 0x45U, 0x04U, 0x40U, 0x41U, 0x00U, 0x41U, 0x00U, 0x42U, 0x7EU, - 0x10U, 0x02U, 0x1AU, 0x0BU, 0x41U, 0x00U, 0x41U, 0x00U, 0x42U, - 0x00U, 0x10U, 0x03U, 0x21U, 0x04U, 0x20U, 0x01U, 0x41U, 0x20U, - 0x6AU, 0x24U, 0x00U, 0x20U, 0x04U, 0x0BU, + 0x41U, 0x8EU, 0x80U, 0x80U, 0x80U, 0x78U, 0x41U, 0x21U, 0x10U, + 0x00U, 0x1AU, 0x20U, 0x00U, 0x20U, 0x01U, 0x6AU, 0x41U, 0x00U, + 0x3AU, 0x00U, 0x00U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, + 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x20U, 0x00U, 0x41U, 0x01U, + 0x6AU, 0x22U, 0x00U, 0x41U, 0x20U, 0x47U, 0x0DU, 0x00U, 0x0BU, + 0x20U, 0x01U, 0x41U, 0x20U, 0x41U, 0x03U, 0x10U, 0x01U, 0x22U, + 0x04U, 0x42U, 0x20U, 0x52U, 0x04U, 0x40U, 0x41U, 0x00U, 0x41U, + 0x00U, 0x20U, 0x04U, 0x10U, 0x02U, 0x1AU, 0x0BU, 0x41U, 0x99U, + 0x80U, 0x80U, 0x80U, 0x78U, 0x41U, 0x21U, 0x10U, 0x00U, 0x1AU, + 0x41U, 0x00U, 0x21U, 0x00U, 0x03U, 0x40U, 0x41U, 0x99U, 0x80U, + 0x80U, 0x80U, 0x78U, 0x41U, 0x21U, 0x10U, 0x00U, 0x1AU, 0x20U, + 0x00U, 0x20U, 0x01U, 0x6AU, 0x2DU, 0x00U, 0x00U, 0x21U, 0x03U, + 0x41U, 0x01U, 0x20U, 0x02U, 0x20U, 0x03U, 0x1BU, 0x21U, 0x02U, + 0x20U, 0x00U, 0x41U, 0x01U, 0x6AU, 0x22U, 0x00U, 0x41U, 0x20U, + 0x47U, 0x0DU, 0x00U, 0x0BU, 0x20U, 0x02U, 0x45U, 0x04U, 0x40U, + 0x41U, 0x00U, 0x41U, 0x00U, 0x42U, 0x7EU, 0x10U, 0x02U, 0x1AU, + 0x0BU, 0x41U, 0x00U, 0x41U, 0x00U, 0x42U, 0x00U, 0x10U, 0x03U, + 0x21U, 0x04U, 0x20U, 0x01U, 0x41U, 0x20U, 0x6AU, 0x24U, 0x00U, + 0x20U, 0x04U, 0x0BU, }}, /* ==== WASM: 2 ==== */ @@ -140,16 +139,16 @@ inline std::map> consensusentropy_test_wasm = extern int32_t _g(uint32_t, uint32_t); extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback(uint32_t read_ptr, uint32_t read_len, int64_t error_code); - extern int64_t dice(uint32_t sides, uint32_t min_tier, uint32_t min_count); + extern int64_t dice(uint32_t sides, uint32_t min_tier); int64_t hook(uint32_t r) { _g(1,1); - int64_t r1 = dice(1000000, 3, 5); + int64_t r1 = dice(1000000, 3); if (r1 < 0) rollback(0, 0, r1); - int64_t r2 = dice(1000000, 3, 5); + int64_t r2 = dice(1000000, 3); if (r2 < 0) rollback(0, 0, r2); @@ -162,28 +161,28 @@ inline std::map> consensusentropy_test_wasm = )[test.hook]", { 0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U, - 0x1AU, 0x04U, 0x60U, 0x03U, 0x7FU, 0x7FU, 0x7EU, 0x01U, 0x7EU, - 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U, 0x03U, 0x7FU, - 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU, - 0x02U, 0x31U, 0x04U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU, - 0x67U, 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x04U, 0x64U, - 0x69U, 0x63U, 0x65U, 0x00U, 0x02U, 0x03U, 0x65U, 0x6EU, 0x76U, - 0x08U, 0x72U, 0x6FU, 0x6CU, 0x6CU, 0x62U, 0x61U, 0x63U, 0x6BU, - 0x00U, 0x00U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x06U, 0x61U, 0x63U, - 0x63U, 0x65U, 0x70U, 0x74U, 0x00U, 0x00U, 0x03U, 0x02U, 0x01U, - 0x03U, 0x05U, 0x03U, 0x01U, 0x00U, 0x01U, 0x07U, 0x08U, 0x01U, - 0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x00U, 0x04U, 0x0AU, 0x62U, - 0x01U, 0x60U, 0x01U, 0x02U, 0x7EU, 0x41U, 0x01U, 0x41U, 0x01U, - 0x10U, 0x00U, 0x1AU, 0x41U, 0xC0U, 0x84U, 0x3DU, 0x41U, 0x03U, - 0x41U, 0x05U, 0x10U, 0x01U, 0x22U, 0x01U, 0x42U, 0x00U, 0x53U, - 0x04U, 0x40U, 0x41U, 0x00U, 0x41U, 0x00U, 0x20U, 0x01U, 0x10U, - 0x02U, 0x1AU, 0x0BU, 0x41U, 0xC0U, 0x84U, 0x3DU, 0x41U, 0x03U, - 0x41U, 0x05U, 0x10U, 0x01U, 0x22U, 0x02U, 0x42U, 0x00U, 0x53U, - 0x04U, 0x40U, 0x41U, 0x00U, 0x41U, 0x00U, 0x20U, 0x02U, 0x10U, - 0x02U, 0x1AU, 0x0BU, 0x20U, 0x01U, 0x20U, 0x02U, 0x51U, 0x04U, - 0x40U, 0x41U, 0x00U, 0x41U, 0x00U, 0x42U, 0x7FU, 0x10U, 0x02U, - 0x1AU, 0x0BU, 0x41U, 0x00U, 0x41U, 0x00U, 0x20U, 0x02U, 0x42U, - 0x14U, 0x86U, 0x20U, 0x01U, 0x84U, 0x10U, 0x03U, 0x0BU, + 0x19U, 0x04U, 0x60U, 0x03U, 0x7FU, 0x7FU, 0x7EU, 0x01U, 0x7EU, + 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U, 0x02U, 0x7FU, + 0x7FU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU, 0x02U, + 0x31U, 0x04U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU, 0x67U, + 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x04U, 0x64U, 0x69U, + 0x63U, 0x65U, 0x00U, 0x02U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x08U, + 0x72U, 0x6FU, 0x6CU, 0x6CU, 0x62U, 0x61U, 0x63U, 0x6BU, 0x00U, + 0x00U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x06U, 0x61U, 0x63U, 0x63U, + 0x65U, 0x70U, 0x74U, 0x00U, 0x00U, 0x03U, 0x02U, 0x01U, 0x03U, + 0x05U, 0x03U, 0x01U, 0x00U, 0x01U, 0x07U, 0x08U, 0x01U, 0x04U, + 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x00U, 0x04U, 0x0AU, 0x5EU, 0x01U, + 0x5CU, 0x01U, 0x02U, 0x7EU, 0x41U, 0x01U, 0x41U, 0x01U, 0x10U, + 0x00U, 0x1AU, 0x41U, 0xC0U, 0x84U, 0x3DU, 0x41U, 0x03U, 0x10U, + 0x01U, 0x22U, 0x01U, 0x42U, 0x00U, 0x53U, 0x04U, 0x40U, 0x41U, + 0x00U, 0x41U, 0x00U, 0x20U, 0x01U, 0x10U, 0x02U, 0x1AU, 0x0BU, + 0x41U, 0xC0U, 0x84U, 0x3DU, 0x41U, 0x03U, 0x10U, 0x01U, 0x22U, + 0x02U, 0x42U, 0x00U, 0x53U, 0x04U, 0x40U, 0x41U, 0x00U, 0x41U, + 0x00U, 0x20U, 0x02U, 0x10U, 0x02U, 0x1AU, 0x0BU, 0x20U, 0x01U, + 0x20U, 0x02U, 0x51U, 0x04U, 0x40U, 0x41U, 0x00U, 0x41U, 0x00U, + 0x42U, 0x7FU, 0x10U, 0x02U, 0x1AU, 0x0BU, 0x41U, 0x00U, 0x41U, + 0x00U, 0x20U, 0x02U, 0x42U, 0x14U, 0x86U, 0x20U, 0x01U, 0x84U, + 0x10U, 0x03U, 0x0BU, }}, /* ==== WASM: 3 ==== */ @@ -191,31 +190,30 @@ inline std::map> consensusentropy_test_wasm = #include extern int32_t _g(uint32_t, uint32_t); extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code); - extern int64_t dice(uint32_t sides, uint32_t min_tier, uint32_t min_count); + extern int64_t dice(uint32_t sides, uint32_t min_tier); int64_t hook(uint32_t r) { _g(1,1); - int64_t result = dice(0, 3, 5); + int64_t result = dice(0, 3); // dice(0) should return negative error code, pass it through return accept(0, 0, result); } )[test.hook]", { 0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U, - 0x1AU, 0x04U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U, - 0x03U, 0x7FU, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x03U, 0x7FU, - 0x7FU, 0x7EU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU, - 0x02U, 0x22U, 0x03U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU, - 0x67U, 0x00U, 0x00U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x04U, 0x64U, - 0x69U, 0x63U, 0x65U, 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U, - 0x06U, 0x61U, 0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x00U, 0x02U, - 0x03U, 0x02U, 0x01U, 0x03U, 0x05U, 0x03U, 0x01U, 0x00U, 0x01U, - 0x07U, 0x08U, 0x01U, 0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x00U, - 0x03U, 0x0AU, 0x19U, 0x01U, 0x17U, 0x00U, 0x41U, 0x01U, 0x41U, - 0x01U, 0x10U, 0x00U, 0x1AU, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, - 0x00U, 0x41U, 0x03U, 0x41U, 0x05U, 0x10U, 0x01U, 0x10U, 0x02U, - 0x0BU, + 0x19U, 0x04U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U, + 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x03U, 0x7FU, 0x7FU, + 0x7EU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU, 0x02U, + 0x22U, 0x03U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU, 0x67U, + 0x00U, 0x00U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x04U, 0x64U, 0x69U, + 0x63U, 0x65U, 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x06U, + 0x61U, 0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x00U, 0x02U, 0x03U, + 0x02U, 0x01U, 0x03U, 0x05U, 0x03U, 0x01U, 0x00U, 0x01U, 0x07U, + 0x08U, 0x01U, 0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x00U, 0x03U, + 0x0AU, 0x17U, 0x01U, 0x15U, 0x00U, 0x41U, 0x01U, 0x41U, 0x01U, + 0x10U, 0x00U, 0x1AU, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, + 0x41U, 0x03U, 0x10U, 0x01U, 0x10U, 0x02U, 0x0BU, }}, /* ==== WASM: 4 ==== */ @@ -223,31 +221,95 @@ inline std::map> consensusentropy_test_wasm = #include extern int32_t _g(uint32_t, uint32_t); extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code); - extern int64_t dice(uint32_t sides, uint32_t min_tier, uint32_t min_count); + extern int64_t entropy_status(uint32_t write_ptr, uint32_t write_len); + #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) + #define OUT_OF_BOUNDS (-1) + #define TOO_SMALL (-4) int64_t hook(uint32_t r) { _g(1,1); - int64_t result = dice(6, 3, 21); - // requirement unmet: pass the error code through - return accept(0, 0, result); + uint8_t status[5] = {0xA5, 0xA5, 0xA5, 0xA5, 0xA5}; + + if (entropy_status((uint32_t)status, 4) != TOO_SMALL) + return accept(0, 0, 10); + for (int i = 0; GUARD(5), i < 5; ++i) + if (status[i] != 0xA5) + return accept(0, 0, 11); + + if (entropy_status(0xFFFFFFFEU, 5) != OUT_OF_BOUNDS) + return accept(0, 0, 12); + + int64_t age = entropy_status((uint32_t)status, 5); + if (age < 0 || age > 1) + return accept(0, 0, 13); + + uint32_t count = ((uint32_t)status[1] << 8) | status[2]; + uint32_t denominator = + ((uint32_t)status[3] << 8) | status[4]; + if (status[0] != 3 || count != 19 || denominator != 20) + return accept(0, 0, 14); + + // Common caller-side policies: tolerate one absent, require + // 4/5 participation, and require an absolute floor of 19. + if (status[0] < 2 || denominator - count > 1) + return accept(0, 0, 15); + if ((uint64_t)5 * count < (uint64_t)4 * denominator) + return accept(0, 0, 16); + if (count < 19) + return accept(0, 0, 17); + + return accept(0, 0, age); } )[test.hook]", { 0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U, - 0x1AU, 0x04U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U, - 0x03U, 0x7FU, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x03U, 0x7FU, - 0x7FU, 0x7EU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU, - 0x02U, 0x22U, 0x03U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU, - 0x67U, 0x00U, 0x00U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x04U, 0x64U, - 0x69U, 0x63U, 0x65U, 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U, + 0x19U, 0x04U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U, + 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x03U, 0x7FU, 0x7FU, + 0x7EU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU, 0x02U, + 0x2CU, 0x03U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU, 0x67U, + 0x00U, 0x00U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0EU, 0x65U, 0x6EU, + 0x74U, 0x72U, 0x6FU, 0x70U, 0x79U, 0x5FU, 0x73U, 0x74U, 0x61U, + 0x74U, 0x75U, 0x73U, 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x06U, 0x61U, 0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x00U, 0x02U, 0x03U, 0x02U, 0x01U, 0x03U, 0x05U, 0x03U, 0x01U, 0x00U, 0x01U, - 0x07U, 0x08U, 0x01U, 0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x00U, - 0x03U, 0x0AU, 0x19U, 0x01U, 0x17U, 0x00U, 0x41U, 0x01U, 0x41U, - 0x01U, 0x10U, 0x00U, 0x1AU, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, - 0x06U, 0x41U, 0x03U, 0x41U, 0x15U, 0x10U, 0x01U, 0x10U, 0x02U, - 0x0BU, + 0x06U, 0x08U, 0x01U, 0x7FU, 0x01U, 0x41U, 0x80U, 0x80U, 0x04U, + 0x0BU, 0x07U, 0x08U, 0x01U, 0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU, + 0x00U, 0x03U, 0x0AU, 0xB2U, 0x02U, 0x01U, 0xAFU, 0x02U, 0x01U, + 0x01U, 0x7EU, 0x23U, 0x00U, 0x41U, 0x10U, 0x6BU, 0x22U, 0x00U, + 0x24U, 0x00U, 0x41U, 0x01U, 0x41U, 0x01U, 0x10U, 0x00U, 0x1AU, + 0x20U, 0x00U, 0x41U, 0xA5U, 0x01U, 0x3AU, 0x00U, 0x0CU, 0x20U, + 0x00U, 0x41U, 0xA5U, 0xCBU, 0x96U, 0xADU, 0x7AU, 0x36U, 0x02U, + 0x08U, 0x02U, 0x40U, 0x20U, 0x00U, 0x41U, 0x08U, 0x6AU, 0x41U, + 0x04U, 0x10U, 0x01U, 0x42U, 0x7CU, 0x52U, 0x04U, 0x40U, 0x42U, + 0x0AU, 0x21U, 0x01U, 0x0CU, 0x01U, 0x0BU, 0x41U, 0x91U, 0x80U, + 0x80U, 0x80U, 0x78U, 0x41U, 0x06U, 0x10U, 0x00U, 0x1AU, 0x42U, + 0x0BU, 0x21U, 0x01U, 0x20U, 0x00U, 0x2DU, 0x00U, 0x08U, 0x41U, + 0xA5U, 0x01U, 0x47U, 0x0DU, 0x00U, 0x41U, 0x91U, 0x80U, 0x80U, + 0x80U, 0x78U, 0x41U, 0x06U, 0x10U, 0x00U, 0x1AU, 0x20U, 0x00U, + 0x2DU, 0x00U, 0x09U, 0x41U, 0xA5U, 0x01U, 0x47U, 0x0DU, 0x00U, + 0x41U, 0x91U, 0x80U, 0x80U, 0x80U, 0x78U, 0x41U, 0x06U, 0x10U, + 0x00U, 0x1AU, 0x20U, 0x00U, 0x2DU, 0x00U, 0x0AU, 0x41U, 0xA5U, + 0x01U, 0x47U, 0x0DU, 0x00U, 0x41U, 0x91U, 0x80U, 0x80U, 0x80U, + 0x78U, 0x41U, 0x06U, 0x10U, 0x00U, 0x1AU, 0x20U, 0x00U, 0x2DU, + 0x00U, 0x0BU, 0x41U, 0xA5U, 0x01U, 0x47U, 0x0DU, 0x00U, 0x41U, + 0x91U, 0x80U, 0x80U, 0x80U, 0x78U, 0x41U, 0x06U, 0x10U, 0x00U, + 0x1AU, 0x20U, 0x00U, 0x2DU, 0x00U, 0x0CU, 0x41U, 0xA5U, 0x01U, + 0x47U, 0x0DU, 0x00U, 0x41U, 0x91U, 0x80U, 0x80U, 0x80U, 0x78U, + 0x41U, 0x06U, 0x10U, 0x00U, 0x1AU, 0x41U, 0x7EU, 0x41U, 0x05U, + 0x10U, 0x01U, 0x42U, 0x7FU, 0x52U, 0x04U, 0x40U, 0x42U, 0x0CU, + 0x21U, 0x01U, 0x0CU, 0x01U, 0x0BU, 0x20U, 0x00U, 0x41U, 0x08U, + 0x6AU, 0x41U, 0x05U, 0x10U, 0x01U, 0x22U, 0x01U, 0x42U, 0x01U, + 0x56U, 0x04U, 0x40U, 0x42U, 0x0DU, 0x21U, 0x01U, 0x0CU, 0x01U, + 0x0BU, 0x42U, 0x0EU, 0x42U, 0x0EU, 0x42U, 0x0EU, 0x20U, 0x01U, + 0x20U, 0x00U, 0x2DU, 0x00U, 0x0CU, 0x20U, 0x00U, 0x2DU, 0x00U, + 0x0BU, 0x41U, 0x08U, 0x74U, 0x72U, 0x41U, 0x14U, 0x47U, 0x1BU, + 0x20U, 0x00U, 0x2DU, 0x00U, 0x0AU, 0x20U, 0x00U, 0x2DU, 0x00U, + 0x09U, 0x41U, 0x08U, 0x74U, 0x72U, 0x41U, 0x13U, 0x47U, 0x1BU, + 0x20U, 0x00U, 0x2DU, 0x00U, 0x08U, 0x41U, 0x03U, 0x47U, 0x1BU, + 0x21U, 0x01U, 0x0BU, 0x41U, 0x00U, 0x41U, 0x00U, 0x20U, 0x01U, + 0x10U, 0x02U, 0x21U, 0x01U, 0x20U, 0x00U, 0x41U, 0x10U, 0x6AU, + 0x24U, 0x00U, 0x20U, 0x01U, 0x0BU, }}, /* ==== WASM: 5 ==== */ @@ -255,30 +317,61 @@ inline std::map> consensusentropy_test_wasm = #include extern int32_t _g(uint32_t, uint32_t); extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code); - extern int64_t dice(uint32_t sides, uint32_t min_tier, uint32_t min_count); + extern int64_t dice(uint32_t sides, uint32_t min_tier); + extern int64_t entropy_status(uint32_t write_ptr, uint32_t write_len); + #define TOO_LITTLE_ENTROPY (-48) int64_t hook(uint32_t r) { _g(1,1); - int64_t result = dice(6, 4, 0); - return accept(0, 0, result); + uint8_t status[5]; + int64_t age = entropy_status((uint32_t)status, 5); + if (age < 0 || age > 1) + return accept(0, 0, 20); + if (status[0] != 1 || status[1] != 0 || status[2] != 0 || + status[3] != 0 || status[4] != 0) + return accept(0, 0, 21); + + int64_t allowed = dice(6, 1); + if (allowed < 0 || allowed > 5) + return accept(0, 0, 22); + if (dice(6, 2) != TOO_LITTLE_ENTROPY) + return accept(0, 0, 23); + + return accept(0, 0, age); } )[test.hook]", { 0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U, - 0x1AU, 0x04U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U, - 0x03U, 0x7FU, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x03U, 0x7FU, - 0x7FU, 0x7EU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU, - 0x02U, 0x22U, 0x03U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU, - 0x67U, 0x00U, 0x00U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x04U, 0x64U, - 0x69U, 0x63U, 0x65U, 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U, - 0x06U, 0x61U, 0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x00U, 0x02U, - 0x03U, 0x02U, 0x01U, 0x03U, 0x05U, 0x03U, 0x01U, 0x00U, 0x01U, - 0x07U, 0x08U, 0x01U, 0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x00U, - 0x03U, 0x0AU, 0x19U, 0x01U, 0x17U, 0x00U, 0x41U, 0x01U, 0x41U, - 0x01U, 0x10U, 0x00U, 0x1AU, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, - 0x06U, 0x41U, 0x04U, 0x41U, 0x00U, 0x10U, 0x01U, 0x10U, 0x02U, - 0x0BU, + 0x19U, 0x04U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, + 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U, 0x03U, 0x7FU, 0x7FU, + 0x7EU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU, 0x02U, + 0x37U, 0x04U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU, 0x67U, + 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0EU, 0x65U, 0x6EU, + 0x74U, 0x72U, 0x6FU, 0x70U, 0x79U, 0x5FU, 0x73U, 0x74U, 0x61U, + 0x74U, 0x75U, 0x73U, 0x00U, 0x00U, 0x03U, 0x65U, 0x6EU, 0x76U, + 0x04U, 0x64U, 0x69U, 0x63U, 0x65U, 0x00U, 0x00U, 0x03U, 0x65U, + 0x6EU, 0x76U, 0x06U, 0x61U, 0x63U, 0x63U, 0x65U, 0x70U, 0x74U, + 0x00U, 0x02U, 0x03U, 0x02U, 0x01U, 0x03U, 0x05U, 0x03U, 0x01U, + 0x00U, 0x01U, 0x06U, 0x08U, 0x01U, 0x7FU, 0x01U, 0x41U, 0x80U, + 0x80U, 0x04U, 0x0BU, 0x07U, 0x08U, 0x01U, 0x04U, 0x68U, 0x6FU, + 0x6FU, 0x6BU, 0x00U, 0x04U, 0x0AU, 0x90U, 0x01U, 0x01U, 0x8DU, + 0x01U, 0x01U, 0x02U, 0x7EU, 0x23U, 0x00U, 0x41U, 0x10U, 0x6BU, + 0x22U, 0x00U, 0x24U, 0x00U, 0x41U, 0x01U, 0x41U, 0x01U, 0x10U, + 0x00U, 0x1AU, 0x02U, 0x40U, 0x20U, 0x00U, 0x41U, 0x0BU, 0x6AU, + 0x41U, 0x05U, 0x10U, 0x01U, 0x22U, 0x02U, 0x42U, 0x01U, 0x56U, + 0x04U, 0x40U, 0x42U, 0x14U, 0x21U, 0x01U, 0x0CU, 0x01U, 0x0BU, + 0x42U, 0x15U, 0x21U, 0x01U, 0x20U, 0x00U, 0x2DU, 0x00U, 0x0BU, + 0x41U, 0x01U, 0x47U, 0x0DU, 0x00U, 0x20U, 0x00U, 0x2DU, 0x00U, + 0x0CU, 0x0DU, 0x00U, 0x20U, 0x00U, 0x2DU, 0x00U, 0x0DU, 0x0DU, + 0x00U, 0x20U, 0x00U, 0x2DU, 0x00U, 0x0EU, 0x0DU, 0x00U, 0x20U, + 0x00U, 0x2DU, 0x00U, 0x0FU, 0x0DU, 0x00U, 0x41U, 0x06U, 0x41U, + 0x01U, 0x10U, 0x02U, 0x42U, 0x05U, 0x56U, 0x04U, 0x40U, 0x42U, + 0x16U, 0x21U, 0x01U, 0x0CU, 0x01U, 0x0BU, 0x42U, 0x17U, 0x20U, + 0x02U, 0x41U, 0x06U, 0x41U, 0x02U, 0x10U, 0x02U, 0x42U, 0x50U, + 0x52U, 0x1BU, 0x21U, 0x01U, 0x0BU, 0x41U, 0x00U, 0x41U, 0x00U, + 0x20U, 0x01U, 0x10U, 0x03U, 0x21U, 0x01U, 0x20U, 0x00U, 0x41U, + 0x10U, 0x6AU, 0x24U, 0x00U, 0x20U, 0x01U, 0x0BU, }}, /* ==== WASM: 6 ==== */ @@ -286,30 +379,29 @@ inline std::map> consensusentropy_test_wasm = #include extern int32_t _g(uint32_t, uint32_t); extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code); - extern int64_t dice(uint32_t sides, uint32_t min_tier, uint32_t min_count); + extern int64_t dice(uint32_t sides, uint32_t min_tier); int64_t hook(uint32_t r) { _g(1,1); - int64_t result = dice(6, 3, 0); + int64_t result = dice(6, 4); return accept(0, 0, result); } )[test.hook]", { 0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U, - 0x1AU, 0x04U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U, - 0x03U, 0x7FU, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x03U, 0x7FU, - 0x7FU, 0x7EU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU, - 0x02U, 0x22U, 0x03U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU, - 0x67U, 0x00U, 0x00U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x04U, 0x64U, - 0x69U, 0x63U, 0x65U, 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U, - 0x06U, 0x61U, 0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x00U, 0x02U, - 0x03U, 0x02U, 0x01U, 0x03U, 0x05U, 0x03U, 0x01U, 0x00U, 0x01U, - 0x07U, 0x08U, 0x01U, 0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x00U, - 0x03U, 0x0AU, 0x19U, 0x01U, 0x17U, 0x00U, 0x41U, 0x01U, 0x41U, - 0x01U, 0x10U, 0x00U, 0x1AU, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, - 0x06U, 0x41U, 0x03U, 0x41U, 0x00U, 0x10U, 0x01U, 0x10U, 0x02U, - 0x0BU, + 0x19U, 0x04U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U, + 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x03U, 0x7FU, 0x7FU, + 0x7EU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU, 0x02U, + 0x22U, 0x03U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU, 0x67U, + 0x00U, 0x00U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x04U, 0x64U, 0x69U, + 0x63U, 0x65U, 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x06U, + 0x61U, 0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x00U, 0x02U, 0x03U, + 0x02U, 0x01U, 0x03U, 0x05U, 0x03U, 0x01U, 0x00U, 0x01U, 0x07U, + 0x08U, 0x01U, 0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x00U, 0x03U, + 0x0AU, 0x17U, 0x01U, 0x15U, 0x00U, 0x41U, 0x01U, 0x41U, 0x01U, + 0x10U, 0x00U, 0x1AU, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x06U, + 0x41U, 0x04U, 0x10U, 0x01U, 0x10U, 0x02U, 0x0BU, }}, /* ==== WASM: 7 ==== */ @@ -317,8 +409,50 @@ inline std::map> consensusentropy_test_wasm = #include extern int32_t _g(uint32_t, uint32_t); extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code); - extern int64_t dice(uint32_t sides, uint32_t min_tier, uint32_t min_count); - extern int64_t random(uint32_t write_ptr, uint32_t write_len, uint32_t min_tier, uint32_t min_count); + extern int64_t dice(uint32_t sides, uint32_t min_tier); + extern int64_t entropy_status(uint32_t write_ptr, uint32_t write_len); + + int64_t hook(uint32_t r) + { + _g(1,1); + uint8_t status[5]; + int64_t result = entropy_status((uint32_t)status, 5); + if (result >= 0) + result = dice(6, 3); + return accept(0, 0, result); + } + )[test.hook]", + { + 0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U, + 0x19U, 0x04U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, + 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U, 0x03U, 0x7FU, 0x7FU, + 0x7EU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU, 0x02U, + 0x37U, 0x04U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU, 0x67U, + 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0EU, 0x65U, 0x6EU, + 0x74U, 0x72U, 0x6FU, 0x70U, 0x79U, 0x5FU, 0x73U, 0x74U, 0x61U, + 0x74U, 0x75U, 0x73U, 0x00U, 0x00U, 0x03U, 0x65U, 0x6EU, 0x76U, + 0x04U, 0x64U, 0x69U, 0x63U, 0x65U, 0x00U, 0x00U, 0x03U, 0x65U, + 0x6EU, 0x76U, 0x06U, 0x61U, 0x63U, 0x63U, 0x65U, 0x70U, 0x74U, + 0x00U, 0x02U, 0x03U, 0x02U, 0x01U, 0x03U, 0x05U, 0x03U, 0x01U, + 0x00U, 0x01U, 0x06U, 0x08U, 0x01U, 0x7FU, 0x01U, 0x41U, 0x80U, + 0x80U, 0x04U, 0x0BU, 0x07U, 0x08U, 0x01U, 0x04U, 0x68U, 0x6FU, + 0x6FU, 0x6BU, 0x00U, 0x04U, 0x0AU, 0x41U, 0x01U, 0x3FU, 0x01U, + 0x01U, 0x7EU, 0x23U, 0x00U, 0x41U, 0x10U, 0x6BU, 0x22U, 0x00U, + 0x24U, 0x00U, 0x41U, 0x01U, 0x41U, 0x01U, 0x10U, 0x00U, 0x1AU, + 0x41U, 0x00U, 0x41U, 0x00U, 0x20U, 0x00U, 0x41U, 0x0BU, 0x6AU, + 0x41U, 0x05U, 0x10U, 0x01U, 0x22U, 0x01U, 0x42U, 0x00U, 0x59U, + 0x04U, 0x7EU, 0x41U, 0x06U, 0x41U, 0x03U, 0x10U, 0x02U, 0x05U, + 0x20U, 0x01U, 0x0BU, 0x10U, 0x03U, 0x21U, 0x01U, 0x20U, 0x00U, + 0x41U, 0x10U, 0x6AU, 0x24U, 0x00U, 0x20U, 0x01U, 0x0BU, + }}, + + /* ==== WASM: 8 ==== */ + {R"[test.hook]( + #include + extern int32_t _g(uint32_t, uint32_t); + extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code); + extern int64_t dice(uint32_t sides, uint32_t min_tier); + extern int64_t random(uint32_t write_ptr, uint32_t write_len, uint32_t min_tier); #define INVALID_ARGUMENT (-7) int64_t hook(uint32_t r) @@ -326,25 +460,25 @@ inline std::map> consensusentropy_test_wasm = _g(1,1); uint8_t buf[32]; - int64_t bad_min_tier = dice(6, 0, 0); + int64_t bad_min_tier = dice(6, 0); if (bad_min_tier != INVALID_ARGUMENT) return accept(0, 0, bad_min_tier); - int64_t ok_full_tier = dice(6, 4, 0); + int64_t ok_full_tier = dice(6, 4); if (ok_full_tier < 0 || ok_full_tier > 5) return accept(0, 0, ok_full_tier); - int64_t bad_high_tier = dice(6, 5, 0); + int64_t bad_high_tier = dice(6, 5); if (bad_high_tier != INVALID_ARGUMENT) return accept(0, 0, bad_high_tier); - int64_t bad_min_count = dice(6, 3, 70000); - if (bad_min_count != INVALID_ARGUMENT) - return accept(0, 0, bad_min_count); + int64_t bad_random_low = random((uint32_t)buf, 32, 0); + if (bad_random_low != INVALID_ARGUMENT) + return accept(0, 0, bad_random_low); - int64_t bad_random_tier = random((uint32_t)buf, 32, 5, 0); - if (bad_random_tier != INVALID_ARGUMENT) - return accept(0, 0, bad_random_tier); + int64_t bad_random_high = random((uint32_t)buf, 32, 5); + if (bad_random_high != INVALID_ARGUMENT) + return accept(0, 0, bad_random_high); // Sentinel distinct from any dice (0..5) / random result and // from INVALID_ARGUMENT, so a regression that lets a bad @@ -354,39 +488,38 @@ inline std::map> consensusentropy_test_wasm = )[test.hook]", { 0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U, - 0x22U, 0x05U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U, - 0x03U, 0x7FU, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x03U, 0x7FU, - 0x7FU, 0x7EU, 0x01U, 0x7EU, 0x60U, 0x04U, 0x7FU, 0x7FU, 0x7FU, - 0x7FU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU, 0x02U, - 0x2FU, 0x04U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU, 0x67U, - 0x00U, 0x00U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x04U, 0x64U, 0x69U, - 0x63U, 0x65U, 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x06U, - 0x61U, 0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x00U, 0x02U, 0x03U, - 0x65U, 0x6EU, 0x76U, 0x06U, 0x72U, 0x61U, 0x6EU, 0x64U, 0x6FU, - 0x6DU, 0x00U, 0x03U, 0x03U, 0x02U, 0x01U, 0x04U, 0x05U, 0x03U, - 0x01U, 0x00U, 0x01U, 0x06U, 0x08U, 0x01U, 0x7FU, 0x01U, 0x41U, - 0x80U, 0x80U, 0x04U, 0x0BU, 0x07U, 0x08U, 0x01U, 0x04U, 0x68U, - 0x6FU, 0x6FU, 0x6BU, 0x00U, 0x04U, 0x0AU, 0xB3U, 0x01U, 0x01U, - 0xB0U, 0x01U, 0x01U, 0x01U, 0x7EU, 0x23U, 0x00U, 0x41U, 0x20U, - 0x6BU, 0x22U, 0x00U, 0x24U, 0x00U, 0x41U, 0x01U, 0x41U, 0x01U, - 0x10U, 0x00U, 0x1AU, 0x02U, 0x7EU, 0x41U, 0x06U, 0x41U, 0x00U, - 0x41U, 0x00U, 0x10U, 0x01U, 0x22U, 0x01U, 0x42U, 0x79U, 0x52U, - 0x04U, 0x40U, 0x41U, 0x00U, 0x41U, 0x00U, 0x20U, 0x01U, 0x10U, - 0x02U, 0x0CU, 0x01U, 0x0BU, 0x41U, 0x06U, 0x41U, 0x04U, 0x41U, - 0x00U, 0x10U, 0x01U, 0x22U, 0x01U, 0x42U, 0x06U, 0x5AU, 0x04U, + 0x20U, 0x05U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U, + 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x03U, 0x7FU, 0x7FU, + 0x7EU, 0x01U, 0x7EU, 0x60U, 0x03U, 0x7FU, 0x7FU, 0x7FU, 0x01U, + 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU, 0x02U, 0x2FU, 0x04U, + 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU, 0x67U, 0x00U, 0x00U, + 0x03U, 0x65U, 0x6EU, 0x76U, 0x04U, 0x64U, 0x69U, 0x63U, 0x65U, + 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x06U, 0x61U, 0x63U, + 0x63U, 0x65U, 0x70U, 0x74U, 0x00U, 0x02U, 0x03U, 0x65U, 0x6EU, + 0x76U, 0x06U, 0x72U, 0x61U, 0x6EU, 0x64U, 0x6FU, 0x6DU, 0x00U, + 0x03U, 0x03U, 0x02U, 0x01U, 0x04U, 0x05U, 0x03U, 0x01U, 0x00U, + 0x01U, 0x06U, 0x08U, 0x01U, 0x7FU, 0x01U, 0x41U, 0x80U, 0x80U, + 0x04U, 0x0BU, 0x07U, 0x08U, 0x01U, 0x04U, 0x68U, 0x6FU, 0x6FU, + 0x6BU, 0x00U, 0x04U, 0x0AU, 0xA9U, 0x01U, 0x01U, 0xA6U, 0x01U, + 0x01U, 0x01U, 0x7EU, 0x23U, 0x00U, 0x41U, 0x20U, 0x6BU, 0x22U, + 0x00U, 0x24U, 0x00U, 0x41U, 0x01U, 0x41U, 0x01U, 0x10U, 0x00U, + 0x1AU, 0x02U, 0x7EU, 0x41U, 0x06U, 0x41U, 0x00U, 0x10U, 0x01U, + 0x22U, 0x01U, 0x42U, 0x79U, 0x52U, 0x04U, 0x40U, 0x41U, 0x00U, + 0x41U, 0x00U, 0x20U, 0x01U, 0x10U, 0x02U, 0x0CU, 0x01U, 0x0BU, + 0x41U, 0x06U, 0x41U, 0x04U, 0x10U, 0x01U, 0x22U, 0x01U, 0x42U, + 0x06U, 0x5AU, 0x04U, 0x40U, 0x41U, 0x00U, 0x41U, 0x00U, 0x20U, + 0x01U, 0x10U, 0x02U, 0x0CU, 0x01U, 0x0BU, 0x41U, 0x06U, 0x41U, + 0x05U, 0x10U, 0x01U, 0x22U, 0x01U, 0x42U, 0x79U, 0x52U, 0x04U, 0x40U, 0x41U, 0x00U, 0x41U, 0x00U, 0x20U, 0x01U, 0x10U, 0x02U, - 0x0CU, 0x01U, 0x0BU, 0x41U, 0x06U, 0x41U, 0x05U, 0x41U, 0x00U, - 0x10U, 0x01U, 0x22U, 0x01U, 0x42U, 0x79U, 0x52U, 0x04U, 0x40U, + 0x0CU, 0x01U, 0x0BU, 0x20U, 0x00U, 0x41U, 0x20U, 0x41U, 0x00U, + 0x10U, 0x03U, 0x22U, 0x01U, 0x42U, 0x79U, 0x52U, 0x04U, 0x40U, 0x41U, 0x00U, 0x41U, 0x00U, 0x20U, 0x01U, 0x10U, 0x02U, 0x0CU, - 0x01U, 0x0BU, 0x41U, 0x06U, 0x41U, 0x03U, 0x41U, 0xF0U, 0xA2U, - 0x04U, 0x10U, 0x01U, 0x22U, 0x01U, 0x42U, 0x79U, 0x52U, 0x04U, - 0x40U, 0x41U, 0x00U, 0x41U, 0x00U, 0x20U, 0x01U, 0x10U, 0x02U, - 0x0CU, 0x01U, 0x0BU, 0x20U, 0x00U, 0x41U, 0x20U, 0x41U, 0x05U, - 0x41U, 0x00U, 0x10U, 0x03U, 0x22U, 0x01U, 0x42U, 0x79U, 0x52U, - 0x04U, 0x40U, 0x41U, 0x00U, 0x41U, 0x00U, 0x20U, 0x01U, 0x10U, - 0x02U, 0x0CU, 0x01U, 0x0BU, 0x41U, 0x00U, 0x41U, 0x00U, 0x42U, - 0x2AU, 0x10U, 0x02U, 0x0BU, 0x21U, 0x01U, 0x20U, 0x00U, 0x41U, - 0x20U, 0x6AU, 0x24U, 0x00U, 0x20U, 0x01U, 0x0BU, + 0x01U, 0x0BU, 0x20U, 0x00U, 0x41U, 0x20U, 0x41U, 0x05U, 0x10U, + 0x03U, 0x22U, 0x01U, 0x42U, 0x79U, 0x52U, 0x04U, 0x40U, 0x41U, + 0x00U, 0x41U, 0x00U, 0x20U, 0x01U, 0x10U, 0x02U, 0x0CU, 0x01U, + 0x0BU, 0x41U, 0x00U, 0x41U, 0x00U, 0x42U, 0x2AU, 0x10U, 0x02U, + 0x0BU, 0x21U, 0x01U, 0x20U, 0x00U, 0x41U, 0x20U, 0x6AU, 0x24U, + 0x00U, 0x20U, 0x01U, 0x0BU, }}, }; diff --git a/src/xrpld/app/consensus/ConsensusEntropyIntent.md b/src/xrpld/app/consensus/ConsensusEntropyIntent.md index 00feb9c99..d7cd9c6da 100644 --- a/src/xrpld/app/consensus/ConsensusEntropyIntent.md +++ b/src/xrpld/app/consensus/ConsensusEntropyIntent.md @@ -127,14 +127,17 @@ dependency on the set that carries the pseudo-tx). pre-injection set hash. **INV-6 — Bounded, opt-in entropy quality.** -Hooks state `min_tier` / `min_count` explicitly (no hidden network default). +Hooks state `min_tier` explicitly on every draw (no hidden network default). Entropy is served iff it is **fresh** (current or previous ledger) **and** meets -the requirement; otherwise the call **fails closed** (`TOO_LITTLE_ENTROPY`). A -hook never silently receives weaker-than-requested entropy. Draws are also -domain-separated by the hook execution role that can share a transaction and -hook hash: strong vs weak, callback vs direct dispatch, and hook chain -position. -*Enforced:* `fairRng` gate. +that class floor; otherwise the call **fails closed** +(`TOO_LITTLE_ENTROPY`). `entropy_status()` separately exposes the stored tier, +contributor count, denominator, and ledger age so hooks can impose proportional +or absolute policies without freezing those policies into the host ABI. +Fallback is tier 1 with count/denominator `0/0`, so callers must classify tier +before arithmetic. Draws are also domain-separated by the hook execution role +that can share a transaction and hook hash: strong vs weak, callback vs direct +dispatch, and hook chain position. +*Enforced:* `fairRng` tier/freshness gate and metadata-only `entropy_status`. **INV-7 — Inert when un-amended.** With `featureConsensusEntropy` off, no RNG sidecar state is consensus-visible and diff --git a/src/xrpld/app/consensus/ConsensusExtensionsDesign.md b/src/xrpld/app/consensus/ConsensusExtensionsDesign.md index ee3780822..f0014432b 100644 --- a/src/xrpld/app/consensus/ConsensusExtensionsDesign.md +++ b/src/xrpld/app/consensus/ConsensusExtensionsDesign.md @@ -375,20 +375,27 @@ digest/tier/count/denominator tuple. This does not make the bitmap non-critical: any disagreement in a ledger-written field is a ledger disagreement. It only keeps transaction ordering semantically tied to the entropy value and quality labels rather than to the accountability label. Hooks -state their own requirements via the required -`min_tier`/`min_count` arguments to `dice()`/`random()`: a hook that demands -validator-tier entropy fails closed with `TOO_LITTLE_ENTROPY` on fallback -ledgers, while a hook that opts into fallback-grade randomness must do so -explicitly at the call site. Valid `min_tier` values are the stored entropy -tiers 1..4; `min_count` must fit the on-ledger `EntropyCount` UINT16 field. -Invalid requirements return `INVALID_ARGUMENT`, while valid-but-unmet -requirements return `TOO_LITTLE_ENTROPY`. +state their class requirement through the mandatory `min_tier` argument to +`dice()`/`random()`: a hook that demands validator-tier entropy fails closed +with `TOO_LITTLE_ENTROPY` on fallback ledgers, while a hook that opts into +fallback-grade randomness must do so explicitly at the call site. Valid +`min_tier` values are the stored entropy tiers 1..4; invalid requirements return +`INVALID_ARGUMENT`, while valid-but-unmet requirements return +`TOO_LITTLE_ENTROPY`. + +`entropy_status(write_ptr, write_len)` writes the fixed five-byte big-endian +tuple `(tier:u8, count:u16, denominator:u16)` and returns the metadata's ledger +age. This lets Hook code implement policies such as one-absent tolerance, +proportional participation, or an absolute floor without widening the frozen +draw API. It exposes no digest. Callers must classify tier before count or +denominator arithmetic because fallback deliberately reports tier 1 and +`0/0`. Open-ledger hook execution is provisional. During speculative open-ledger -execution, `dice()`/`random()` can only use the previous ledger's finalized -entropy; final buildLCL execution sees the current ledger's entropy pseudo-tx -after it updates the SLE. Hooks that need final entropy must treat open-ledger -RNG results as previews. +execution, `dice()`/`random()` and `entropy_status()` can only use the previous +ledger's finalized entropy (status age 1); final buildLCL execution sees the +current ledger's entropy pseudo-tx after it updates the SLE (status age 0). +Hooks that need final entropy must treat open-ledger RNG results as previews. The fallback digest derives from the agreed pre-injection tx set hash to avoid circularity, and entropy pseudo-tx deduplication is value-based: if the agreed diff --git a/src/xrpld/app/hook/detail/applyHook.cpp b/src/xrpld/app/hook/detail/applyHook.cpp index 3e6e9f002..2860dec16 100644 --- a/src/xrpld/app/hook/detail/applyHook.cpp +++ b/src/xrpld/app/hook/detail/applyHook.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -4059,24 +4060,59 @@ DEFINE_HOOK_FUNCTION( //@@end xport-impl inline bool -invalidEntropyRequirement(uint32_t minTier, uint32_t minCount) +invalidEntropyRequirement(uint32_t minTier) { return minTier < entropyTierConsensusFallback || - minTier > entropyTierValidatorFull || - minCount > std::numeric_limits::max(); + minTier > entropyTierValidatorFull; +} + +struct EntropySnapshot +{ + std::shared_ptr sle; + std::uint32_t age; + std::uint8_t tier; + std::uint16_t count; + std::uint16_t denominator; +}; + +inline std::variant +readEntropySnapshot(ApplyView& view) +{ + auto sle = view.peek(ripple::keylet::consensusEntropy()); + if (!sle) + return hook_api::hook_return_code::DOESNT_EXIST; + + if (!sle->isFieldPresent(sfDigest) || + !sle->isFieldPresent(sfLedgerSequence) || + !sle->isFieldPresent(sfEntropyTier) || + !sle->isFieldPresent(sfEntropyCount) || + !sle->isFieldPresent(sfEntropyDenominator) || + !sle->isFieldPresent(sfEntropyContributors)) + return hook_api::hook_return_code::INTERNAL_ERROR; + + auto const seq = view.info().seq; + auto const entropySeq = sle->getFieldU32(sfLedgerSequence); + if (entropySeq > seq) + return hook_api::hook_return_code::INTERNAL_ERROR; + + return EntropySnapshot{ + sle, + seq - entropySeq, + sle->getFieldU8(sfEntropyTier), + sle->getFieldU16(sfEntropyCount), + sle->getFieldU16(sfEntropyDenominator)}; } // byteCount must be a multiple of 32. -// minTier/minCount are the CALLER'S stated requirements (required hook API -// arguments — there is deliberately no network-wide default): entropy is -// usable iff tier >= minTier && count >= minCount, in addition to freshness. +// minTier is the CALLER'S stated class requirement (a required hook API +// argument — there is deliberately no network-wide default). Count and +// denominator policy is available separately through entropy_status(). inline std::vector fairRng( ApplyContext& applyCtx, hook::HookResult& hr, uint32_t byteCount, - uint32_t minTier, - uint32_t minCount) + uint32_t minTier) { if (byteCount > 512) byteCount = 512; @@ -4088,28 +4124,17 @@ fairRng( return {}; auto& view = applyCtx.view(); - - auto const sleEntropy = view.peek(ripple::keylet::consensusEntropy()); - auto const seq = view.info().seq; - - auto const entropySeq = - sleEntropy ? sleEntropy->getFieldU32(sfLedgerSequence) : 0u; + auto snapshot = readEntropySnapshot(view); + if (!std::holds_alternative(snapshot)) + return {}; + auto const& entropy = std::get(snapshot); // Open-ledger hook execution is provisional and can only see the previous // ledger's finalized entropy. Final buildLCL execution sees the current // ledger's entropy pseudo-tx after it updates this SLE. That open-vs-final // skew is inherent to speculative execution; callers that need final // entropy must treat open-ledger dice/random results as previews. - // Defensive: sfEntropyTier is soeREQUIRED, so any entry this code wrote - // carries it. A missing field can only come from a pre-tier-3 persisted - // entry; treat that as tier 0 (none) so the requirement check fails closed. - auto const entropyTier = - sleEntropy && sleEntropy->isFieldPresent(sfEntropyTier) - ? sleEntropy->getFieldU8(sfEntropyTier) - : std::uint8_t{0}; - if (!sleEntropy || entropySeq > seq || (seq - entropySeq) > 1 || - entropyTier < minTier || - sleEntropy->getFieldU16(sfEntropyCount) < minCount) + if (entropy.age > 1 || entropy.tier < minTier) return {}; // we'll generate bytes in lots of 32 @@ -4130,7 +4155,7 @@ fairRng( hr.hookChainPosition, hr.isStrong ? std::string("strong") : std::string("weak"), hr.isCallback ? std::string("callback") : std::string("direct"), - sleEntropy->getFieldH256(sfDigest), + entropy.sle->getFieldH256(sfDigest), hr.rngCallCounter++); std::vector bytesOut; @@ -4151,22 +4176,17 @@ fairRng( return bytesOut; } -DEFINE_HOOK_FUNCTION( - int64_t, - dice, - uint32_t sides, - uint32_t min_tier, - uint32_t min_count) +DEFINE_HOOK_FUNCTION(int64_t, dice, uint32_t sides, uint32_t min_tier) { HOOK_SETUP(); if (sides == 0) return INVALID_ARGUMENT; - if (invalidEntropyRequirement(min_tier, min_count)) + if (invalidEntropyRequirement(min_tier)) return INVALID_ARGUMENT; - auto vec = fairRng(applyCtx, hookCtx.result, 32, min_tier, min_count); + auto vec = fairRng(applyCtx, hookCtx.result, 32, min_tier); if (vec.empty()) return TOO_LITTLE_ENTROPY; @@ -4206,8 +4226,7 @@ DEFINE_HOOK_FUNCTION( random, uint32_t write_ptr, uint32_t write_len, - uint32_t min_tier, - uint32_t min_count) + uint32_t min_tier) { HOOK_SETUP(); @@ -4233,10 +4252,10 @@ DEFINE_HOOK_FUNCTION( if (NOT_IN_BOUNDS(write_ptr, write_len, memory_length)) return OUT_OF_BOUNDS; - if (invalidEntropyRequirement(min_tier, min_count)) + if (invalidEntropyRequirement(min_tier)) return INVALID_ARGUMENT; - auto vec = fairRng(applyCtx, hookCtx.result, required, min_tier, min_count); + auto vec = fairRng(applyCtx, hookCtx.result, required, min_tier); if (vec.empty()) return TOO_LITTLE_ENTROPY; @@ -4246,6 +4265,42 @@ DEFINE_HOOK_FUNCTION( HOOK_TEARDOWN(); } + +DEFINE_HOOK_FUNCTION( + int64_t, + entropy_status, + uint32_t write_ptr, + uint32_t write_len) +{ + HOOK_SETUP(); + + constexpr std::size_t statusSize = 5; + if (write_len < statusSize) + return TOO_SMALL; + + if (NOT_IN_BOUNDS(write_ptr, statusSize, memory_length)) + return OUT_OF_BOUNDS; + + auto snapshot = readEntropySnapshot(view); + if (std::holds_alternative(snapshot)) + return std::get(snapshot); + + auto const& entropy = std::get(snapshot); + std::array const status{ + entropy.tier, + static_cast(entropy.count >> 8), + static_cast(entropy.count), + static_cast(entropy.denominator >> 8), + static_cast(entropy.denominator)}; + + if (!WasmEdge_ResultOK(WasmEdge_MemoryInstanceSetData( + memoryCtx, status.data(), write_ptr, status.size()))) + return INTERNAL_ERROR; + + return entropy.age; + + HOOK_TEARDOWN(); +} /* DEFINE_HOOK_FUNCTION(