fix(protocol): require validated validator bitsets

This commit is contained in:
Nicholas Dudfield
2026-07-13 18:44:13 +07:00
parent 0536e2e9eb
commit 62050233a5
3 changed files with 60 additions and 60 deletions

View File

@@ -30,9 +30,44 @@
namespace ripple {
struct ValidatorBitsetInfo
class ValidatedValidatorBitset
{
std::size_t selected = 0;
Slice bitset_;
std::size_t memberCount_ = 0;
std::size_t selected_ = 0;
ValidatedValidatorBitset(
Slice bitset,
std::size_t memberCount,
std::size_t selected)
: bitset_(bitset), memberCount_(memberCount), selected_(selected)
{
}
public:
/** Construct a validated, non-owning view over a validator bitset.
The caller must keep the bitset storage alive while using the view.
*/
static std::optional<ValidatedValidatorBitset>
make(Slice bitset, std::size_t memberCount);
std::size_t
selected() const
{
return selected_;
}
bool
contains(std::size_t position) const
{
if (position >= memberCount_)
return false;
auto const byte = position / 8;
return (bitset_[byte] &
static_cast<std::uint8_t>(1u << (position % 8))) != 0;
}
};
constexpr std::size_t
@@ -47,8 +82,14 @@ validatorBitsetBytes(std::size_t memberCount)
unused high bit in the final byte clear. The returned population can be
used by a caller's profile-specific admission and quorum rules.
*/
inline std::optional<ValidatorBitsetInfo>
inline std::optional<ValidatedValidatorBitset>
validateValidatorBitset(Slice bitset, std::size_t memberCount)
{
return ValidatedValidatorBitset::make(bitset, memberCount);
}
inline std::optional<ValidatedValidatorBitset>
ValidatedValidatorBitset::make(Slice bitset, std::size_t memberCount)
{
if (bitset.size() != validatorBitsetBytes(memberCount))
return std::nullopt;
@@ -66,22 +107,7 @@ validateValidatorBitset(Slice bitset, std::size_t memberCount)
for (auto const byte : bitset)
selected += std::popcount(byte);
return ValidatorBitsetInfo{selected};
}
inline bool
validatorBitsetContains(
Slice bitset,
std::size_t memberCount,
std::size_t position)
{
if (position >= memberCount ||
bitset.size() != validatorBitsetBytes(memberCount))
return false;
auto const byte = position / 8;
return (bitset[byte] & static_cast<std::uint8_t>(1u << (position % 8))) !=
0;
return ValidatedValidatorBitset{bitset, memberCount, selected};
}
template <class Predicate>

View File

@@ -49,17 +49,15 @@ public:
auto const info = validateValidatorBitset(makeSlice(bitset), 10);
BEAST_EXPECT(info);
if (info)
BEAST_EXPECT(info->selected == 3);
BEAST_EXPECT(validatorBitsetContains(makeSlice(bitset), 10, 0));
BEAST_EXPECT(validatorBitsetContains(makeSlice(bitset), 10, 7));
BEAST_EXPECT(!validatorBitsetContains(makeSlice(bitset), 10, 8));
BEAST_EXPECT(validatorBitsetContains(makeSlice(bitset), 10, 9));
BEAST_EXPECT(!validatorBitsetContains(makeSlice(bitset), 10, 10));
BEAST_EXPECT(!validatorBitsetContains(makeSlice(bitset), 10, 80));
Blob const wrongSize{0x81};
BEAST_EXPECT(!validatorBitsetContains(makeSlice(wrongSize), 10, 0));
{
BEAST_EXPECT(info->selected() == 3);
BEAST_EXPECT(info->contains(0));
BEAST_EXPECT(info->contains(7));
BEAST_EXPECT(!info->contains(8));
BEAST_EXPECT(info->contains(9));
BEAST_EXPECT(!info->contains(10));
BEAST_EXPECT(!info->contains(80));
}
}
void
@@ -78,7 +76,7 @@ public:
auto const info = validateValidatorBitset(makeSlice(exact), 10);
BEAST_EXPECT(info);
if (info)
BEAST_EXPECT(info->selected == 10);
BEAST_EXPECT(info->selected() == 10);
BEAST_EXPECT(!validateValidatorBitset(makeSlice(highBitSet), 10));
BEAST_EXPECT(!validateValidatorBitset(makeSlice(tooLong), 10));
@@ -86,7 +84,7 @@ public:
auto const full = validateValidatorBitset(makeSlice(fullByte), 8);
BEAST_EXPECT(full);
if (full)
BEAST_EXPECT(full->selected == 8);
BEAST_EXPECT(full->selected() == 8);
}
void

View File

@@ -39,6 +39,7 @@
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Sign.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/ValidatorBitset.h>
#include <set>
#include <string_view>
@@ -46,21 +47,6 @@ namespace ripple {
namespace {
std::size_t
countSetBits(Blob const& bytes)
{
std::size_t count = 0;
for (auto byte : bytes)
{
while (byte != 0)
{
byte &= static_cast<std::uint8_t>(byte - 1);
++count;
}
}
return count;
}
bool
validEntropyContributorMask(
std::uint8_t tier,
@@ -77,19 +63,9 @@ validEntropyContributorMask(
if (tier < entropyTierParticipantAligned || tier > entropyTierValidatorFull)
return false;
auto const expectedSize = (denominator + 7) / 8;
if (contributors.size() != expectedSize)
return false;
if (auto const usedBits = denominator % 8;
usedBits != 0 && !contributors.empty())
{
auto const validBits = static_cast<std::uint8_t>((1u << usedBits) - 1u);
if ((contributors.back() & ~validBits) != 0)
return false;
}
return countSetBits(contributors) == count;
auto const bitset =
validateValidatorBitset(makeSlice(contributors), denominator);
return bitset && bitset->selected() == count;
}
} // namespace