mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
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:
@@ -402,7 +402,7 @@ public:
|
||||
v->setFieldV256 (sfAmendments, field);
|
||||
|
||||
v->setTrusted();
|
||||
validations [calcNodeID(val)] = v;
|
||||
validations [val] = v;
|
||||
}
|
||||
|
||||
ourVotes = table.doValidation (enabled);
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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 ()));
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user