diff --git a/include/xrpl/protocol/ExportLimits.h b/include/xrpl/protocol/ExportLimits.h index a8d2fa79a..05b2046b6 100644 --- a/include/xrpl/protocol/ExportLimits.h +++ b/include/xrpl/protocol/ExportLimits.h @@ -29,6 +29,18 @@ struct ExportLimits // reserves a separate operator signer must select fewer validators. static constexpr std::size_t maxCommitteeMembers = 32; + // V1 witnesses require the standard 80% quorum of the intent-selected + // committee, rounded up. The intent selects members but cannot lower this + // threshold. Split quotient/remainder arithmetic avoids overflow while + // preserving ceil(memberCount * 0.8). + static constexpr std::size_t + committeeQuorumThreshold(std::size_t memberCount) + { + auto const quotient = memberCount / 5; + auto const remainder = memberCount % 5; + return quotient * 4 + (remainder * 4 + 4) / 5; + } + // Maximum exports a single hook execution may produce. Hook API ABI // constant hook_api::max_export must stay equal. static constexpr std::uint8_t maxExportsPerHook = 2; diff --git a/src/test/protocol/ExportKeylet_test.cpp b/src/test/protocol/ExportKeylet_test.cpp index 6cc040daf..0b754c0a5 100644 --- a/src/test/protocol/ExportKeylet_test.cpp +++ b/src/test/protocol/ExportKeylet_test.cpp @@ -27,6 +27,7 @@ #include #include +#include #include namespace ripple { @@ -116,6 +117,15 @@ public: BEAST_EXPECT(ExportLimits::maxCommitteeMaskBytes == 32); BEAST_EXPECT( ExportLimits::maxCommitteeMembers == STTx::maxMultiSigners()); + + std::array constexpr expected{ + 0, 1, 2, 3, 4, 4, 5, 6, 7, 8, 8}; + for (std::size_t members = 0; members < expected.size(); ++members) + BEAST_EXPECT( + ExportLimits::committeeQuorumThreshold(members) == + expected[members]); + BEAST_EXPECT(ExportLimits::committeeQuorumThreshold(28) == 23); + BEAST_EXPECT(ExportLimits::committeeQuorumThreshold(32) == 26); } void