Skip to content

Commit

Permalink
Add validator_list_threshold to config
Browse files Browse the repository at this point in the history
  • Loading branch information
Bronek committed Aug 29, 2024
1 parent 2f432e8 commit 9e85a73
Show file tree
Hide file tree
Showing 9 changed files with 470 additions and 40 deletions.
43 changes: 22 additions & 21 deletions include/xrpl/protocol/jss.h
Original file line number Diff line number Diff line change
Expand Up @@ -738,27 +738,28 @@ JSS(validated); // out: NetworkOPs, RPCHelpers, AccountTx*
JSS(validator_list_expires); // out: NetworkOps, ValidatorList
JSS(validator_list); // out: NetworkOps, ValidatorList
JSS(validators);
JSS(validated_hash); // out: NetworkOPs
JSS(validated_ledger); // out: NetworkOPs
JSS(validated_ledger_index); // out: SubmitTransaction
JSS(validated_ledgers); // out: NetworkOPs
JSS(validation_key); // out: ValidationCreate, ValidationSeed
JSS(validation_private_key); // out: ValidationCreate
JSS(validation_public_key); // out: ValidationCreate, ValidationSeed
JSS(validation_quorum); // out: NetworkOPs
JSS(validation_seed); // out: ValidationCreate, ValidationSeed
JSS(validations); // out: AmendmentTableImpl
JSS(validator_sites); // out: ValidatorSites
JSS(value); // out: STAmount
JSS(version); // out: RPCVersion
JSS(vetoed); // out: AmendmentTableImpl
JSS(volume_a); // out: BookChanges
JSS(volume_b); // out: BookChanges
JSS(vote); // in: Feature
JSS(vote_slots); // out: amm_info
JSS(vote_weight); // out: amm_info
JSS(warning); // rpc:
JSS(warnings); // out: server_info, server_state
JSS(validated_hash); // out: NetworkOPs
JSS(validated_ledger); // out: NetworkOPs
JSS(validated_ledger_index); // out: SubmitTransaction
JSS(validated_ledgers); // out: NetworkOPs
JSS(validation_key); // out: ValidationCreate, ValidationSeed
JSS(validation_private_key); // out: ValidationCreate
JSS(validation_public_key); // out: ValidationCreate, ValidationSeed
JSS(validation_quorum); // out: NetworkOPs
JSS(validation_seed); // out: ValidationCreate, ValidationSeed
JSS(validations); // out: AmendmentTableImpl
JSS(validator_list_threshold); // out: ValidatorList
JSS(validator_sites); // out: ValidatorSites
JSS(value); // out: STAmount
JSS(version); // out: RPCVersion
JSS(vetoed); // out: AmendmentTableImpl
JSS(volume_a); // out: BookChanges
JSS(volume_b); // out: BookChanges
JSS(vote); // in: Feature
JSS(vote_slots); // out: amm_info
JSS(vote_weight); // out: amm_info
JSS(warning); // rpc:
JSS(warnings); // out: server_info, server_state
JSS(workers);
JSS(write_load); // out: GetCounts
JSS(xchain_owned_claim_id); // in: LedgerEntry, AccountObjects
Expand Down
266 changes: 252 additions & 14 deletions src/test/app/ValidatorList_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,33 @@ class ValidatorList_test : public beast::unit_test::suite
BEAST_EXPECT(trustedKeys->load({}, emptyCfgKeys, cfgPublishers));
for (auto const& key : keys)
BEAST_EXPECT(trustedKeys->trustedPublisher(key));
BEAST_EXPECT(
trustedKeys->getListThreshold() == keys.size() / 2 + 1);
}
{
ManifestCache manifests;
auto trustedKeys = std::make_unique<ValidatorList>(
manifests,
manifests,
env.timeKeeper(),
app.config().legacy("database_path"),
env.journal);

std::vector<PublicKey> keys(
{randomMasterKey(),
randomMasterKey(),
randomMasterKey(),
randomMasterKey()});
std::vector<std::string> cfgPublishers;
for (auto const& key : keys)
cfgPublishers.push_back(strHex(key));

// explicitly set the list threshold
BEAST_EXPECT(trustedKeys->load(
{}, emptyCfgKeys, cfgPublishers, std::size_t(2)));
for (auto const& key : keys)
BEAST_EXPECT(trustedKeys->trustedPublisher(key));
BEAST_EXPECT(trustedKeys->getListThreshold() == 2);
}
{
// Attempt to load a publisher key that has been revoked.
Expand All @@ -452,15 +479,54 @@ class ValidatorList_test : public beast::unit_test::suite
pubRevokedSigning.second,
std::numeric_limits<std::uint32_t>::max())));

// this one is not revoked (and not in manifest cache at all.)
auto legitKey = randomMasterKey();
// these two are not revoked (and not in the manifest cache at all.)
auto legitKey1 = randomMasterKey();
auto legitKey2 = randomMasterKey();

std::vector<std::string> cfgPublishers = {
strHex(pubRevokedPublic), strHex(legitKey)};
strHex(pubRevokedPublic), strHex(legitKey1), strHex(legitKey2)};
BEAST_EXPECT(trustedKeys->load({}, emptyCfgKeys, cfgPublishers));

BEAST_EXPECT(!trustedKeys->trustedPublisher(pubRevokedPublic));
BEAST_EXPECT(trustedKeys->trustedPublisher(legitKey));
BEAST_EXPECT(trustedKeys->trustedPublisher(legitKey1));
BEAST_EXPECT(trustedKeys->trustedPublisher(legitKey2));
// 1 is threshold for 2 publishers (not for 3, which would yield 2)
BEAST_EXPECT(trustedKeys->getListThreshold() == 1);
}
{
// One (of two) publisher keys has been revoked, the user had
// explicitly set validator list threshold to 2.
ManifestCache valManifests;
ManifestCache pubManifests;
auto trustedKeys = std::make_unique<ValidatorList>(
valManifests,
pubManifests,
env.timeKeeper(),
app.config().legacy("database_path"),
env.journal);

auto const pubRevokedSecret = randomSecretKey();
auto const pubRevokedPublic =
derivePublicKey(KeyType::ed25519, pubRevokedSecret);
auto const pubRevokedSigning = randomKeyPair(KeyType::secp256k1);
// make this manifest revoked (seq num = max)
// -- thus should not be loaded
pubManifests.applyManifest(*deserializeManifest(makeManifestString(
pubRevokedPublic,
pubRevokedSecret,
pubRevokedSigning.first,
pubRevokedSigning.second,
std::numeric_limits<std::uint32_t>::max())));

// these two are not revoked (and not in the manifest cache at all.)
auto legitKey = randomMasterKey();

std::vector<std::string> cfgPublishers = {
strHex(pubRevokedPublic), strHex(legitKey)};
// This is fatal - we know only one non-revoked publisher but every
// validator needs to be advertised by at least two publishers
BEAST_EXPECT(!trustedKeys->load(
{}, emptyCfgKeys, cfgPublishers, std::size_t(2)));
}
}

Expand Down Expand Up @@ -1530,11 +1596,22 @@ class ValidatorList_test : public beast::unit_test::suite
calcNodeID(valKeys.back().masterPublic));
}

auto addPublishedList = [this,
&env,
&trustedKeys,
&valKeys,
&siteUri]() {
// locals[0]: from 0 to maxKeys - 4
// locals[1]: from 1 to maxKeys - 2
// locals[2]: from 2 to maxKeys
constexpr static int publishers = 3;
std::array<
std::pair<
decltype(valKeys)::const_iterator,
decltype(valKeys)::const_iterator>,
publishers>
locals = {
std::make_pair(valKeys.cbegin(), valKeys.cend() - 4),
std::make_pair(valKeys.cbegin() + 1, valKeys.cend() - 2),
std::make_pair(valKeys.cbegin() + 2, valKeys.cend()),
};

auto addPublishedList = [&, this](int i) {
auto const publisherSecret = randomSecretKey();
auto const publisherPublic =
derivePublicKey(KeyType::ed25519, publisherSecret);
Expand All @@ -1550,16 +1627,18 @@ class ValidatorList_test : public beast::unit_test::suite
{strHex(publisherPublic)});
std::vector<std::string> emptyCfgKeys;

BEAST_EXPECT(
trustedKeys->load({}, emptyCfgKeys, cfgPublishers));
BEAST_EXPECT(trustedKeys->load(
{}, emptyCfgKeys, cfgPublishers, std::size_t(1)));

auto const version = 1;
auto const sequence = 1;
using namespace std::chrono_literals;
NetClock::time_point const validUntil =
env.timeKeeper().now() + 3600s;
std::vector<Validator> localKeys{
locals[i].first, locals[i].second};
auto const blob = makeList(
valKeys, sequence, validUntil.time_since_epoch().count());
localKeys, sequence, validUntil.time_since_epoch().count());
auto const sig = signList(blob, pubSigningKeys);

BEAST_EXPECT(
Expand All @@ -1571,8 +1650,9 @@ class ValidatorList_test : public beast::unit_test::suite
};

// Apply multiple published lists
for (auto i = 0; i < 3; ++i)
addPublishedList();
for (auto i = 0; i < publishers; ++i)
addPublishedList(i);
BEAST_EXPECT(trustedKeys->getListThreshold() == 1);

TrustChanges changes = trustedKeys->updateTrusted(
activeValidators,
Expand All @@ -1593,6 +1673,164 @@ class ValidatorList_test : public beast::unit_test::suite
BEAST_EXPECT(changes.added == added);
BEAST_EXPECT(changes.removed.empty());
}
{
// Trusted set should include validators from intersection of lists
ManifestCache manifests;
auto trustedKeys = std::make_unique<ValidatorList>(
manifests,
manifests,
env.timeKeeper(),
app.config().legacy("database_path"),
env.journal);

hash_set<NodeID> activeValidators;
std::vector<Validator> valKeys;
valKeys.reserve(maxKeys);

while (valKeys.size() != maxKeys)
{
valKeys.push_back(randomValidator());
activeValidators.emplace(
calcNodeID(valKeys.back().masterPublic));
}

// locals[0]: from 0 to maxKeys - 4
// locals[1]: from 1 to maxKeys - 2
// locals[2]: from 2 to maxKeys
// interesection of at least 2: same as locals[1]
// intersection when 1 is dropped: from 2 to maxKeys - 4
constexpr static int publishers = 3;
std::array<
std::pair<
decltype(valKeys)::const_iterator,
decltype(valKeys)::const_iterator>,
publishers>
locals = {
std::make_pair(valKeys.cbegin(), valKeys.cend() - 4),
std::make_pair(valKeys.cbegin() + 1, valKeys.cend() - 2),
std::make_pair(valKeys.cbegin() + 2, valKeys.cend()),
};

auto addPublishedList = [&, this](
int i,
NetClock::time_point& validUntil1) {
auto const publisherSecret = randomSecretKey();
auto const publisherPublic =
derivePublicKey(KeyType::ed25519, publisherSecret);
auto const pubSigningKeys = randomKeyPair(KeyType::secp256k1);
auto const manifest = base64_encode(makeManifestString(
publisherPublic,
publisherSecret,
pubSigningKeys.first,
pubSigningKeys.second,
1));

std::vector<std::string> cfgPublishers(
{strHex(publisherPublic)});
std::vector<std::string> emptyCfgKeys;

BEAST_EXPECT(
trustedKeys->load({}, emptyCfgKeys, cfgPublishers));

auto const version = 1;
auto const sequence = 1;
using namespace std::chrono_literals;
// Want to drop 1 sooner
NetClock::time_point const validUntil =
env.timeKeeper().now() + (i != 1 ? 3600s : 60s);
if (i == 1)
validUntil1 = validUntil;
std::vector<Validator> localKeys{
locals[i].first, locals[i].second};
auto const blob = makeList(
localKeys, sequence, validUntil.time_since_epoch().count());
auto const sig = signList(blob, pubSigningKeys);

BEAST_EXPECT(
ListDisposition::accepted ==
trustedKeys
->applyLists(
manifest, version, {{blob, sig, {}}}, siteUri)
.bestDisposition());
};

// Apply multiple published lists
// validUntil1 is expiration time for locals[1]
NetClock::time_point validUntil1;
for (auto i = 0; i < publishers; ++i)
addPublishedList(i, validUntil1);
BEAST_EXPECT(trustedKeys->getListThreshold() == 2);

TrustChanges changes = trustedKeys->updateTrusted(
activeValidators,
env.timeKeeper().now(),
env.app().getOPs(),
env.app().overlay(),
env.app().getHashRouter());

BEAST_EXPECT(
trustedKeys->quorum() ==
std::ceil((valKeys.size() - 3) * 0.8f));

for (auto const& val : valKeys)
BEAST_EXPECT(trustedKeys->listed(val.masterPublic));

hash_set<NodeID> added;
for (std::size_t i = 0; i < maxKeys; ++i)
{
auto const& val = valKeys[i];
if (i >= 1 && i < maxKeys - 2)
{
BEAST_EXPECT(trustedKeys->trusted(val.masterPublic));
added.insert(calcNodeID(val.masterPublic));
}
else
{
BEAST_EXPECT(!trustedKeys->trusted(val.masterPublic));
}
}
BEAST_EXPECT(changes.added == added);
BEAST_EXPECT(changes.removed.empty());

// Expire locals[1]
env.timeKeeper().set(validUntil1);
changes = trustedKeys->updateTrusted(
activeValidators,
env.timeKeeper().now(),
env.app().getOPs(),
env.app().overlay(),
env.app().getHashRouter());

BEAST_EXPECT(
trustedKeys->quorum() ==
std::numeric_limits<std::size_t>::max());
// TODO This is safer; above is not good
// BEAST_EXPECT(
// trustedKeys->quorum() ==
// std::ceil((valKeys.size() - 6) * 0.8f));

for (auto const& val : valKeys)
BEAST_EXPECT(trustedKeys->listed(val.masterPublic));

hash_set<NodeID> removed;
for (std::size_t i = 0; i < maxKeys; ++i)
{
auto const& val = valKeys[i];
if (i >= 2 && i < maxKeys - 4)
{
BEAST_EXPECT(trustedKeys->trusted(val.masterPublic));
}
else
{
BEAST_EXPECT(!trustedKeys->trusted(val.masterPublic));
if (i >= 1 && i < maxKeys - 2)
removed.insert(calcNodeID(val.masterPublic));
}
}

BEAST_EXPECT(changes.added.empty());
BEAST_EXPECT(changes.removed == removed);
}
}

void
Expand Down
Loading

0 comments on commit 9e85a73

Please sign in to comment.