mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
Support UNLs with future effective dates:
* Creates a version 2 of the UNL file format allowing publishers to pre-publish the next UNL while the current one is still valid. * Version 1 of the UNL file format is still valid and backward compatible. * Also causes rippled to lock down if it has no valid UNLs, similar to being amendment blocked, except reversible. * Resolves #3548 * Resolves #3470
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -53,6 +53,7 @@ realValidatorContents()
|
||||
}
|
||||
|
||||
auto constexpr default_expires = std::chrono::seconds{3600};
|
||||
auto constexpr default_effective_overlap = std::chrono::seconds{30};
|
||||
} // namespace detail
|
||||
|
||||
class ValidatorSite_test : public beast::unit_test::suite
|
||||
@@ -135,6 +136,8 @@ private:
|
||||
bool failApply = false;
|
||||
int serverVersion = 1;
|
||||
std::chrono::seconds expiresFromNow = detail::default_expires;
|
||||
std::chrono::seconds effectiveOverlap =
|
||||
detail::default_effective_overlap;
|
||||
int expectedRefreshMin = 0;
|
||||
};
|
||||
void
|
||||
@@ -146,13 +149,17 @@ private:
|
||||
boost::adaptors::transformed(
|
||||
[](FetchListConfig const& cfg) {
|
||||
return cfg.path +
|
||||
(cfg.ssl ? " [https]" : " [http]");
|
||||
(cfg.ssl ? " [https] v" : " [http] v") +
|
||||
std::to_string(cfg.serverVersion) +
|
||||
" " + cfg.msg;
|
||||
}),
|
||||
", ");
|
||||
using namespace jtx;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
Env env(*this);
|
||||
auto& trustedKeys = env.app().validators();
|
||||
env.timeKeeper().set(env.timeKeeper().now() + 30s);
|
||||
|
||||
test::StreamSink sink;
|
||||
beast::Journal journal{sink};
|
||||
@@ -184,10 +191,17 @@ private:
|
||||
while (item.list.size() < listSize)
|
||||
item.list.push_back(TrustedPublisherServer::randomValidator());
|
||||
|
||||
NetClock::time_point const expires =
|
||||
env.timeKeeper().now() + cfg.expiresFromNow;
|
||||
NetClock::time_point const effective2 =
|
||||
expires - cfg.effectiveOverlap;
|
||||
NetClock::time_point const expires2 =
|
||||
effective2 + cfg.expiresFromNow;
|
||||
item.server = make_TrustedPublisherServer(
|
||||
env.app().getIOService(),
|
||||
item.list,
|
||||
env.timeKeeper().now() + cfg.expiresFromNow,
|
||||
expires,
|
||||
{{effective2, expires2}},
|
||||
cfg.ssl,
|
||||
cfg.serverVersion);
|
||||
cfgPublishers.push_back(strHex(item.server->publisherPublic()));
|
||||
@@ -201,7 +215,6 @@ private:
|
||||
BEAST_EXPECT(
|
||||
trustedKeys.load(emptyLocalKey, emptyCfgKeys, cfgPublishers));
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
// Normally, tests will only need a fraction of this time,
|
||||
// but sometimes DNS resolution takes an inordinate amount
|
||||
// of time, so the test will just wait.
|
||||
@@ -381,8 +394,15 @@ public:
|
||||
{
|
||||
// fetch single site
|
||||
testFetchList({{"/validators", "", ssl}});
|
||||
testFetchList({{"/validators2", "", ssl}});
|
||||
// fetch multiple sites
|
||||
testFetchList({{"/validators", "", ssl}, {"/validators", "", ssl}});
|
||||
testFetchList(
|
||||
{{"/validators", "", ssl}, {"/validators2", "", ssl}});
|
||||
testFetchList(
|
||||
{{"/validators2", "", ssl}, {"/validators", "", ssl}});
|
||||
testFetchList(
|
||||
{{"/validators2", "", ssl}, {"/validators2", "", ssl}});
|
||||
// fetch single site with single redirects
|
||||
testFetchList({{"/redirect_once/301", "", ssl}});
|
||||
testFetchList({{"/redirect_once/302", "", ssl}});
|
||||
@@ -391,6 +411,19 @@ public:
|
||||
// one redirect, one not
|
||||
testFetchList(
|
||||
{{"/validators", "", ssl}, {"/redirect_once/302", "", ssl}});
|
||||
testFetchList(
|
||||
{{"/validators2", "", ssl}, {"/redirect_once/302", "", ssl}});
|
||||
// UNLs with a "gap" between validUntil of one and validFrom of the
|
||||
// next
|
||||
testFetchList(
|
||||
{{"/validators2",
|
||||
"",
|
||||
ssl,
|
||||
false,
|
||||
false,
|
||||
1,
|
||||
detail::default_expires,
|
||||
std::chrono::seconds{-90}}});
|
||||
// fetch single site with undending redirect (fails to load)
|
||||
testFetchList(
|
||||
{{"/redirect_forever/301",
|
||||
@@ -418,6 +451,14 @@ public:
|
||||
ssl,
|
||||
true,
|
||||
true}});
|
||||
// one undending redirect, one not
|
||||
testFetchList(
|
||||
{{"/validators2", "", ssl},
|
||||
{"/redirect_forever/302",
|
||||
"Exceeded max redirects",
|
||||
ssl,
|
||||
true,
|
||||
true}});
|
||||
// invalid redir Location
|
||||
testFetchList(
|
||||
{{"/redirect_to/ftp://invalid-url/302",
|
||||
@@ -438,6 +479,12 @@ public:
|
||||
ssl,
|
||||
true,
|
||||
true}});
|
||||
testFetchList(
|
||||
{{"/validators2/bad",
|
||||
"Unable to parse JSON response",
|
||||
ssl,
|
||||
true,
|
||||
true}});
|
||||
// error status returned
|
||||
testFetchList(
|
||||
{{"/bad-resource", "returned bad status", ssl, true, true}});
|
||||
@@ -455,30 +502,96 @@ public:
|
||||
ssl,
|
||||
true,
|
||||
true}});
|
||||
testFetchList(
|
||||
{{"/validators2/missing",
|
||||
"Missing fields in JSON response",
|
||||
ssl,
|
||||
true,
|
||||
true}});
|
||||
// timeout
|
||||
testFetchList({{"/sleep/13", "took too long", ssl, true, true}});
|
||||
// bad manifest format using known versions
|
||||
// * Retrieves a v1 formatted list claiming version 2
|
||||
testFetchList(
|
||||
{{"/validators", "Missing fields", ssl, true, true, 2}});
|
||||
// * Retrieves a v2 formatted list claiming version 1
|
||||
testFetchList(
|
||||
{{"/validators2", "Missing fields", ssl, true, true, 0}});
|
||||
// bad manifest version
|
||||
// Because versions other than 1 are treated as v2, the v1
|
||||
// list won't have the blobs_v2 fields, and thus will claim to have
|
||||
// missing fields
|
||||
testFetchList(
|
||||
{{"/validators", "Unsupported version", ssl, false, true, 4}});
|
||||
using namespace std::chrono_literals;
|
||||
// get old validator list
|
||||
{{"/validators", "Missing fields", ssl, true, true, 4}});
|
||||
testFetchList(
|
||||
{{"/validators",
|
||||
"Stale validator list",
|
||||
{{"/validators2",
|
||||
"1 unsupported version",
|
||||
ssl,
|
||||
false,
|
||||
true,
|
||||
1,
|
||||
0s}});
|
||||
// force an out-of-range expiration value
|
||||
4}});
|
||||
using namespace std::chrono_literals;
|
||||
// get expired validator list
|
||||
testFetchList(
|
||||
{{"/validators",
|
||||
"Invalid validator list",
|
||||
"Applied 1 expired validator list(s)",
|
||||
ssl,
|
||||
false,
|
||||
false,
|
||||
1,
|
||||
0s}});
|
||||
testFetchList(
|
||||
{{"/validators2",
|
||||
"Applied 1 expired validator list(s)",
|
||||
ssl,
|
||||
false,
|
||||
false,
|
||||
1,
|
||||
0s,
|
||||
-1s}});
|
||||
// force an out-of-range validUntil value
|
||||
testFetchList(
|
||||
{{"/validators",
|
||||
"1 invalid validator list(s)",
|
||||
ssl,
|
||||
false,
|
||||
true,
|
||||
1,
|
||||
std::chrono::seconds{Json::Value::maxInt + 1}}});
|
||||
// force an out-of-range validUntil value on the future list
|
||||
// The first list is accepted. The second fails. The parser
|
||||
// returns the "best" result, so this looks like a success.
|
||||
testFetchList(
|
||||
{{"/validators2",
|
||||
"",
|
||||
ssl,
|
||||
false,
|
||||
false,
|
||||
1,
|
||||
std::chrono::seconds{Json::Value::maxInt - 300},
|
||||
299s}});
|
||||
// force an out-of-range validFrom value
|
||||
// The first list is accepted. The second fails. The parser
|
||||
// returns the "best" result, so this looks like a success.
|
||||
testFetchList(
|
||||
{{"/validators2",
|
||||
"",
|
||||
ssl,
|
||||
false,
|
||||
false,
|
||||
1,
|
||||
std::chrono::seconds{Json::Value::maxInt - 300},
|
||||
301s}});
|
||||
// force an out-of-range validUntil value on _both_ lists
|
||||
testFetchList(
|
||||
{{"/validators2",
|
||||
"2 invalid validator list(s)",
|
||||
ssl,
|
||||
false,
|
||||
true,
|
||||
1,
|
||||
std::chrono::seconds{Json::Value::maxInt + 1},
|
||||
std::chrono::seconds{Json::Value::maxInt - 6000}}});
|
||||
// verify refresh intervals are properly clamped
|
||||
testFetchList(
|
||||
{{"/validators/refresh/0",
|
||||
@@ -488,6 +601,17 @@ public:
|
||||
false,
|
||||
1,
|
||||
detail::default_expires,
|
||||
detail::default_effective_overlap,
|
||||
1}}); // minimum of 1 minute
|
||||
testFetchList(
|
||||
{{"/validators2/refresh/0",
|
||||
"",
|
||||
ssl,
|
||||
false,
|
||||
false,
|
||||
1,
|
||||
detail::default_expires,
|
||||
detail::default_effective_overlap,
|
||||
1}}); // minimum of 1 minute
|
||||
testFetchList(
|
||||
{{"/validators/refresh/10",
|
||||
@@ -497,6 +621,17 @@ public:
|
||||
false,
|
||||
1,
|
||||
detail::default_expires,
|
||||
detail::default_effective_overlap,
|
||||
10}}); // 10 minutes is fine
|
||||
testFetchList(
|
||||
{{"/validators2/refresh/10",
|
||||
"",
|
||||
ssl,
|
||||
false,
|
||||
false,
|
||||
1,
|
||||
detail::default_expires,
|
||||
detail::default_effective_overlap,
|
||||
10}}); // 10 minutes is fine
|
||||
testFetchList(
|
||||
{{"/validators/refresh/2000",
|
||||
@@ -506,6 +641,17 @@ public:
|
||||
false,
|
||||
1,
|
||||
detail::default_expires,
|
||||
detail::default_effective_overlap,
|
||||
60 * 24}}); // max of 24 hours
|
||||
testFetchList(
|
||||
{{"/validators2/refresh/2000",
|
||||
"",
|
||||
ssl,
|
||||
false,
|
||||
false,
|
||||
1,
|
||||
detail::default_expires,
|
||||
detail::default_effective_overlap,
|
||||
60 * 24}}); // max of 24 hours
|
||||
}
|
||||
testFileURLs();
|
||||
|
||||
@@ -1860,7 +1860,12 @@ class NegativeUNLVoteFilterValidations_test : public beast::unit_test::suite
|
||||
auto& local = *nUnlKeys.begin();
|
||||
std::vector<std::string> cfgPublishers;
|
||||
validators.load(local, cfgKeys, cfgPublishers);
|
||||
validators.updateTrusted(activeValidators);
|
||||
validators.updateTrusted(
|
||||
activeValidators,
|
||||
env.timeKeeper().now(),
|
||||
env.app().getOPs(),
|
||||
env.app().overlay(),
|
||||
env.app().getHashRouter());
|
||||
BEAST_EXPECT(validators.getTrustedMasterKeys().size() == numNodes);
|
||||
validators.setNegativeUNL(nUnlKeys);
|
||||
BEAST_EXPECT(validators.getNegativeUNL().size() == negUnlSize);
|
||||
|
||||
@@ -57,7 +57,12 @@ class TrustedPublisherServer
|
||||
socket_type sock_;
|
||||
endpoint_type ep_;
|
||||
boost::asio::ip::tcp::acceptor acceptor_;
|
||||
// Generates a version 1 validator list, using the int parameter as the
|
||||
// actual version.
|
||||
std::function<std::string(int)> getList_;
|
||||
// Generates a version 2 validator list, using the int parameter as the
|
||||
// actual version.
|
||||
std::function<std::string(int)> getList2_;
|
||||
|
||||
// The SSL context is required, and holds certificates
|
||||
bool useSSL_;
|
||||
@@ -91,6 +96,18 @@ class TrustedPublisherServer
|
||||
sslCtx_.use_tmp_dh(boost::asio::buffer(dh().data(), dh().size()));
|
||||
}
|
||||
|
||||
struct BlobInfo
|
||||
{
|
||||
BlobInfo(std::string b, std::string s) : blob(b), signature(s)
|
||||
{
|
||||
}
|
||||
|
||||
// base-64 encoded JSON containing the validator list.
|
||||
std::string blob;
|
||||
// hex-encoded signature of the blob using the publisher's signing key
|
||||
std::string signature;
|
||||
};
|
||||
|
||||
public:
|
||||
struct Validator
|
||||
{
|
||||
@@ -144,14 +161,19 @@ public:
|
||||
1)};
|
||||
}
|
||||
|
||||
// TrustedPublisherServer must be accessed through a shared_ptr
|
||||
// TrustedPublisherServer must be accessed through a shared_ptr.
|
||||
// This constructor is only public so std::make_shared has access.
|
||||
// The function`make_TrustedPublisherServer` should be used to create
|
||||
// instances.
|
||||
// The `futures` member is expected to be structured as
|
||||
// effective / expiration time point pairs for use in version 2 UNLs
|
||||
TrustedPublisherServer(
|
||||
boost::asio::io_context& ioc,
|
||||
std::vector<Validator> const& validators,
|
||||
NetClock::time_point expiration,
|
||||
NetClock::time_point validUntil,
|
||||
std::vector<
|
||||
std::pair<NetClock::time_point, NetClock::time_point>> const&
|
||||
futures,
|
||||
bool useSSL = false,
|
||||
int version = 1,
|
||||
bool immediateStart = true,
|
||||
@@ -170,29 +192,80 @@ public:
|
||||
auto const manifest = makeManifestString(
|
||||
publisherPublic_, publisherSecret_, keys.first, keys.second, 1);
|
||||
|
||||
std::string data = "{\"sequence\":" + std::to_string(sequence) +
|
||||
",\"expiration\":" +
|
||||
std::to_string(expiration.time_since_epoch().count()) +
|
||||
",\"validators\":[";
|
||||
std::vector<BlobInfo> blobInfo;
|
||||
blobInfo.reserve(futures.size() + 1);
|
||||
auto const [data, blob] = [&]() -> std::pair<std::string, std::string> {
|
||||
// Builds the validator list, then encodes it into a blob.
|
||||
std::string data = "{\"sequence\":" + std::to_string(sequence) +
|
||||
",\"expiration\":" +
|
||||
std::to_string(validUntil.time_since_epoch().count()) +
|
||||
",\"validators\":[";
|
||||
|
||||
for (auto const& val : validators)
|
||||
{
|
||||
data += "{\"validation_public_key\":\"" + strHex(val.masterPublic) +
|
||||
"\",\"manifest\":\"" + val.manifest + "\"},";
|
||||
}
|
||||
data.pop_back();
|
||||
data += "]}";
|
||||
std::string blob = base64_encode(data);
|
||||
auto const sig = sign(keys.first, keys.second, makeSlice(data));
|
||||
getList_ = [blob, sig, manifest, version](int interval) {
|
||||
for (auto const& val : validators)
|
||||
{
|
||||
data += "{\"validation_public_key\":\"" +
|
||||
strHex(val.masterPublic) + "\",\"manifest\":\"" +
|
||||
val.manifest + "\"},";
|
||||
}
|
||||
data.pop_back();
|
||||
data += "]}";
|
||||
std::string blob = base64_encode(data);
|
||||
return std::make_pair(data, blob);
|
||||
}();
|
||||
auto const sig = strHex(sign(keys.first, keys.second, makeSlice(data)));
|
||||
blobInfo.emplace_back(blob, sig);
|
||||
getList_ = [blob = blob, sig, manifest, version](int interval) {
|
||||
// Build the contents of a version 1 format UNL file
|
||||
std::stringstream l;
|
||||
l << "{\"blob\":\"" << blob << "\""
|
||||
<< ",\"signature\":\"" << strHex(sig) << "\""
|
||||
<< ",\"signature\":\"" << sig << "\""
|
||||
<< ",\"manifest\":\"" << manifest << "\""
|
||||
<< ",\"refresh_interval\": " << interval
|
||||
<< ",\"version\":" << version << '}';
|
||||
return l.str();
|
||||
};
|
||||
for (auto const& future : futures)
|
||||
{
|
||||
std::string data = "{\"sequence\":" + std::to_string(++sequence) +
|
||||
",\"effective\":" +
|
||||
std::to_string(future.first.time_since_epoch().count()) +
|
||||
",\"expiration\":" +
|
||||
std::to_string(future.second.time_since_epoch().count()) +
|
||||
",\"validators\":[";
|
||||
|
||||
// Use the same set of validators for simplicity
|
||||
for (auto const& val : validators)
|
||||
{
|
||||
data += "{\"validation_public_key\":\"" +
|
||||
strHex(val.masterPublic) + "\",\"manifest\":\"" +
|
||||
val.manifest + "\"},";
|
||||
}
|
||||
data.pop_back();
|
||||
data += "]}";
|
||||
std::string blob = base64_encode(data);
|
||||
auto const sig =
|
||||
strHex(sign(keys.first, keys.second, makeSlice(data)));
|
||||
blobInfo.emplace_back(blob, sig);
|
||||
}
|
||||
getList2_ = [blobInfo, manifest, version](int interval) {
|
||||
// Build the contents of a version 2 format UNL file
|
||||
// Use `version + 1` to get 2 for most tests, but have
|
||||
// a "bad" version number for tests that provide an override.
|
||||
std::stringstream l;
|
||||
for (auto const& info : blobInfo)
|
||||
{
|
||||
l << "{\"blob\":\"" << info.blob << "\""
|
||||
<< ",\"signature\":\"" << info.signature << "\"},";
|
||||
}
|
||||
std::string blobs = l.str();
|
||||
blobs.pop_back();
|
||||
l.str(std::string());
|
||||
l << "{\"blobs_v2\": [ " << blobs << "],\"manifest\":\"" << manifest
|
||||
<< "\""
|
||||
<< ",\"refresh_interval\": " << interval
|
||||
<< ",\"version\":" << (version + 1) << '}';
|
||||
return l.str();
|
||||
};
|
||||
|
||||
if (useSSL_)
|
||||
{
|
||||
@@ -505,7 +578,26 @@ private:
|
||||
res.keep_alive(req.keep_alive());
|
||||
bool prepare = true;
|
||||
|
||||
if (boost::starts_with(path, "/validators"))
|
||||
if (boost::starts_with(path, "/validators2"))
|
||||
{
|
||||
res.result(http::status::ok);
|
||||
res.insert("Content-Type", "application/json");
|
||||
if (path == "/validators2/bad")
|
||||
res.body() = "{ 'bad': \"2']";
|
||||
else if (path == "/validators2/missing")
|
||||
res.body() = "{\"version\": 2}";
|
||||
else
|
||||
{
|
||||
int refresh = 5;
|
||||
constexpr char const* refreshPrefix =
|
||||
"/validators2/refresh/";
|
||||
if (boost::starts_with(path, refreshPrefix))
|
||||
refresh = boost::lexical_cast<unsigned int>(
|
||||
path.substr(strlen(refreshPrefix)));
|
||||
res.body() = getList2_(refresh);
|
||||
}
|
||||
}
|
||||
else if (boost::starts_with(path, "/validators"))
|
||||
{
|
||||
res.result(http::status::ok);
|
||||
res.insert("Content-Type", "application/json");
|
||||
@@ -516,9 +608,11 @@ private:
|
||||
else
|
||||
{
|
||||
int refresh = 5;
|
||||
if (boost::starts_with(path, "/validators/refresh"))
|
||||
constexpr char const* refreshPrefix =
|
||||
"/validators/refresh/";
|
||||
if (boost::starts_with(path, refreshPrefix))
|
||||
refresh = boost::lexical_cast<unsigned int>(
|
||||
path.substr(20));
|
||||
path.substr(strlen(refreshPrefix)));
|
||||
res.body() = getList_(refresh);
|
||||
}
|
||||
}
|
||||
@@ -618,14 +712,16 @@ inline std::shared_ptr<TrustedPublisherServer>
|
||||
make_TrustedPublisherServer(
|
||||
boost::asio::io_context& ioc,
|
||||
std::vector<TrustedPublisherServer::Validator> const& validators,
|
||||
NetClock::time_point expiration,
|
||||
NetClock::time_point validUntil,
|
||||
std::vector<std::pair<NetClock::time_point, NetClock::time_point>> const&
|
||||
futures,
|
||||
bool useSSL = false,
|
||||
int version = 1,
|
||||
bool immediateStart = true,
|
||||
int sequence = 1)
|
||||
{
|
||||
auto const r = std::make_shared<TrustedPublisherServer>(
|
||||
ioc, validators, expiration, useSSL, version, sequence);
|
||||
ioc, validators, validUntil, futures, useSSL, version, sequence);
|
||||
if (immediateStart)
|
||||
r->start();
|
||||
return r;
|
||||
|
||||
@@ -40,6 +40,8 @@ class DatabaseDownloader_test : public beast::unit_test::suite
|
||||
env.app().getIOService(),
|
||||
list,
|
||||
env.timeKeeper().now() + std::chrono::seconds{3600},
|
||||
// No future VLs
|
||||
{},
|
||||
ssl);
|
||||
}
|
||||
|
||||
|
||||
@@ -344,11 +344,43 @@ public:
|
||||
return list;
|
||||
}
|
||||
|
||||
std::shared_ptr<protocol::TMValidatorListCollection>
|
||||
buildValidatorListCollection()
|
||||
{
|
||||
auto list = std::make_shared<protocol::TMValidatorListCollection>();
|
||||
|
||||
auto master = randomKeyPair(KeyType::ed25519);
|
||||
auto signing = randomKeyPair(KeyType::ed25519);
|
||||
STObject st(sfGeneric);
|
||||
st[sfSequence] = 0;
|
||||
st[sfPublicKey] = std::get<0>(master);
|
||||
st[sfSigningPubKey] = std::get<0>(signing);
|
||||
st[sfDomain] = makeSlice(std::string("example.com"));
|
||||
sign(
|
||||
st,
|
||||
HashPrefix::manifest,
|
||||
KeyType::ed25519,
|
||||
std::get<1>(master),
|
||||
sfMasterSignature);
|
||||
sign(st, HashPrefix::manifest, KeyType::ed25519, std::get<1>(signing));
|
||||
Serializer s;
|
||||
st.add(s);
|
||||
list->set_manifest(s.data(), s.size());
|
||||
list->set_version(4);
|
||||
STObject signature(sfSignature);
|
||||
ripple::sign(
|
||||
st, HashPrefix::manifest, KeyType::ed25519, std::get<1>(signing));
|
||||
Serializer s1;
|
||||
st.add(s1);
|
||||
auto& blob = *list->add_blobs();
|
||||
blob.set_signature(s1.data(), s1.size());
|
||||
blob.set_blob(strHex(s.getString()));
|
||||
return list;
|
||||
}
|
||||
|
||||
void
|
||||
testProtocol()
|
||||
{
|
||||
testcase("Message Compression");
|
||||
|
||||
auto thresh = beast::severities::Severity::kInfo;
|
||||
auto logs = std::make_unique<Logs>(thresh);
|
||||
|
||||
@@ -359,6 +391,7 @@ public:
|
||||
protocol::TMLedgerData ledger_data;
|
||||
protocol::TMGetObjectByHash get_object;
|
||||
protocol::TMValidatorList validator_list;
|
||||
protocol::TMValidatorListCollection validator_list_collection;
|
||||
|
||||
// 4.5KB
|
||||
doTest(buildManifests(20), protocol::mtMANIFESTS, 4, "TMManifests20");
|
||||
@@ -418,6 +451,11 @@ public:
|
||||
protocol::mtVALIDATORLIST,
|
||||
4,
|
||||
"TMValidatorList");
|
||||
doTest(
|
||||
buildValidatorListCollection(),
|
||||
protocol::mtVALIDATORLISTCOLLECTION,
|
||||
4,
|
||||
"TMValidatorListCollection");
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -46,6 +46,8 @@ class ShardArchiveHandler_test : public beast::unit_test::suite
|
||||
env.app().getIOService(),
|
||||
list,
|
||||
env.timeKeeper().now() + std::chrono::seconds{3600},
|
||||
// No future VLs
|
||||
{},
|
||||
ssl);
|
||||
}
|
||||
|
||||
|
||||
@@ -189,12 +189,20 @@ public:
|
||||
// Manage single-thread io_service for server.
|
||||
BasicApp worker{1};
|
||||
using namespace std::chrono_literals;
|
||||
NetClock::time_point const expiration{3600s};
|
||||
NetClock::time_point const validUntil{3600s};
|
||||
NetClock::time_point const validFrom2{validUntil - 60s};
|
||||
NetClock::time_point const validUntil2{validFrom2 + 3600s};
|
||||
auto server = make_TrustedPublisherServer(
|
||||
worker.get_io_service(), validators, expiration, false, 1, false);
|
||||
worker.get_io_service(),
|
||||
validators,
|
||||
validUntil,
|
||||
{{validFrom2, validUntil2}},
|
||||
false,
|
||||
1,
|
||||
false);
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Publisher list site unavailable
|
||||
// Publisher list site unavailable v1
|
||||
{
|
||||
// Publisher site information
|
||||
using namespace std::string_literals;
|
||||
@@ -261,11 +269,78 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
// Publisher list site unavailable v2
|
||||
{
|
||||
// Publisher site information
|
||||
using namespace std::string_literals;
|
||||
std::string siteURI =
|
||||
"http://"s + getEnvLocalhostAddr() + ":1234/validators2";
|
||||
|
||||
Env env{
|
||||
*this,
|
||||
envconfig([&](std::unique_ptr<Config> cfg) {
|
||||
cfg->section(SECTION_VALIDATOR_LIST_SITES).append(siteURI);
|
||||
cfg->section(SECTION_VALIDATOR_LIST_KEYS)
|
||||
.append(strHex(server->publisherPublic()));
|
||||
return cfg;
|
||||
}),
|
||||
};
|
||||
|
||||
env.app().validatorSites().start();
|
||||
env.app().validatorSites().join();
|
||||
|
||||
{
|
||||
auto const jrr = env.rpc("server_info")[jss::result];
|
||||
BEAST_EXPECT(
|
||||
jrr[jss::info][jss::validator_list][jss::expiration] ==
|
||||
"unknown");
|
||||
}
|
||||
{
|
||||
auto const jrr = env.rpc("server_state")[jss::result];
|
||||
BEAST_EXPECT(
|
||||
jrr[jss::state][jss::validator_list_expires].asInt() == 0);
|
||||
}
|
||||
{
|
||||
auto const jrr = env.rpc("validators")[jss::result];
|
||||
BEAST_EXPECT(
|
||||
jrr[jss::validation_quorum].asUInt() ==
|
||||
std::numeric_limits<std::uint32_t>::max());
|
||||
BEAST_EXPECT(jrr[jss::local_static_keys].size() == 0);
|
||||
BEAST_EXPECT(jrr[jss::trusted_validator_keys].size() == 0);
|
||||
BEAST_EXPECT(
|
||||
jrr[jss::validator_list][jss::expiration] == "unknown");
|
||||
|
||||
if (BEAST_EXPECT(jrr[jss::publisher_lists].size() == 1))
|
||||
{
|
||||
auto jp = jrr[jss::publisher_lists][0u];
|
||||
BEAST_EXPECT(jp[jss::available] == false);
|
||||
BEAST_EXPECT(jp[jss::list].size() == 0);
|
||||
BEAST_EXPECT(!jp.isMember(jss::seq));
|
||||
BEAST_EXPECT(!jp.isMember(jss::expiration));
|
||||
BEAST_EXPECT(!jp.isMember(jss::version));
|
||||
BEAST_EXPECT(
|
||||
jp[jss::pubkey_publisher] ==
|
||||
strHex(server->publisherPublic()));
|
||||
}
|
||||
BEAST_EXPECT(jrr[jss::signing_keys].size() == 0);
|
||||
}
|
||||
{
|
||||
auto const jrr = env.rpc("validator_list_sites")[jss::result];
|
||||
if (BEAST_EXPECT(jrr[jss::validator_sites].size() == 1))
|
||||
{
|
||||
auto js = jrr[jss::validator_sites][0u];
|
||||
BEAST_EXPECT(js[jss::refresh_interval_min].asUInt() == 5);
|
||||
BEAST_EXPECT(js[jss::uri] == siteURI);
|
||||
BEAST_EXPECT(js.isMember(jss::last_refresh_time));
|
||||
BEAST_EXPECT(js[jss::last_refresh_status] == "invalid");
|
||||
}
|
||||
}
|
||||
}
|
||||
//----------------------------------------------------------------------
|
||||
// Publisher list site available
|
||||
server->start();
|
||||
// Publisher list site available v1
|
||||
{
|
||||
server->start();
|
||||
|
||||
std::stringstream uri;
|
||||
uri << "http://" << server->local_endpoint() << "/validators";
|
||||
auto siteURI = uri.str();
|
||||
@@ -286,26 +361,31 @@ public:
|
||||
for (auto const& val : validators)
|
||||
startKeys.insert(calcNodeID(val.masterPublic));
|
||||
|
||||
env.app().validators().updateTrusted(startKeys);
|
||||
env.app().validators().updateTrusted(
|
||||
startKeys,
|
||||
env.timeKeeper().now(),
|
||||
env.app().getOPs(),
|
||||
env.app().overlay(),
|
||||
env.app().getHashRouter());
|
||||
|
||||
{
|
||||
auto const jrr = env.rpc("server_info")[jss::result];
|
||||
BEAST_EXPECT(
|
||||
jrr[jss::info][jss::validator_list][jss::expiration] ==
|
||||
to_string(expiration));
|
||||
to_string(validUntil));
|
||||
}
|
||||
{
|
||||
auto const jrr = env.rpc("server_state")[jss::result];
|
||||
BEAST_EXPECT(
|
||||
jrr[jss::state][jss::validator_list_expires].asUInt() ==
|
||||
expiration.time_since_epoch().count());
|
||||
validUntil.time_since_epoch().count());
|
||||
}
|
||||
{
|
||||
auto const jrr = env.rpc("validators")[jss::result];
|
||||
BEAST_EXPECT(jrr[jss::validation_quorum].asUInt() == 2);
|
||||
BEAST_EXPECT(
|
||||
jrr[jss::validator_list][jss::expiration] ==
|
||||
to_string(expiration));
|
||||
to_string(validUntil));
|
||||
BEAST_EXPECT(jrr[jss::local_static_keys].size() == 0);
|
||||
|
||||
BEAST_EXPECT(
|
||||
@@ -334,7 +414,7 @@ public:
|
||||
BEAST_EXPECT(
|
||||
jp[jss::pubkey_publisher] ==
|
||||
strHex(server->publisherPublic()));
|
||||
BEAST_EXPECT(jp[jss::expiration] == to_string(expiration));
|
||||
BEAST_EXPECT(jp[jss::expiration] == to_string(validUntil));
|
||||
BEAST_EXPECT(jp[jss::version] == 1);
|
||||
}
|
||||
auto jsk = jrr[jss::signing_keys];
|
||||
@@ -361,6 +441,129 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
// Publisher list site available v2
|
||||
{
|
||||
std::stringstream uri;
|
||||
uri << "http://" << server->local_endpoint() << "/validators2";
|
||||
auto siteURI = uri.str();
|
||||
|
||||
Env env{
|
||||
*this,
|
||||
envconfig([&](std::unique_ptr<Config> cfg) {
|
||||
cfg->section(SECTION_VALIDATOR_LIST_SITES).append(siteURI);
|
||||
cfg->section(SECTION_VALIDATOR_LIST_KEYS)
|
||||
.append(strHex(server->publisherPublic()));
|
||||
return cfg;
|
||||
}),
|
||||
};
|
||||
|
||||
env.app().validatorSites().start();
|
||||
env.app().validatorSites().join();
|
||||
hash_set<NodeID> startKeys;
|
||||
for (auto const& val : validators)
|
||||
startKeys.insert(calcNodeID(val.masterPublic));
|
||||
|
||||
env.app().validators().updateTrusted(
|
||||
startKeys,
|
||||
env.timeKeeper().now(),
|
||||
env.app().getOPs(),
|
||||
env.app().overlay(),
|
||||
env.app().getHashRouter());
|
||||
|
||||
{
|
||||
auto const jrr = env.rpc("server_info")[jss::result];
|
||||
BEAST_EXPECT(
|
||||
jrr[jss::info][jss::validator_list][jss::expiration] ==
|
||||
to_string(validUntil2));
|
||||
}
|
||||
{
|
||||
auto const jrr = env.rpc("server_state")[jss::result];
|
||||
BEAST_EXPECT(
|
||||
jrr[jss::state][jss::validator_list_expires].asUInt() ==
|
||||
validUntil2.time_since_epoch().count());
|
||||
}
|
||||
{
|
||||
auto const jrr = env.rpc("validators")[jss::result];
|
||||
BEAST_EXPECT(jrr[jss::validation_quorum].asUInt() == 2);
|
||||
BEAST_EXPECT(
|
||||
jrr[jss::validator_list][jss::expiration] ==
|
||||
to_string(validUntil2));
|
||||
BEAST_EXPECT(jrr[jss::local_static_keys].size() == 0);
|
||||
|
||||
BEAST_EXPECT(
|
||||
jrr[jss::trusted_validator_keys].size() ==
|
||||
expectedKeys.size());
|
||||
for (auto const& jKey : jrr[jss::trusted_validator_keys])
|
||||
{
|
||||
BEAST_EXPECT(expectedKeys.count(jKey.asString()) == 1);
|
||||
}
|
||||
|
||||
if (BEAST_EXPECT(jrr[jss::publisher_lists].size() == 1))
|
||||
{
|
||||
auto jp = jrr[jss::publisher_lists][0u];
|
||||
BEAST_EXPECT(jp[jss::available] == true);
|
||||
if (BEAST_EXPECT(jp[jss::list].size() == 2))
|
||||
{
|
||||
// check entries
|
||||
std::set<std::string> foundKeys;
|
||||
for (auto const& k : jp[jss::list])
|
||||
{
|
||||
foundKeys.insert(k.asString());
|
||||
}
|
||||
BEAST_EXPECT(foundKeys == expectedKeys);
|
||||
}
|
||||
BEAST_EXPECT(jp[jss::seq].asUInt() == 1);
|
||||
BEAST_EXPECT(
|
||||
jp[jss::pubkey_publisher] ==
|
||||
strHex(server->publisherPublic()));
|
||||
BEAST_EXPECT(jp[jss::expiration] == to_string(validUntil));
|
||||
BEAST_EXPECT(jp[jss::version] == 2);
|
||||
if (BEAST_EXPECT(jp.isMember(jss::remaining)) &&
|
||||
BEAST_EXPECT(jp[jss::remaining].isArray()) &&
|
||||
BEAST_EXPECT(jp[jss::remaining].size() == 1))
|
||||
{
|
||||
auto const& r = jp[jss::remaining][0u];
|
||||
if (BEAST_EXPECT(r[jss::list].size() == 2))
|
||||
{
|
||||
// check entries
|
||||
std::set<std::string> foundKeys;
|
||||
for (auto const& k : r[jss::list])
|
||||
{
|
||||
foundKeys.insert(k.asString());
|
||||
}
|
||||
BEAST_EXPECT(foundKeys == expectedKeys);
|
||||
}
|
||||
BEAST_EXPECT(r[jss::seq].asUInt() == 2);
|
||||
BEAST_EXPECT(
|
||||
r[jss::effective] == to_string(validFrom2));
|
||||
BEAST_EXPECT(
|
||||
r[jss::expiration] == to_string(validUntil2));
|
||||
}
|
||||
}
|
||||
auto jsk = jrr[jss::signing_keys];
|
||||
BEAST_EXPECT(jsk.size() == 2);
|
||||
for (auto const& val : validators)
|
||||
{
|
||||
BEAST_EXPECT(jsk.isMember(toStr(val.masterPublic)));
|
||||
BEAST_EXPECT(
|
||||
jsk[toStr(val.masterPublic)] ==
|
||||
toStr(val.signingPublic));
|
||||
}
|
||||
}
|
||||
{
|
||||
auto const jrr = env.rpc("validator_list_sites")[jss::result];
|
||||
if (BEAST_EXPECT(jrr[jss::validator_sites].size() == 1))
|
||||
{
|
||||
auto js = jrr[jss::validator_sites][0u];
|
||||
BEAST_EXPECT(js[jss::refresh_interval_min].asUInt() == 5);
|
||||
BEAST_EXPECT(js[jss::uri] == siteURI);
|
||||
BEAST_EXPECT(js[jss::last_refresh_status] == "accepted");
|
||||
// The actual time of the update will vary run to run, so
|
||||
// just verify the time is there
|
||||
BEAST_EXPECT(js.isMember(jss::last_refresh_time));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
Reference in New Issue
Block a user