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);
|
||||
|
||||
437
src/test/app/Manifest_test.cpp
Normal file
437
src/test/app/Manifest_test.cpp
Normal file
@@ -0,0 +1,437 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright 2014 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <ripple/app/misc/Manifest.h>
|
||||
#include <ripple/app/misc/ValidatorList.h>
|
||||
#include <ripple/basics/contract.h>
|
||||
#include <ripple/basics/StringUtilities.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 test {
|
||||
|
||||
class Manifest_test : public beast::unit_test::suite
|
||||
{
|
||||
private:
|
||||
static PublicKey randomNode ()
|
||||
{
|
||||
return derivePublicKey (
|
||||
KeyType::secp256k1,
|
||||
randomSecretKey());
|
||||
}
|
||||
|
||||
static PublicKey randomMasterKey ()
|
||||
{
|
||||
return derivePublicKey (
|
||||
KeyType::ed25519,
|
||||
randomSecretKey());
|
||||
}
|
||||
|
||||
static void cleanupDatabaseDir (boost::filesystem::path const& dbPath)
|
||||
{
|
||||
using namespace boost::filesystem;
|
||||
if (!exists (dbPath) || !is_directory (dbPath) || !is_empty (dbPath))
|
||||
return;
|
||||
remove (dbPath);
|
||||
}
|
||||
|
||||
static void setupDatabaseDir (boost::filesystem::path const& dbPath)
|
||||
{
|
||||
using namespace boost::filesystem;
|
||||
if (!exists (dbPath))
|
||||
{
|
||||
create_directory (dbPath);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_directory (dbPath))
|
||||
{
|
||||
// someone created a file where we want to put our directory
|
||||
Throw<std::runtime_error> ("Cannot create directory: " +
|
||||
dbPath.string ());
|
||||
}
|
||||
}
|
||||
static boost::filesystem::path getDatabasePath ()
|
||||
{
|
||||
return boost::filesystem::current_path () / "manifest_test_databases";
|
||||
}
|
||||
|
||||
public:
|
||||
Manifest_test ()
|
||||
{
|
||||
try
|
||||
{
|
||||
setupDatabaseDir (getDatabasePath ());
|
||||
}
|
||||
catch (std::exception const&)
|
||||
{
|
||||
}
|
||||
}
|
||||
~Manifest_test ()
|
||||
{
|
||||
try
|
||||
{
|
||||
cleanupDatabaseDir (getDatabasePath ());
|
||||
}
|
||||
catch (std::exception const&)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
int seq, bool broken = false)
|
||||
{
|
||||
auto const pk = derivePublicKey(type, sk);
|
||||
auto const spk = derivePublicKey(stype, ssk);
|
||||
|
||||
STObject st(sfGeneric);
|
||||
st[sfSequence] = seq;
|
||||
st[sfPublicKey] = pk;
|
||||
st[sfSigningPubKey] = spk;
|
||||
|
||||
sign(st, HashPrefix::manifest, stype, ssk);
|
||||
BEAST_EXPECT(verify(st, HashPrefix::manifest, spk));
|
||||
|
||||
sign(st, HashPrefix::manifest, type, sk, sfMasterSignature);
|
||||
BEAST_EXPECT(verify(
|
||||
st, HashPrefix::manifest, pk, sfMasterSignature));
|
||||
|
||||
if (broken)
|
||||
{
|
||||
set(st, sfSequence, seq + 1);
|
||||
}
|
||||
|
||||
Serializer s;
|
||||
st.add(s);
|
||||
|
||||
std::string const m (static_cast<char const*> (s.data()), s.size());
|
||||
if (auto r = Manifest::make_Manifest (std::move (m)))
|
||||
return std::move (*r);
|
||||
Throw<std::runtime_error> ("Could not create a manifest");
|
||||
return *Manifest::make_Manifest(std::move(m)); // Silence compiler warning.
|
||||
}
|
||||
|
||||
Manifest
|
||||
clone (Manifest const& m)
|
||||
{
|
||||
return Manifest (m.serialized, m.masterKey, m.signingKey, m.sequence);
|
||||
}
|
||||
|
||||
void testLoadStore (ManifestCache& m)
|
||||
{
|
||||
testcase ("load/store");
|
||||
|
||||
std::string const dbName("ManifestCacheTestDB");
|
||||
{
|
||||
// create a database, save the manifest to the db and reload and
|
||||
// check that the manifest caches are the same
|
||||
DatabaseCon::Setup setup;
|
||||
setup.dataDir = getDatabasePath ();
|
||||
DatabaseCon dbCon(setup, dbName, WalletDBInit, WalletDBCount);
|
||||
|
||||
auto getPopulatedManifests =
|
||||
[](ManifestCache const& cache) -> std::vector<Manifest const*>
|
||||
{
|
||||
std::vector<Manifest const*> result;
|
||||
result.reserve (32);
|
||||
cache.for_each_manifest (
|
||||
[&result](Manifest const& m)
|
||||
{result.push_back (&m);});
|
||||
return result;
|
||||
};
|
||||
auto sort =
|
||||
[](std::vector<Manifest const*> mv) -> std::vector<Manifest const*>
|
||||
{
|
||||
std::sort (mv.begin (),
|
||||
mv.end (),
|
||||
[](Manifest const* lhs, Manifest const* rhs)
|
||||
{return lhs->serialized < rhs->serialized;});
|
||||
return mv;
|
||||
};
|
||||
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);
|
||||
|
||||
{
|
||||
// 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, "ValidatorManifests");
|
||||
for (auto const& man : inManifests)
|
||||
BEAST_EXPECT(
|
||||
loaded.getSigningKey (man->masterKey) == man->masterKey);
|
||||
}
|
||||
{
|
||||
// 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)
|
||||
s1.push_back (toBase58(
|
||||
TokenType::TOKEN_NODE_PUBLIC, man->masterKey));
|
||||
unl->load (emptyLocalKey, s1, keys);
|
||||
|
||||
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)));
|
||||
|
||||
if (inManifests.size () == loadedManifests.size ())
|
||||
{
|
||||
BEAST_EXPECT(std::equal
|
||||
(inManifests.begin (), inManifests.end (),
|
||||
loadedManifests.begin (),
|
||||
[](Manifest const* lhs, Manifest const* rhs)
|
||||
{return *lhs == *rhs;}));
|
||||
}
|
||||
else
|
||||
{
|
||||
fail ();
|
||||
}
|
||||
}
|
||||
{
|
||||
// load config manifest
|
||||
std::vector<std::string> const badManifest ({"bad manifest"});
|
||||
|
||||
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 () /
|
||||
boost::filesystem::path (dbName));
|
||||
}
|
||||
|
||||
void testGetSignature()
|
||||
{
|
||||
testcase ("getSignature");
|
||||
auto const sk = randomSecretKey();
|
||||
auto const pk = derivePublicKey(KeyType::ed25519, sk);
|
||||
auto const kp = randomKeyPair(KeyType::secp256k1);
|
||||
auto const m = make_Manifest (
|
||||
sk, KeyType::ed25519, kp.second, KeyType::secp256k1, 0);
|
||||
|
||||
STObject st(sfGeneric);
|
||||
st[sfSequence] = 0;
|
||||
st[sfPublicKey] = pk;
|
||||
st[sfSigningPubKey] = kp.first;
|
||||
Serializer ss;
|
||||
ss.add32(HashPrefix::manifest);
|
||||
st.addWithoutSigningFields(ss);
|
||||
auto const sig = sign(KeyType::secp256k1, kp.second, ss.slice());
|
||||
BEAST_EXPECT(strHex(sig) == strHex(m.getSignature()));
|
||||
|
||||
auto const masterSig = sign(KeyType::ed25519, sk, ss.slice());
|
||||
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;
|
||||
{
|
||||
testcase ("apply");
|
||||
auto const accepted = ManifestDisposition::accepted;
|
||||
auto const stale = ManifestDisposition::stale;
|
||||
auto const invalid = ManifestDisposition::invalid;
|
||||
|
||||
auto const sk_a = randomSecretKey();
|
||||
auto const pk_a = derivePublicKey(KeyType::ed25519, sk_a);
|
||||
auto const kp_a = randomKeyPair(KeyType::secp256k1);
|
||||
auto const s_a0 = make_Manifest (
|
||||
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 kp_b = randomKeyPair(KeyType::secp256k1);
|
||||
auto const s_b0 = make_Manifest (
|
||||
sk_b, KeyType::ed25519, kp_b.second, KeyType::secp256k1, 0);
|
||||
auto const s_b1 = make_Manifest (
|
||||
sk_b, KeyType::ed25519, kp_b.second, KeyType::secp256k1, 1);
|
||||
auto const s_b2 = make_Manifest (
|
||||
sk_b, KeyType::ed25519, kp_b.second, KeyType::secp256k1, 2,
|
||||
true); // broken
|
||||
auto const fake = s_b1.serialized + '\0';
|
||||
|
||||
// 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);
|
||||
|
||||
BEAST_EXPECT(cache.applyManifest (clone (s_a1)) == accepted);
|
||||
BEAST_EXPECT(cache.applyManifest (clone (s_a1)) == stale);
|
||||
BEAST_EXPECT(cache.applyManifest (clone (s_a0)) == 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));
|
||||
|
||||
// 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(!Manifest::make_Manifest(fake));
|
||||
BEAST_EXPECT(cache.applyManifest (clone (s_b2)) == invalid);
|
||||
}
|
||||
testLoadStore (cache);
|
||||
testGetSignature ();
|
||||
testGetKeys ();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(Manifest,app,ripple);
|
||||
|
||||
} // 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
|
||||
|
||||
Reference in New Issue
Block a user