Files
rippled/src/test/app/ValidatorList_test.cpp
Scott Schurr 0839a202c9 Reduce console noise coming from unit tests:
A few unit tests have historically generated a lot of noise
to the console from log writes.  This noise was not useful
and made it harder to locate actual test failures.

By changing the log level of these tests from
- severities::kError to
- severities::kDisabled
it was possible to remove that noise coming from the logs.
2022-07-17 22:17:24 -07:00

2432 lines
92 KiB
C++

//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright 2015 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <ripple/app/misc/ValidatorList.h>
#include <ripple/basics/Slice.h>
#include <ripple/basics/base64.h>
#include <ripple/basics/strHex.h>
#include <ripple/overlay/impl/ProtocolMessage.h>
#include <ripple/protocol/HashPrefix.h>
#include <ripple/protocol/PublicKey.h>
#include <ripple/protocol/SecretKey.h>
#include <ripple/protocol/Sign.h>
#include <ripple/protocol/digest.h>
#include <ripple/protocol/jss.h>
#include <ripple/protocol/messages.h>
#include <test/jtx.h>
#include <boost/beast/core/multi_buffer.hpp>
namespace ripple {
namespace 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;
sign(st, HashPrefix::manifest, *publicKeyType(spk), ssk);
}
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;
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,
signingKeys.first,
base64_encode(makeManifestString(
masterPublic,
secret,
signingKeys.first,
signingKeys.second,
1))};
}
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);
}
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();
PublicKey emptyLocalKey;
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(
emptyLocalKey, emptyCfgKeys, emptyCfgPublishers));
// load local validator key with or without manifest
BEAST_EXPECT(trustedKeys->load(
localSigningPublicOuter, emptyCfgKeys, emptyCfgPublishers));
BEAST_EXPECT(trustedKeys->listed(localSigningPublicOuter));
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(emptyLocalKey, 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> cfgMasterKeys(
{format(masterNode1), format(masterNode2, " Comment")});
BEAST_EXPECT(trustedKeys->load(
emptyLocalKey, cfgMasterKeys, emptyCfgPublishers));
BEAST_EXPECT(trustedKeys->listed(masterNode1));
BEAST_EXPECT(trustedKeys->listed(masterNode2));
// load should reject invalid config keys
BEAST_EXPECT(!trustedKeys->load(
emptyLocalKey, {"NotAPublicKey"}, emptyCfgPublishers));
BEAST_EXPECT(!trustedKeys->load(
emptyLocalKey,
{format(randomNode(), "!")},
emptyCfgPublishers));
// load terminates when encountering an invalid entry
auto const goodKey = randomNode();
BEAST_EXPECT(!trustedKeys->load(
emptyLocalKey,
{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);
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);
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(emptyLocalKey, emptyCfgKeys, badPublishers));
// load should reject validator list signing keys with invalid
// encoding
std::vector<PublicKey> keys(
{randomMasterKey(), randomMasterKey(), randomMasterKey()});
badPublishers.clear();
for (auto const& key : keys)
badPublishers.push_back(toBase58(TokenType::NodePublic, key));
BEAST_EXPECT(
!trustedKeys->load(emptyLocalKey, 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;
for (auto const& key : keys)
cfgPublishers.push_back(strHex(key));
BEAST_EXPECT(
trustedKeys->load(emptyLocalKey, emptyCfgKeys, cfgPublishers));
for (auto const& key : keys)
BEAST_EXPECT(trustedKeys->trustedPublisher(key));
}
{
// 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
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 manifest cache at all.)
auto legitKey = randomMasterKey();
std::vector<std::string> cfgPublishers = {
strHex(pubRevokedPublic), strHex(legitKey)};
BEAST_EXPECT(
trustedKeys->load(emptyLocalKey, emptyCfgKeys, cfgPublishers));
BEAST_EXPECT(!trustedKeys->trustedPublisher(pubRevokedPublic));
BEAST_EXPECT(trustedKeys->trustedPublisher(legitKey));
}
}
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) {
const auto 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().timeKeeper(),
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);
const auto 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> cfgKeys1({strHex(publisherPublic)});
PublicKey emptyLocalKey;
std::vector<std::string> emptyCfgKeys;
BEAST_EXPECT(trustedKeys->load(emptyLocalKey, emptyCfgKeys, cfgKeys1));
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));
// 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().overlay(),
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().overlay(),
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().timeKeeper(),
app.config().legacy("database_path"),
env.journal);
auto const publisherSecret = randomSecretKey();
auto const publisherPublic =
derivePublicKey(KeyType::ed25519, publisherSecret);
const auto 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> cfgKeys1({strHex(publisherPublic)});
PublicKey emptyLocalKey;
std::vector<std::string> emptyCfgKeys;
BEAST_EXPECT(trustedKeys->load(emptyLocalKey, emptyCfgKeys, cfgKeys1));
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);
const auto 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))
{
auto const& a = *available;
BEAST_EXPECT(!a);
}
}
{
// bad version 3
auto const available = trustedKeys->getAvailable(hexPublic, 3);
if (BEAST_EXPECT(available))
{
auto const& a = *available;
BEAST_EXPECT(!a);
}
}
{
// version 1
auto const available = trustedKeys->getAvailable(hexPublic, 1);
if (BEAST_EXPECT(available))
{
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))
{
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";
PublicKey emptyLocalKeyOuter;
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> 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(
emptyLocalKeyOuter, 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().overlay(),
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().overlay(),
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> cfgKeys(
{toBase58(TokenType::NodePublic, masterPublic)});
BEAST_EXPECT(trustedKeysOuter->load(
emptyLocalKeyOuter, 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().overlay(),
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(
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(
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));
BEAST_EXPECT(mMax->revoked());
BEAST_EXPECT(
manifestsOuter.applyManifest(std::move(*mMax)) ==
ManifestDisposition::accepted);
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().overlay(),
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> cfgPublishers({strHex(publisherPublic)});
std::vector<std::string> emptyCfgKeys;
BEAST_EXPECT(trustedKeys->load(
emptyLocalKeyOuter, emptyCfgKeys, cfgPublishers));
TrustChanges changes = trustedKeys->updateTrusted(
activeValidatorsOuter,
env.timeKeeper().now(),
env.app().getOPs(),
env.app().overlay(),
env.app().getHashRouter());
BEAST_EXPECT(changes.removed.empty());
BEAST_EXPECT(changes.added.empty());
BEAST_EXPECT(
trustedKeys->quorum() ==
std::numeric_limits<std::size_t>::max());
}
{
// 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 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(
emptyLocalKeyOuter, cfgKeys, cfgPublishersOuter));
TrustChanges changes = trustedKeys->updateTrusted(
activeValidators,
env.timeKeeper().now(),
env.app().getOPs(),
env.app().overlay(),
env.app().getHashRouter());
BEAST_EXPECT(changes.removed.empty());
BEAST_EXPECT(changes.added == expectedTrusted);
BEAST_EXPECT(trustedKeys->quorum() == minQuorum);
// Use normal quorum when seen validators >= quorum
activeValidators.emplace(toBeSeen);
changes = trustedKeys->updateTrusted(
activeValidators,
env.timeKeeper().now(),
env.app().getOPs(),
env.app().overlay(),
env.app().getHashRouter());
BEAST_EXPECT(changes.removed.empty());
BEAST_EXPECT(changes.added.empty());
BEAST_EXPECT(trustedKeys->quorum() == std::ceil(n * 0.8f));
}
{
// Remove expired published list
auto trustedKeys = std::make_unique<ValidatorList>(
manifestsOuter,
manifestsOuter,
env.app().timeKeeper(),
app.config().legacy("database_path"),
env.journal);
PublicKey emptyLocalKey;
std::vector<std::string> 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> cfgKeys({strHex(publisherKeys.first)});
BEAST_EXPECT(
trustedKeys->load(emptyLocalKey, emptyCfgKeys, cfgKeys));
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().overlay(),
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().overlay(),
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().overlay(),
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> 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(
emptyLocalKeyOuter, cfgKeys, cfgPublishers));
TrustChanges changes = trustedKeys->updateTrusted(
activeValidators,
env.timeKeeper().now(),
env.app().getOPs(),
env.app().overlay(),
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> 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 changes = trustedKeys->updateTrusted(
activeValidators,
env.timeKeeper().now(),
env.app().getOPs(),
env.app().overlay(),
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));
}
auto addPublishedList = [this,
&env,
&trustedKeys,
&valKeys,
&siteUri]() {
auto const publisherSecret = randomSecretKey();
auto const publisherPublic =
derivePublicKey(KeyType::ed25519, publisherSecret);
auto const pubSigningKeys = randomKeyPair(KeyType::secp256k1);
auto const manifest = base64_encode(makeManifestString(
publisherPublic,
publisherSecret,
pubSigningKeys.first,
pubSigningKeys.second,
1));
std::vector<std::string> cfgPublishers(
{strHex(publisherPublic)});
PublicKey emptyLocalKey;
std::vector<std::string> emptyCfgKeys;
BEAST_EXPECT(trustedKeys->load(
emptyLocalKey, emptyCfgKeys, cfgPublishers));
auto const version = 1;
auto const sequence = 1;
using namespace std::chrono_literals;
NetClock::time_point const validUntil =
env.timeKeeper().now() + 3600s;
auto const blob = makeList(
valKeys, 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 < 3; ++i)
addPublishedList();
TrustChanges changes = trustedKeys->updateTrusted(
activeValidators,
env.timeKeeper().now(),
env.app().getOPs(),
env.app().overlay(),
env.app().getHashRouter());
BEAST_EXPECT(
trustedKeys->quorum() == std::ceil(valKeys.size() * 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());
}
}
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 emptyLocalKey;
PublicKey localCfgListed = randomNode();
trustedKeys->load(emptyLocalKey, {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().timeKeeper(),
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> cfgPublishers(
{strHex(publisherPublic)});
PublicKey emptyLocalKey;
std::vector<std::string> emptyCfgKeys;
BEAST_EXPECT(trustedKeys->load(
emptyLocalKey, 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,
manifest,
{{blob1, sig1, {}}, {blob2, sig2, {}}},
version,
{expiration1, expiration2}};
};
// Configure two publishers and prepare 2 lists
PreparedList prep1 = addPublishedList();
env.timeKeeper().set(env.timeKeeper().now() + 200s);
PreparedList 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().overlay(),
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().overlay(),
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);
PublicKey emptyLocalKey;
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> 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(emptyLocalKey, cfgKeys, cfgPublishers))
{
trustedKeys->updateTrusted(
activeValidators,
env.timeKeeper().now(),
env.app().getOPs(),
env.app().overlay(),
env.app().getHashRouter());
if (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> activeValidators;
//== Combinations ==
std::array<std::uint32_t, 4> unlSizes = {34, 35, 39, 60};
std::array<std::uint32_t, 4> 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 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().overlay(),
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.find(n) == nUnl.end())
return false;
}
validators->updateTrusted(
activeValidators,
env.timeKeeper().now(),
env.app().getOPs(),
env.app().overlay(),
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 ss = 33;
std::vector<uint8_t> data(ss, 0);
data[0] = 0xED;
for (int i = 0; i < 6; ++i)
{
Slice s(data.data(), ss);
data[1]++;
nUnl.emplace(s);
}
validators->setNegativeUNL(nUnl);
validators->updateTrusted(
activeValidators,
env.timeKeeper().now(),
env.app().getOPs(),
env.app().overlay(),
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().overlay(),
env.app().getHashRouter());
BEAST_EXPECT(validators->quorum() == 48);
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().overlay(),
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 ripple::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> 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::mtVALIDATORLIST))
{
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::mtVALIDATORLISTCOLLECTION))
{
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.size() == 0);
// 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);
BEAST_EXPECT(msg->manifest() == *expected.manifest);
BEAST_EXPECT(msg->blob() == expected.blob);
BEAST_EXPECT(msg->signature() == expected.signature);
}
BEAST_EXPECT(
messageWithHash.hash ==
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.size() == 0);
// 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}}});
}
public:
void
run() override
{
testGenesisQuorum();
testConfigLoad();
testApplyLists();
testGetAvailable();
testUpdateTrusted();
testExpires();
testNegativeUNL();
testSha512Hash();
testBuildMessages();
}
}; // namespace test
BEAST_DEFINE_TESTSUITE(ValidatorList, app, ripple);
} // namespace test
} // namespace ripple