Dynamize trusted validator list and quorum (RIPD-1220):

Instead of specifying a static list of trusted validators in the config
or validators file, the configuration can now include trusted validator
list publisher keys.

The trusted validator list and quorum are now reset each consensus
round using the latest validator lists and the list of recent
validations seen. The minimum validation quorum is now only
configurable via the command line.
This commit is contained in:
wilsonianb
2016-08-30 09:46:24 -07:00
committed by seelabs
parent 74977ab3db
commit e823e60ca0
42 changed files with 2482 additions and 1570 deletions

View File

@@ -402,7 +402,7 @@ public:
v->setFieldV256 (sfAmendments, field);
v->setTrusted();
validations [calcNodeID(val)] = v;
validations [val] = v;
}
ourVotes = table.doValidation (enabled);

View File

@@ -17,23 +17,25 @@
*/
//==============================================================================
#include <BeastConfig.h>
#include <ripple/app/misc/Manifest.h>
#include <ripple/app/misc/ValidatorList.h>
#include <ripple/basics/contract.h>
#include <ripple/basics/StringUtilities.h>
#include <test/jtx/TestSuite.h>
#include <ripple/overlay/impl/Manifest.h>
#include <test/jtx.h>
#include <ripple/core/DatabaseCon.h>
#include <ripple/app/main/DBInit.h>
#include <ripple/protocol/SecretKey.h>
#include <ripple/protocol/Sign.h>
#include <ripple/protocol/STExchange.h>
#include <beast/core/detail/base64.hpp>
#include <boost/filesystem.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/utility/in_place_factory.hpp>
namespace ripple {
namespace tests {
namespace test {
class manifest_test : public ripple::TestSuite
class Manifest_test : public beast::unit_test::suite
{
private:
static PublicKey randomNode ()
@@ -78,8 +80,9 @@ private:
{
return boost::filesystem::current_path () / "manifest_test_databases";
}
public:
manifest_test ()
Manifest_test ()
{
try
{
@@ -89,7 +92,7 @@ public:
{
}
}
~manifest_test ()
~Manifest_test ()
{
try
{
@@ -100,6 +103,30 @@ public:
}
}
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;
st[sfSigningPubKey] = spk;
sign(st, HashPrefix::manifest, *publicKeyType(spk), ssk);
sign(st, HashPrefix::manifest, *publicKeyType(pk), sk,
sfMasterSignature);
Serializer s;
st.add(s);
return beast::detail::base64_encode (std::string(
static_cast<char const*> (s.data()), s.size()));
}
Manifest
make_Manifest
(SecretKey const& sk, KeyType type, SecretKey const& ssk, KeyType stype,
@@ -129,10 +156,10 @@ public:
st.add(s);
std::string const m (static_cast<char const*> (s.data()), s.size());
if (auto r = ripple::make_Manifest (std::move (m)))
if (auto r = Manifest::make_Manifest (std::move (m)))
return std::move (*r);
Throw<std::runtime_error> ("Could not create a manifest");
return *ripple::make_Manifest(std::move(m)); // Silence compiler warning.
return *Manifest::make_Manifest(std::move(m)); // Silence compiler warning.
}
Manifest
@@ -141,105 +168,7 @@ public:
return Manifest (m.serialized, m.masterKey, m.signingKey, m.sequence);
}
void
testConfigLoad ()
{
testcase ("Config Load");
ManifestCache cache;
beast::Journal journal;
std::vector<PublicKey> network;
network.reserve(8);
while (network.size () != 8)
network.push_back (randomMasterKey());
auto format = [](
PublicKey const &publicKey,
char const* comment = nullptr)
{
auto ret = toBase58(
TokenType::TOKEN_NODE_PUBLIC,
publicKey);
if (comment)
ret += comment;
return ret;
};
Section s1;
// Correct (empty) configuration
BEAST_EXPECT(cache.loadValidatorKeys (s1, journal));
BEAST_EXPECT(cache.size() == 0);
// Correct configuration
s1.append (format (network[0]));
s1.append (format (network[1], " Comment"));
s1.append (format (network[2], " Multi Word Comment"));
s1.append (format (network[3], " Leading Whitespace"));
s1.append (format (network[4], " Trailing Whitespace "));
s1.append (format (network[5], " Leading & Trailing Whitespace "));
s1.append (format (network[6], " Leading, Trailing & Internal Whitespace "));
s1.append (format (network[7], " "));
BEAST_EXPECT(cache.loadValidatorKeys (s1, journal));
for (auto const& n : network)
BEAST_EXPECT(cache.trusted (n));
// Incorrect configurations:
Section s2;
s2.append ("NotAPublicKey");
BEAST_EXPECT(!cache.loadValidatorKeys (s2, journal));
Section s3;
s3.append (format (network[0], "!"));
BEAST_EXPECT(!cache.loadValidatorKeys (s3, journal));
Section s4;
s4.append (format (network[0], "! Comment"));
BEAST_EXPECT(!cache.loadValidatorKeys (s4, journal));
// Check if we properly terminate when we encounter
// a malformed or unparseable entry:
auto const masterKey1 = randomMasterKey();
auto const masterKey2 = randomMasterKey ();
Section s5;
s5.append (format (masterKey1, "XXX"));
s5.append (format (masterKey2));
BEAST_EXPECT(!cache.loadValidatorKeys (s5, journal));
BEAST_EXPECT(!cache.trusted (masterKey1));
BEAST_EXPECT(!cache.trusted (masterKey2));
// Reject secp256k1 permanent validator keys
auto const node1 = randomNode ();
auto const node2 = randomNode ();
Section s6;
s6.append (format (node1));
s6.append (format (node2, " Comment"));
BEAST_EXPECT(!cache.loadValidatorKeys (s6, journal));
BEAST_EXPECT(!cache.trusted (node1));
BEAST_EXPECT(!cache.trusted (node2));
// Trust our own master public key from configured manifest
auto unl = std::make_unique<ValidatorList> (journal);
auto const sk = randomSecretKey();
auto const kp = randomKeyPair(KeyType::secp256k1);
auto const m = make_Manifest (
sk, KeyType::ed25519, kp.second, KeyType::secp256k1, 0);
cache.configManifest (clone (m), *unl, journal);
BEAST_EXPECT(cache.trusted (m.masterKey));
}
void testLoadStore (ManifestCache const& m, ValidatorList& unl)
void testLoadStore (ManifestCache& m)
{
testcase ("load/store");
@@ -251,13 +180,6 @@ public:
setup.dataDir = getDatabasePath ();
DatabaseCon dbCon(setup, dbName, WalletDBInit, WalletDBCount);
if (!m.size ())
fail ();
m.save (dbCon);
beast::Journal journal;
auto getPopulatedManifests =
[](ManifestCache const& cache) -> std::vector<Manifest const*>
{
@@ -279,21 +201,45 @@ public:
};
std::vector<Manifest const*> const inManifests (
sort (getPopulatedManifests (m)));
beast::Journal journal;
jtx::Env env (*this);
auto unl = std::make_unique<ValidatorList> (
m, m, env.timeKeeper(), journal);
{
// load should not load untrusted master keys from db
// save should not store untrusted master keys to db
m.save (dbCon, "ValidatorManifests",
[&unl](PublicKey const& pubKey)
{
return unl->listed (pubKey);
});
ManifestCache loaded;
loaded.load (dbCon, unl, journal);
BEAST_EXPECT(loaded.size() == 0);
loaded.load (dbCon, "ValidatorManifests");
for (auto const& man : inManifests)
BEAST_EXPECT(
loaded.getSigningKey (man->masterKey) == man->masterKey);
}
{
// load should load all trusted master keys from db
ManifestCache loaded;
// save should store all trusted master keys to db
PublicKey emptyLocalKey;
std::vector<std::string> s1;
std::vector<std::string> keys;
std::vector<std::string> cfgManifest;
for (auto const& man : inManifests)
loaded.addTrustedKey (man->masterKey, "");
s1.push_back (toBase58(
TokenType::TOKEN_NODE_PUBLIC, man->masterKey));
unl->load (emptyLocalKey, s1, keys);
loaded.load (dbCon, unl, journal);
m.save (dbCon, "ValidatorManifests",
[&unl](PublicKey const& pubKey)
{
return unl->listed (pubKey);
});
ManifestCache loaded;
loaded.load (dbCon, "ValidatorManifests");
std::vector<Manifest const*> const loadedManifests (
sort (getPopulatedManifests (loaded)));
@@ -312,18 +258,23 @@ public:
}
}
{
// load should remove master key from permanent key list
ManifestCache loaded;
auto const iMan = inManifests.begin();
// load config manifest
std::vector<std::string> const badManifest ({"bad manifest"});
if (!*iMan)
fail ();
BEAST_EXPECT(m.trusted((*iMan)->masterKey));
BEAST_EXPECT(unl.insertPermanentKey((*iMan)->masterKey, "trusted key"));
BEAST_EXPECT(unl.trusted((*iMan)->masterKey));
loaded.load (dbCon, unl, journal);
BEAST_EXPECT(!unl.trusted((*iMan)->masterKey));
BEAST_EXPECT(loaded.trusted((*iMan)->masterKey));
ManifestCache loaded;
BEAST_EXPECT(! loaded.load (
dbCon, "ValidatorManifests", badManifest));
auto const sk = randomSecretKey();
auto const pk = derivePublicKey(KeyType::ed25519, sk);
auto const kp = randomKeyPair(KeyType::secp256k1);
std::vector<std::string> const cfgManifest ({
makeManifestString (pk, sk, kp.first, kp.second, 0)
});
BEAST_EXPECT(loaded.load (
dbCon, "ValidatorManifests", cfgManifest));
}
}
boost::filesystem::remove (getDatabasePath () /
@@ -353,16 +304,76 @@ public:
BEAST_EXPECT(strHex(masterSig) == strHex(m.getMasterSignature()));
}
void testGetKeys()
{
testcase ("getKeys");
ManifestCache cache;
auto const sk = randomSecretKey();
auto const pk = derivePublicKey(KeyType::ed25519, sk);
// getSigningKey should return same key if there is no manifest
BEAST_EXPECT(cache.getSigningKey(pk) == pk);
// getSigningKey should return the ephemeral public key
// for the listed validator master public key
// getMasterKey should return the listed validator master key
// for that ephemeral public key
auto const kp0 = randomKeyPair(KeyType::secp256k1);
auto const m0 = make_Manifest (
sk, KeyType::ed25519, kp0.second, KeyType::secp256k1, 0);
BEAST_EXPECT(cache.applyManifest(clone (m0)) ==
ManifestDisposition::accepted);
BEAST_EXPECT(cache.getSigningKey(pk) == kp0.first);
BEAST_EXPECT(cache.getMasterKey(kp0.first) == pk);
// getSigningKey should return the latest ephemeral public key
// for the listed validator master public key
// getMasterKey should only return a master key for the latest
// ephemeral public key
auto const kp1 = randomKeyPair(KeyType::secp256k1);
auto const m1 = make_Manifest (
sk, KeyType::ed25519, kp1.second, KeyType::secp256k1, 1);
BEAST_EXPECT(cache.applyManifest(clone (m1)) ==
ManifestDisposition::accepted);
BEAST_EXPECT(cache.getSigningKey(pk) == kp1.first);
BEAST_EXPECT(cache.getMasterKey(kp1.first) == pk);
BEAST_EXPECT(cache.getMasterKey(kp0.first) == kp0.first);
// getSigningKey and getMasterKey should return the same keys if
// a new manifest is applied with the same signing key but a higher
// sequence
auto const m2 = make_Manifest (
sk, KeyType::ed25519, kp1.second, KeyType::secp256k1, 2);
BEAST_EXPECT(cache.applyManifest(clone (m2)) ==
ManifestDisposition::accepted);
BEAST_EXPECT(cache.getSigningKey(pk) == kp1.first);
BEAST_EXPECT(cache.getMasterKey(kp1.first) == pk);
BEAST_EXPECT(cache.getMasterKey(kp0.first) == kp0.first);
// getSigningKey should return boost::none for a
// revoked master public key
// getMasterKey should return boost::none for an ephemeral public key
// from a revoked master public key
auto const kpMax = randomKeyPair(KeyType::secp256k1);
auto const mMax = make_Manifest (
sk, KeyType::ed25519, kpMax.second, KeyType::secp256k1,
std::numeric_limits<std::uint32_t>::max ());
BEAST_EXPECT(cache.applyManifest(clone (mMax)) ==
ManifestDisposition::accepted);
BEAST_EXPECT(cache.revoked(pk));
BEAST_EXPECT(cache.getSigningKey(pk) == pk);
BEAST_EXPECT(cache.getMasterKey(kpMax.first) == kpMax.first);
BEAST_EXPECT(cache.getMasterKey(kp1.first) == kp1.first);
}
void
run() override
{
ManifestCache cache;
beast::Journal journal;
auto unl = std::make_unique<ValidatorList> (journal);
{
testcase ("apply");
auto const accepted = ManifestDisposition::accepted;
auto const untrusted = ManifestDisposition::untrusted;
auto const stale = ManifestDisposition::stale;
auto const invalid = ManifestDisposition::invalid;
@@ -373,9 +384,11 @@ public:
sk_a, KeyType::ed25519, kp_a.second, KeyType::secp256k1, 0);
auto const s_a1 = make_Manifest (
sk_a, KeyType::ed25519, kp_a.second, KeyType::secp256k1, 1);
auto const s_aMax = make_Manifest (
sk_a, KeyType::ed25519, kp_a.second, KeyType::secp256k1,
std::numeric_limits<std::uint32_t>::max ());
auto const sk_b = randomSecretKey();
auto const pk_b = derivePublicKey(KeyType::ed25519, sk_b);
auto const kp_b = randomKeyPair(KeyType::secp256k1);
auto const s_b0 = make_Manifest (
sk_b, KeyType::ed25519, kp_b.second, KeyType::secp256k1, 0);
@@ -386,45 +399,39 @@ public:
true); // broken
auto const fake = s_b1.serialized + '\0';
BEAST_EXPECT(cache.applyManifest (clone (s_a0), *unl, journal) == untrusted);
// applyManifest should accept new manifests with
// higher sequence numbers
BEAST_EXPECT(cache.applyManifest (clone (s_a0)) == accepted);
BEAST_EXPECT(cache.applyManifest (clone (s_a0)) == stale);
cache.addTrustedKey (pk_a, "a");
cache.addTrustedKey (pk_b, "b");
BEAST_EXPECT(cache.applyManifest (clone (s_a1)) == accepted);
BEAST_EXPECT(cache.applyManifest (clone (s_a1)) == stale);
BEAST_EXPECT(cache.applyManifest (clone (s_a0)) == stale);
BEAST_EXPECT(cache.applyManifest (clone (s_a0), *unl, journal) == accepted);
BEAST_EXPECT(cache.applyManifest (clone (s_a0), *unl, journal) == stale);
// applyManifest should accept manifests with max sequence numbers
// that revoke the master public key
BEAST_EXPECT(!cache.revoked (pk_a));
BEAST_EXPECT(s_aMax.revoked ());
BEAST_EXPECT(cache.applyManifest (clone (s_aMax)) == accepted);
BEAST_EXPECT(cache.applyManifest (clone (s_aMax)) == stale);
BEAST_EXPECT(cache.applyManifest (clone (s_a1)) == stale);
BEAST_EXPECT(cache.applyManifest (clone (s_a0)) == stale);
BEAST_EXPECT(cache.revoked (pk_a));
BEAST_EXPECT(cache.applyManifest (clone (s_a1), *unl, journal) == accepted);
BEAST_EXPECT(cache.applyManifest (clone (s_a1), *unl, journal) == stale);
BEAST_EXPECT(cache.applyManifest (clone (s_a0), *unl, journal) == stale);
// applyManifest should reject manifests with invalid signatures
BEAST_EXPECT(cache.applyManifest (clone (s_b0)) == accepted);
BEAST_EXPECT(cache.applyManifest (clone (s_b0)) == stale);
BEAST_EXPECT(cache.applyManifest (clone (s_b0), *unl, journal) == accepted);
BEAST_EXPECT(cache.applyManifest (clone (s_b0), *unl, journal) == stale);
BEAST_EXPECT(!ripple::make_Manifest(fake));
BEAST_EXPECT(cache.applyManifest (clone (s_b2), *unl, journal) == invalid);
// When trusted permanent key is found as manifest master key
// move to manifest cache
auto const sk_c = randomSecretKey();
auto const pk_c = derivePublicKey(KeyType::ed25519, sk_c);
auto const kp_c = randomKeyPair(KeyType::secp256k1);
auto const s_c0 = make_Manifest (
sk_c, KeyType::ed25519, kp_c.second, KeyType::secp256k1, 0);
BEAST_EXPECT(unl->insertPermanentKey(pk_c, "trusted key"));
BEAST_EXPECT(unl->trusted(pk_c));
BEAST_EXPECT(!cache.trusted(pk_c));
BEAST_EXPECT(cache.applyManifest(clone (s_c0), *unl, journal) == accepted);
BEAST_EXPECT(!unl->trusted(pk_c));
BEAST_EXPECT(cache.trusted(pk_c));
BEAST_EXPECT(!Manifest::make_Manifest(fake));
BEAST_EXPECT(cache.applyManifest (clone (s_b2)) == invalid);
}
testConfigLoad();
testLoadStore (cache, *unl);
testLoadStore (cache);
testGetSignature ();
testGetKeys ();
}
};
BEAST_DEFINE_TESTSUITE(manifest,overlay,ripple);
BEAST_DEFINE_TESTSUITE(Manifest,app,ripple);
} // tests
} // test
} // ripple

View File

@@ -17,48 +17,111 @@
*/
//==============================================================================
#include <BeastConfig.h>
#include <beast/core/detail/base64.hpp>
#include <ripple/basics/Slice.h>
#include <test/jtx/TestSuite.h>
#include <ripple/basics/strHex.h>
#include <ripple/app/misc/ValidatorList.h>
#include <test/jtx.h>
#include <ripple/protocol/digest.h>
#include <ripple/protocol/HashPrefix.h>
#include <ripple/protocol/PublicKey.h>
#include <ripple/protocol/SecretKey.h>
#include <ripple/protocol/Sign.h>
namespace ripple {
namespace tests {
namespace test {
class ValidatorList_test : public ripple::TestSuite
class ValidatorList_test : public beast::unit_test::suite
{
private:
static
PublicKey
randomNode ()
{
return derivePublicKey (
KeyType::secp256k1,
randomSecretKey());
return derivePublicKey (KeyType::secp256k1, randomSecretKey());
}
static
PublicKey
randomMasterKey ()
{
return derivePublicKey (
KeyType::ed25519,
randomSecretKey());
return derivePublicKey (KeyType::ed25519, randomSecretKey());
}
static
bool
isPresent (
std::vector<PublicKey> container,
PublicKey const& item)
std::string
makeManifestString (
PublicKey const& pk,
SecretKey const& sk,
PublicKey const& spk,
SecretKey const& ssk,
int seq)
{
auto found = std::find (
std::begin (container),
std::end (container),
item);
STObject st(sfGeneric);
st[sfSequence] = seq;
st[sfPublicKey] = pk;
st[sfSigningPubKey] = spk;
return (found != std::end (container));
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());
}
std::string
makeList (
std::vector <PublicKey> const& validators,
std::size_t sequence,
std::size_t expiration)
{
std::string data =
"{\"sequence\":" + std::to_string(sequence) +
",\"expiration\":" + std::to_string(expiration) +
",\"validators\":[";
for (auto const& val : validators)
{
data += "{\"validation_public_key\":\"" + strHex(val) + "\"},";
}
data.pop_back();
data += "]}";
return beast::detail::base64_encode(data);
}
std::string
signList (
std::string const& blob,
std::pair<PublicKey, SecretKey> const& keys)
{
auto const data = beast::detail::base64_decode (blob);
return strHex(sign(
keys.first, keys.second, makeSlice(data)));
}
void
testGenesisQuorum ()
{
testcase ("Genesis Quorum");
beast::Journal journal;
ManifestCache manifests;
jtx::Env env (*this);
{
auto trustedKeys = std::make_unique <ValidatorList> (
manifests, manifests, env.timeKeeper(), journal);
BEAST_EXPECT(trustedKeys->quorum () == 1);
}
{
std::size_t minQuorum = 0;
auto trustedKeys = std::make_unique <ValidatorList> (
manifests, manifests, env.timeKeeper(), journal, minQuorum);
BEAST_EXPECT(trustedKeys->quorum () == minQuorum);
}
}
void
@@ -66,21 +129,28 @@ private:
{
testcase ("Config Load");
auto validators = std::make_unique <ValidatorList> (beast::Journal ());
beast::Journal journal;
jtx::Env env (*this);
PublicKey emptyLocalKey;
std::vector<std::string> emptyCfgKeys;
std::vector<std::string> emptyCfgPublishers;
std::vector<PublicKey> network;
network.reserve(8);
auto const localSigningKeys = randomKeyPair(KeyType::secp256k1);
auto const localSigningPublic = localSigningKeys.first;
auto const localSigningSecret = localSigningKeys.second;
auto const localMasterSecret = randomSecretKey();
auto const localMasterPublic = derivePublicKey(
KeyType::ed25519, localMasterSecret);
while (network.size () != 8)
network.push_back (randomNode());
auto cfgManifest = makeManifestString (
localMasterPublic, localMasterSecret,
localSigningPublic, localSigningSecret, 1);
auto format = [](
PublicKey const &publicKey,
char const* comment = nullptr)
{
auto ret = toBase58(
TokenType::TOKEN_NODE_PUBLIC,
publicKey);
auto ret = toBase58 (TokenType::TOKEN_NODE_PUBLIC, publicKey);
if (comment)
ret += comment;
@@ -88,213 +158,592 @@ private:
return ret;
};
Section s1;
std::vector<PublicKey> configList;
configList.reserve(8);
// Correct (empty) configuration
BEAST_EXPECT(validators->load (s1));
BEAST_EXPECT(validators->size() == 0);
while (configList.size () != 8)
configList.push_back (randomNode());
// Correct configuration
s1.append (format (network[0]));
s1.append (format (network[1], " Comment"));
s1.append (format (network[2], " Multi Word Comment"));
s1.append (format (network[3], " Leading Whitespace"));
s1.append (format (network[4], " Trailing Whitespace "));
s1.append (format (network[5], " Leading & Trailing Whitespace "));
s1.append (format (network[6], " Leading, Trailing & Internal Whitespace "));
s1.append (format (network[7], " "));
BEAST_EXPECT(validators->load (s1));
for (auto const& n : network)
BEAST_EXPECT(validators->trusted (n));
// Incorrect configurations:
Section s2;
s2.append ("NotAPublicKey");
BEAST_EXPECT(!validators->load (s2));
Section s3;
s3.append (format (network[0], "!"));
BEAST_EXPECT(!validators->load (s3));
Section s4;
s4.append (format (network[0], "! Comment"));
BEAST_EXPECT(!validators->load (s4));
// Check if we properly terminate when we encounter
// a malformed or unparseable entry:
auto const node1 = randomNode();
auto const node2 = randomNode ();
Section s5;
s5.append (format (node1, "XXX"));
s5.append (format (node2));
BEAST_EXPECT(!validators->load (s5));
BEAST_EXPECT(!validators->trusted (node1));
BEAST_EXPECT(!validators->trusted (node2));
// Add Ed25519 master public keys to permanent validators list
auto const masterNode1 = randomMasterKey ();
auto const masterNode2 = randomMasterKey ();
Section s6;
s6.append (format (masterNode1));
s6.append (format (masterNode2, " Comment"));
BEAST_EXPECT(validators->load (s6));
BEAST_EXPECT(validators->trusted (masterNode1));
BEAST_EXPECT(validators->trusted (masterNode2));
}
void
testMembership ()
{
// The servers on the permanentValidators
std::vector<PublicKey> permanentValidators;
std::vector<PublicKey> ephemeralValidators;
while (permanentValidators.size () != 64)
permanentValidators.push_back (randomNode());
while (ephemeralValidators.size () != 64)
ephemeralValidators.push_back (randomNode());
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], " ")
});
{
testcase ("Membership: No Validators");
ManifestCache manifests;
auto trustedKeys = std::make_unique <ValidatorList> (
manifests, manifests, env.timeKeeper(), journal);
auto vl = std::make_unique <ValidatorList> (beast::Journal ());
// Correct (empty) configuration
BEAST_EXPECT(trustedKeys->load (
emptyLocalKey, emptyCfgKeys, emptyCfgPublishers));
for (auto const& v : permanentValidators)
BEAST_EXPECT(!vl->trusted (v));
// load local validator key with or without manifest
BEAST_EXPECT(trustedKeys->load (
localSigningPublic, emptyCfgKeys, emptyCfgPublishers));
BEAST_EXPECT(trustedKeys->listed (localSigningPublic));
for (auto const& v : ephemeralValidators)
BEAST_EXPECT(!vl->trusted (v));
manifests.applyManifest (*Manifest::make_Manifest(cfgManifest));
BEAST_EXPECT(trustedKeys->load (
localSigningPublic, emptyCfgKeys, emptyCfgPublishers));
BEAST_EXPECT(trustedKeys->listed (localMasterPublic));
BEAST_EXPECT(trustedKeys->listed (localSigningPublic));
}
{
testcase ("Membership: Non-Empty, Some Present, Some Not Present");
// load should add validator keys from config
ManifestCache manifests;
auto trustedKeys = std::make_unique <ValidatorList> (
manifests, manifests, env.timeKeeper(), journal);
std::vector<PublicKey> p (
permanentValidators.begin (),
permanentValidators.begin () + 16);
BEAST_EXPECT(trustedKeys->load (
emptyLocalKey, cfgKeys, emptyCfgPublishers));
while (p.size () != 32)
p.push_back (randomNode());
for (auto const& n : configList)
BEAST_EXPECT(trustedKeys->listed (n));
std::vector<PublicKey> e (
ephemeralValidators.begin (),
ephemeralValidators.begin () + 16);
// load should accept Ed25519 master public keys
auto const masterNode1 = randomMasterKey ();
auto const masterNode2 = randomMasterKey ();
while (e.size () != 32)
e.push_back (randomNode());
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));
auto vl = std::make_unique <ValidatorList> (beast::Journal ());
// load should reject invalid config keys
std::vector<std::string> badKeys({"NotAPublicKey"});
BEAST_EXPECT(!trustedKeys->load (
emptyLocalKey, badKeys, emptyCfgPublishers));
for (auto const& v : p)
vl->insertPermanentKey (v, "");
badKeys[0] = format (randomNode(), "!");
BEAST_EXPECT(!trustedKeys->load (
emptyLocalKey, badKeys, emptyCfgPublishers));
for (auto const& v : e)
vl->insertEphemeralKey (v, "");
badKeys[0] = format (randomNode(), "! Comment");
BEAST_EXPECT(!trustedKeys->load (
emptyLocalKey, badKeys, emptyCfgPublishers));
for (auto const& v : p)
BEAST_EXPECT(vl->trusted (v));
// load terminates when encountering an invalid entry
auto const goodKey = randomNode();
badKeys.push_back (format (goodKey));
BEAST_EXPECT(!trustedKeys->load (
emptyLocalKey, badKeys, emptyCfgPublishers));
BEAST_EXPECT(!trustedKeys->listed (goodKey));
}
{
// local validator key on config list
ManifestCache manifests;
auto trustedKeys = std::make_unique <ValidatorList> (
manifests, manifests, env.timeKeeper(), journal);
for (auto const& v : e)
BEAST_EXPECT(vl->trusted (v));
auto const localSigningPublic = parseBase58<PublicKey> (
TokenType::TOKEN_NODE_PUBLIC, cfgKeys.front());
for (auto const& v : permanentValidators)
BEAST_EXPECT(static_cast<bool>(vl->trusted (v)) == isPresent (p, v));
BEAST_EXPECT(trustedKeys->load (
*localSigningPublic, cfgKeys, emptyCfgPublishers));
for (auto const& v : ephemeralValidators)
BEAST_EXPECT(static_cast<bool>(vl->trusted (v)) == isPresent (e, v));
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(), journal);
auto const localSigningPublic = randomNode();
BEAST_EXPECT(trustedKeys->load (
localSigningPublic, cfgKeys, emptyCfgPublishers));
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(), journal);
manifests.applyManifest (*Manifest::make_Manifest(cfgManifest));
BEAST_EXPECT(trustedKeys->load (
localSigningPublic, cfgKeys, emptyCfgPublishers));
BEAST_EXPECT(trustedKeys->listed (localSigningPublic));
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(), 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::TOKEN_NODE_PUBLIC, 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));
}
}
void
testModification ()
testApplyList ()
{
testcase ("Insertion and Removal");
testcase ("Apply list");
auto vl = std::make_unique <ValidatorList> (beast::Journal ());
beast::Journal journal;
ManifestCache manifests;
jtx::Env env (*this);
auto trustedKeys = std::make_unique<ValidatorList> (
manifests, manifests, env.app().timeKeeper(), journal);
auto const v = randomNode ();
auto const publisherSecret = randomSecretKey();
auto const publisherPublic =
derivePublicKey(KeyType::ed25519, publisherSecret);
auto const pubSigningKeys1 = randomKeyPair(KeyType::secp256k1);
auto const manifest1 = beast::detail::base64_encode(makeManifestString (
publisherPublic, publisherSecret,
pubSigningKeys1.first, pubSigningKeys1.second, 1));
// Inserting a new permanent key succeeds
BEAST_EXPECT(vl->insertPermanentKey (v, "Permanent"));
{
auto member = vl->member (v);
BEAST_EXPECT(static_cast<bool>(member));
BEAST_EXPECT(member->compare("Permanent") == 0);
}
// Inserting the same permanent key fails:
BEAST_EXPECT(!vl->insertPermanentKey (v, ""));
{
auto member = vl->member (v);
BEAST_EXPECT(static_cast<bool>(member));
BEAST_EXPECT(member->compare("Permanent") == 0);
}
// Inserting the same key as ephemeral fails:
BEAST_EXPECT(!vl->insertEphemeralKey (v, "Ephemeral"));
{
auto member = vl->member (v);
BEAST_EXPECT(static_cast<bool>(member));
BEAST_EXPECT(member->compare("Permanent") == 0);
}
// Removing the key as ephemeral fails:
BEAST_EXPECT(!vl->removeEphemeralKey (v));
{
auto member = vl->member (v);
BEAST_EXPECT(static_cast<bool>(member));
BEAST_EXPECT(member->compare("Permanent") == 0);
}
// Deleting the key as permanent succeeds:
BEAST_EXPECT(vl->removePermanentKey (v));
BEAST_EXPECT(!static_cast<bool>(vl->trusted (v)));
std::vector<std::string> cfgKeys1({
strHex(publisherPublic)});
PublicKey emptyLocalKey;
std::vector<std::string> emptyCfgKeys;
BEAST_EXPECT(trustedKeys->load (
emptyLocalKey, emptyCfgKeys, cfgKeys1));
auto constexpr listSize = 20;
std::vector<PublicKey> list1;
list1.reserve (listSize);
while (list1.size () < listSize)
list1.push_back (randomNode());
std::vector<PublicKey> list2;
list2.reserve (listSize);
while (list2.size () < listSize)
list2.push_back (randomNode());
// do not apply expired list
auto const version = 1;
auto const sequence = 1;
auto const expiredblob = makeList (
list1, sequence, env.timeKeeper().now().time_since_epoch().count());
auto const expiredSig = signList (expiredblob, pubSigningKeys1);
BEAST_EXPECT(ListDisposition::stale ==
trustedKeys->applyList (
manifest1, expiredblob, expiredSig, version));
// apply single list
NetClock::time_point const expiration =
env.timeKeeper().now() + 3600s;
auto const blob1 = makeList (
list1, sequence, expiration.time_since_epoch().count());
auto const sig1 = signList (blob1, pubSigningKeys1);
BEAST_EXPECT(ListDisposition::accepted == trustedKeys->applyList (
manifest1, blob1, sig1, version));
for (auto const& val : list1)
BEAST_EXPECT(trustedKeys->listed (val));
// do not use list from untrusted publisher
auto const untrustedManifest = beast::detail::base64_encode(
makeManifestString (
randomMasterKey(), publisherSecret,
pubSigningKeys1.first, pubSigningKeys1.second, 1));
BEAST_EXPECT(ListDisposition::untrusted == trustedKeys->applyList (
untrustedManifest, blob1, sig1, version));
// do not use list with unhandled version
auto const badVersion = 666;
BEAST_EXPECT(ListDisposition::unsupported_version ==
trustedKeys->applyList (
manifest1, blob1, sig1, badVersion));
// apply list with highest sequence number
auto const sequence2 = 2;
auto const blob2 = makeList (
list2, sequence2, expiration.time_since_epoch().count());
auto const sig2 = signList (blob2, pubSigningKeys1);
BEAST_EXPECT(ListDisposition::accepted ==
trustedKeys->applyList (
manifest1, blob2, sig2, version));
for (auto const& val : list1)
BEAST_EXPECT(! trustedKeys->listed (val));
for (auto const& val : list2)
BEAST_EXPECT(trustedKeys->listed (val));
// do not re-apply lists with past or current sequence numbers
BEAST_EXPECT(ListDisposition::stale ==
trustedKeys->applyList (
manifest1, blob1, sig1, version));
BEAST_EXPECT(ListDisposition::stale ==
trustedKeys->applyList (
manifest1, blob2, sig2, version));
// apply list with new publisher key updated by manifest
auto const pubSigningKeys2 = randomKeyPair(KeyType::secp256k1);
auto manifest2 = beast::detail::base64_encode(makeManifestString (
publisherPublic, publisherSecret,
pubSigningKeys2.first, pubSigningKeys2.second, 2));
auto const sequence3 = 3;
auto const blob3 = makeList (
list1, sequence3, expiration.time_since_epoch().count());
auto const sig3 = signList (blob3, pubSigningKeys2);
BEAST_EXPECT(ListDisposition::accepted ==
trustedKeys->applyList (
manifest2, blob3, sig3, version));
auto const sequence4 = 4;
auto const blob4 = makeList (
list1, sequence4, expiration.time_since_epoch().count());
auto const badSig = signList (blob4, pubSigningKeys1);
BEAST_EXPECT(ListDisposition::invalid ==
trustedKeys->applyList (
manifest1, blob4, badSig, version));
// 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 = beast::detail::base64_encode(makeManifestString (
publisherPublic, publisherSecret,
pubSigningKeys2.first, pubSigningKeys2.second,
std::numeric_limits<std::uint32_t>::max ()));
auto const sequence5 = 5;
auto const blob5 = makeList (
list1, sequence5, expiration.time_since_epoch().count());
auto const sig5 = signList (blob5, signingKeysMax);
BEAST_EXPECT(ListDisposition::untrusted ==
trustedKeys->applyList (
maxManifest, blob5, sig5, version));
BEAST_EXPECT(! trustedKeys->trustedPublisher(publisherPublic));
for (auto const& val : list1)
BEAST_EXPECT(! trustedKeys->listed (val));
}
void
testUpdate ()
{
testcase ("Update");
PublicKey emptyLocalKey;
ManifestCache manifests;
jtx::Env env (*this);
auto trustedKeys = std::make_unique <ValidatorList> (
manifests, manifests, env.timeKeeper(), beast::Journal ());
std::vector<std::string> cfgPublishers;
hash_set<PublicKey> activeValidators;
// Insert an ephemeral validator key
BEAST_EXPECT(vl->insertEphemeralKey (v, "Ephemeral"));
{
auto member = vl->member (v);
BEAST_EXPECT(static_cast<bool>(member));
BEAST_EXPECT(member->compare("Ephemeral") == 0);
std::vector<std::string> cfgKeys;
cfgKeys.reserve(20);
while (cfgKeys.size () != 20)
{
auto const valKey = randomNode();
cfgKeys.push_back (toBase58(
TokenType::TOKEN_NODE_PUBLIC, valKey));
if (cfgKeys.size () <= 15)
activeValidators.emplace (valKey);
}
BEAST_EXPECT(trustedKeys->load (
emptyLocalKey, cfgKeys, cfgPublishers));
// onConsensusStart should make all available configured
// validators trusted
trustedKeys->onConsensusStart (activeValidators);
BEAST_EXPECT(trustedKeys->quorum () == 12);
std::size_t i = 0;
for (auto const& val : cfgKeys)
{
if (auto const valKey = parseBase58<PublicKey>(
TokenType::TOKEN_NODE_PUBLIC, val))
{
BEAST_EXPECT(trustedKeys->listed (*valKey));
if (i++ < activeValidators.size ())
BEAST_EXPECT(trustedKeys->trusted (*valKey));
else
BEAST_EXPECT(!trustedKeys->trusted (*valKey));
}
else
fail ();
}
}
// Inserting the same ephemeral key fails
BEAST_EXPECT(!vl->insertEphemeralKey (v, ""));
{
auto member = vl->member (v);
BEAST_EXPECT(static_cast<bool>(member));
BEAST_EXPECT(member->compare("Ephemeral") == 0);
// update with manifests
auto const masterPrivate = randomSecretKey();
auto const masterPublic =
derivePublicKey(KeyType::ed25519, masterPrivate);
std::vector<std::string> cfgKeys ({
toBase58 (TokenType::TOKEN_NODE_PUBLIC, masterPublic)});
BEAST_EXPECT(trustedKeys->load (
emptyLocalKey, cfgKeys, cfgPublishers));
auto const signingKeys1 = randomKeyPair(KeyType::secp256k1);
auto const signingPublic1 = signingKeys1.first;
activeValidators.emplace (masterPublic);
// Should not trust ephemeral signing key if there is no manifest
trustedKeys->onConsensusStart (activeValidators);
BEAST_EXPECT(trustedKeys->listed (masterPublic));
BEAST_EXPECT(trustedKeys->trusted (masterPublic));
BEAST_EXPECT(!trustedKeys->listed (signingPublic1));
BEAST_EXPECT(!trustedKeys->trusted (signingPublic1));
// Should trust the ephemeral signing key from the applied manifest
auto m1 = Manifest::make_Manifest (makeManifestString (
masterPublic, masterPrivate,
signingPublic1, signingKeys1.second, 1));
BEAST_EXPECT(
manifests.applyManifest(std::move (*m1)) ==
ManifestDisposition::accepted);
trustedKeys->onConsensusStart (activeValidators);
BEAST_EXPECT(trustedKeys->quorum () == 13);
BEAST_EXPECT(trustedKeys->listed (masterPublic));
BEAST_EXPECT(trustedKeys->trusted (masterPublic));
BEAST_EXPECT(trustedKeys->listed (signingPublic1));
BEAST_EXPECT(trustedKeys->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 = Manifest::make_Manifest (makeManifestString (
masterPublic, masterPrivate,
signingPublic2, signingKeys2.second, 2));
BEAST_EXPECT(
manifests.applyManifest(std::move (*m2)) ==
ManifestDisposition::accepted);
trustedKeys->onConsensusStart (activeValidators);
BEAST_EXPECT(trustedKeys->quorum () == 13);
BEAST_EXPECT(trustedKeys->listed (masterPublic));
BEAST_EXPECT(trustedKeys->trusted (masterPublic));
BEAST_EXPECT(trustedKeys->listed (signingPublic2));
BEAST_EXPECT(trustedKeys->trusted (signingPublic2));
BEAST_EXPECT(!trustedKeys->listed (signingPublic1));
BEAST_EXPECT(!trustedKeys->trusted (signingPublic1));
// Should not trust keys from revoked master public key
auto const signingKeysMax = randomKeyPair(KeyType::secp256k1);
auto const signingPublicMax = signingKeysMax.first;
activeValidators.emplace (signingPublicMax);
auto mMax = Manifest::make_Manifest (makeManifestString (
masterPublic, masterPrivate,
signingPublicMax, signingKeysMax.second,
std::numeric_limits<std::uint32_t>::max ()));
BEAST_EXPECT(mMax->revoked ());
BEAST_EXPECT(
manifests.applyManifest(std::move (*mMax)) ==
ManifestDisposition::accepted);
BEAST_EXPECT(manifests.getSigningKey (masterPublic) == masterPublic);
BEAST_EXPECT(manifests.revoked (masterPublic));
trustedKeys->onConsensusStart (activeValidators);
BEAST_EXPECT(trustedKeys->quorum () == 12);
BEAST_EXPECT(trustedKeys->listed (masterPublic));
BEAST_EXPECT(!trustedKeys->trusted (masterPublic));
BEAST_EXPECT(!trustedKeys->listed (signingPublicMax));
BEAST_EXPECT(!trustedKeys->trusted (signingPublicMax));
BEAST_EXPECT(!trustedKeys->listed (signingPublic2));
BEAST_EXPECT(!trustedKeys->trusted (signingPublic2));
BEAST_EXPECT(!trustedKeys->listed (signingPublic1));
BEAST_EXPECT(!trustedKeys->trusted (signingPublic1));
}
// Inserting the same key as permanent fails:
BEAST_EXPECT(!vl->insertPermanentKey (v, "Permanent"));
{
auto member = vl->member (v);
BEAST_EXPECT(static_cast<bool>(member));
BEAST_EXPECT(member->compare("Ephemeral") == 0);
// Make quorum unattainable if lists from any publishers are unavailable
auto trustedKeys = std::make_unique <ValidatorList> (
manifests, manifests, env.timeKeeper(), beast::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 (
emptyLocalKey, emptyCfgKeys, cfgPublishers));
trustedKeys->onConsensusStart (activeValidators);
BEAST_EXPECT(trustedKeys->quorum () ==
std::numeric_limits<std::size_t>::max());
}
// Deleting the key as permanent fails:
BEAST_EXPECT(!vl->removePermanentKey (v));
{
auto member = vl->member (v);
BEAST_EXPECT(static_cast<bool>(member));
BEAST_EXPECT(member->compare("Ephemeral") == 0);
// Trust all listed validators if none are active
auto trustedKeys = std::make_unique <ValidatorList> (
manifests, manifests, env.timeKeeper(), beast::Journal ());
std::vector<PublicKey> keys ({ randomNode (), randomNode () });
hash_set<PublicKey> activeValidators;
std::vector<std::string> cfgKeys ({
toBase58 (TokenType::TOKEN_NODE_PUBLIC, keys[0]),
toBase58 (TokenType::TOKEN_NODE_PUBLIC, keys[1])});
BEAST_EXPECT(trustedKeys->load (
emptyLocalKey, cfgKeys, cfgPublishers));
trustedKeys->onConsensusStart (activeValidators);
BEAST_EXPECT(trustedKeys->quorum () == 2);
for (auto const& key : keys)
BEAST_EXPECT(trustedKeys->trusted (key));
}
{
// Should use custom minimum quorum
std::size_t const minQuorum = 0;
ManifestCache manifests;
auto trustedKeys = std::make_unique <ValidatorList> (
manifests, manifests, env.timeKeeper(), beast::Journal (), minQuorum);
auto const node = randomNode ();
std::vector<std::string> cfgKeys ({
toBase58 (TokenType::TOKEN_NODE_PUBLIC, node)});
hash_set<PublicKey> activeValidators;
BEAST_EXPECT(trustedKeys->load (
emptyLocalKey, cfgKeys, cfgPublishers));
trustedKeys->onConsensusStart (activeValidators);
BEAST_EXPECT(trustedKeys->quorum () == minQuorum);
activeValidators.emplace (node);
trustedKeys->onConsensusStart (activeValidators);
BEAST_EXPECT(trustedKeys->quorum () == 1);
}
{
// Increase quorum when running as an unlisted validator
auto trustedKeys = std::make_unique <ValidatorList> (
manifests, manifests, env.timeKeeper(), beast::Journal ());
std::vector<PublicKey> keys ({ randomNode (), randomNode () });
hash_set<PublicKey> activeValidators ({ keys[0] });
std::vector<std::string> cfgKeys ({
toBase58 (TokenType::TOKEN_NODE_PUBLIC, keys[0]),
toBase58 (TokenType::TOKEN_NODE_PUBLIC, keys[1])});
auto const localKey = randomNode ();
BEAST_EXPECT(trustedKeys->load (
localKey, cfgKeys, cfgPublishers));
trustedKeys->onConsensusStart (activeValidators);
BEAST_EXPECT(trustedKeys->quorum () == 3);
// local validator key is always trusted
BEAST_EXPECT(trustedKeys->trusted (localKey));
}
{
// Remove expired published list
auto trustedKeys = std::make_unique<ValidatorList> (
manifests, manifests, env.app().timeKeeper(), beast::Journal ());
PublicKey emptyLocalKey;
std::vector<std::string> emptyCfgKeys;
auto const publisherKeys = randomKeyPair(KeyType::secp256k1);
auto const pubSigningKeys = randomKeyPair(KeyType::secp256k1);
auto const manifest = beast::detail::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<PublicKey> list ({randomNode()});
hash_set<PublicKey> activeValidators ({ list[0] });
// do not apply expired list
auto const version = 1;
auto const sequence = 1;
NetClock::time_point const expiration =
env.timeKeeper().now() + 60s;
auto const blob = makeList (
list, sequence, expiration.time_since_epoch().count());
auto const sig = signList (blob, pubSigningKeys);
BEAST_EXPECT(ListDisposition::accepted ==
trustedKeys->applyList (
manifest, blob, sig, version));
trustedKeys->onConsensusStart (activeValidators);
BEAST_EXPECT(trustedKeys->trusted (list[0]));
env.timeKeeper().set(expiration);
trustedKeys->onConsensusStart (activeValidators);
BEAST_EXPECT(! trustedKeys->trusted (list[0]));
}
// Deleting the key as ephemeral succeeds:
BEAST_EXPECT(vl->removeEphemeralKey (v));
BEAST_EXPECT(!vl->trusted(v));
}
public:
void
run() override
{
testConfigLoad();
testMembership ();
testModification ();
testGenesisQuorum ();
testConfigLoad ();
testApplyList ();
testUpdate ();
}
};
BEAST_DEFINE_TESTSUITE(ValidatorList, app, ripple);
} // tests
} // test
} // ripple

View File

@@ -285,9 +285,9 @@ public:
}
};
std::string valFileContents (boost::optional<int> const& quorum)
std::string valFileContents ()
{
static boost::format configContentsTemplate (R"rippleConfig(
std::string configContents (R"rippleConfig(
[validators]
n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7
n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj
@@ -300,14 +300,11 @@ nHUhG1PgAG8H8myUENypM35JgfqXAKNQvRVVAFDRzJrny5eZN8d5
nHBu9PTL9dn2GuZtdW4U2WzBwffyX9qsQCd9CNU4Z5YG3PQfViM8
nHUPDdcdb2Y5DZAJne4c2iabFuAP3F34xZUgYQT2NH7qfkdapgnz
%1%
[validator_list_keys]
03E74EE14CB525AFBB9F1B7D86CD58ECC4B91452294B42AB4E78F260BD905C091D
030775A669685BD6ABCEBD80385921C7851783D991A8055FD21D2F3966C96F1B56
)rippleConfig");
std::string quorumSection =
quorum ? "[validation_quorum]\n" + to_string(*quorum) : "";
return boost::str (
configContentsTemplate % quorumSection);
return configContents;
}
/**
@@ -321,7 +318,7 @@ private:
public:
ValidatorsTxtGuard (beast::unit_test::suite& test,
path subDir, path const& validatorsFileName,
boost::optional<int> const& quorum, bool useCounter = true)
bool useCounter = true)
: ConfigGuard (test, std::move (subDir), useCounter)
{
using namespace boost::filesystem;
@@ -332,7 +329,7 @@ public:
if (!exists (validatorsFile_))
{
std::ofstream o (validatorsFile_.string ());
o << valFileContents (quorum);
o << valFileContents ();
}
else
{
@@ -392,9 +389,6 @@ port_wss_admin
[ssl_verify]
0
[validation_quorum]
3
)rippleConfig");
c.loadFromString (toLoad);
@@ -499,9 +493,8 @@ port_wss_admin
}
{
// load should throw for invalid [validators_file]
int const quorum = 3;
detail::ValidatorsTxtGuard const vtg (
*this, "test_cfg", "validators.cfg", quorum);
*this, "test_cfg", "validators.cfg");
path const invalidFile = current_path () / vtg.subdir ();
boost::format cc ("[validators_file]\n%1%\n");
std::string error;
@@ -517,7 +510,7 @@ port_wss_admin
BEAST_EXPECT(error == expectedError);
}
{
// load validators and quorum from config
// load validators from config into single section
Config c;
std::string toLoad(R"rippleConfig(
[validators]
@@ -528,53 +521,59 @@ n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C
[validator_keys]
nHUhG1PgAG8H8myUENypM35JgfqXAKNQvRVVAFDRzJrny5eZN8d5
nHBu9PTL9dn2GuZtdW4U2WzBwffyX9qsQCd9CNU4Z5YG3PQfViM8
[validation_quorum]
4
)rippleConfig");
c.loadFromString (toLoad);
BEAST_EXPECT(c.legacy ("validators_file").empty ());
BEAST_EXPECT(c.section (SECTION_VALIDATORS).values ().size () == 3);
BEAST_EXPECT(c.section (SECTION_VALIDATOR_KEYS).values ().size () == 2);
BEAST_EXPECT(c.VALIDATION_QUORUM == 4);
BEAST_EXPECT(c.section (SECTION_VALIDATORS).values ().size () == 5);
}
{
// load validator list keys from config
Config c;
std::string toLoad(R"rippleConfig(
[validator_list_keys]
021A99A537FDEBC34E4FCA03B39BEADD04299BB19E85097EC92B15A3518801E566
)rippleConfig");
c.loadFromString (toLoad);
BEAST_EXPECT(
c.section (SECTION_VALIDATOR_LIST_KEYS).values ().size () == 1);
BEAST_EXPECT(
c.section (SECTION_VALIDATOR_LIST_KEYS).values ()[0] ==
"021A99A537FDEBC34E4FCA03B39BEADD04299BB19E85097EC92B15A3518801E566");
}
{
// load from specified [validators_file] absolute path
int const quorum = 3;
detail::ValidatorsTxtGuard const vtg (
*this, "test_cfg", "validators.cfg", quorum);
*this, "test_cfg", "validators.cfg");
BEAST_EXPECT(vtg.validatorsFileExists ());
Config c;
boost::format cc ("[validators_file]\n%1%\n");
c.loadFromString (boost::str (cc % vtg.validatorsFile ()));
BEAST_EXPECT(c.legacy ("validators_file") == vtg.validatorsFile ());
BEAST_EXPECT(c.section (SECTION_VALIDATORS).values ().size () == 5);
BEAST_EXPECT(c.section (SECTION_VALIDATOR_KEYS).values ().size () == 3);
BEAST_EXPECT(c.VALIDATION_QUORUM == quorum);
BEAST_EXPECT(c.section (SECTION_VALIDATORS).values ().size () == 8);
BEAST_EXPECT(
c.section (SECTION_VALIDATOR_LIST_KEYS).values ().size () == 2);
}
{
// load from specified [validators_file] file name
// in config directory
int const quorum = 3;
std::string const valFileName = "validators.txt";
detail::ValidatorsTxtGuard const vtg (
*this, "test_cfg", valFileName, quorum);
*this, "test_cfg", valFileName);
detail::RippledCfgGuard const rcg (
*this, vtg.subdir (), "", valFileName, false);
BEAST_EXPECT(vtg.validatorsFileExists ());
BEAST_EXPECT(rcg.configFileExists ());
auto const& c (rcg.config ());
BEAST_EXPECT(c.legacy ("validators_file") == valFileName);
BEAST_EXPECT(c.section (SECTION_VALIDATORS).values ().size () == 5);
BEAST_EXPECT(c.section (SECTION_VALIDATOR_KEYS).values ().size () == 3);
BEAST_EXPECT(c.VALIDATION_QUORUM == quorum);
BEAST_EXPECT(c.section (SECTION_VALIDATORS).values ().size () == 8);
BEAST_EXPECT(
c.section (SECTION_VALIDATOR_LIST_KEYS).values ().size () == 2);
}
{
// load from specified [validators_file] relative path
// to config directory
int const quorum = 3;
detail::ValidatorsTxtGuard const vtg (
*this, "test_cfg", "validators.txt", quorum);
*this, "test_cfg", "validators.txt");
auto const valFilePath = ".." / vtg.subdir() / "validators.txt";
detail::RippledCfgGuard const rcg (
*this, vtg.subdir (), "", valFilePath, false);
@@ -582,63 +581,41 @@ nHBu9PTL9dn2GuZtdW4U2WzBwffyX9qsQCd9CNU4Z5YG3PQfViM8
BEAST_EXPECT(rcg.configFileExists ());
auto const& c (rcg.config ());
BEAST_EXPECT(c.legacy ("validators_file") == valFilePath);
BEAST_EXPECT(c.section (SECTION_VALIDATORS).values ().size () == 5);
BEAST_EXPECT(c.section (SECTION_VALIDATOR_KEYS).values ().size () == 3);
BEAST_EXPECT(c.VALIDATION_QUORUM == quorum);
BEAST_EXPECT(c.section (SECTION_VALIDATORS).values ().size () == 8);
BEAST_EXPECT(
c.section (SECTION_VALIDATOR_LIST_KEYS).values ().size () == 2);
}
{
// load from validators file in default location
int const quorum = 3;
detail::ValidatorsTxtGuard const vtg (
*this, "test_cfg", "validators.txt", quorum);
*this, "test_cfg", "validators.txt");
detail::RippledCfgGuard const rcg (*this, vtg.subdir (),
"", "", false);
BEAST_EXPECT(vtg.validatorsFileExists ());
BEAST_EXPECT(rcg.configFileExists ());
auto const& c (rcg.config ());
BEAST_EXPECT(c.legacy ("validators_file").empty ());
BEAST_EXPECT(c.section (SECTION_VALIDATORS).values ().size () == 5);
BEAST_EXPECT(c.section (SECTION_VALIDATOR_KEYS).values ().size () == 3);
BEAST_EXPECT(c.VALIDATION_QUORUM == quorum);
BEAST_EXPECT(c.section (SECTION_VALIDATORS).values ().size () == 8);
BEAST_EXPECT(
c.section (SECTION_VALIDATOR_LIST_KEYS).values ().size () == 2);
}
{
// load from specified [validators_file] instead
// of default location
int const quorum = 3;
detail::ValidatorsTxtGuard const vtg (
*this, "test_cfg", "validators.cfg", quorum);
*this, "test_cfg", "validators.cfg");
BEAST_EXPECT(vtg.validatorsFileExists ());
detail::ValidatorsTxtGuard const vtgDefault (
*this, vtg.subdir (), "validators.txt", 4, false);
*this, vtg.subdir (), "validators.txt", false);
BEAST_EXPECT(vtgDefault.validatorsFileExists ());
detail::RippledCfgGuard const rcg (
*this, vtg.subdir (), "", vtg.validatorsFile (), false);
BEAST_EXPECT(rcg.configFileExists ());
auto const& c (rcg.config ());
BEAST_EXPECT(c.legacy ("validators_file") == vtg.validatorsFile ());
BEAST_EXPECT(c.section (SECTION_VALIDATORS).values ().size () == 5);
BEAST_EXPECT(c.section (SECTION_VALIDATOR_KEYS).values ().size () == 3);
BEAST_EXPECT(c.VALIDATION_QUORUM == quorum);
}
{
// do not load quorum from validators file if in config
boost::format cc (R"rippleConfig(
[validators_file]
%1%
[validation_quorum]
4
)rippleConfig");
int const quorum = 3;
detail::ValidatorsTxtGuard const vtg (
*this, "test_cfg", "validators.cfg", quorum);
BEAST_EXPECT(vtg.validatorsFileExists ());
Config c;
c.loadFromString (boost::str (cc % vtg.validatorsFile ()));
BEAST_EXPECT(c.legacy ("validators_file") == vtg.validatorsFile ());
BEAST_EXPECT(c.section (SECTION_VALIDATORS).values ().size () == 5);
BEAST_EXPECT(c.section (SECTION_VALIDATOR_KEYS).values ().size () == 3);
BEAST_EXPECT(c.VALIDATION_QUORUM == 4);
BEAST_EXPECT(c.section (SECTION_VALIDATORS).values ().size () == 8);
BEAST_EXPECT(
c.section (SECTION_VALIDATOR_LIST_KEYS).values ().size () == 2);
}
{
@@ -658,51 +635,34 @@ n9LdgEtkmGB9E2h3K4Vp7iGUaKuq23Zr32ehxiU8FWY7xoxbWTSA
nHB1X37qrniVugfQcuBTAjswphC1drx7QjFFojJPZwKHHnt8kU7v
nHUkAWDR4cB8AgPg7VXMX6et8xRTQb2KJfgv1aBEXozwrawRKgMB
[validator_list_keys]
021A99A537FDEBC34E4FCA03B39BEADD04299BB19E85097EC92B15A3518801E566
)rippleConfig");
int const quorum = 3;
detail::ValidatorsTxtGuard const vtg (
*this, "test_cfg", "validators.cfg", quorum);
*this, "test_cfg", "validators.cfg");
BEAST_EXPECT(vtg.validatorsFileExists ());
Config c;
c.loadFromString (boost::str (cc % vtg.validatorsFile ()));
BEAST_EXPECT(c.legacy ("validators_file") == vtg.validatorsFile ());
BEAST_EXPECT(c.section (SECTION_VALIDATORS).values ().size () == 10);
BEAST_EXPECT(c.section (SECTION_VALIDATOR_KEYS).values ().size () == 5);
BEAST_EXPECT(c.VALIDATION_QUORUM == quorum);
BEAST_EXPECT(c.section (SECTION_VALIDATORS).values ().size () == 15);
BEAST_EXPECT(
c.section (SECTION_VALIDATOR_LIST_KEYS).values ().size () == 3);
}
{
// load should throw if [validators] and [validator_keys] are
// missing from rippled cfg and validators file
// load should throw if [validators], [validator_keys] and
// [validator_list_keys] are missing from rippled cfg and
// validators file
Config c;
boost::format cc ("[validators_file]\n%1%\n");
std::string error;
detail::ValidatorsTxtGuard const vtg (
*this, "test_cfg", "validators.cfg", boost::none);
*this, "test_cfg", "validators.cfg");
BEAST_EXPECT(vtg.validatorsFileExists ());
auto const expectedError =
"The file specified in [validators_file] does not contain a "
"[validators] or [validator_keys] section: " +
"[validators], [validator_keys] or [validator_list_keys] section: " +
vtg.validatorsFile ();
std::ofstream o (vtg.validatorsFile ());
o << "[validation_quorum]\n3\n";
try {
Config c;
c.loadFromString (boost::str (cc % vtg.validatorsFile ()));
} catch (std::runtime_error& e) {
error = e.what();
}
BEAST_EXPECT(error == expectedError);
}
{
// load should throw if [validation_quorum] is
// missing from rippled cfg and validators file
boost::format cc ("[validators_file]\n%1%\n");
std::string error;
detail::ValidatorsTxtGuard const vtg (
*this, "test_cfg", "validators.cfg", boost::none);
BEAST_EXPECT(vtg.validatorsFileExists ());
auto const expectedError =
"The file specified in [validators_file] does not contain a "
"[validation_quorum] section: " + vtg.validatorsFile ();
try {
Config c;
c.loadFromString (boost::str (cc % vtg.validatorsFile ()));

View File

@@ -57,10 +57,13 @@ public:
[validation_seed]
%2%
[validators]
%3%
)rippleConfig");
p->loadFromString (boost::str (
toLoad % validator::manifest % validator::seed));
toLoad % validator::manifest % validator::seed % validator::master_key));
setupConfigForUnitTests(*p);

View File

@@ -28,6 +28,7 @@
#include <test/app/HashRouter_test.cpp>
#include <test/app/LedgerLoad_test.cpp>
#include <test/app/LoadFeeTrack_test.cpp>
#include <test/app/Manifest_test.cpp>
#include <test/app/MultiSign_test.cpp>
#include <test/app/OfferStream_test.cpp>
#include <test/app/Offer_test.cpp>

View File

@@ -19,6 +19,5 @@
//==============================================================================
#include <test/overlay/cluster_test.cpp>
#include <test/overlay/manifest_test.cpp>
#include <test/overlay/short_read_test.cpp>
#include <test/overlay/TMHello_test.cpp>