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:
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
|
||||
Reference in New Issue
Block a user