mirror of
https://github.com/XRPLF/rippled.git
synced 2026-04-29 15:37:57 +00:00
Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com> Co-authored-by: Bart <bthomee@users.noreply.github.com>
3953 lines
158 KiB
C++
3953 lines
158 KiB
C++
|
|
#include <test/jtx/Env.h>
|
|
#include <test/jtx/envconfig.h>
|
|
|
|
#include <xrpld/app/misc/ValidatorList.h>
|
|
#include <xrpld/overlay/Compression.h>
|
|
#include <xrpld/overlay/detail/ProtocolMessage.h>
|
|
|
|
#include <xrpl/basics/Slice.h>
|
|
#include <xrpl/basics/UnorderedContainers.h>
|
|
#include <xrpl/basics/base64.h>
|
|
#include <xrpl/basics/chrono.h>
|
|
#include <xrpl/basics/strHex.h>
|
|
#include <xrpl/beast/unit_test/suite.h>
|
|
#include <xrpl/beast/utility/Journal.h>
|
|
#include <xrpl/protocol/HashPrefix.h>
|
|
#include <xrpl/protocol/KeyType.h>
|
|
#include <xrpl/protocol/PublicKey.h>
|
|
#include <xrpl/protocol/SField.h>
|
|
#include <xrpl/protocol/STObject.h>
|
|
#include <xrpl/protocol/SecretKey.h>
|
|
#include <xrpl/protocol/Serializer.h>
|
|
#include <xrpl/protocol/Sign.h>
|
|
#include <xrpl/protocol/UintTypes.h>
|
|
#include <xrpl/protocol/digest.h>
|
|
#include <xrpl/protocol/jss.h>
|
|
#include <xrpl/protocol/tokens.h>
|
|
#include <xrpl/server/Manifest.h>
|
|
|
|
#include <boost/asio/buffer.hpp>
|
|
#include <boost/beast/core/multi_buffer.hpp>
|
|
#include <boost/system/detail/error_code.hpp>
|
|
|
|
#include <xrpl.pb.h>
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <chrono>
|
|
#include <cmath>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <initializer_list>
|
|
#include <limits>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
namespace xrpl::test {
|
|
|
|
class ValidatorList_test : public beast::unit_test::suite
|
|
{
|
|
private:
|
|
struct Validator
|
|
{
|
|
PublicKey masterPublic;
|
|
PublicKey signingPublic;
|
|
std::string manifest;
|
|
};
|
|
|
|
static PublicKey
|
|
randomNode()
|
|
{
|
|
return derivePublicKey(KeyType::secp256k1, randomSecretKey());
|
|
}
|
|
|
|
static PublicKey
|
|
randomMasterKey()
|
|
{
|
|
return derivePublicKey(KeyType::ed25519, randomSecretKey());
|
|
}
|
|
|
|
static std::string
|
|
makeManifestString(
|
|
PublicKey const& pk,
|
|
SecretKey const& sk,
|
|
PublicKey const& spk,
|
|
SecretKey const& ssk,
|
|
int seq)
|
|
{
|
|
STObject st(sfGeneric);
|
|
st[sfSequence] = seq;
|
|
st[sfPublicKey] = pk;
|
|
|
|
if (seq != std::numeric_limits<std::uint32_t>::max())
|
|
{
|
|
st[sfSigningPubKey] = spk;
|
|
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
|
|
sign(st, HashPrefix::manifest, *publicKeyType(spk), ssk);
|
|
}
|
|
|
|
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
|
|
sign(st, HashPrefix::manifest, *publicKeyType(pk), sk, sfMasterSignature);
|
|
|
|
Serializer s;
|
|
st.add(s);
|
|
|
|
return std::string(static_cast<char const*>(s.data()), s.size());
|
|
}
|
|
|
|
static std::string
|
|
makeRevocationString(PublicKey const& pk, SecretKey const& sk)
|
|
{
|
|
STObject st(sfGeneric);
|
|
st[sfSequence] = std::numeric_limits<std::uint32_t>::max();
|
|
st[sfPublicKey] = pk;
|
|
|
|
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
|
|
sign(st, HashPrefix::manifest, *publicKeyType(pk), sk, sfMasterSignature);
|
|
|
|
Serializer s;
|
|
st.add(s);
|
|
|
|
return std::string(static_cast<char const*>(s.data()), s.size());
|
|
}
|
|
|
|
static Validator
|
|
randomValidator()
|
|
{
|
|
auto const secret = randomSecretKey();
|
|
auto const masterPublic = derivePublicKey(KeyType::ed25519, secret);
|
|
auto const signingKeys = randomKeyPair(KeyType::secp256k1);
|
|
return {
|
|
.masterPublic = masterPublic,
|
|
.signingPublic = signingKeys.first,
|
|
.manifest = base64_encode(makeManifestString(
|
|
masterPublic, secret, signingKeys.first, signingKeys.second, 1))};
|
|
}
|
|
|
|
static std::string
|
|
makeList(
|
|
std::vector<Validator> const& validators,
|
|
std::size_t sequence,
|
|
std::size_t validUntil,
|
|
std::optional<std::size_t> validFrom = {})
|
|
{
|
|
std::string data = "{\"sequence\":" + std::to_string(sequence) +
|
|
",\"expiration\":" + std::to_string(validUntil);
|
|
if (validFrom)
|
|
data += ",\"effective\":" + std::to_string(*validFrom);
|
|
data += ",\"validators\":[";
|
|
|
|
for (auto const& val : validators)
|
|
{
|
|
data += "{\"validation_public_key\":\"" + strHex(val.masterPublic) +
|
|
"\",\"manifest\":\"" + val.manifest + "\"},";
|
|
}
|
|
|
|
data.pop_back();
|
|
data += "]}";
|
|
return base64_encode(data);
|
|
}
|
|
|
|
static std::string
|
|
signList(std::string const& blob, std::pair<PublicKey, SecretKey> const& keys)
|
|
{
|
|
auto const data = base64_decode(blob);
|
|
return strHex(sign(keys.first, keys.second, makeSlice(data)));
|
|
}
|
|
|
|
static hash_set<NodeID>
|
|
asNodeIDs(std::initializer_list<PublicKey> const& pks)
|
|
{
|
|
hash_set<NodeID> res;
|
|
res.reserve(pks.size());
|
|
for (auto const& pk : pks)
|
|
res.insert(calcNodeID(pk));
|
|
return res;
|
|
}
|
|
|
|
void
|
|
checkResult(
|
|
ValidatorList::PublisherListStats const& result,
|
|
PublicKey pubKey,
|
|
ListDisposition expectedWorst,
|
|
ListDisposition expectedBest)
|
|
{
|
|
BEAST_EXPECT(
|
|
result.bestDisposition() > ListDisposition::same_sequence ||
|
|
(result.publisherKey && *result.publisherKey == pubKey));
|
|
BEAST_EXPECT(result.bestDisposition() == expectedBest);
|
|
BEAST_EXPECT(result.worstDisposition() == expectedWorst);
|
|
}
|
|
|
|
void
|
|
testGenesisQuorum()
|
|
{
|
|
testcase("Genesis Quorum");
|
|
|
|
ManifestCache manifests;
|
|
jtx::Env env(*this);
|
|
auto& app = env.app();
|
|
{
|
|
auto trustedKeys = std::make_unique<ValidatorList>(
|
|
manifests,
|
|
manifests,
|
|
env.timeKeeper(),
|
|
app.config().legacy("database_path"),
|
|
env.journal);
|
|
BEAST_EXPECT(trustedKeys->quorum() == 1);
|
|
}
|
|
{
|
|
std::size_t minQuorum = 0;
|
|
auto trustedKeys = std::make_unique<ValidatorList>(
|
|
manifests,
|
|
manifests,
|
|
env.timeKeeper(),
|
|
app.config().legacy("database_path"),
|
|
env.journal,
|
|
minQuorum);
|
|
BEAST_EXPECT(trustedKeys->quorum() == minQuorum);
|
|
}
|
|
}
|
|
|
|
void
|
|
testConfigLoad()
|
|
{
|
|
testcase("Config Load");
|
|
|
|
jtx::Env env(*this, jtx::envconfig(), nullptr, beast::severities::kDisabled);
|
|
auto& app = env.app();
|
|
std::vector<std::string> const emptyCfgKeys;
|
|
std::vector<std::string> const emptyCfgPublishers;
|
|
|
|
auto const localSigningKeys = randomKeyPair(KeyType::secp256k1);
|
|
auto const localSigningPublicOuter = localSigningKeys.first;
|
|
auto const localSigningSecret = localSigningKeys.second;
|
|
auto const localMasterSecret = randomSecretKey();
|
|
auto const localMasterPublic = derivePublicKey(KeyType::ed25519, localMasterSecret);
|
|
|
|
std::string const cfgManifest(makeManifestString(
|
|
localMasterPublic, localMasterSecret, localSigningPublicOuter, localSigningSecret, 1));
|
|
|
|
auto format = [](PublicKey const& publicKey, char const* comment = nullptr) {
|
|
auto ret = toBase58(TokenType::NodePublic, publicKey);
|
|
|
|
if (comment)
|
|
ret += comment;
|
|
|
|
return ret;
|
|
};
|
|
|
|
std::vector<PublicKey> configList;
|
|
configList.reserve(8);
|
|
|
|
while (configList.size() != 8)
|
|
configList.push_back(randomNode());
|
|
|
|
// Correct configuration
|
|
std::vector<std::string> cfgKeys(
|
|
{format(configList[0]),
|
|
format(configList[1], " Comment"),
|
|
format(configList[2], " Multi Word Comment"),
|
|
format(configList[3], " Leading Whitespace"),
|
|
format(configList[4], " Trailing Whitespace "),
|
|
format(configList[5], " Leading & Trailing Whitespace "),
|
|
format(configList[6], " Leading, Trailing & Internal Whitespace "),
|
|
format(configList[7], " ")});
|
|
|
|
{
|
|
ManifestCache manifests;
|
|
auto trustedKeys = std::make_unique<ValidatorList>(
|
|
manifests,
|
|
manifests,
|
|
env.timeKeeper(),
|
|
app.config().legacy("database_path"),
|
|
env.journal);
|
|
|
|
// Correct (empty) configuration
|
|
BEAST_EXPECT(trustedKeys->load({}, emptyCfgKeys, emptyCfgPublishers));
|
|
|
|
// load local validator key with or without manifest
|
|
BEAST_EXPECT(
|
|
trustedKeys->load(localSigningPublicOuter, emptyCfgKeys, emptyCfgPublishers));
|
|
BEAST_EXPECT(trustedKeys->listed(localSigningPublicOuter));
|
|
|
|
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
|
|
manifests.applyManifest(*deserializeManifest(cfgManifest));
|
|
BEAST_EXPECT(
|
|
trustedKeys->load(localSigningPublicOuter, emptyCfgKeys, emptyCfgPublishers));
|
|
|
|
BEAST_EXPECT(trustedKeys->listed(localMasterPublic));
|
|
BEAST_EXPECT(trustedKeys->listed(localSigningPublicOuter));
|
|
}
|
|
{
|
|
// load should add validator keys from config
|
|
ManifestCache manifests;
|
|
auto trustedKeys = std::make_unique<ValidatorList>(
|
|
manifests,
|
|
manifests,
|
|
env.timeKeeper(),
|
|
app.config().legacy("database_path"),
|
|
env.journal);
|
|
|
|
BEAST_EXPECT(trustedKeys->load({}, cfgKeys, emptyCfgPublishers));
|
|
|
|
for (auto const& n : configList)
|
|
BEAST_EXPECT(trustedKeys->listed(n));
|
|
|
|
// load should accept Ed25519 master public keys
|
|
auto const masterNode1 = randomMasterKey();
|
|
auto const masterNode2 = randomMasterKey();
|
|
|
|
std::vector<std::string> const cfgMasterKeys(
|
|
{format(masterNode1), format(masterNode2, " Comment")});
|
|
BEAST_EXPECT(trustedKeys->load({}, cfgMasterKeys, emptyCfgPublishers));
|
|
BEAST_EXPECT(trustedKeys->listed(masterNode1));
|
|
BEAST_EXPECT(trustedKeys->listed(masterNode2));
|
|
|
|
// load should reject invalid config keys
|
|
BEAST_EXPECT(!trustedKeys->load({}, {"NotAPublicKey"}, emptyCfgPublishers));
|
|
BEAST_EXPECT(!trustedKeys->load({}, {format(randomNode(), "!")}, emptyCfgPublishers));
|
|
|
|
// load terminates when encountering an invalid entry
|
|
auto const goodKey = randomNode();
|
|
BEAST_EXPECT(!trustedKeys->load(
|
|
{}, {format(randomNode(), "!"), format(goodKey)}, emptyCfgPublishers));
|
|
BEAST_EXPECT(!trustedKeys->listed(goodKey));
|
|
}
|
|
{
|
|
// local validator key on config list
|
|
ManifestCache manifests;
|
|
auto trustedKeys = std::make_unique<ValidatorList>(
|
|
manifests,
|
|
manifests,
|
|
env.timeKeeper(),
|
|
app.config().legacy("database_path"),
|
|
env.journal);
|
|
|
|
auto const localSigningPublic =
|
|
parseBase58<PublicKey>(TokenType::NodePublic, cfgKeys.front());
|
|
|
|
BEAST_EXPECT(trustedKeys->load(localSigningPublic, cfgKeys, emptyCfgPublishers));
|
|
BEAST_EXPECT(trustedKeys->localPublicKey() == localSigningPublic);
|
|
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
|
|
BEAST_EXPECT(trustedKeys->listed(*localSigningPublic));
|
|
for (auto const& n : configList)
|
|
BEAST_EXPECT(trustedKeys->listed(n));
|
|
}
|
|
{
|
|
// local validator key not on config list
|
|
ManifestCache manifests;
|
|
auto trustedKeys = std::make_unique<ValidatorList>(
|
|
manifests,
|
|
manifests,
|
|
env.timeKeeper(),
|
|
app.config().legacy("database_path"),
|
|
env.journal);
|
|
|
|
auto const localSigningPublic = randomNode();
|
|
BEAST_EXPECT(trustedKeys->load(localSigningPublic, cfgKeys, emptyCfgPublishers));
|
|
|
|
BEAST_EXPECT(trustedKeys->localPublicKey() == localSigningPublic);
|
|
BEAST_EXPECT(trustedKeys->listed(localSigningPublic));
|
|
for (auto const& n : configList)
|
|
BEAST_EXPECT(trustedKeys->listed(n));
|
|
}
|
|
{
|
|
// local validator key (with manifest) not on config list
|
|
ManifestCache manifests;
|
|
auto trustedKeys = std::make_unique<ValidatorList>(
|
|
manifests,
|
|
manifests,
|
|
env.timeKeeper(),
|
|
app.config().legacy("database_path"),
|
|
env.journal);
|
|
|
|
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
|
|
manifests.applyManifest(*deserializeManifest(cfgManifest));
|
|
|
|
BEAST_EXPECT(trustedKeys->load(localSigningPublicOuter, cfgKeys, emptyCfgPublishers));
|
|
|
|
BEAST_EXPECT(trustedKeys->localPublicKey() == localMasterPublic);
|
|
BEAST_EXPECT(trustedKeys->listed(localSigningPublicOuter));
|
|
BEAST_EXPECT(trustedKeys->listed(localMasterPublic));
|
|
for (auto const& n : configList)
|
|
BEAST_EXPECT(trustedKeys->listed(n));
|
|
}
|
|
{
|
|
ManifestCache manifests;
|
|
auto trustedKeys = std::make_unique<ValidatorList>(
|
|
manifests,
|
|
manifests,
|
|
env.timeKeeper(),
|
|
app.config().legacy("database_path"),
|
|
env.journal);
|
|
|
|
// load should reject invalid validator list signing keys
|
|
std::vector<std::string> badPublishers({"NotASigningKey"});
|
|
BEAST_EXPECT(!trustedKeys->load({}, emptyCfgKeys, badPublishers));
|
|
|
|
// load should reject validator list signing keys with invalid
|
|
// encoding
|
|
std::vector<PublicKey> const keys(
|
|
{randomMasterKey(), randomMasterKey(), randomMasterKey()});
|
|
badPublishers.clear();
|
|
for (auto const& key : keys)
|
|
badPublishers.push_back(toBase58(TokenType::NodePublic, key));
|
|
|
|
BEAST_EXPECT(!trustedKeys->load({}, emptyCfgKeys, badPublishers));
|
|
for (auto const& key : keys)
|
|
BEAST_EXPECT(!trustedKeys->trustedPublisher(key));
|
|
|
|
// load should accept valid validator list publisher keys
|
|
std::vector<std::string> cfgPublishers;
|
|
cfgPublishers.reserve(keys.size());
|
|
for (auto const& key : keys)
|
|
cfgPublishers.push_back(strHex(key));
|
|
|
|
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> const keys(
|
|
{randomMasterKey(), randomMasterKey(), randomMasterKey(), randomMasterKey()});
|
|
std::vector<std::string> cfgPublishers;
|
|
cfgPublishers.reserve(keys.size());
|
|
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.
|
|
// Should fail
|
|
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
|
|
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
|
|
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 legitKey1 = randomMasterKey();
|
|
auto legitKey2 = randomMasterKey();
|
|
|
|
std::vector<std::string> const cfgPublishers = {
|
|
strHex(pubRevokedPublic), strHex(legitKey1), strHex(legitKey2)};
|
|
BEAST_EXPECT(trustedKeys->load({}, emptyCfgKeys, cfgPublishers));
|
|
|
|
BEAST_EXPECT(!trustedKeys->trustedPublisher(pubRevokedPublic));
|
|
BEAST_EXPECT(trustedKeys->trustedPublisher(legitKey1));
|
|
BEAST_EXPECT(trustedKeys->trustedPublisher(legitKey2));
|
|
// 2 is the threshold for 3 publishers (even though 1 is revoked)
|
|
BEAST_EXPECT(trustedKeys->getListThreshold() == 2);
|
|
}
|
|
{
|
|
// 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
|
|
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
|
|
pubManifests.applyManifest(*deserializeManifest(makeManifestString(
|
|
pubRevokedPublic,
|
|
pubRevokedSecret,
|
|
pubRevokedSigning.first,
|
|
pubRevokedSigning.second,
|
|
std::numeric_limits<std::uint32_t>::max())));
|
|
|
|
// this one is not revoked (and not in the manifest cache at all.)
|
|
auto legitKey = randomMasterKey();
|
|
|
|
std::vector<std::string> const cfgPublishers = {
|
|
strHex(pubRevokedPublic), strHex(legitKey)};
|
|
BEAST_EXPECT(trustedKeys->load({}, emptyCfgKeys, cfgPublishers, std::size_t(2)));
|
|
|
|
BEAST_EXPECT(!trustedKeys->trustedPublisher(pubRevokedPublic));
|
|
BEAST_EXPECT(trustedKeys->trustedPublisher(legitKey));
|
|
// 2 is the threshold, as requested in configuration
|
|
BEAST_EXPECT(trustedKeys->getListThreshold() == 2);
|
|
}
|
|
}
|
|
|
|
void
|
|
testApplyLists()
|
|
{
|
|
testcase("Apply list");
|
|
using namespace std::chrono_literals;
|
|
|
|
std::string const siteUri = "testApplyList.test";
|
|
|
|
auto checkAvailable =
|
|
[this](
|
|
auto const& trustedKeys,
|
|
auto const& hexPublic,
|
|
auto const& manifest,
|
|
auto const version,
|
|
std::vector<std::pair<std::string, std::string>> const& expected) {
|
|
auto const available = trustedKeys->getAvailable(hexPublic);
|
|
|
|
BEAST_EXPECT(!version || available);
|
|
if (available)
|
|
{
|
|
auto const& a = *available;
|
|
BEAST_EXPECT(a[jss::public_key] == hexPublic);
|
|
BEAST_EXPECT(a[jss::manifest] == manifest);
|
|
// Because multiple lists were processed, the version was
|
|
// overridden
|
|
BEAST_EXPECT(a[jss::version] == version);
|
|
if (version == 1)
|
|
{
|
|
BEAST_EXPECT(expected.size() == 1);
|
|
BEAST_EXPECT(a[jss::blob] == expected[0].first);
|
|
BEAST_EXPECT(a[jss::signature] == expected[0].second);
|
|
BEAST_EXPECT(!a.isMember(jss::blobs_v2));
|
|
}
|
|
else if (BEAST_EXPECT(a.isMember(jss::blobs_v2)))
|
|
{
|
|
BEAST_EXPECT(!a.isMember(jss::blob));
|
|
BEAST_EXPECT(!a.isMember(jss::signature));
|
|
auto const& blobs_v2 = a[jss::blobs_v2];
|
|
BEAST_EXPECT(blobs_v2.isArray() && blobs_v2.size() == expected.size());
|
|
|
|
for (unsigned int i = 0; i < expected.size(); ++i)
|
|
{
|
|
BEAST_EXPECT(blobs_v2[i][jss::blob] == expected[i].first);
|
|
BEAST_EXPECT(blobs_v2[i][jss::signature] == expected[i].second);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
ManifestCache manifests;
|
|
jtx::Env env(*this);
|
|
auto& app = env.app();
|
|
auto trustedKeys = std::make_unique<ValidatorList>(
|
|
manifests,
|
|
manifests,
|
|
env.app().getTimeKeeper(),
|
|
app.config().legacy("database_path"),
|
|
env.journal);
|
|
|
|
auto expectTrusted = [this, &trustedKeys](std::vector<Validator> const& list) {
|
|
for (auto const& val : list)
|
|
{
|
|
BEAST_EXPECT(trustedKeys->listed(val.masterPublic));
|
|
BEAST_EXPECT(trustedKeys->listed(val.signingPublic));
|
|
}
|
|
};
|
|
|
|
auto expectUntrusted = [this, &trustedKeys](std::vector<Validator> const& list) {
|
|
for (auto const& val : list)
|
|
{
|
|
BEAST_EXPECT(!trustedKeys->listed(val.masterPublic));
|
|
BEAST_EXPECT(!trustedKeys->listed(val.signingPublic));
|
|
}
|
|
};
|
|
|
|
auto const publisherSecret = randomSecretKey();
|
|
auto const publisherPublic = derivePublicKey(KeyType::ed25519, publisherSecret);
|
|
auto const hexPublic = strHex(publisherPublic.begin(), publisherPublic.end());
|
|
auto const pubSigningKeys1 = randomKeyPair(KeyType::secp256k1);
|
|
auto const manifest1 = base64_encode(makeManifestString(
|
|
publisherPublic, publisherSecret, pubSigningKeys1.first, pubSigningKeys1.second, 1));
|
|
|
|
std::vector<std::string> const cfgPublisherKeys({strHex(publisherPublic)});
|
|
std::vector<std::string> const emptyCfgKeys;
|
|
|
|
BEAST_EXPECT(trustedKeys->load({}, emptyCfgKeys, cfgPublisherKeys));
|
|
|
|
std::map<std::size_t, std::vector<Validator>> const lists = []() {
|
|
auto constexpr listSize = 20;
|
|
auto constexpr numLists = 9;
|
|
std::map<std::size_t, std::vector<Validator>> lists;
|
|
// 1-based to correspond with the individually named blobs below.
|
|
for (auto i = 1; i <= numLists; ++i)
|
|
{
|
|
auto& list = lists[i];
|
|
list.reserve(listSize);
|
|
while (list.size() < listSize)
|
|
list.push_back(randomValidator());
|
|
}
|
|
return lists;
|
|
}();
|
|
|
|
// Attempt an expired list (fail) and a single list (succeed)
|
|
env.timeKeeper().set(env.timeKeeper().now() + 1s);
|
|
auto const version = 1;
|
|
auto const sequence1 = 1;
|
|
auto const expiredblob =
|
|
makeList(lists.at(1), sequence1, env.timeKeeper().now().time_since_epoch().count());
|
|
auto const expiredSig = signList(expiredblob, pubSigningKeys1);
|
|
|
|
NetClock::time_point const validUntil = env.timeKeeper().now() + 3600s;
|
|
auto const sequence2 = 2;
|
|
auto const blob2 = makeList(lists.at(2), sequence2, validUntil.time_since_epoch().count());
|
|
auto const sig2 = signList(blob2, pubSigningKeys1);
|
|
|
|
checkResult(
|
|
trustedKeys->applyLists(
|
|
manifest1, version, {{expiredblob, expiredSig, {}}, {blob2, sig2, {}}}, siteUri),
|
|
publisherPublic,
|
|
ListDisposition::expired,
|
|
ListDisposition::accepted);
|
|
|
|
expectTrusted(lists.at(2));
|
|
|
|
checkAvailable(trustedKeys, hexPublic, manifest1, version, {{blob2, sig2}});
|
|
|
|
// Do not apply future lists, but process them
|
|
auto const version2 = 2;
|
|
auto const sequence7 = 7;
|
|
auto const effective7 = validUntil - 60s;
|
|
auto const expiration7 = effective7 + 3600s;
|
|
auto const blob7 = makeList(
|
|
lists.at(7),
|
|
sequence7,
|
|
expiration7.time_since_epoch().count(),
|
|
effective7.time_since_epoch().count());
|
|
auto const sig7 = signList(blob7, pubSigningKeys1);
|
|
|
|
auto const sequence8 = 8;
|
|
auto const effective8 = expiration7 - 60s;
|
|
auto const expiration8 = effective8 + 3600s;
|
|
auto const blob8 = makeList(
|
|
lists.at(8),
|
|
sequence8,
|
|
expiration8.time_since_epoch().count(),
|
|
effective8.time_since_epoch().count());
|
|
auto const sig8 = signList(blob8, pubSigningKeys1);
|
|
|
|
checkResult(
|
|
trustedKeys->applyLists(
|
|
manifest1, version2, {{blob7, sig7, {}}, {blob8, sig8, {}}}, siteUri),
|
|
publisherPublic,
|
|
ListDisposition::pending,
|
|
ListDisposition::pending);
|
|
|
|
expectUntrusted(lists.at(7));
|
|
expectUntrusted(lists.at(8));
|
|
|
|
// Do not apply out-of-order future list, but process it
|
|
auto const sequence6 = 6;
|
|
auto const effective6 = effective7 - 60s;
|
|
auto const expiration6 = effective6 + 3600s;
|
|
auto const blob6 = makeList(
|
|
lists.at(6),
|
|
sequence6,
|
|
expiration6.time_since_epoch().count(),
|
|
effective6.time_since_epoch().count());
|
|
auto const sig6 = signList(blob6, pubSigningKeys1);
|
|
|
|
// Process future list that is overridden by a later list
|
|
auto const sequence6a = 5;
|
|
auto const effective6a = effective6 + 60s;
|
|
auto const expiration6a = effective6a + 3600s;
|
|
auto const blob6a = makeList(
|
|
lists.at(5),
|
|
sequence6a,
|
|
expiration6a.time_since_epoch().count(),
|
|
effective6a.time_since_epoch().count());
|
|
auto const sig6a = signList(blob6a, pubSigningKeys1);
|
|
|
|
checkResult(
|
|
trustedKeys->applyLists(
|
|
manifest1, version, {{blob6a, sig6a, {}}, {blob6, sig6, {}}}, siteUri),
|
|
publisherPublic,
|
|
ListDisposition::pending,
|
|
ListDisposition::pending);
|
|
|
|
expectUntrusted(lists.at(6));
|
|
expectTrusted(lists.at(2));
|
|
|
|
// Do not apply re-process lists known future sequence numbers
|
|
|
|
checkResult(
|
|
trustedKeys->applyLists(
|
|
manifest1, version, {{blob7, sig7, {}}, {blob6, sig6, {}}}, siteUri),
|
|
publisherPublic,
|
|
ListDisposition::known_sequence,
|
|
ListDisposition::known_sequence);
|
|
|
|
expectUntrusted(lists.at(6));
|
|
expectUntrusted(lists.at(7));
|
|
expectTrusted(lists.at(2));
|
|
|
|
// try empty or mangled manifest
|
|
checkResult(
|
|
trustedKeys->applyLists("", version, {{blob7, sig7, {}}, {blob6, sig6, {}}}, siteUri),
|
|
publisherPublic,
|
|
ListDisposition::invalid,
|
|
ListDisposition::invalid);
|
|
|
|
checkResult(
|
|
trustedKeys->applyLists(
|
|
base64_encode("not a manifest"),
|
|
version,
|
|
{{blob7, sig7, {}}, {blob6, sig6, {}}},
|
|
siteUri),
|
|
publisherPublic,
|
|
ListDisposition::invalid,
|
|
ListDisposition::invalid);
|
|
|
|
// do not use list from untrusted publisher
|
|
auto const untrustedManifest = base64_encode(makeManifestString(
|
|
randomMasterKey(), publisherSecret, pubSigningKeys1.first, pubSigningKeys1.second, 1));
|
|
|
|
checkResult(
|
|
trustedKeys->applyLists(untrustedManifest, version, {{blob2, sig2, {}}}, siteUri),
|
|
publisherPublic,
|
|
ListDisposition::untrusted,
|
|
ListDisposition::untrusted);
|
|
|
|
// do not use list with unhandled version
|
|
auto const badVersion = 666;
|
|
checkResult(
|
|
trustedKeys->applyLists(manifest1, badVersion, {{blob2, sig2, {}}}, siteUri),
|
|
publisherPublic,
|
|
ListDisposition::unsupported_version,
|
|
ListDisposition::unsupported_version);
|
|
|
|
// apply list with highest sequence number
|
|
auto const sequence3 = 3;
|
|
auto const blob3 = makeList(lists.at(3), sequence3, validUntil.time_since_epoch().count());
|
|
auto const sig3 = signList(blob3, pubSigningKeys1);
|
|
|
|
checkResult(
|
|
trustedKeys->applyLists(manifest1, version, {{blob3, sig3, {}}}, siteUri),
|
|
publisherPublic,
|
|
ListDisposition::accepted,
|
|
ListDisposition::accepted);
|
|
|
|
expectUntrusted(lists.at(1));
|
|
expectUntrusted(lists.at(2));
|
|
expectTrusted(lists.at(3));
|
|
|
|
// Note that blob6a is not present, because it was dropped during
|
|
// processing
|
|
checkAvailable(
|
|
trustedKeys,
|
|
hexPublic,
|
|
manifest1,
|
|
2,
|
|
{{blob3, sig3}, {blob6, sig6}, {blob7, sig7}, {blob8, sig8}});
|
|
|
|
// do not re-apply lists with past or current sequence numbers
|
|
checkResult(
|
|
trustedKeys->applyLists(
|
|
manifest1, version, {{blob2, sig2, {}}, {blob3, sig3, {}}}, siteUri),
|
|
publisherPublic,
|
|
ListDisposition::stale,
|
|
ListDisposition::same_sequence);
|
|
|
|
// apply list with new publisher key updated by manifest. Also send some
|
|
// old lists along with the old manifest
|
|
auto const pubSigningKeys2 = randomKeyPair(KeyType::secp256k1);
|
|
auto manifest2 = base64_encode(makeManifestString(
|
|
publisherPublic, publisherSecret, pubSigningKeys2.first, pubSigningKeys2.second, 2));
|
|
|
|
auto const sequence4 = 4;
|
|
auto const blob4 = makeList(lists.at(4), sequence4, validUntil.time_since_epoch().count());
|
|
auto const sig4 = signList(blob4, pubSigningKeys2);
|
|
|
|
checkResult(
|
|
trustedKeys->applyLists(
|
|
manifest2,
|
|
version,
|
|
{{blob2, sig2, manifest1}, {blob3, sig3, manifest1}, {blob4, sig4, {}}},
|
|
siteUri),
|
|
publisherPublic,
|
|
ListDisposition::stale,
|
|
ListDisposition::accepted);
|
|
|
|
expectUntrusted(lists.at(2));
|
|
expectUntrusted(lists.at(3));
|
|
expectTrusted(lists.at(4));
|
|
|
|
checkAvailable(
|
|
trustedKeys,
|
|
hexPublic,
|
|
manifest2,
|
|
2,
|
|
{{blob4, sig4}, {blob6, sig6}, {blob7, sig7}, {blob8, sig8}});
|
|
|
|
auto const sequence5 = 5;
|
|
auto const blob5 = makeList(lists.at(5), sequence5, validUntil.time_since_epoch().count());
|
|
auto const badSig = signList(blob5, pubSigningKeys1);
|
|
checkResult(
|
|
trustedKeys->applyLists(manifest1, version, {{blob5, badSig, {}}}, siteUri),
|
|
publisherPublic,
|
|
ListDisposition::invalid,
|
|
ListDisposition::invalid);
|
|
|
|
expectUntrusted(lists.at(2));
|
|
expectUntrusted(lists.at(3));
|
|
expectTrusted(lists.at(4));
|
|
expectUntrusted(lists.at(5));
|
|
|
|
// Reprocess the pending list, but the signature is no longer valid
|
|
checkResult(
|
|
trustedKeys->applyLists(
|
|
manifest1, version, {{blob7, sig7, {}}, {blob8, sig8, {}}}, siteUri),
|
|
publisherPublic,
|
|
ListDisposition::invalid,
|
|
ListDisposition::invalid);
|
|
|
|
expectTrusted(lists.at(4));
|
|
expectUntrusted(lists.at(7));
|
|
expectUntrusted(lists.at(8));
|
|
|
|
// Automatically rotate the first pending already processed list using
|
|
// updateTrusted. Note that the timekeeper is NOT moved, so the close
|
|
// time will be ahead of the test's wall clock
|
|
trustedKeys->updateTrusted(
|
|
{},
|
|
effective6 + 1s,
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
|
|
expectUntrusted(lists.at(3));
|
|
expectTrusted(lists.at(6));
|
|
|
|
checkAvailable(
|
|
trustedKeys, hexPublic, manifest2, 2, {{blob6, sig6}, {blob7, sig7}, {blob8, sig8}});
|
|
|
|
// Automatically rotate the LAST pending list using updateTrusted,
|
|
// bypassing blob7. Note that the timekeeper IS moved, so the provided
|
|
// close time will be behind the test's wall clock, and thus the wall
|
|
// clock is used.
|
|
env.timeKeeper().set(effective8);
|
|
trustedKeys->updateTrusted(
|
|
{},
|
|
effective8 + 1s,
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
|
|
expectUntrusted(lists.at(6));
|
|
expectUntrusted(lists.at(7));
|
|
expectTrusted(lists.at(8));
|
|
|
|
checkAvailable(trustedKeys, hexPublic, manifest2, 2, {{blob8, sig8}});
|
|
|
|
// resign the pending list with new key and validate it, but it's
|
|
// already valid Also try reprocessing the pending list with an
|
|
// explicit manifest
|
|
// - it is still invalid
|
|
auto const sig8_2 = signList(blob8, pubSigningKeys2);
|
|
|
|
checkResult(
|
|
trustedKeys->applyLists(
|
|
manifest2, version, {{blob8, sig8, manifest1}, {blob8, sig8_2, {}}}, siteUri),
|
|
publisherPublic,
|
|
ListDisposition::invalid,
|
|
ListDisposition::same_sequence);
|
|
|
|
expectTrusted(lists.at(8));
|
|
|
|
checkAvailable(trustedKeys, hexPublic, manifest2, 2, {{blob8, sig8}});
|
|
|
|
// do not apply list with revoked publisher key
|
|
// applied list is removed due to revoked publisher key
|
|
auto const signingKeysMax = randomKeyPair(KeyType::secp256k1);
|
|
auto maxManifest = base64_encode(makeRevocationString(publisherPublic, publisherSecret));
|
|
|
|
auto const sequence9 = 9;
|
|
auto const blob9 = makeList(lists.at(9), sequence9, validUntil.time_since_epoch().count());
|
|
auto const sig9 = signList(blob9, signingKeysMax);
|
|
|
|
checkResult(
|
|
trustedKeys->applyLists(maxManifest, version, {{blob9, sig9, {}}}, siteUri),
|
|
publisherPublic,
|
|
ListDisposition::untrusted,
|
|
ListDisposition::untrusted);
|
|
|
|
BEAST_EXPECT(!trustedKeys->trustedPublisher(publisherPublic));
|
|
for (auto const& [num, list] : lists)
|
|
{
|
|
(void)num;
|
|
expectUntrusted(list);
|
|
}
|
|
|
|
checkAvailable(trustedKeys, hexPublic, manifest2, 0, {});
|
|
}
|
|
|
|
void
|
|
testGetAvailable()
|
|
{
|
|
testcase("GetAvailable");
|
|
using namespace std::chrono_literals;
|
|
|
|
std::string const siteUri = "testApplyList.test";
|
|
|
|
ManifestCache manifests;
|
|
jtx::Env env(*this);
|
|
auto& app = env.app();
|
|
auto trustedKeys = std::make_unique<ValidatorList>(
|
|
manifests,
|
|
manifests,
|
|
env.app().getTimeKeeper(),
|
|
app.config().legacy("database_path"),
|
|
env.journal);
|
|
|
|
auto const publisherSecret = randomSecretKey();
|
|
auto const publisherPublic = derivePublicKey(KeyType::ed25519, publisherSecret);
|
|
auto const hexPublic = strHex(publisherPublic.begin(), publisherPublic.end());
|
|
auto const pubSigningKeys1 = randomKeyPair(KeyType::secp256k1);
|
|
auto const manifest = base64_encode(makeManifestString(
|
|
publisherPublic, publisherSecret, pubSigningKeys1.first, pubSigningKeys1.second, 1));
|
|
|
|
std::vector<std::string> const cfgPublisherKeys({strHex(publisherPublic)});
|
|
std::vector<std::string> const emptyCfgKeys;
|
|
|
|
BEAST_EXPECT(trustedKeys->load({}, emptyCfgKeys, cfgPublisherKeys));
|
|
|
|
std::vector<Validator> const list = []() {
|
|
auto constexpr listSize = 20;
|
|
std::vector<Validator> list;
|
|
list.reserve(listSize);
|
|
while (list.size() < listSize)
|
|
list.push_back(randomValidator());
|
|
return list;
|
|
}();
|
|
|
|
// Process a list
|
|
env.timeKeeper().set(env.timeKeeper().now() + 1s);
|
|
NetClock::time_point const validUntil = env.timeKeeper().now() + 3600s;
|
|
auto const blob = makeList(list, 1, validUntil.time_since_epoch().count());
|
|
auto const sig = signList(blob, pubSigningKeys1);
|
|
|
|
{
|
|
// list unavailable
|
|
auto const available = trustedKeys->getAvailable(hexPublic);
|
|
BEAST_EXPECT(!available);
|
|
}
|
|
|
|
BEAST_EXPECT(
|
|
trustedKeys->applyLists(manifest, 1, {{blob, sig, {}}}, siteUri).bestDisposition() ==
|
|
ListDisposition::accepted);
|
|
|
|
{
|
|
// invalid public key
|
|
auto const available = trustedKeys->getAvailable(hexPublic + "invalid", 1);
|
|
BEAST_EXPECT(!available);
|
|
}
|
|
|
|
{
|
|
// unknown public key
|
|
auto const badSecret = randomSecretKey();
|
|
auto const badPublic = derivePublicKey(KeyType::ed25519, badSecret);
|
|
auto const hexBad = strHex(badPublic.begin(), badPublic.end());
|
|
|
|
auto const available = trustedKeys->getAvailable(hexBad, 1);
|
|
BEAST_EXPECT(!available);
|
|
}
|
|
{
|
|
// bad version 0
|
|
auto const available = trustedKeys->getAvailable(hexPublic, 0);
|
|
if (BEAST_EXPECT(available))
|
|
{
|
|
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
|
|
auto const& a = *available;
|
|
BEAST_EXPECT(!a);
|
|
}
|
|
}
|
|
{
|
|
// bad version 3
|
|
auto const available = trustedKeys->getAvailable(hexPublic, 3);
|
|
if (BEAST_EXPECT(available))
|
|
{
|
|
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
|
|
auto const& a = *available;
|
|
BEAST_EXPECT(!a);
|
|
}
|
|
}
|
|
{
|
|
// version 1
|
|
auto const available = trustedKeys->getAvailable(hexPublic, 1);
|
|
if (BEAST_EXPECT(available))
|
|
{
|
|
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
|
|
auto const& a = *available;
|
|
BEAST_EXPECT(a[jss::public_key] == hexPublic);
|
|
BEAST_EXPECT(a[jss::manifest] == manifest);
|
|
BEAST_EXPECT(a[jss::version] == 1);
|
|
|
|
BEAST_EXPECT(a[jss::blob] == blob);
|
|
BEAST_EXPECT(a[jss::signature] == sig);
|
|
BEAST_EXPECT(!a.isMember(jss::blobs_v2));
|
|
}
|
|
}
|
|
|
|
{
|
|
// version 2
|
|
auto const available = trustedKeys->getAvailable(hexPublic, 2);
|
|
if (BEAST_EXPECT(available))
|
|
{
|
|
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
|
|
auto const& a = *available;
|
|
BEAST_EXPECT(a[jss::public_key] == hexPublic);
|
|
BEAST_EXPECT(a[jss::manifest] == manifest);
|
|
BEAST_EXPECT(a[jss::version] == 2);
|
|
|
|
if (BEAST_EXPECT(a.isMember(jss::blobs_v2)))
|
|
{
|
|
BEAST_EXPECT(!a.isMember(jss::blob));
|
|
BEAST_EXPECT(!a.isMember(jss::signature));
|
|
auto const& blobs_v2 = a[jss::blobs_v2];
|
|
BEAST_EXPECT(blobs_v2.isArray() && blobs_v2.size() == 1);
|
|
|
|
BEAST_EXPECT(blobs_v2[0u][jss::blob] == blob);
|
|
BEAST_EXPECT(blobs_v2[0u][jss::signature] == sig);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
testUpdateTrusted()
|
|
{
|
|
testcase("Update trusted");
|
|
|
|
std::string const siteUri = "testUpdateTrusted.test";
|
|
|
|
ManifestCache manifestsOuter;
|
|
jtx::Env env(*this);
|
|
auto& app = env.app();
|
|
auto trustedKeysOuter = std::make_unique<ValidatorList>(
|
|
manifestsOuter,
|
|
manifestsOuter,
|
|
env.timeKeeper(),
|
|
app.config().legacy("database_path"),
|
|
env.journal);
|
|
|
|
std::vector<std::string> const cfgPublishersOuter;
|
|
hash_set<NodeID> activeValidatorsOuter;
|
|
|
|
std::size_t const maxKeys = 40;
|
|
{
|
|
std::vector<std::string> cfgKeys;
|
|
cfgKeys.reserve(maxKeys);
|
|
hash_set<NodeID> unseenValidators;
|
|
|
|
while (cfgKeys.size() != maxKeys)
|
|
{
|
|
auto const valKey = randomNode();
|
|
cfgKeys.push_back(toBase58(TokenType::NodePublic, valKey));
|
|
if (cfgKeys.size() <= maxKeys - 5)
|
|
{
|
|
activeValidatorsOuter.emplace(calcNodeID(valKey));
|
|
}
|
|
else
|
|
{
|
|
unseenValidators.emplace(calcNodeID(valKey));
|
|
}
|
|
}
|
|
|
|
BEAST_EXPECT(trustedKeysOuter->load({}, cfgKeys, cfgPublishersOuter));
|
|
|
|
// updateTrusted should make all configured validators trusted
|
|
// even if they are not active/seen
|
|
TrustChanges changes = trustedKeysOuter->updateTrusted(
|
|
activeValidatorsOuter,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
|
|
for (auto const& val : unseenValidators)
|
|
activeValidatorsOuter.emplace(val);
|
|
|
|
BEAST_EXPECT(changes.added == activeValidatorsOuter);
|
|
BEAST_EXPECT(changes.removed.empty());
|
|
BEAST_EXPECT(trustedKeysOuter->quorum() == std::ceil(cfgKeys.size() * 0.8f));
|
|
for (auto const& val : cfgKeys)
|
|
{
|
|
if (auto const valKey = parseBase58<PublicKey>(TokenType::NodePublic, val))
|
|
{
|
|
BEAST_EXPECT(trustedKeysOuter->listed(*valKey));
|
|
BEAST_EXPECT(trustedKeysOuter->trusted(*valKey));
|
|
}
|
|
else
|
|
{
|
|
fail();
|
|
}
|
|
}
|
|
|
|
changes = trustedKeysOuter->updateTrusted(
|
|
activeValidatorsOuter,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(changes.added.empty());
|
|
BEAST_EXPECT(changes.removed.empty());
|
|
BEAST_EXPECT(trustedKeysOuter->quorum() == std::ceil(cfgKeys.size() * 0.8f));
|
|
}
|
|
{
|
|
// update with manifests
|
|
auto const masterPrivate = randomSecretKey();
|
|
auto const masterPublic = derivePublicKey(KeyType::ed25519, masterPrivate);
|
|
|
|
std::vector<std::string> const cfgKeys({toBase58(TokenType::NodePublic, masterPublic)});
|
|
|
|
BEAST_EXPECT(trustedKeysOuter->load({}, cfgKeys, cfgPublishersOuter));
|
|
|
|
auto const signingKeys1 = randomKeyPair(KeyType::secp256k1);
|
|
auto const signingPublic1 = signingKeys1.first;
|
|
activeValidatorsOuter.emplace(calcNodeID(masterPublic));
|
|
|
|
// Should not trust ephemeral signing key if there is no manifest
|
|
TrustChanges changes = trustedKeysOuter->updateTrusted(
|
|
activeValidatorsOuter,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(changes.added == asNodeIDs({masterPublic}));
|
|
BEAST_EXPECT(changes.removed.empty());
|
|
BEAST_EXPECT(trustedKeysOuter->quorum() == std::ceil((maxKeys + 1) * 0.8f));
|
|
BEAST_EXPECT(trustedKeysOuter->listed(masterPublic));
|
|
BEAST_EXPECT(trustedKeysOuter->trusted(masterPublic));
|
|
BEAST_EXPECT(!trustedKeysOuter->listed(signingPublic1));
|
|
BEAST_EXPECT(!trustedKeysOuter->trusted(signingPublic1));
|
|
|
|
// Should trust the ephemeral signing key from the applied manifest
|
|
auto m1 = deserializeManifest(makeManifestString(
|
|
masterPublic, masterPrivate, signingPublic1, signingKeys1.second, 1));
|
|
|
|
BEAST_EXPECT(
|
|
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
|
|
manifestsOuter.applyManifest(std::move(*m1)) == ManifestDisposition::accepted);
|
|
BEAST_EXPECT(trustedKeysOuter->listed(masterPublic));
|
|
BEAST_EXPECT(trustedKeysOuter->trusted(masterPublic));
|
|
BEAST_EXPECT(trustedKeysOuter->listed(signingPublic1));
|
|
BEAST_EXPECT(trustedKeysOuter->trusted(signingPublic1));
|
|
|
|
// Should only trust the ephemeral signing key
|
|
// from the newest applied manifest
|
|
auto const signingKeys2 = randomKeyPair(KeyType::secp256k1);
|
|
auto const signingPublic2 = signingKeys2.first;
|
|
auto m2 = deserializeManifest(makeManifestString(
|
|
masterPublic, masterPrivate, signingPublic2, signingKeys2.second, 2));
|
|
BEAST_EXPECT(
|
|
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
|
|
manifestsOuter.applyManifest(std::move(*m2)) == ManifestDisposition::accepted);
|
|
BEAST_EXPECT(trustedKeysOuter->listed(masterPublic));
|
|
BEAST_EXPECT(trustedKeysOuter->trusted(masterPublic));
|
|
BEAST_EXPECT(trustedKeysOuter->listed(signingPublic2));
|
|
BEAST_EXPECT(trustedKeysOuter->trusted(signingPublic2));
|
|
BEAST_EXPECT(!trustedKeysOuter->listed(signingPublic1));
|
|
BEAST_EXPECT(!trustedKeysOuter->trusted(signingPublic1));
|
|
|
|
// Should not trust keys from revoked master public key
|
|
auto const signingKeysMax = randomKeyPair(KeyType::secp256k1);
|
|
auto const signingPublicMax = signingKeysMax.first;
|
|
activeValidatorsOuter.emplace(calcNodeID(signingPublicMax));
|
|
auto mMax = deserializeManifest(makeRevocationString(masterPublic, masterPrivate));
|
|
|
|
// NOLINTBEGIN(bugprone-unchecked-optional-access)
|
|
BEAST_EXPECT(mMax->revoked());
|
|
BEAST_EXPECT(
|
|
manifestsOuter.applyManifest(std::move(*mMax)) == ManifestDisposition::accepted);
|
|
// NOLINTEND(bugprone-unchecked-optional-access)
|
|
|
|
BEAST_EXPECT(manifestsOuter.getSigningKey(masterPublic) == masterPublic);
|
|
BEAST_EXPECT(manifestsOuter.revoked(masterPublic));
|
|
|
|
// Revoked key remains trusted until list is updated
|
|
BEAST_EXPECT(trustedKeysOuter->listed(masterPublic));
|
|
BEAST_EXPECT(trustedKeysOuter->trusted(masterPublic));
|
|
|
|
changes = trustedKeysOuter->updateTrusted(
|
|
activeValidatorsOuter,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(changes.removed == asNodeIDs({masterPublic}));
|
|
BEAST_EXPECT(changes.added.empty());
|
|
BEAST_EXPECT(trustedKeysOuter->quorum() == std::ceil(maxKeys * 0.8f));
|
|
BEAST_EXPECT(trustedKeysOuter->listed(masterPublic));
|
|
BEAST_EXPECT(!trustedKeysOuter->trusted(masterPublic));
|
|
BEAST_EXPECT(!trustedKeysOuter->listed(signingPublicMax));
|
|
BEAST_EXPECT(!trustedKeysOuter->trusted(signingPublicMax));
|
|
BEAST_EXPECT(!trustedKeysOuter->listed(signingPublic2));
|
|
BEAST_EXPECT(!trustedKeysOuter->trusted(signingPublic2));
|
|
BEAST_EXPECT(!trustedKeysOuter->listed(signingPublic1));
|
|
BEAST_EXPECT(!trustedKeysOuter->trusted(signingPublic1));
|
|
}
|
|
{
|
|
// Make quorum unattainable if lists from any publishers are
|
|
// unavailable
|
|
auto trustedKeys = std::make_unique<ValidatorList>(
|
|
manifestsOuter,
|
|
manifestsOuter,
|
|
env.timeKeeper(),
|
|
app.config().legacy("database_path"),
|
|
env.journal);
|
|
auto const publisherSecret = randomSecretKey();
|
|
auto const publisherPublic = derivePublicKey(KeyType::ed25519, publisherSecret);
|
|
|
|
std::vector<std::string> const cfgPublishers({strHex(publisherPublic)});
|
|
std::vector<std::string> const emptyCfgKeys;
|
|
|
|
BEAST_EXPECT(trustedKeys->load({}, emptyCfgKeys, cfgPublishers));
|
|
|
|
TrustChanges const changes = trustedKeys->updateTrusted(
|
|
activeValidatorsOuter,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(changes.removed.empty());
|
|
BEAST_EXPECT(changes.added.empty());
|
|
BEAST_EXPECT(trustedKeys->quorum() == std::numeric_limits<std::size_t>::max());
|
|
}
|
|
{
|
|
// Trust explicitly listed validators also when list threshold is
|
|
// higher than 1
|
|
auto trustedKeys = std::make_unique<ValidatorList>(
|
|
manifestsOuter,
|
|
manifestsOuter,
|
|
env.timeKeeper(),
|
|
app.config().legacy("database_path"),
|
|
env.journal);
|
|
auto const masterPrivate = randomSecretKey();
|
|
auto const masterPublic = derivePublicKey(KeyType::ed25519, masterPrivate);
|
|
std::vector<std::string> const cfgKeys({toBase58(TokenType::NodePublic, masterPublic)});
|
|
|
|
auto const publisher1Secret = randomSecretKey();
|
|
auto const publisher1Public = derivePublicKey(KeyType::ed25519, publisher1Secret);
|
|
auto const publisher2Secret = randomSecretKey();
|
|
auto const publisher2Public = derivePublicKey(KeyType::ed25519, publisher2Secret);
|
|
std::vector<std::string> const cfgPublishers(
|
|
{strHex(publisher1Public), strHex(publisher2Public)});
|
|
|
|
BEAST_EXPECT(trustedKeys->load({}, cfgKeys, cfgPublishers, std::size_t(2)));
|
|
|
|
TrustChanges const changes = trustedKeys->updateTrusted(
|
|
activeValidatorsOuter,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(changes.removed.empty());
|
|
BEAST_EXPECT(changes.added.size() == 1);
|
|
BEAST_EXPECT(trustedKeys->listed(masterPublic));
|
|
BEAST_EXPECT(trustedKeys->trusted(masterPublic));
|
|
}
|
|
{
|
|
// Should use custom minimum quorum
|
|
std::size_t const minQuorum = 1;
|
|
ManifestCache manifests;
|
|
auto trustedKeys = std::make_unique<ValidatorList>(
|
|
manifests,
|
|
manifests,
|
|
env.timeKeeper(),
|
|
app.config().legacy("database_path"),
|
|
env.journal,
|
|
minQuorum);
|
|
|
|
std::size_t const n = 10;
|
|
std::vector<std::string> cfgKeys;
|
|
cfgKeys.reserve(n);
|
|
hash_set<NodeID> expectedTrusted;
|
|
hash_set<NodeID> activeValidators;
|
|
NodeID toBeSeen;
|
|
|
|
while (cfgKeys.size() < n)
|
|
{
|
|
auto const valKey = randomNode();
|
|
cfgKeys.push_back(toBase58(TokenType::NodePublic, valKey));
|
|
expectedTrusted.emplace(calcNodeID(valKey));
|
|
if (cfgKeys.size() < std::ceil(n * 0.8f))
|
|
{
|
|
activeValidators.emplace(calcNodeID(valKey));
|
|
}
|
|
else if (cfgKeys.size() < std::ceil(n * 0.8f))
|
|
{
|
|
toBeSeen = calcNodeID(valKey);
|
|
}
|
|
}
|
|
|
|
BEAST_EXPECT(trustedKeys->load({}, cfgKeys, cfgPublishersOuter));
|
|
|
|
TrustChanges changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(changes.removed.empty());
|
|
BEAST_EXPECT(changes.added == expectedTrusted);
|
|
BEAST_EXPECT(trustedKeys->quorum() == minQuorum);
|
|
|
|
// Use configured quorum even when seen validators >= quorum
|
|
activeValidators.emplace(toBeSeen);
|
|
changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(changes.removed.empty());
|
|
BEAST_EXPECT(changes.added.empty());
|
|
BEAST_EXPECT(trustedKeys->quorum() == minQuorum);
|
|
}
|
|
{
|
|
// Remove expired published list
|
|
auto trustedKeys = std::make_unique<ValidatorList>(
|
|
manifestsOuter,
|
|
manifestsOuter,
|
|
env.app().getTimeKeeper(),
|
|
app.config().legacy("database_path"),
|
|
env.journal);
|
|
|
|
std::vector<std::string> const emptyCfgKeys;
|
|
auto const publisherKeys = randomKeyPair(KeyType::secp256k1);
|
|
auto const pubSigningKeys = randomKeyPair(KeyType::secp256k1);
|
|
auto const manifest = base64_encode(makeManifestString(
|
|
publisherKeys.first,
|
|
publisherKeys.second,
|
|
pubSigningKeys.first,
|
|
pubSigningKeys.second,
|
|
1));
|
|
|
|
std::vector<std::string> const cfgPublisherKeys({strHex(publisherKeys.first)});
|
|
|
|
BEAST_EXPECT(trustedKeys->load({}, emptyCfgKeys, cfgPublisherKeys));
|
|
|
|
std::vector<Validator> list({randomValidator(), randomValidator()});
|
|
hash_set<NodeID> activeValidators(
|
|
asNodeIDs({list[0].masterPublic, list[1].masterPublic}));
|
|
|
|
// do not apply expired list
|
|
auto const version = 1;
|
|
auto const sequence = 1;
|
|
using namespace std::chrono_literals;
|
|
NetClock::time_point const validUntil = env.timeKeeper().now() + 60s;
|
|
auto const blob = makeList(list, sequence, validUntil.time_since_epoch().count());
|
|
auto const sig = signList(blob, pubSigningKeys);
|
|
|
|
BEAST_EXPECT(
|
|
ListDisposition::accepted ==
|
|
trustedKeys->applyLists(manifest, version, {{blob, sig, {}}}, siteUri)
|
|
.bestDisposition());
|
|
|
|
TrustChanges changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(changes.removed.empty());
|
|
BEAST_EXPECT(changes.added == activeValidators);
|
|
for (Validator const& val : list)
|
|
{
|
|
BEAST_EXPECT(trustedKeys->trusted(val.masterPublic));
|
|
BEAST_EXPECT(trustedKeys->trusted(val.signingPublic));
|
|
}
|
|
BEAST_EXPECT(trustedKeys->quorum() == 2);
|
|
|
|
env.timeKeeper().set(validUntil);
|
|
changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(changes.removed == activeValidators);
|
|
BEAST_EXPECT(changes.added.empty());
|
|
BEAST_EXPECT(!trustedKeys->trusted(list[0].masterPublic));
|
|
BEAST_EXPECT(!trustedKeys->trusted(list[1].masterPublic));
|
|
BEAST_EXPECT(trustedKeys->quorum() == std::numeric_limits<std::size_t>::max());
|
|
|
|
// (Re)trust validators from new valid list
|
|
std::vector<Validator> list2({list[0], randomValidator()});
|
|
activeValidators.insert(calcNodeID(list2[1].masterPublic));
|
|
auto const sequence2 = 2;
|
|
NetClock::time_point const expiration2 = env.timeKeeper().now() + 60s;
|
|
auto const blob2 = makeList(list2, sequence2, expiration2.time_since_epoch().count());
|
|
auto const sig2 = signList(blob2, pubSigningKeys);
|
|
|
|
BEAST_EXPECT(
|
|
ListDisposition::accepted ==
|
|
trustedKeys->applyLists(manifest, version, {{blob2, sig2, {}}}, siteUri)
|
|
.bestDisposition());
|
|
|
|
changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(changes.removed.empty());
|
|
BEAST_EXPECT(
|
|
changes.added == asNodeIDs({list2[0].masterPublic, list2[1].masterPublic}));
|
|
for (Validator const& val : list2)
|
|
{
|
|
BEAST_EXPECT(trustedKeys->trusted(val.masterPublic));
|
|
BEAST_EXPECT(trustedKeys->trusted(val.signingPublic));
|
|
}
|
|
BEAST_EXPECT(!trustedKeys->trusted(list[1].masterPublic));
|
|
BEAST_EXPECT(!trustedKeys->trusted(list[1].signingPublic));
|
|
BEAST_EXPECT(trustedKeys->quorum() == 2);
|
|
}
|
|
{
|
|
// Test 1-9 configured validators
|
|
auto trustedKeys = std::make_unique<ValidatorList>(
|
|
manifestsOuter,
|
|
manifestsOuter,
|
|
env.timeKeeper(),
|
|
app.config().legacy("database_path"),
|
|
env.journal);
|
|
|
|
std::vector<std::string> const cfgPublishers;
|
|
hash_set<NodeID> activeValidators;
|
|
hash_set<PublicKey> activeKeys;
|
|
|
|
std::vector<std::string> cfgKeys;
|
|
cfgKeys.reserve(9);
|
|
|
|
while (cfgKeys.size() < cfgKeys.capacity())
|
|
{
|
|
auto const valKey = randomNode();
|
|
cfgKeys.push_back(toBase58(TokenType::NodePublic, valKey));
|
|
activeValidators.emplace(calcNodeID(valKey));
|
|
activeKeys.emplace(valKey);
|
|
BEAST_EXPECT(trustedKeys->load({}, cfgKeys, cfgPublishers));
|
|
TrustChanges const changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(changes.removed.empty());
|
|
BEAST_EXPECT(changes.added == asNodeIDs({valKey}));
|
|
BEAST_EXPECT(trustedKeys->quorum() == std::ceil(cfgKeys.size() * 0.8f));
|
|
for (auto const& key : activeKeys)
|
|
BEAST_EXPECT(trustedKeys->trusted(key));
|
|
}
|
|
}
|
|
{
|
|
// Test 2-9 configured validators as validator
|
|
auto trustedKeys = std::make_unique<ValidatorList>(
|
|
manifestsOuter,
|
|
manifestsOuter,
|
|
env.timeKeeper(),
|
|
app.config().legacy("database_path"),
|
|
env.journal);
|
|
|
|
auto const localKey = randomNode();
|
|
std::vector<std::string> const cfgPublishers;
|
|
hash_set<NodeID> activeValidators;
|
|
hash_set<PublicKey> activeKeys;
|
|
std::vector<std::string> cfgKeys{toBase58(TokenType::NodePublic, localKey)};
|
|
cfgKeys.reserve(9);
|
|
|
|
while (cfgKeys.size() < cfgKeys.capacity())
|
|
{
|
|
auto const valKey = randomNode();
|
|
cfgKeys.push_back(toBase58(TokenType::NodePublic, valKey));
|
|
activeValidators.emplace(calcNodeID(valKey));
|
|
activeKeys.emplace(valKey);
|
|
|
|
BEAST_EXPECT(trustedKeys->load(localKey, cfgKeys, cfgPublishers));
|
|
TrustChanges const changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(changes.removed.empty());
|
|
if (cfgKeys.size() > 2)
|
|
{
|
|
BEAST_EXPECT(changes.added == asNodeIDs({valKey}));
|
|
}
|
|
else
|
|
{
|
|
BEAST_EXPECT(changes.added == asNodeIDs({localKey, valKey}));
|
|
}
|
|
|
|
BEAST_EXPECT(trustedKeys->quorum() == std::ceil(cfgKeys.size() * 0.8f));
|
|
|
|
for (auto const& key : activeKeys)
|
|
BEAST_EXPECT(trustedKeys->trusted(key));
|
|
}
|
|
}
|
|
{
|
|
// Trusted set should include all validators from multiple 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
|
|
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);
|
|
auto const pubSigningKeys = randomKeyPair(KeyType::secp256k1);
|
|
auto const manifest = base64_encode(makeManifestString(
|
|
publisherPublic,
|
|
publisherSecret,
|
|
pubSigningKeys.first,
|
|
pubSigningKeys.second,
|
|
1));
|
|
|
|
std::vector<std::string> const cfgPublishers({strHex(publisherPublic)});
|
|
std::vector<std::string> const emptyCfgKeys;
|
|
|
|
// Threshold of 1 will result in a union of all the lists
|
|
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> const 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
|
|
for (auto i = 0; i < publishers; ++i)
|
|
addPublishedList(i);
|
|
BEAST_EXPECT(trustedKeys->getListThreshold() == 1);
|
|
|
|
TrustChanges const changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
|
|
BEAST_EXPECT(trustedKeys->quorum() == std::ceil(valKeys.size() * 0.8f));
|
|
|
|
hash_set<NodeID> added;
|
|
for (auto const& val : valKeys)
|
|
{
|
|
BEAST_EXPECT(trustedKeys->trusted(val.masterPublic));
|
|
added.insert(calcNodeID(val.masterPublic));
|
|
}
|
|
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
|
|
// intersection 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, NetClock::time_point& validUntil2) {
|
|
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> const cfgPublishers({strHex(publisherPublic)});
|
|
std::vector<std::string> const 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
|
|
std::chrono::seconds duration;
|
|
if (i == 2)
|
|
{
|
|
duration = 120s;
|
|
}
|
|
else if (i == 1)
|
|
{
|
|
duration = 60s;
|
|
}
|
|
else
|
|
{
|
|
duration = 3600s;
|
|
}
|
|
NetClock::time_point const validUntil = env.timeKeeper().now() + duration;
|
|
if (i == 1)
|
|
{
|
|
validUntil1 = validUntil;
|
|
}
|
|
else if (i == 2)
|
|
{
|
|
validUntil2 = validUntil;
|
|
}
|
|
std::vector<Validator> const 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, validUntil2;
|
|
for (auto i = 0; i < publishers; ++i)
|
|
addPublishedList(i, validUntil1, validUntil2);
|
|
BEAST_EXPECT(trustedKeys->getListThreshold() == 2);
|
|
|
|
TrustChanges changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
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().getOverlay(),
|
|
env.app().getHashRouter());
|
|
|
|
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);
|
|
|
|
// Expire locals[2], which removes all validators
|
|
env.timeKeeper().set(validUntil2);
|
|
changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
|
|
BEAST_EXPECT(trustedKeys->quorum() == std::numeric_limits<std::size_t>::max());
|
|
|
|
removed.clear();
|
|
for (std::size_t i = 0; i < maxKeys; ++i)
|
|
{
|
|
auto const& val = valKeys[i];
|
|
if (i < maxKeys - 4)
|
|
{
|
|
BEAST_EXPECT(trustedKeys->listed(val.masterPublic));
|
|
}
|
|
else
|
|
{
|
|
BEAST_EXPECT(!trustedKeys->listed(val.masterPublic));
|
|
}
|
|
|
|
BEAST_EXPECT(!trustedKeys->trusted(val.masterPublic));
|
|
if (i >= 2 && i < maxKeys - 4)
|
|
removed.insert(calcNodeID(val.masterPublic));
|
|
}
|
|
|
|
BEAST_EXPECT(changes.added.empty());
|
|
BEAST_EXPECT(changes.removed == removed);
|
|
}
|
|
}
|
|
|
|
void
|
|
testExpires()
|
|
{
|
|
testcase("Expires");
|
|
|
|
std::string const siteUri = "testExpires.test";
|
|
|
|
jtx::Env env(*this);
|
|
auto& app = env.app();
|
|
|
|
auto toStr = [](PublicKey const& publicKey) {
|
|
return toBase58(TokenType::NodePublic, publicKey);
|
|
};
|
|
|
|
// Config listed keys
|
|
{
|
|
ManifestCache manifests;
|
|
auto trustedKeys = std::make_unique<ValidatorList>(
|
|
manifests,
|
|
manifests,
|
|
env.timeKeeper(),
|
|
app.config().legacy("database_path"),
|
|
env.journal);
|
|
|
|
// Empty list has no expiration
|
|
BEAST_EXPECT(trustedKeys->expires() == std::nullopt);
|
|
|
|
// Config listed keys have maximum expiry
|
|
PublicKey const localCfgListed = randomNode();
|
|
trustedKeys->load({}, {toStr(localCfgListed)}, {});
|
|
BEAST_EXPECT(
|
|
trustedKeys->expires() &&
|
|
trustedKeys->expires().value() == NetClock::time_point::max());
|
|
BEAST_EXPECT(trustedKeys->listed(localCfgListed));
|
|
}
|
|
|
|
// Published keys with expirations
|
|
{
|
|
ManifestCache manifests;
|
|
auto trustedKeys = std::make_unique<ValidatorList>(
|
|
manifests,
|
|
manifests,
|
|
env.app().getTimeKeeper(),
|
|
app.config().legacy("database_path"),
|
|
env.journal);
|
|
|
|
std::vector<Validator> validators = {randomValidator()};
|
|
hash_set<NodeID> activeValidators;
|
|
for (Validator const& val : validators)
|
|
activeValidators.insert(calcNodeID(val.masterPublic));
|
|
// Store prepared list data to control when it is applied
|
|
struct PreparedList
|
|
{
|
|
PublicKey publisherPublic;
|
|
std::string manifest;
|
|
std::vector<ValidatorBlobInfo> blobs;
|
|
int version;
|
|
std::vector<NetClock::time_point> expirations;
|
|
};
|
|
|
|
using namespace std::chrono_literals;
|
|
auto addPublishedList = [this, &env, &trustedKeys, &validators]() {
|
|
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> const cfgPublishers({strHex(publisherPublic)});
|
|
std::vector<std::string> const emptyCfgKeys;
|
|
|
|
BEAST_EXPECT(trustedKeys->load({}, emptyCfgKeys, cfgPublishers));
|
|
|
|
auto const version = 2;
|
|
auto const sequence1 = 1;
|
|
NetClock::time_point const expiration1 = env.timeKeeper().now() + 1800s;
|
|
auto const blob1 =
|
|
makeList(validators, sequence1, expiration1.time_since_epoch().count());
|
|
auto const sig1 = signList(blob1, pubSigningKeys);
|
|
|
|
NetClock::time_point const effective2 = expiration1 - 300s;
|
|
NetClock::time_point const expiration2 = effective2 + 1800s;
|
|
auto const sequence2 = 2;
|
|
auto const blob2 = makeList(
|
|
validators,
|
|
sequence2,
|
|
expiration2.time_since_epoch().count(),
|
|
effective2.time_since_epoch().count());
|
|
auto const sig2 = signList(blob2, pubSigningKeys);
|
|
|
|
return PreparedList{
|
|
.publisherPublic = publisherPublic,
|
|
.manifest = manifest,
|
|
.blobs = {{blob1, sig1, {}}, {blob2, sig2, {}}},
|
|
.version = version,
|
|
.expirations = {expiration1, expiration2}};
|
|
};
|
|
|
|
// Configure two publishers and prepare 2 lists
|
|
PreparedList prep1 = addPublishedList();
|
|
env.timeKeeper().set(env.timeKeeper().now() + 200s);
|
|
PreparedList const prep2 = addPublishedList();
|
|
|
|
// Initially, no list has been published, so no known expiration
|
|
BEAST_EXPECT(trustedKeys->expires() == std::nullopt);
|
|
|
|
// Apply first list
|
|
checkResult(
|
|
trustedKeys->applyLists(prep1.manifest, prep1.version, prep1.blobs, siteUri),
|
|
prep1.publisherPublic,
|
|
ListDisposition::pending,
|
|
ListDisposition::accepted);
|
|
|
|
// One list still hasn't published, so expiration is still
|
|
// unknown
|
|
BEAST_EXPECT(trustedKeys->expires() == std::nullopt);
|
|
|
|
// Apply second list
|
|
checkResult(
|
|
trustedKeys->applyLists(prep2.manifest, prep2.version, prep2.blobs, siteUri),
|
|
prep2.publisherPublic,
|
|
ListDisposition::pending,
|
|
ListDisposition::accepted);
|
|
// We now have loaded both lists, so expiration is known
|
|
BEAST_EXPECT(
|
|
trustedKeys->expires() &&
|
|
trustedKeys->expires().value() == prep1.expirations.back());
|
|
|
|
// Advance past the first list's LAST validFrom date. It remains
|
|
// the earliest validUntil, while rotating in the second list
|
|
{
|
|
env.timeKeeper().set(prep1.expirations.front() - 1s);
|
|
auto changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(
|
|
trustedKeys->expires() &&
|
|
trustedKeys->expires().value() == prep1.expirations.back());
|
|
BEAST_EXPECT(!changes.added.empty());
|
|
BEAST_EXPECT(changes.removed.empty());
|
|
}
|
|
|
|
// Advance past the first list's LAST validUntil, but it remains
|
|
// the earliest validUntil, while being invalidated
|
|
{
|
|
env.timeKeeper().set(prep1.expirations.back() + 1s);
|
|
auto changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(
|
|
trustedKeys->expires() &&
|
|
trustedKeys->expires().value() == prep1.expirations.back());
|
|
BEAST_EXPECT(changes.added.empty());
|
|
BEAST_EXPECT(changes.removed.empty());
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
testNegativeUNL()
|
|
{
|
|
testcase("NegativeUNL");
|
|
jtx::Env env(*this);
|
|
ManifestCache manifests;
|
|
|
|
auto createValidatorList =
|
|
[&](std::uint32_t vlSize,
|
|
std::optional<std::size_t> minimumQuorum = {}) -> std::shared_ptr<ValidatorList> {
|
|
auto trustedKeys = std::make_shared<ValidatorList>(
|
|
manifests,
|
|
manifests,
|
|
env.timeKeeper(),
|
|
env.app().config().legacy("database_path"),
|
|
env.journal,
|
|
minimumQuorum);
|
|
|
|
std::vector<std::string> const cfgPublishers;
|
|
std::vector<std::string> cfgKeys;
|
|
hash_set<NodeID> activeValidators;
|
|
cfgKeys.reserve(vlSize);
|
|
while (cfgKeys.size() < cfgKeys.capacity())
|
|
{
|
|
auto const valKey = randomNode();
|
|
cfgKeys.push_back(toBase58(TokenType::NodePublic, valKey));
|
|
activeValidators.emplace(calcNodeID(valKey));
|
|
}
|
|
if (trustedKeys->load({}, cfgKeys, cfgPublishers))
|
|
{
|
|
trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
if (minimumQuorum == trustedKeys->quorum() ||
|
|
trustedKeys->quorum() == std::ceil(cfgKeys.size() * 0.8f))
|
|
return trustedKeys;
|
|
}
|
|
return nullptr;
|
|
};
|
|
|
|
/*
|
|
* Test NegativeUNL
|
|
* == Combinations ==
|
|
* -- UNL size: 34, 35, 57
|
|
* -- nUNL size: 0%, 20%, 30%, 50%
|
|
*
|
|
* == with UNL size 60
|
|
* -- set == get,
|
|
* -- check quorum, with nUNL size: 0, 12, 30, 18
|
|
* -- nUNL overlap: |nUNL - UNL| = 5, with nUNL size: 18
|
|
* -- with command line minimumQuorum = 50%,
|
|
* seen_reliable affected by nUNL
|
|
*/
|
|
|
|
{
|
|
hash_set<NodeID> const activeValidators;
|
|
//== Combinations ==
|
|
std::array<std::uint32_t, 4> const unlSizes = {34, 35, 39, 60};
|
|
std::array<std::uint32_t, 4> const nUnlPercent = {0, 20, 30, 50};
|
|
for (auto us : unlSizes)
|
|
{
|
|
for (auto np : nUnlPercent)
|
|
{
|
|
auto validators = createValidatorList(us);
|
|
BEAST_EXPECT(validators);
|
|
if (validators)
|
|
{
|
|
std::uint32_t const nUnlSize = us * np / 100;
|
|
auto unl = validators->getTrustedMasterKeys();
|
|
hash_set<PublicKey> nUnl;
|
|
auto it = unl.begin();
|
|
for (std::uint32_t i = 0; i < nUnlSize; ++i)
|
|
{
|
|
nUnl.insert(*it);
|
|
++it;
|
|
}
|
|
validators->setNegativeUNL(nUnl);
|
|
validators->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(
|
|
validators->quorum() ==
|
|
static_cast<std::size_t>(
|
|
std::ceil(std::max((us - nUnlSize) * 0.8f, us * 0.6f))));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
//== with UNL size 60
|
|
auto validators = createValidatorList(60);
|
|
BEAST_EXPECT(validators);
|
|
if (validators)
|
|
{
|
|
hash_set<NodeID> activeValidators;
|
|
auto unl = validators->getTrustedMasterKeys();
|
|
BEAST_EXPECT(unl.size() == 60);
|
|
{
|
|
//-- set == get,
|
|
//-- check quorum, with nUNL size: 0, 30, 18, 12
|
|
auto nUnlChange = [&](std::uint32_t nUnlSize, std::uint32_t quorum) -> bool {
|
|
hash_set<PublicKey> nUnl;
|
|
auto it = unl.begin();
|
|
for (std::uint32_t i = 0; i < nUnlSize; ++i)
|
|
{
|
|
nUnl.insert(*it);
|
|
++it;
|
|
}
|
|
validators->setNegativeUNL(nUnl);
|
|
auto nUnl_temp = validators->getNegativeUNL();
|
|
if (nUnl_temp.size() == nUnl.size())
|
|
{
|
|
for (auto& n : nUnl_temp)
|
|
{
|
|
if (!nUnl.contains(n))
|
|
return false;
|
|
}
|
|
validators->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
return validators->quorum() == quorum;
|
|
}
|
|
return false;
|
|
};
|
|
BEAST_EXPECT(nUnlChange(0, 48));
|
|
BEAST_EXPECT(nUnlChange(30, 36));
|
|
BEAST_EXPECT(nUnlChange(18, 36));
|
|
BEAST_EXPECT(nUnlChange(12, 39));
|
|
}
|
|
|
|
{
|
|
// nUNL overlap: |nUNL - UNL| = 5, with nUNL size:
|
|
// 18
|
|
auto nUnl = validators->getNegativeUNL();
|
|
BEAST_EXPECT(nUnl.size() == 12);
|
|
std::size_t const ss = 33;
|
|
std::vector<uint8_t> data(ss, 0);
|
|
data[0] = 0xED;
|
|
for (int i = 0; i < 6; ++i)
|
|
{
|
|
Slice const s(data.data(), ss);
|
|
data[1]++;
|
|
nUnl.emplace(s);
|
|
}
|
|
validators->setNegativeUNL(nUnl);
|
|
validators->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(validators->quorum() == 39);
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
//== with UNL size 60
|
|
//-- with command line minimumQuorum = 50%,
|
|
// seen_reliable affected by nUNL
|
|
auto validators = createValidatorList(60, 30);
|
|
BEAST_EXPECT(validators);
|
|
if (validators)
|
|
{
|
|
hash_set<NodeID> activeValidators;
|
|
hash_set<PublicKey> unl = validators->getTrustedMasterKeys();
|
|
auto it = unl.begin();
|
|
for (std::uint32_t i = 0; i < 50; ++i)
|
|
{
|
|
activeValidators.insert(calcNodeID(*it));
|
|
++it;
|
|
}
|
|
validators->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(validators->quorum() == 30);
|
|
hash_set<PublicKey> nUnl;
|
|
it = unl.begin();
|
|
for (std::uint32_t i = 0; i < 20; ++i)
|
|
{
|
|
nUnl.insert(*it);
|
|
++it;
|
|
}
|
|
validators->setNegativeUNL(nUnl);
|
|
validators->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(validators->quorum() == 30);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
testSha512Hash()
|
|
{
|
|
testcase("Sha512 hashing");
|
|
// Tests that ValidatorList hash_append helpers with a single blob
|
|
// returns the same result as xrpl::Sha512Half used by the
|
|
// TMValidatorList protocol message handler
|
|
std::string const manifest = "This is not really a manifest";
|
|
std::string const blob = "This is not really a blob";
|
|
std::string const signature = "This is not really a signature";
|
|
std::uint32_t const version = 1;
|
|
|
|
auto const global = sha512Half(manifest, blob, signature, version);
|
|
BEAST_EXPECT(!!global);
|
|
|
|
std::vector<ValidatorBlobInfo> blobVector(1);
|
|
blobVector[0].blob = blob;
|
|
blobVector[0].signature = signature;
|
|
BEAST_EXPECT(global == sha512Half(manifest, blobVector, version));
|
|
BEAST_EXPECT(global != sha512Half(signature, blobVector, version));
|
|
|
|
{
|
|
std::map<std::size_t, ValidatorBlobInfo> const blobMap{{99, blobVector[0]}};
|
|
BEAST_EXPECT(global == sha512Half(manifest, blobMap, version));
|
|
BEAST_EXPECT(global != sha512Half(blob, blobMap, version));
|
|
}
|
|
|
|
{
|
|
protocol::TMValidatorList msg1;
|
|
msg1.set_manifest(manifest);
|
|
msg1.set_blob(blob);
|
|
msg1.set_signature(signature);
|
|
msg1.set_version(version);
|
|
BEAST_EXPECT(global == sha512Half(msg1));
|
|
msg1.set_signature(blob);
|
|
BEAST_EXPECT(global != sha512Half(msg1));
|
|
}
|
|
|
|
{
|
|
protocol::TMValidatorListCollection msg2;
|
|
msg2.set_manifest(manifest);
|
|
msg2.set_version(version);
|
|
auto& bi = *msg2.add_blobs();
|
|
bi.set_blob(blob);
|
|
bi.set_signature(signature);
|
|
BEAST_EXPECT(global == sha512Half(msg2));
|
|
bi.set_manifest(manifest);
|
|
BEAST_EXPECT(global != sha512Half(msg2));
|
|
}
|
|
}
|
|
|
|
void
|
|
testBuildMessages()
|
|
{
|
|
testcase("Build and split messages");
|
|
|
|
std::uint32_t const manifestCutoff = 7;
|
|
auto extractHeader = [this](Message& message) {
|
|
auto const& buffer = message.getBuffer(compression::Compressed::Off);
|
|
|
|
boost::beast::multi_buffer buffers;
|
|
|
|
// simulate multi-buffer
|
|
auto start = buffer.begin();
|
|
auto end = buffer.end();
|
|
std::vector<std::uint8_t> slice(start, end);
|
|
buffers.commit(
|
|
boost::asio::buffer_copy(
|
|
buffers.prepare(slice.size()), boost::asio::buffer(slice)));
|
|
|
|
boost::system::error_code ec;
|
|
auto header = detail::parseMessageHeader(ec, buffers.data(), buffers.size());
|
|
BEAST_EXPECT(!ec);
|
|
return std::make_pair(header, buffers);
|
|
};
|
|
auto extractProtocolMessage1 = [this, &extractHeader](Message& message) {
|
|
auto [header, buffers] = extractHeader(message);
|
|
if (BEAST_EXPECT(header) &&
|
|
BEAST_EXPECT(header->message_type == protocol::mtVALIDATOR_LIST))
|
|
{
|
|
auto const msg =
|
|
detail::parseMessageContent<protocol::TMValidatorList>(*header, buffers.data());
|
|
BEAST_EXPECT(msg);
|
|
return msg;
|
|
}
|
|
return std::shared_ptr<protocol::TMValidatorList>();
|
|
};
|
|
auto extractProtocolMessage2 = [this, &extractHeader](Message& message) {
|
|
auto [header, buffers] = extractHeader(message);
|
|
if (BEAST_EXPECT(header) &&
|
|
BEAST_EXPECT(header->message_type == protocol::mtVALIDATOR_LIST_COLLECTION))
|
|
{
|
|
auto const msg = detail::parseMessageContent<protocol::TMValidatorListCollection>(
|
|
*header, buffers.data());
|
|
BEAST_EXPECT(msg);
|
|
return msg;
|
|
}
|
|
return std::shared_ptr<protocol::TMValidatorListCollection>();
|
|
};
|
|
auto verifyMessage =
|
|
[this, manifestCutoff, &extractProtocolMessage1, &extractProtocolMessage2](
|
|
auto const version,
|
|
auto const& manifest,
|
|
auto const& blobInfos,
|
|
auto const& messages,
|
|
std::vector<std::pair<std::size_t, std::vector<std::uint32_t>>> expectedInfo) {
|
|
BEAST_EXPECT(messages.size() == expectedInfo.size());
|
|
auto msgIter = expectedInfo.begin();
|
|
for (auto const& messageWithHash : messages)
|
|
{
|
|
if (!BEAST_EXPECT(msgIter != expectedInfo.end()))
|
|
break;
|
|
if (!BEAST_EXPECT(messageWithHash.message))
|
|
continue;
|
|
auto const& expectedSeqs = msgIter->second;
|
|
auto seqIter = expectedSeqs.begin();
|
|
auto const size =
|
|
messageWithHash.message->getBuffer(compression::Compressed::Off).size();
|
|
// This size is arbitrary, but shouldn't change
|
|
BEAST_EXPECT(size == msgIter->first);
|
|
if (expectedSeqs.size() == 1)
|
|
{
|
|
auto const msg = extractProtocolMessage1(*messageWithHash.message);
|
|
auto const expectedVersion = 1;
|
|
if (BEAST_EXPECT(msg))
|
|
{
|
|
BEAST_EXPECT(msg->version() == expectedVersion);
|
|
if (!BEAST_EXPECT(seqIter != expectedSeqs.end()))
|
|
continue;
|
|
auto const& expectedBlob = blobInfos.at(*seqIter);
|
|
BEAST_EXPECT((*seqIter < manifestCutoff) == !!expectedBlob.manifest);
|
|
auto const expectedManifest =
|
|
*seqIter < manifestCutoff && expectedBlob.manifest
|
|
? *expectedBlob.manifest
|
|
: manifest;
|
|
BEAST_EXPECT(msg->manifest() == expectedManifest);
|
|
BEAST_EXPECT(msg->blob() == expectedBlob.blob);
|
|
BEAST_EXPECT(msg->signature() == expectedBlob.signature);
|
|
++seqIter;
|
|
BEAST_EXPECT(seqIter == expectedSeqs.end());
|
|
|
|
BEAST_EXPECT(
|
|
messageWithHash.hash ==
|
|
sha512Half(
|
|
expectedManifest,
|
|
expectedBlob.blob,
|
|
expectedBlob.signature,
|
|
expectedVersion));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
std::vector<ValidatorBlobInfo> hashingBlobs;
|
|
hashingBlobs.reserve(msgIter->second.size());
|
|
|
|
auto const msg = extractProtocolMessage2(*messageWithHash.message);
|
|
if (BEAST_EXPECT(msg))
|
|
{
|
|
BEAST_EXPECT(msg->version() == version);
|
|
BEAST_EXPECT(msg->manifest() == manifest);
|
|
for (auto const& blobInfo : msg->blobs())
|
|
{
|
|
if (!BEAST_EXPECT(seqIter != expectedSeqs.end()))
|
|
break;
|
|
auto const& expectedBlob = blobInfos.at(*seqIter);
|
|
hashingBlobs.push_back(expectedBlob);
|
|
BEAST_EXPECT(blobInfo.has_manifest() == !!expectedBlob.manifest);
|
|
BEAST_EXPECT(
|
|
blobInfo.has_manifest() == (*seqIter < manifestCutoff));
|
|
|
|
if (*seqIter < manifestCutoff)
|
|
BEAST_EXPECT(blobInfo.manifest() == *expectedBlob.manifest);
|
|
BEAST_EXPECT(blobInfo.blob() == expectedBlob.blob);
|
|
BEAST_EXPECT(blobInfo.signature() == expectedBlob.signature);
|
|
++seqIter;
|
|
}
|
|
BEAST_EXPECT(seqIter == expectedSeqs.end());
|
|
}
|
|
BEAST_EXPECT(
|
|
messageWithHash.hash == sha512Half(manifest, hashingBlobs, version));
|
|
}
|
|
++msgIter;
|
|
}
|
|
BEAST_EXPECT(msgIter == expectedInfo.end());
|
|
};
|
|
auto verifyBuildMessages = [this](
|
|
std::pair<std::size_t, std::size_t> const& result,
|
|
std::size_t expectedSequence,
|
|
std::size_t expectedSize) {
|
|
BEAST_EXPECT(result.first == expectedSequence);
|
|
BEAST_EXPECT(result.second == expectedSize);
|
|
};
|
|
|
|
std::string const manifest = "This is not a manifest";
|
|
std::uint32_t const version = 2;
|
|
// Mutable so items can be removed in later tests.
|
|
auto const blobInfos = [manifestCutoff = manifestCutoff]() {
|
|
std::map<std::size_t, ValidatorBlobInfo> bis;
|
|
|
|
for (auto seq : {5, 6, 7, 10, 12})
|
|
{
|
|
auto& b = bis[seq];
|
|
std::stringstream s;
|
|
s << "This is not a blob with sequence " << seq;
|
|
b.blob = s.str();
|
|
s.str(std::string());
|
|
s << "This is not a signature for sequence " << seq;
|
|
b.signature = s.str();
|
|
if (seq < manifestCutoff)
|
|
{
|
|
// add a manifest for the "early" blobs
|
|
s.str(std::string());
|
|
s << "This is not manifest " << seq;
|
|
b.manifest = s.str();
|
|
}
|
|
}
|
|
return bis;
|
|
}();
|
|
auto const maxSequence = blobInfos.rbegin()->first;
|
|
BEAST_EXPECT(maxSequence == 12);
|
|
|
|
std::vector<ValidatorList::MessageWithHash> messages;
|
|
|
|
// Version 1
|
|
|
|
// This peer has a VL ahead of our "current"
|
|
verifyBuildMessages(
|
|
ValidatorList::buildValidatorListMessages(
|
|
1, 8, maxSequence, version, manifest, blobInfos, messages),
|
|
0,
|
|
0);
|
|
BEAST_EXPECT(messages.empty());
|
|
|
|
// Don't repeat the work if messages is populated, even though the
|
|
// peerSequence provided indicates it should. Note that this
|
|
// situation is contrived for this test and should never happen in
|
|
// real code.
|
|
messages.emplace_back();
|
|
verifyBuildMessages(
|
|
ValidatorList::buildValidatorListMessages(
|
|
1, 3, maxSequence, version, manifest, blobInfos, messages),
|
|
5,
|
|
0);
|
|
BEAST_EXPECT(messages.size() == 1 && !messages.front().message);
|
|
|
|
// Generate a version 1 message
|
|
messages.clear();
|
|
verifyBuildMessages(
|
|
ValidatorList::buildValidatorListMessages(
|
|
1, 3, maxSequence, version, manifest, blobInfos, messages),
|
|
5,
|
|
1);
|
|
if (BEAST_EXPECT(messages.size() == 1) && BEAST_EXPECT(messages.front().message))
|
|
{
|
|
auto const& messageWithHash = messages.front();
|
|
auto const msg = extractProtocolMessage1(*messageWithHash.message);
|
|
auto const size =
|
|
messageWithHash.message->getBuffer(compression::Compressed::Off).size();
|
|
// This size is arbitrary, but shouldn't change
|
|
BEAST_EXPECT(size == 108);
|
|
auto const& expected = blobInfos.at(5);
|
|
if (BEAST_EXPECT(msg))
|
|
{
|
|
BEAST_EXPECT(msg->version() == 1);
|
|
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
|
|
BEAST_EXPECT(msg->manifest() == *expected.manifest);
|
|
BEAST_EXPECT(msg->blob() == expected.blob);
|
|
BEAST_EXPECT(msg->signature() == expected.signature);
|
|
}
|
|
BEAST_EXPECT(
|
|
messageWithHash.hash ==
|
|
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
|
|
sha512Half(*expected.manifest, expected.blob, expected.signature, 1));
|
|
}
|
|
|
|
// Version 2
|
|
|
|
messages.clear();
|
|
|
|
// This peer has a VL ahead of us.
|
|
verifyBuildMessages(
|
|
ValidatorList::buildValidatorListMessages(
|
|
2, maxSequence * 2, maxSequence, version, manifest, blobInfos, messages),
|
|
0,
|
|
0);
|
|
BEAST_EXPECT(messages.empty());
|
|
|
|
// Don't repeat the work if messages is populated, even though the
|
|
// peerSequence provided indicates it should. Note that this
|
|
// situation is contrived for this test and should never happen in
|
|
// real code.
|
|
messages.emplace_back();
|
|
verifyBuildMessages(
|
|
ValidatorList::buildValidatorListMessages(
|
|
2, 3, maxSequence, version, manifest, blobInfos, messages),
|
|
maxSequence,
|
|
0);
|
|
BEAST_EXPECT(messages.size() == 1 && !messages.front().message);
|
|
|
|
// Generate a version 2 message. Don't send the current
|
|
messages.clear();
|
|
verifyBuildMessages(
|
|
ValidatorList::buildValidatorListMessages(
|
|
2, 5, maxSequence, version, manifest, blobInfos, messages),
|
|
maxSequence,
|
|
4);
|
|
verifyMessage(version, manifest, blobInfos, messages, {{372, {6, 7, 10, 12}}});
|
|
|
|
// Test message splitting on size limits.
|
|
|
|
// Set a limit that should give two messages
|
|
messages.clear();
|
|
verifyBuildMessages(
|
|
ValidatorList::buildValidatorListMessages(
|
|
2, 5, maxSequence, version, manifest, blobInfos, messages, 300),
|
|
maxSequence,
|
|
4);
|
|
verifyMessage(version, manifest, blobInfos, messages, {{212, {6, 7}}, {192, {10, 12}}});
|
|
|
|
// Set a limit between the size of the two earlier messages so one
|
|
// will split and the other won't
|
|
messages.clear();
|
|
verifyBuildMessages(
|
|
ValidatorList::buildValidatorListMessages(
|
|
2, 5, maxSequence, version, manifest, blobInfos, messages, 200),
|
|
maxSequence,
|
|
4);
|
|
verifyMessage(
|
|
version, manifest, blobInfos, messages, {{108, {6}}, {108, {7}}, {192, {10, 12}}});
|
|
|
|
// Set a limit so that all the VLs are sent individually
|
|
messages.clear();
|
|
verifyBuildMessages(
|
|
ValidatorList::buildValidatorListMessages(
|
|
2, 5, maxSequence, version, manifest, blobInfos, messages, 150),
|
|
maxSequence,
|
|
4);
|
|
verifyMessage(
|
|
version,
|
|
manifest,
|
|
blobInfos,
|
|
messages,
|
|
{{108, {6}}, {108, {7}}, {110, {10}}, {110, {12}}});
|
|
|
|
// Set a limit smaller than some of the messages. Because single
|
|
// messages send regardless, they will all still be sent
|
|
messages.clear();
|
|
verifyBuildMessages(
|
|
ValidatorList::buildValidatorListMessages(
|
|
2, 5, maxSequence, version, manifest, blobInfos, messages, 108),
|
|
maxSequence,
|
|
4);
|
|
verifyMessage(
|
|
version,
|
|
manifest,
|
|
blobInfos,
|
|
messages,
|
|
{{108, {6}}, {108, {7}}, {110, {10}}, {110, {12}}});
|
|
}
|
|
|
|
void
|
|
testQuorumDisabled()
|
|
{
|
|
testcase("Test quorum disabled");
|
|
|
|
std::string const siteUri = "testQuorumDisabled.test";
|
|
jtx::Env env(*this);
|
|
auto& app = env.app();
|
|
|
|
constexpr std::size_t maxKeys = 20;
|
|
hash_set<NodeID> activeValidators;
|
|
std::vector<Validator> valKeys;
|
|
while (valKeys.size() != maxKeys)
|
|
{
|
|
valKeys.push_back(randomValidator());
|
|
activeValidators.emplace(calcNodeID(valKeys.back().masterPublic));
|
|
}
|
|
|
|
struct Publisher
|
|
{
|
|
bool revoked;
|
|
PublicKey pubKey;
|
|
std::pair<PublicKey, SecretKey> signingKeys;
|
|
std::string manifest;
|
|
NetClock::time_point expiry = {}; // NOLINT(readability-redundant-member-init)
|
|
};
|
|
|
|
// Create ValidatorList with a set of countTotal publishers, of which
|
|
// first countRevoked are revoked and the last one expires early
|
|
auto makeValidatorList = [&, this](
|
|
std::size_t countTotal,
|
|
std::size_t countRevoked,
|
|
std::size_t listThreshold,
|
|
ManifestCache& pubManifests,
|
|
ManifestCache& valManifests,
|
|
std::optional<Validator> self,
|
|
std::vector<Publisher>& publishers // out
|
|
) -> std::unique_ptr<ValidatorList> {
|
|
auto result = std::make_unique<ValidatorList>(
|
|
valManifests,
|
|
pubManifests,
|
|
env.timeKeeper(),
|
|
app.config().legacy("database_path"),
|
|
env.journal);
|
|
|
|
std::vector<std::string> cfgPublishers;
|
|
for (std::size_t i = 0; i < countTotal; ++i)
|
|
{
|
|
auto const publisherSecret = randomSecretKey();
|
|
auto const publisherPublic = derivePublicKey(KeyType::ed25519, publisherSecret);
|
|
auto const pubSigningKeys = randomKeyPair(KeyType::secp256k1);
|
|
cfgPublishers.push_back(strHex(publisherPublic));
|
|
|
|
constexpr auto revoked = std::numeric_limits<std::uint32_t>::max();
|
|
auto const manifest = base64_encode(makeManifestString(
|
|
publisherPublic,
|
|
publisherSecret,
|
|
pubSigningKeys.first,
|
|
pubSigningKeys.second,
|
|
i < countRevoked ? revoked : 1));
|
|
publishers.push_back(
|
|
Publisher{
|
|
.revoked = i < countRevoked,
|
|
.pubKey = publisherPublic,
|
|
.signingKeys = pubSigningKeys,
|
|
.manifest = manifest,
|
|
});
|
|
}
|
|
|
|
std::vector<std::string> const emptyCfgKeys;
|
|
auto threshold = listThreshold > 0 ? std::optional(listThreshold) : std::nullopt;
|
|
if (self)
|
|
{
|
|
valManifests.applyManifest(*deserializeManifest(base64_decode(self->manifest)));
|
|
BEAST_EXPECT(
|
|
result->load(self->signingPublic, emptyCfgKeys, cfgPublishers, threshold));
|
|
}
|
|
else
|
|
{
|
|
BEAST_EXPECT(result->load({}, emptyCfgKeys, cfgPublishers, threshold));
|
|
}
|
|
|
|
for (std::size_t i = 0; i < countTotal; ++i)
|
|
{
|
|
using namespace std::chrono_literals;
|
|
publishers[i].expiry = env.timeKeeper().now() + (i == countTotal - 1 ? 60s : 3600s);
|
|
auto const blob =
|
|
makeList(valKeys, 1, publishers[i].expiry.time_since_epoch().count());
|
|
auto const sig = signList(blob, publishers[i].signingKeys);
|
|
|
|
BEAST_EXPECT(
|
|
result->applyLists(publishers[i].manifest, 1, {{blob, sig, {}}}, siteUri)
|
|
.bestDisposition() ==
|
|
(publishers[i].revoked ? ListDisposition::untrusted
|
|
: ListDisposition::accepted));
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
// Test cases use 5 publishers.
|
|
constexpr auto quorumDisabled = std::numeric_limits<std::size_t>::max();
|
|
{
|
|
// List threshold = 5 (same as number of trusted publishers)
|
|
ManifestCache pubManifests;
|
|
ManifestCache valManifests;
|
|
std::vector<Publisher> publishers;
|
|
// Self is a random validator
|
|
auto const self = randomValidator();
|
|
auto const keysTotal = valKeys.size() + 1;
|
|
auto trustedKeys = makeValidatorList(
|
|
5, //
|
|
0,
|
|
5,
|
|
pubManifests,
|
|
valManifests,
|
|
self,
|
|
publishers);
|
|
BEAST_EXPECT(trustedKeys->getListThreshold() == 5);
|
|
for (auto const& p : publishers)
|
|
BEAST_EXPECT(trustedKeys->trustedPublisher(p.pubKey));
|
|
|
|
TrustChanges changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(trustedKeys->quorum() == std::ceil(keysTotal * 0.8f));
|
|
BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal);
|
|
|
|
hash_set<NodeID> added;
|
|
added.insert(calcNodeID(self.masterPublic));
|
|
for (auto const& val : valKeys)
|
|
{
|
|
BEAST_EXPECT(trustedKeys->trusted(val.masterPublic));
|
|
added.insert(calcNodeID(val.masterPublic));
|
|
}
|
|
BEAST_EXPECT(changes.added == added);
|
|
BEAST_EXPECT(changes.removed.empty());
|
|
|
|
// Expire one publisher - only trusted validator is self
|
|
env.timeKeeper().set(publishers.back().expiry);
|
|
changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled);
|
|
BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == 1);
|
|
|
|
hash_set<NodeID> removed;
|
|
BEAST_EXPECT(trustedKeys->trusted(self.masterPublic));
|
|
for (auto const& val : valKeys)
|
|
{
|
|
BEAST_EXPECT(trustedKeys->listed(val.masterPublic));
|
|
BEAST_EXPECT(!trustedKeys->trusted(val.masterPublic));
|
|
removed.insert(calcNodeID(val.masterPublic));
|
|
}
|
|
BEAST_EXPECT(changes.added.empty());
|
|
BEAST_EXPECT(changes.removed == removed);
|
|
}
|
|
{
|
|
// List threshold = 5 (same as number of trusted publishers)
|
|
ManifestCache pubManifests;
|
|
ManifestCache valManifests;
|
|
std::vector<Publisher> publishers;
|
|
auto const keysTotal = valKeys.size();
|
|
auto trustedKeys = makeValidatorList(
|
|
5, //
|
|
0,
|
|
5,
|
|
pubManifests,
|
|
valManifests,
|
|
{},
|
|
publishers);
|
|
BEAST_EXPECT(trustedKeys->getListThreshold() == 5);
|
|
for (auto const& p : publishers)
|
|
BEAST_EXPECT(trustedKeys->trustedPublisher(p.pubKey));
|
|
|
|
TrustChanges changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(trustedKeys->quorum() == std::ceil(keysTotal * 0.8f));
|
|
BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal);
|
|
|
|
hash_set<NodeID> added;
|
|
for (auto const& val : valKeys)
|
|
{
|
|
BEAST_EXPECT(trustedKeys->trusted(val.masterPublic));
|
|
added.insert(calcNodeID(val.masterPublic));
|
|
}
|
|
BEAST_EXPECT(changes.added == added);
|
|
BEAST_EXPECT(changes.removed.empty());
|
|
|
|
// Expire one publisher - no trusted validators
|
|
env.timeKeeper().set(publishers.back().expiry);
|
|
changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled);
|
|
BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().empty());
|
|
|
|
hash_set<NodeID> removed;
|
|
for (auto const& val : valKeys)
|
|
{
|
|
BEAST_EXPECT(trustedKeys->listed(val.masterPublic));
|
|
BEAST_EXPECT(!trustedKeys->trusted(val.masterPublic));
|
|
removed.insert(calcNodeID(val.masterPublic));
|
|
}
|
|
BEAST_EXPECT(changes.added.empty());
|
|
BEAST_EXPECT(changes.removed == removed);
|
|
}
|
|
{
|
|
// List threshold = 4, 1 publisher is revoked
|
|
ManifestCache pubManifests;
|
|
ManifestCache valManifests;
|
|
std::vector<Publisher> publishers;
|
|
// Self is in UNL
|
|
auto const self = valKeys[1];
|
|
auto const keysTotal = valKeys.size();
|
|
auto trustedKeys = makeValidatorList(
|
|
5, //
|
|
1,
|
|
4,
|
|
pubManifests,
|
|
valManifests,
|
|
self,
|
|
publishers);
|
|
BEAST_EXPECT(trustedKeys->getListThreshold() == 4);
|
|
int untrustedCount = 0;
|
|
for (auto const& p : publishers)
|
|
{
|
|
bool const trusted = trustedKeys->trustedPublisher(p.pubKey);
|
|
BEAST_EXPECT(p.revoked ^ trusted);
|
|
untrustedCount += trusted ? 0 : 1;
|
|
}
|
|
BEAST_EXPECT(untrustedCount == 1);
|
|
|
|
TrustChanges changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(trustedKeys->quorum() == std::ceil(keysTotal * 0.8f));
|
|
BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal);
|
|
|
|
hash_set<NodeID> added;
|
|
for (auto const& val : valKeys)
|
|
{
|
|
BEAST_EXPECT(trustedKeys->trusted(val.masterPublic));
|
|
added.insert(calcNodeID(val.masterPublic));
|
|
}
|
|
BEAST_EXPECT(changes.added == added);
|
|
BEAST_EXPECT(changes.removed.empty());
|
|
|
|
// Expire one publisher - only trusted validator is self
|
|
env.timeKeeper().set(publishers.back().expiry);
|
|
changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled);
|
|
BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == 1);
|
|
|
|
hash_set<NodeID> removed;
|
|
BEAST_EXPECT(trustedKeys->trusted(self.masterPublic));
|
|
for (auto const& val : valKeys)
|
|
{
|
|
BEAST_EXPECT(trustedKeys->listed(val.masterPublic));
|
|
if (val.masterPublic != self.masterPublic)
|
|
{
|
|
BEAST_EXPECT(!trustedKeys->trusted(val.masterPublic));
|
|
removed.insert(calcNodeID(val.masterPublic));
|
|
}
|
|
}
|
|
BEAST_EXPECT(changes.added.empty());
|
|
BEAST_EXPECT(changes.removed == removed);
|
|
}
|
|
{
|
|
// List threshold = 3 (default), 2 publishers are revoked
|
|
ManifestCache pubManifests;
|
|
ManifestCache valManifests;
|
|
std::vector<Publisher> publishers;
|
|
// Self is a random validator
|
|
auto const self = randomValidator();
|
|
auto const keysTotal = valKeys.size() + 1;
|
|
auto trustedKeys = makeValidatorList(
|
|
5, //
|
|
2,
|
|
0,
|
|
pubManifests,
|
|
valManifests,
|
|
self,
|
|
publishers);
|
|
BEAST_EXPECT(trustedKeys->getListThreshold() == 3);
|
|
int untrustedCount = 0;
|
|
for (auto const& p : publishers)
|
|
{
|
|
bool const trusted = trustedKeys->trustedPublisher(p.pubKey);
|
|
BEAST_EXPECT(p.revoked ^ trusted);
|
|
untrustedCount += trusted ? 0 : 1;
|
|
}
|
|
BEAST_EXPECT(untrustedCount == 2);
|
|
|
|
TrustChanges changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(trustedKeys->quorum() == std::ceil(keysTotal * 0.8f));
|
|
BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal);
|
|
|
|
hash_set<NodeID> added;
|
|
added.insert(calcNodeID(self.masterPublic));
|
|
for (auto const& val : valKeys)
|
|
{
|
|
BEAST_EXPECT(trustedKeys->trusted(val.masterPublic));
|
|
added.insert(calcNodeID(val.masterPublic));
|
|
}
|
|
BEAST_EXPECT(changes.added == added);
|
|
BEAST_EXPECT(changes.removed.empty());
|
|
|
|
// Expire one publisher - no quorum, only trusted validator is self
|
|
env.timeKeeper().set(publishers.back().expiry);
|
|
changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled);
|
|
BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == 1);
|
|
|
|
hash_set<NodeID> removed;
|
|
BEAST_EXPECT(trustedKeys->trusted(self.masterPublic));
|
|
for (auto const& val : valKeys)
|
|
{
|
|
BEAST_EXPECT(trustedKeys->listed(val.masterPublic));
|
|
BEAST_EXPECT(!trustedKeys->trusted(val.masterPublic));
|
|
removed.insert(calcNodeID(val.masterPublic));
|
|
}
|
|
BEAST_EXPECT(changes.added.empty());
|
|
BEAST_EXPECT(changes.removed == removed);
|
|
}
|
|
{
|
|
// List threshold = 3 (default), 2 publishers are revoked
|
|
ManifestCache pubManifests;
|
|
ManifestCache valManifests;
|
|
std::vector<Publisher> publishers;
|
|
// Self is in UNL
|
|
auto const self = valKeys[5];
|
|
auto const keysTotal = valKeys.size();
|
|
auto trustedKeys = makeValidatorList(
|
|
5, //
|
|
2,
|
|
0,
|
|
pubManifests,
|
|
valManifests,
|
|
self,
|
|
publishers);
|
|
BEAST_EXPECT(trustedKeys->getListThreshold() == 3);
|
|
int untrustedCount = 0;
|
|
for (auto const& p : publishers)
|
|
{
|
|
bool const trusted = trustedKeys->trustedPublisher(p.pubKey);
|
|
BEAST_EXPECT(p.revoked ^ trusted);
|
|
untrustedCount += trusted ? 0 : 1;
|
|
}
|
|
BEAST_EXPECT(untrustedCount == 2);
|
|
|
|
TrustChanges changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(trustedKeys->quorum() == std::ceil(keysTotal * 0.8f));
|
|
BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal);
|
|
|
|
hash_set<NodeID> added;
|
|
for (auto const& val : valKeys)
|
|
{
|
|
BEAST_EXPECT(trustedKeys->trusted(val.masterPublic));
|
|
added.insert(calcNodeID(val.masterPublic));
|
|
}
|
|
BEAST_EXPECT(changes.added == added);
|
|
BEAST_EXPECT(changes.removed.empty());
|
|
|
|
// Expire one publisher - no quorum, only trusted validator is self
|
|
env.timeKeeper().set(publishers.back().expiry);
|
|
changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled);
|
|
BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == 1);
|
|
|
|
hash_set<NodeID> removed;
|
|
BEAST_EXPECT(trustedKeys->trusted(self.masterPublic));
|
|
for (auto const& val : valKeys)
|
|
{
|
|
BEAST_EXPECT(trustedKeys->listed(val.masterPublic));
|
|
if (val.masterPublic != self.masterPublic)
|
|
{
|
|
BEAST_EXPECT(!trustedKeys->trusted(val.masterPublic));
|
|
removed.insert(calcNodeID(val.masterPublic));
|
|
}
|
|
}
|
|
BEAST_EXPECT(changes.added.empty());
|
|
BEAST_EXPECT(changes.removed == removed);
|
|
}
|
|
{
|
|
// List threshold = 3 (default), 2 publishers are revoked
|
|
ManifestCache pubManifests;
|
|
ManifestCache valManifests;
|
|
std::vector<Publisher> publishers;
|
|
auto const keysTotal = valKeys.size();
|
|
auto trustedKeys = makeValidatorList(
|
|
5, //
|
|
2,
|
|
0,
|
|
pubManifests,
|
|
valManifests,
|
|
{},
|
|
publishers);
|
|
BEAST_EXPECT(trustedKeys->getListThreshold() == 3);
|
|
int untrustedCount = 0;
|
|
for (auto const& p : publishers)
|
|
{
|
|
bool const trusted = trustedKeys->trustedPublisher(p.pubKey);
|
|
BEAST_EXPECT(p.revoked ^ trusted);
|
|
untrustedCount += trusted ? 0 : 1;
|
|
}
|
|
BEAST_EXPECT(untrustedCount == 2);
|
|
|
|
TrustChanges changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(trustedKeys->quorum() == std::ceil(keysTotal * 0.8f));
|
|
BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal);
|
|
|
|
hash_set<NodeID> added;
|
|
for (auto const& val : valKeys)
|
|
{
|
|
BEAST_EXPECT(trustedKeys->trusted(val.masterPublic));
|
|
added.insert(calcNodeID(val.masterPublic));
|
|
}
|
|
BEAST_EXPECT(changes.added == added);
|
|
BEAST_EXPECT(changes.removed.empty());
|
|
|
|
// Expire one publisher - no quorum, no trusted validators
|
|
env.timeKeeper().set(publishers.back().expiry);
|
|
changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled);
|
|
BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().empty());
|
|
|
|
hash_set<NodeID> removed;
|
|
for (auto const& val : valKeys)
|
|
{
|
|
BEAST_EXPECT(trustedKeys->listed(val.masterPublic));
|
|
BEAST_EXPECT(!trustedKeys->trusted(val.masterPublic));
|
|
removed.insert(calcNodeID(val.masterPublic));
|
|
}
|
|
BEAST_EXPECT(changes.added.empty());
|
|
BEAST_EXPECT(changes.removed == removed);
|
|
}
|
|
{
|
|
// List threshold = 2, 1 publisher is revoked
|
|
ManifestCache pubManifests;
|
|
ManifestCache valManifests;
|
|
std::vector<Publisher> publishers;
|
|
// Self is a random validator
|
|
auto const self = randomValidator();
|
|
auto const keysTotal = valKeys.size() + 1;
|
|
auto trustedKeys = makeValidatorList(
|
|
5, //
|
|
1,
|
|
2,
|
|
pubManifests,
|
|
valManifests,
|
|
self,
|
|
publishers);
|
|
BEAST_EXPECT(trustedKeys->getListThreshold() == 2);
|
|
int untrustedCount = 0;
|
|
for (auto const& p : publishers)
|
|
{
|
|
bool const trusted = trustedKeys->trustedPublisher(p.pubKey);
|
|
BEAST_EXPECT(p.revoked ^ trusted);
|
|
untrustedCount += trusted ? 0 : 1;
|
|
}
|
|
BEAST_EXPECT(untrustedCount == 1);
|
|
|
|
TrustChanges changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(trustedKeys->quorum() == std::ceil(keysTotal * 0.8f));
|
|
BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal);
|
|
|
|
hash_set<NodeID> added;
|
|
added.insert(calcNodeID(self.masterPublic));
|
|
for (auto const& val : valKeys)
|
|
{
|
|
BEAST_EXPECT(trustedKeys->trusted(val.masterPublic));
|
|
added.insert(calcNodeID(val.masterPublic));
|
|
}
|
|
BEAST_EXPECT(changes.added == added);
|
|
BEAST_EXPECT(changes.removed.empty());
|
|
|
|
// Expire one publisher - no quorum
|
|
env.timeKeeper().set(publishers.back().expiry);
|
|
changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled);
|
|
BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal);
|
|
|
|
BEAST_EXPECT(trustedKeys->trusted(self.masterPublic));
|
|
for (auto const& val : valKeys)
|
|
{
|
|
BEAST_EXPECT(trustedKeys->listed(val.masterPublic));
|
|
BEAST_EXPECT(trustedKeys->trusted(val.masterPublic));
|
|
}
|
|
BEAST_EXPECT(changes.added.empty());
|
|
BEAST_EXPECT(changes.removed.empty());
|
|
}
|
|
{
|
|
// List threshold = 1
|
|
ManifestCache pubManifests;
|
|
ManifestCache valManifests;
|
|
std::vector<Publisher> publishers;
|
|
// Self is a random validator
|
|
auto const self = randomValidator();
|
|
auto const keysTotal = valKeys.size() + 1;
|
|
auto trustedKeys = makeValidatorList(
|
|
5, //
|
|
0,
|
|
1,
|
|
pubManifests,
|
|
valManifests,
|
|
self,
|
|
publishers);
|
|
BEAST_EXPECT(trustedKeys->getListThreshold() == 1);
|
|
for (auto const& p : publishers)
|
|
BEAST_EXPECT(trustedKeys->trustedPublisher(p.pubKey));
|
|
|
|
TrustChanges changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(trustedKeys->quorum() == std::ceil(keysTotal * 0.8f));
|
|
BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal);
|
|
|
|
hash_set<NodeID> added;
|
|
added.insert(calcNodeID(self.masterPublic));
|
|
for (auto const& val : valKeys)
|
|
{
|
|
BEAST_EXPECT(trustedKeys->trusted(val.masterPublic));
|
|
added.insert(calcNodeID(val.masterPublic));
|
|
}
|
|
BEAST_EXPECT(changes.added == added);
|
|
BEAST_EXPECT(changes.removed.empty());
|
|
|
|
// Expire one publisher - no quorum
|
|
env.timeKeeper().set(publishers.back().expiry);
|
|
changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled);
|
|
BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal);
|
|
|
|
BEAST_EXPECT(trustedKeys->trusted(self.masterPublic));
|
|
for (auto const& val : valKeys)
|
|
{
|
|
BEAST_EXPECT(trustedKeys->listed(val.masterPublic));
|
|
BEAST_EXPECT(trustedKeys->trusted(val.masterPublic));
|
|
}
|
|
BEAST_EXPECT(changes.added.empty());
|
|
BEAST_EXPECT(changes.removed.empty());
|
|
}
|
|
{
|
|
// List threshold = 1
|
|
ManifestCache pubManifests;
|
|
ManifestCache valManifests;
|
|
std::vector<Publisher> publishers;
|
|
// Self is in UNL
|
|
auto const self = valKeys[7];
|
|
auto const keysTotal = valKeys.size();
|
|
auto trustedKeys = makeValidatorList(
|
|
5, //
|
|
0,
|
|
1,
|
|
pubManifests,
|
|
valManifests,
|
|
self,
|
|
publishers);
|
|
BEAST_EXPECT(trustedKeys->getListThreshold() == 1);
|
|
for (auto const& p : publishers)
|
|
BEAST_EXPECT(trustedKeys->trustedPublisher(p.pubKey));
|
|
|
|
TrustChanges changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(trustedKeys->quorum() == std::ceil(keysTotal * 0.8f));
|
|
BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal);
|
|
|
|
hash_set<NodeID> added;
|
|
for (auto const& val : valKeys)
|
|
{
|
|
BEAST_EXPECT(trustedKeys->trusted(val.masterPublic));
|
|
added.insert(calcNodeID(val.masterPublic));
|
|
}
|
|
BEAST_EXPECT(changes.added == added);
|
|
BEAST_EXPECT(changes.removed.empty());
|
|
|
|
// Expire one publisher - no quorum
|
|
env.timeKeeper().set(publishers.back().expiry);
|
|
changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled);
|
|
BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal);
|
|
|
|
BEAST_EXPECT(trustedKeys->trusted(self.masterPublic));
|
|
for (auto const& val : valKeys)
|
|
{
|
|
BEAST_EXPECT(trustedKeys->listed(val.masterPublic));
|
|
BEAST_EXPECT(trustedKeys->trusted(val.masterPublic));
|
|
}
|
|
BEAST_EXPECT(changes.added.empty());
|
|
BEAST_EXPECT(changes.removed.empty());
|
|
}
|
|
{
|
|
// List threshold = 1
|
|
ManifestCache pubManifests;
|
|
ManifestCache valManifests;
|
|
std::vector<Publisher> publishers;
|
|
auto const keysTotal = valKeys.size();
|
|
auto trustedKeys = makeValidatorList(
|
|
5, //
|
|
0,
|
|
1,
|
|
pubManifests,
|
|
valManifests,
|
|
{},
|
|
publishers);
|
|
BEAST_EXPECT(trustedKeys->getListThreshold() == 1);
|
|
for (auto const& p : publishers)
|
|
BEAST_EXPECT(trustedKeys->trustedPublisher(p.pubKey));
|
|
|
|
TrustChanges changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(trustedKeys->quorum() == std::ceil(keysTotal * 0.8f));
|
|
BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal);
|
|
|
|
hash_set<NodeID> added;
|
|
for (auto const& val : valKeys)
|
|
{
|
|
BEAST_EXPECT(trustedKeys->trusted(val.masterPublic));
|
|
added.insert(calcNodeID(val.masterPublic));
|
|
}
|
|
BEAST_EXPECT(changes.added == added);
|
|
BEAST_EXPECT(changes.removed.empty());
|
|
|
|
// Expire one publisher - no quorum
|
|
env.timeKeeper().set(publishers.back().expiry);
|
|
changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled);
|
|
BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal);
|
|
|
|
for (auto const& val : valKeys)
|
|
{
|
|
BEAST_EXPECT(trustedKeys->listed(val.masterPublic));
|
|
BEAST_EXPECT(trustedKeys->trusted(val.masterPublic));
|
|
}
|
|
BEAST_EXPECT(changes.added.empty());
|
|
BEAST_EXPECT(changes.removed.empty());
|
|
}
|
|
|
|
// Test cases use 2 publishers
|
|
{
|
|
// List threshold = 1, 1 publisher revoked
|
|
ManifestCache pubManifests;
|
|
ManifestCache valManifests;
|
|
std::vector<Publisher> publishers;
|
|
// Self is a random validator
|
|
auto const self = randomValidator();
|
|
auto const keysTotal = valKeys.size() + 1;
|
|
auto trustedKeys = makeValidatorList(
|
|
2, //
|
|
1,
|
|
1,
|
|
pubManifests,
|
|
valManifests,
|
|
self,
|
|
publishers);
|
|
BEAST_EXPECT(trustedKeys->getListThreshold() == 1);
|
|
int untrustedCount = 0;
|
|
for (auto const& p : publishers)
|
|
{
|
|
bool const trusted = trustedKeys->trustedPublisher(p.pubKey);
|
|
BEAST_EXPECT(p.revoked ^ trusted);
|
|
untrustedCount += trusted ? 0 : 1;
|
|
}
|
|
BEAST_EXPECT(untrustedCount == 1);
|
|
|
|
TrustChanges changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled);
|
|
BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal);
|
|
|
|
hash_set<NodeID> added;
|
|
added.insert(calcNodeID(self.masterPublic));
|
|
for (auto const& val : valKeys)
|
|
{
|
|
BEAST_EXPECT(trustedKeys->trusted(val.masterPublic));
|
|
added.insert(calcNodeID(val.masterPublic));
|
|
}
|
|
BEAST_EXPECT(changes.added == added);
|
|
BEAST_EXPECT(changes.removed.empty());
|
|
|
|
// Expire one publisher - no quorum, only trusted validator is self
|
|
env.timeKeeper().set(publishers.back().expiry);
|
|
changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled);
|
|
BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == 1);
|
|
|
|
hash_set<NodeID> removed;
|
|
BEAST_EXPECT(trustedKeys->trusted(self.masterPublic));
|
|
for (auto const& val : valKeys)
|
|
{
|
|
BEAST_EXPECT(!trustedKeys->listed(val.masterPublic));
|
|
BEAST_EXPECT(!trustedKeys->trusted(val.masterPublic));
|
|
removed.insert(calcNodeID(val.masterPublic));
|
|
}
|
|
BEAST_EXPECT(changes.added.empty());
|
|
BEAST_EXPECT(changes.removed == removed);
|
|
}
|
|
{
|
|
// List threshold = 1, 1 publisher revoked
|
|
ManifestCache pubManifests;
|
|
ManifestCache valManifests;
|
|
std::vector<Publisher> publishers;
|
|
// Self is in UNL
|
|
auto const self = valKeys[5];
|
|
auto const keysTotal = valKeys.size();
|
|
auto trustedKeys = makeValidatorList(
|
|
2, //
|
|
1,
|
|
1,
|
|
pubManifests,
|
|
valManifests,
|
|
self,
|
|
publishers);
|
|
BEAST_EXPECT(trustedKeys->getListThreshold() == 1);
|
|
int untrustedCount = 0;
|
|
for (auto const& p : publishers)
|
|
{
|
|
bool const trusted = trustedKeys->trustedPublisher(p.pubKey);
|
|
BEAST_EXPECT(p.revoked ^ trusted);
|
|
untrustedCount += trusted ? 0 : 1;
|
|
}
|
|
BEAST_EXPECT(untrustedCount == 1);
|
|
|
|
TrustChanges changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled);
|
|
BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal);
|
|
|
|
hash_set<NodeID> added;
|
|
for (auto const& val : valKeys)
|
|
{
|
|
BEAST_EXPECT(trustedKeys->trusted(val.masterPublic));
|
|
added.insert(calcNodeID(val.masterPublic));
|
|
}
|
|
BEAST_EXPECT(changes.added == added);
|
|
BEAST_EXPECT(changes.removed.empty());
|
|
|
|
// Expire one publisher - no quorum, only trusted validator is self
|
|
env.timeKeeper().set(publishers.back().expiry);
|
|
changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled);
|
|
BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == 1);
|
|
|
|
hash_set<NodeID> removed;
|
|
BEAST_EXPECT(trustedKeys->trusted(self.masterPublic));
|
|
for (auto const& val : valKeys)
|
|
{
|
|
if (val.masterPublic != self.masterPublic)
|
|
{
|
|
BEAST_EXPECT(!trustedKeys->listed(val.masterPublic));
|
|
BEAST_EXPECT(!trustedKeys->trusted(val.masterPublic));
|
|
removed.insert(calcNodeID(val.masterPublic));
|
|
}
|
|
}
|
|
BEAST_EXPECT(changes.added.empty());
|
|
BEAST_EXPECT(changes.removed == removed);
|
|
}
|
|
{
|
|
// List threshold = 1, 1 publisher revoked
|
|
ManifestCache pubManifests;
|
|
ManifestCache valManifests;
|
|
std::vector<Publisher> publishers;
|
|
auto const keysTotal = valKeys.size();
|
|
auto trustedKeys = makeValidatorList(
|
|
2, //
|
|
1,
|
|
1,
|
|
pubManifests,
|
|
valManifests,
|
|
{},
|
|
publishers);
|
|
BEAST_EXPECT(trustedKeys->getListThreshold() == 1);
|
|
int untrustedCount = 0;
|
|
for (auto const& p : publishers)
|
|
{
|
|
bool const trusted = trustedKeys->trustedPublisher(p.pubKey);
|
|
BEAST_EXPECT(p.revoked ^ trusted);
|
|
untrustedCount += trusted ? 0 : 1;
|
|
}
|
|
BEAST_EXPECT(untrustedCount == 1);
|
|
|
|
TrustChanges changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled);
|
|
BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal);
|
|
|
|
hash_set<NodeID> added;
|
|
for (auto const& val : valKeys)
|
|
{
|
|
BEAST_EXPECT(trustedKeys->trusted(val.masterPublic));
|
|
added.insert(calcNodeID(val.masterPublic));
|
|
}
|
|
BEAST_EXPECT(changes.added == added);
|
|
BEAST_EXPECT(changes.removed.empty());
|
|
|
|
// Expire one publisher - no quorum, no trusted validators
|
|
env.timeKeeper().set(publishers.back().expiry);
|
|
changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled);
|
|
BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().empty());
|
|
|
|
hash_set<NodeID> removed;
|
|
for (auto const& val : valKeys)
|
|
{
|
|
BEAST_EXPECT(!trustedKeys->listed(val.masterPublic));
|
|
BEAST_EXPECT(!trustedKeys->trusted(val.masterPublic));
|
|
removed.insert(calcNodeID(val.masterPublic));
|
|
}
|
|
BEAST_EXPECT(changes.added.empty());
|
|
BEAST_EXPECT(changes.removed == removed);
|
|
}
|
|
{
|
|
// List threshold = 2 (same as number of trusted publishers)
|
|
ManifestCache pubManifests;
|
|
ManifestCache valManifests;
|
|
std::vector<Publisher> publishers;
|
|
// Self is a random validator
|
|
auto const self = randomValidator();
|
|
auto const keysTotal = valKeys.size() + 1;
|
|
auto trustedKeys = makeValidatorList(
|
|
2, //
|
|
0,
|
|
2,
|
|
pubManifests,
|
|
valManifests,
|
|
self,
|
|
publishers);
|
|
BEAST_EXPECT(trustedKeys->getListThreshold() == 2);
|
|
for (auto const& p : publishers)
|
|
BEAST_EXPECT(trustedKeys->trustedPublisher(p.pubKey));
|
|
|
|
TrustChanges changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(trustedKeys->quorum() == std::ceil(keysTotal * 0.8f));
|
|
BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal);
|
|
|
|
hash_set<NodeID> added;
|
|
added.insert(calcNodeID(self.masterPublic));
|
|
for (auto const& val : valKeys)
|
|
{
|
|
BEAST_EXPECT(trustedKeys->trusted(val.masterPublic));
|
|
added.insert(calcNodeID(val.masterPublic));
|
|
}
|
|
BEAST_EXPECT(changes.added == added);
|
|
BEAST_EXPECT(changes.removed.empty());
|
|
|
|
// Expire one publisher - only trusted validator is self
|
|
env.timeKeeper().set(publishers.back().expiry);
|
|
changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled);
|
|
BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == 1);
|
|
|
|
hash_set<NodeID> removed;
|
|
BEAST_EXPECT(trustedKeys->trusted(self.masterPublic));
|
|
for (auto const& val : valKeys)
|
|
{
|
|
BEAST_EXPECT(trustedKeys->listed(val.masterPublic));
|
|
BEAST_EXPECT(!trustedKeys->trusted(val.masterPublic));
|
|
removed.insert(calcNodeID(val.masterPublic));
|
|
}
|
|
BEAST_EXPECT(changes.added.empty());
|
|
BEAST_EXPECT(changes.removed == removed);
|
|
}
|
|
{
|
|
// List threshold = 2 (same as number of trusted publishers)
|
|
ManifestCache pubManifests;
|
|
ManifestCache valManifests;
|
|
std::vector<Publisher> publishers;
|
|
// Self is in UNL
|
|
auto const self = valKeys[5];
|
|
auto const keysTotal = valKeys.size();
|
|
auto trustedKeys = makeValidatorList(
|
|
2, //
|
|
0,
|
|
2,
|
|
pubManifests,
|
|
valManifests,
|
|
self,
|
|
publishers);
|
|
BEAST_EXPECT(trustedKeys->getListThreshold() == 2);
|
|
for (auto const& p : publishers)
|
|
BEAST_EXPECT(trustedKeys->trustedPublisher(p.pubKey));
|
|
|
|
TrustChanges changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(trustedKeys->quorum() == std::ceil(keysTotal * 0.8f));
|
|
BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal);
|
|
|
|
hash_set<NodeID> added;
|
|
added.insert(calcNodeID(self.masterPublic));
|
|
for (auto const& val : valKeys)
|
|
{
|
|
BEAST_EXPECT(trustedKeys->trusted(val.masterPublic));
|
|
added.insert(calcNodeID(val.masterPublic));
|
|
}
|
|
BEAST_EXPECT(changes.added == added);
|
|
BEAST_EXPECT(changes.removed.empty());
|
|
|
|
// Expire one publisher - only trusted validator is self
|
|
env.timeKeeper().set(publishers.back().expiry);
|
|
changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled);
|
|
BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == 1);
|
|
|
|
hash_set<NodeID> removed;
|
|
BEAST_EXPECT(trustedKeys->trusted(self.masterPublic));
|
|
for (auto const& val : valKeys)
|
|
{
|
|
if (val.masterPublic != self.masterPublic)
|
|
{
|
|
BEAST_EXPECT(trustedKeys->listed(val.masterPublic));
|
|
BEAST_EXPECT(!trustedKeys->trusted(val.masterPublic));
|
|
removed.insert(calcNodeID(val.masterPublic));
|
|
}
|
|
}
|
|
BEAST_EXPECT(changes.added.empty());
|
|
BEAST_EXPECT(changes.removed == removed);
|
|
}
|
|
{
|
|
// List threshold = 2 (same as number of trusted publishers)
|
|
ManifestCache pubManifests;
|
|
ManifestCache valManifests;
|
|
std::vector<Publisher> publishers;
|
|
auto const keysTotal = valKeys.size();
|
|
auto trustedKeys = makeValidatorList(
|
|
2, //
|
|
0,
|
|
2,
|
|
pubManifests,
|
|
valManifests,
|
|
{},
|
|
publishers);
|
|
BEAST_EXPECT(trustedKeys->getListThreshold() == 2);
|
|
for (auto const& p : publishers)
|
|
BEAST_EXPECT(trustedKeys->trustedPublisher(p.pubKey));
|
|
|
|
TrustChanges changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(trustedKeys->quorum() == std::ceil(keysTotal * 0.8f));
|
|
BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal);
|
|
|
|
hash_set<NodeID> added;
|
|
for (auto const& val : valKeys)
|
|
{
|
|
BEAST_EXPECT(trustedKeys->trusted(val.masterPublic));
|
|
added.insert(calcNodeID(val.masterPublic));
|
|
}
|
|
BEAST_EXPECT(changes.added == added);
|
|
BEAST_EXPECT(changes.removed.empty());
|
|
|
|
// Expire one publisher - no trusted validators
|
|
env.timeKeeper().set(publishers.back().expiry);
|
|
changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled);
|
|
BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().empty());
|
|
|
|
hash_set<NodeID> removed;
|
|
for (auto const& val : valKeys)
|
|
{
|
|
BEAST_EXPECT(trustedKeys->listed(val.masterPublic));
|
|
BEAST_EXPECT(!trustedKeys->trusted(val.masterPublic));
|
|
removed.insert(calcNodeID(val.masterPublic));
|
|
}
|
|
BEAST_EXPECT(changes.added.empty());
|
|
BEAST_EXPECT(changes.removed == removed);
|
|
}
|
|
|
|
// Test case for 1 publisher
|
|
{
|
|
// List threshold = 1 (default), no publisher revoked
|
|
ManifestCache pubManifests;
|
|
ManifestCache valManifests;
|
|
std::vector<Publisher> publishers;
|
|
// Self is a random validator
|
|
auto const self = randomValidator();
|
|
auto const keysTotal = valKeys.size() + 1;
|
|
auto trustedKeys = makeValidatorList(
|
|
1, //
|
|
0,
|
|
0,
|
|
pubManifests,
|
|
valManifests,
|
|
self,
|
|
publishers);
|
|
BEAST_EXPECT(trustedKeys->getListThreshold() == 1);
|
|
for (auto const& p : publishers)
|
|
BEAST_EXPECT(trustedKeys->trustedPublisher(p.pubKey));
|
|
|
|
TrustChanges changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(trustedKeys->quorum() == std::ceil(keysTotal * 0.8f));
|
|
BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal);
|
|
|
|
hash_set<NodeID> added;
|
|
added.insert(calcNodeID(self.masterPublic));
|
|
for (auto const& val : valKeys)
|
|
{
|
|
BEAST_EXPECT(trustedKeys->trusted(val.masterPublic));
|
|
added.insert(calcNodeID(val.masterPublic));
|
|
}
|
|
BEAST_EXPECT(changes.added == added);
|
|
BEAST_EXPECT(changes.removed.empty());
|
|
|
|
// Expire one publisher - no quorum, only trusted validator is self
|
|
env.timeKeeper().set(publishers.back().expiry);
|
|
changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled);
|
|
BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == 1);
|
|
|
|
hash_set<NodeID> removed;
|
|
BEAST_EXPECT(trustedKeys->trusted(self.masterPublic));
|
|
for (auto const& val : valKeys)
|
|
{
|
|
BEAST_EXPECT(!trustedKeys->listed(val.masterPublic));
|
|
BEAST_EXPECT(!trustedKeys->trusted(val.masterPublic));
|
|
removed.insert(calcNodeID(val.masterPublic));
|
|
}
|
|
BEAST_EXPECT(changes.added.empty());
|
|
BEAST_EXPECT(changes.removed == removed);
|
|
}
|
|
|
|
// Test case for 3 publishers (for 2 is a block above)
|
|
{
|
|
// List threshold = 2 (default), no publisher revoked
|
|
ManifestCache pubManifests;
|
|
ManifestCache valManifests;
|
|
std::vector<Publisher> publishers;
|
|
// Self is in UNL
|
|
auto const self = valKeys[2];
|
|
auto const keysTotal = valKeys.size();
|
|
auto trustedKeys = makeValidatorList(
|
|
3, //
|
|
0,
|
|
0,
|
|
pubManifests,
|
|
valManifests,
|
|
self,
|
|
publishers);
|
|
BEAST_EXPECT(trustedKeys->getListThreshold() == 2);
|
|
for (auto const& p : publishers)
|
|
BEAST_EXPECT(trustedKeys->trustedPublisher(p.pubKey));
|
|
|
|
TrustChanges const changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(trustedKeys->quorum() == std::ceil(keysTotal * 0.8f));
|
|
BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal);
|
|
|
|
hash_set<NodeID> added;
|
|
for (auto const& val : valKeys)
|
|
{
|
|
BEAST_EXPECT(trustedKeys->trusted(val.masterPublic));
|
|
added.insert(calcNodeID(val.masterPublic));
|
|
}
|
|
BEAST_EXPECT(changes.added == added);
|
|
BEAST_EXPECT(changes.removed.empty());
|
|
}
|
|
|
|
// Test case for 4 publishers
|
|
{
|
|
// List threshold = 3 (default), no publisher revoked
|
|
ManifestCache pubManifests;
|
|
ManifestCache valManifests;
|
|
std::vector<Publisher> publishers;
|
|
auto const keysTotal = valKeys.size();
|
|
auto trustedKeys = makeValidatorList(
|
|
4, //
|
|
0,
|
|
0,
|
|
pubManifests,
|
|
valManifests,
|
|
{},
|
|
publishers);
|
|
BEAST_EXPECT(trustedKeys->getListThreshold() == 3);
|
|
for (auto const& p : publishers)
|
|
BEAST_EXPECT(trustedKeys->trustedPublisher(p.pubKey));
|
|
|
|
TrustChanges const changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(trustedKeys->quorum() == std::ceil(keysTotal * 0.8f));
|
|
BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal);
|
|
|
|
hash_set<NodeID> added;
|
|
for (auto const& val : valKeys)
|
|
{
|
|
BEAST_EXPECT(trustedKeys->trusted(val.masterPublic));
|
|
added.insert(calcNodeID(val.masterPublic));
|
|
}
|
|
BEAST_EXPECT(changes.added == added);
|
|
BEAST_EXPECT(changes.removed.empty());
|
|
}
|
|
|
|
// Test case for 6 publishers (for 5 is a large block above)
|
|
{
|
|
// List threshold = 4 (default), 2 publishers revoked
|
|
ManifestCache pubManifests;
|
|
ManifestCache valManifests;
|
|
std::vector<Publisher> publishers;
|
|
// Self is a random validator
|
|
auto const self = randomValidator();
|
|
auto const keysTotal = valKeys.size() + 1;
|
|
auto trustedKeys = makeValidatorList(
|
|
6, //
|
|
2,
|
|
0,
|
|
pubManifests,
|
|
valManifests,
|
|
self,
|
|
publishers);
|
|
BEAST_EXPECT(trustedKeys->getListThreshold() == 4);
|
|
int untrustedCount = 0;
|
|
for (auto const& p : publishers)
|
|
{
|
|
bool const trusted = trustedKeys->trustedPublisher(p.pubKey);
|
|
BEAST_EXPECT(p.revoked ^ trusted);
|
|
untrustedCount += trusted ? 0 : 1;
|
|
}
|
|
BEAST_EXPECT(untrustedCount == 2);
|
|
|
|
TrustChanges changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(trustedKeys->quorum() == std::ceil(keysTotal * 0.8f));
|
|
BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal);
|
|
|
|
hash_set<NodeID> added;
|
|
added.insert(calcNodeID(self.masterPublic));
|
|
for (auto const& val : valKeys)
|
|
{
|
|
BEAST_EXPECT(trustedKeys->trusted(val.masterPublic));
|
|
added.insert(calcNodeID(val.masterPublic));
|
|
}
|
|
BEAST_EXPECT(changes.added == added);
|
|
BEAST_EXPECT(changes.removed.empty());
|
|
|
|
// Expire one publisher - no quorum, only trusted validator is self
|
|
env.timeKeeper().set(publishers.back().expiry);
|
|
changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled);
|
|
BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == 1);
|
|
|
|
hash_set<NodeID> removed;
|
|
BEAST_EXPECT(trustedKeys->trusted(self.masterPublic));
|
|
for (auto const& val : valKeys)
|
|
{
|
|
BEAST_EXPECT(trustedKeys->listed(val.masterPublic));
|
|
BEAST_EXPECT(!trustedKeys->trusted(val.masterPublic));
|
|
removed.insert(calcNodeID(val.masterPublic));
|
|
}
|
|
BEAST_EXPECT(changes.added.empty());
|
|
BEAST_EXPECT(changes.removed == removed);
|
|
}
|
|
|
|
// Test case for 7 publishers
|
|
{
|
|
// List threshold = 4 (default), 3 publishers revoked
|
|
ManifestCache pubManifests;
|
|
ManifestCache valManifests;
|
|
std::vector<Publisher> publishers;
|
|
// Self is in UNL
|
|
auto const self = valKeys[2];
|
|
auto const keysTotal = valKeys.size();
|
|
auto trustedKeys = makeValidatorList(
|
|
7, //
|
|
3,
|
|
0,
|
|
pubManifests,
|
|
valManifests,
|
|
self,
|
|
publishers);
|
|
BEAST_EXPECT(trustedKeys->getListThreshold() == 4);
|
|
int untrustedCount = 0;
|
|
for (auto const& p : publishers)
|
|
{
|
|
bool const trusted = trustedKeys->trustedPublisher(p.pubKey);
|
|
BEAST_EXPECT(p.revoked ^ trusted);
|
|
untrustedCount += trusted ? 0 : 1;
|
|
}
|
|
BEAST_EXPECT(untrustedCount == 3);
|
|
|
|
TrustChanges changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(trustedKeys->quorum() == std::ceil(keysTotal * 0.8f));
|
|
BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal);
|
|
|
|
hash_set<NodeID> added;
|
|
for (auto const& val : valKeys)
|
|
{
|
|
BEAST_EXPECT(trustedKeys->trusted(val.masterPublic));
|
|
added.insert(calcNodeID(val.masterPublic));
|
|
}
|
|
BEAST_EXPECT(changes.added == added);
|
|
BEAST_EXPECT(changes.removed.empty());
|
|
|
|
// Expire one publisher - only trusted validator is self
|
|
env.timeKeeper().set(publishers.back().expiry);
|
|
changes = trustedKeys->updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().getOverlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled);
|
|
BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == 1);
|
|
|
|
hash_set<NodeID> removed;
|
|
BEAST_EXPECT(trustedKeys->trusted(self.masterPublic));
|
|
for (auto const& val : valKeys)
|
|
{
|
|
if (val.masterPublic != self.masterPublic)
|
|
{
|
|
BEAST_EXPECT(trustedKeys->listed(val.masterPublic));
|
|
BEAST_EXPECT(!trustedKeys->trusted(val.masterPublic));
|
|
removed.insert(calcNodeID(val.masterPublic));
|
|
}
|
|
}
|
|
BEAST_EXPECT(changes.added.empty());
|
|
BEAST_EXPECT(changes.removed == removed);
|
|
}
|
|
}
|
|
|
|
public:
|
|
void
|
|
run() override
|
|
{
|
|
testGenesisQuorum();
|
|
testConfigLoad();
|
|
testApplyLists();
|
|
testGetAvailable();
|
|
testUpdateTrusted();
|
|
testExpires();
|
|
testNegativeUNL();
|
|
testSha512Hash();
|
|
testBuildMessages();
|
|
testQuorumDisabled();
|
|
}
|
|
}; // namespace test
|
|
|
|
BEAST_DEFINE_TESTSUITE(ValidatorList, app, xrpl);
|
|
|
|
} // namespace xrpl::test
|