Enchance /crawl API endpoint with local server information (RIPD-1644):

The /crawl API endpoint allows developers to examine the structure of
the XRP Ledger's overlay network.

This commit adds additional information about the local server to the
/crawl endpoint, making it possible for developers to create data-rich
network-wide status dashboards.

Related:
 - https://developers.ripple.com/peer-protocol.html
 - https://github.com/ripple/rippled-network-crawler
This commit is contained in:
Joseph Busch
2018-11-19 15:27:16 -06:00
committed by Nik Bougalis
parent ea76103d5f
commit 494724578a
11 changed files with 339 additions and 91 deletions

View File

@@ -20,7 +20,9 @@
#
# 7. Voting
#
# 8. Example Settings
# 8. Misc Settings
#
# 9. Example Settings
#
#-------------------------------------------------------------------------------
#
@@ -1039,6 +1041,46 @@
# [signing_support]
# true
#
# [crawl]
#
# List of options to control what data is reported through the /crawl endpoint
# See https://developers.ripple.com/peer-protocol.html#peer-crawler
#
# <flag>
#
# Enable or disable access to /crawl requests. Default is '1'
#
# overlay = <flag>
#
# Report information about peers this server is connected to, similar
# to the "peers" RPC API. Default is '1'.
#
# server = <flag>
#
# Report information about the local server, similar to the "server_state"
# RPC API. Default is '1'.
#
# counts = <flag>
#
# Report information about the local server health counters, similar to
# the "get_counts" RPC API. Default is '0'.
#
# unl = <flag>
#
# Report information about the local server's validator lists, similar to
# the "validators" and "validator_list_sites" RPC APIs. Default is '1'.
#
# Example:
#
# [crawl]
# 0
#
# [crawl]
# overlay = 1 # report peer overlay info
# server = 1 # report local server info
# counts = 0 # do not report server counts
# unl = 1 # report server validator lists
#
#-------------------------------------------------------------------------------
#
# 9. Example Settings

View File

@@ -122,6 +122,7 @@ class ValidatorList
std::vector<PublicKey> list;
std::size_t sequence;
TimeKeeper::time_point expiration;
std::string siteUri;
};
ManifestCache& validatorManifests_;
@@ -200,7 +201,8 @@ public:
std::string const& manifest,
std::string const& blob,
std::string const& signature,
std::uint32_t version);
std::uint32_t version,
std::string siteUri);
/** Update trusted nodes

View File

@@ -190,13 +190,13 @@ ValidatorList::load (
return true;
}
ListDisposition
ValidatorList::applyList (
std::string const& manifest,
std::string const& blob,
std::string const& signature,
std::uint32_t version)
std::uint32_t version,
std::string siteUri)
{
if (version != requiredListVersion)
return ListDisposition::unsupported_version;
@@ -215,6 +215,7 @@ ValidatorList::applyList (
publisherLists_[pubKey].sequence = list["sequence"].asUInt ();
publisherLists_[pubKey].expiration = TimeKeeper::time_point{
TimeKeeper::duration{list["expiration"].asUInt()}};
publisherLists_[pubKey].siteUri = std::move(siteUri);
std::vector<PublicKey>& publisherList = publisherLists_[pubKey].list;
std::vector<PublicKey> oldList = publisherList;
@@ -544,6 +545,7 @@ ValidatorList::getJson() const
Json::Value& curr = jPublisherLists.append(Json::objectValue);
curr[jss::pubkey_publisher] = strHex(p.first);
curr[jss::available] = p.second.available;
curr[jss::uri] = p.second.siteUri;
if(p.second.expiration != TimeKeeper::time_point{})
{
curr[jss::seq] = static_cast<Json::UInt>(p.second.sequence);

View File

@@ -320,7 +320,8 @@ ValidatorSite::parseJsonResponse (
body["manifest"].asString (),
body["blob"].asString (),
body["signature"].asString(),
body["version"].asUInt());
body["version"].asUInt(),
sites_[siteIdx].activeResource->uri);
sites_[siteIdx].lastRefreshStatus.emplace(
Site::Status{clock_type::now(), disp, ""});

View File

@@ -70,6 +70,7 @@ public:
bool expire = false;
beast::IP::Address public_ip;
int ipLimit = 0;
std::uint32_t crawlOptions = 0;
};
using PeerSequence = std::vector <std::shared_ptr<Peer>>;
@@ -104,11 +105,6 @@ public:
std::size_t
size () = 0;
/** Returns information reported to the crawl cgi command. */
virtual
Json::Value
crawl() = 0;
/** Return diagnostics on the status of all peers.
@deprecated This is superceded by PropertyStream
*/

View File

@@ -21,6 +21,7 @@
#include <ripple/app/misc/HashRouter.h>
#include <ripple/app/misc/NetworkOPs.h>
#include <ripple/app/misc/ValidatorList.h>
#include <ripple/app/misc/ValidatorSite.h>
#include <ripple/basics/base64.h>
#include <ripple/basics/make_SSLContext.h>
#include <ripple/beast/core/LexicalCast.h>
@@ -32,6 +33,7 @@
#include <ripple/overlay/impl/PeerImp.h>
#include <ripple/peerfinder/make_Manager.h>
#include <ripple/rpc/json_body.h>
#include <ripple/rpc/handlers/GetCounts.h>
#include <ripple/server/SimpleWriter.h>
#include <boost/utility/in_place_factory.hpp>
@@ -58,6 +60,18 @@ struct get_peer_json
}
};
namespace CrawlOptions
{
enum
{
Disabled = 0,
Overlay = (1 << 0),
ServerInfo = (1 << 1),
ServerCounts = (1 << 2),
Unl = (1 << 3)
};
}
//------------------------------------------------------------------------------
OverlayImpl::Child::Child (OverlayImpl& overlay)
@@ -860,7 +874,7 @@ OverlayImpl::limit()
}
Json::Value
OverlayImpl::crawl()
OverlayImpl::getOverlayInfo()
{
using namespace std::chrono;
Json::Value jv;
@@ -912,6 +926,65 @@ OverlayImpl::crawl()
return jv;
}
Json::Value
OverlayImpl::getServerInfo()
{
bool const humanReadable = false;
bool const admin = false;
bool const counters = false;
Json::Value server_info = app_.getOPs().getServerInfo(humanReadable, admin, counters);
// Filter out some information
server_info.removeMember(jss::hostid);
server_info.removeMember(jss::load_factor_fee_escalation);
server_info.removeMember(jss::load_factor_fee_queue);
if (server_info.isMember(jss::validated_ledger))
{
Json::Value& validated_ledger = server_info[jss::validated_ledger];
validated_ledger.removeMember(jss::base_fee);
validated_ledger.removeMember(jss::reserve_base_xrp);
validated_ledger.removeMember(jss::reserve_inc_xrp);
}
return server_info;
}
Json::Value
OverlayImpl::getServerCounts()
{
return getCountsJson(app_, 10);
}
Json::Value
OverlayImpl::getUnlInfo()
{
Json::Value validators = app_.validators().getJson();
if (validators.isMember(jss::publisher_lists))
{
Json::Value& publisher_lists = validators[jss::publisher_lists];
for (auto& publisher : publisher_lists)
{
publisher.removeMember(jss::list);
}
}
validators.removeMember(jss::signing_keys);
Json::Value validatorSites = app_.validatorSites().getJson();
if (validatorSites.isMember(jss::validator_sites))
{
validators[jss::validator_sites] = std::move(validatorSites[jss::validator_sites]);
}
return validators;
}
// Returns information on verified peers.
Json::Value
OverlayImpl::json ()
@@ -923,7 +996,7 @@ bool
OverlayImpl::processRequest (http_request_type const& req,
Handoff& handoff)
{
if (req.target() != "/crawl")
if (req.target() != "/crawl" || setup_.crawlOptions == CrawlOptions::Disabled)
return false;
boost::beast::http::response<json_body> msg;
@@ -932,7 +1005,25 @@ OverlayImpl::processRequest (http_request_type const& req,
msg.insert("Server", BuildInfo::getFullVersionString());
msg.insert("Content-Type", "application/json");
msg.insert("Connection", "close");
msg.body()["overlay"] = crawl();
msg.body()["version"] = Json::Value(1u);
if (setup_.crawlOptions & CrawlOptions::Overlay)
{
msg.body()["overlay"] = getOverlayInfo();
}
if (setup_.crawlOptions & CrawlOptions::ServerInfo)
{
msg.body()["server"] = getServerInfo();
}
if (setup_.crawlOptions & CrawlOptions::ServerCounts)
{
msg.body()["counts"] = getServerCounts();
}
if (setup_.crawlOptions & CrawlOptions::Unl)
{
msg.body()["unl"] = getUnlInfo();
}
msg.prepare_payload();
handoff.response = std::make_shared<SimpleWriter>(msg);
return true;
@@ -1145,23 +1236,72 @@ Overlay::Setup
setup_Overlay (BasicConfig const& config)
{
Overlay::Setup setup;
auto const& section = config.section("overlay");
setup.context = make_SSLContext("");
setup.expire = get<bool>(section, "expire", false);
set (setup.ipLimit, "ip_limit", section);
if (setup.ipLimit < 0)
Throw<std::runtime_error> ("Configured IP limit is invalid");
std::string ip;
set (ip, "public_ip", section);
if (! ip.empty ())
{
boost::system::error_code ec;
setup.public_ip = beast::IP::Address::from_string (ip, ec);
if (ec || beast::IP::is_private (setup.public_ip))
Throw<std::runtime_error> ("Configured public IP is invalid");
auto const& section = config.section("overlay");
setup.context = make_SSLContext("");
setup.expire = get<bool>(section, "expire", false);
set(setup.ipLimit, "ip_limit", section);
if (setup.ipLimit < 0)
Throw<std::runtime_error>("Configured IP limit is invalid");
std::string ip;
set(ip, "public_ip", section);
if (!ip.empty())
{
boost::system::error_code ec;
setup.public_ip = beast::IP::Address::from_string(ip, ec);
if (ec || beast::IP::is_private(setup.public_ip))
Throw<std::runtime_error>("Configured public IP is invalid");
}
}
{
auto const& section = config.section("crawl");
auto const& values = section.values();
if (values.size() > 1)
{
Throw<std::runtime_error>(
"Configured [crawl] section is invalid, too many values");
}
bool crawlEnabled = true;
// Only allow "0|1" as a value
if (values.size() == 1)
{
try
{
crawlEnabled = boost::lexical_cast<bool>(values.front());
}
catch (boost::bad_lexical_cast const&)
{
Throw<std::runtime_error>(
"Configured [crawl] section has invalid value: " + values.front());
}
}
if (crawlEnabled)
{
if (get<bool>(section, "overlay", true))
{
setup.crawlOptions |= CrawlOptions::Overlay;
}
if (get<bool>(section, "server", true))
{
setup.crawlOptions |= CrawlOptions::ServerInfo;
}
if (get<bool>(section, "counts", false))
{
setup.crawlOptions |= CrawlOptions::ServerCounts;
}
if (get<bool>(section, "unl", true))
{
setup.crawlOptions |= CrawlOptions::Unl;
}
}
}
return setup;
}

View File

@@ -172,6 +172,18 @@ public:
http_request_type&& request,
endpoint_type remote_endpoint) override;
void
connect(beast::IP::Endpoint const& remote_endpoint) override;
int
limit() override;
std::size_t
size() override;
Json::Value
json() override;
PeerSequence
getActivePeers() override;
@@ -374,24 +386,33 @@ private:
processRequest (http_request_type const& req,
Handoff& handoff);
void
connect (beast::IP::Endpoint const& remote_endpoint) override;
/* The number of active peers on the network
Active peers are only those peers that have completed the handshake
and are running the Ripple protocol.
/** Returns information about peers on the overlay network.
Reported through the /crawl API
Controlled through the config section [crawl] overlay=[0|1]
*/
std::size_t
size() override;
int
limit () override;
Json::Value
crawl() override;
getOverlayInfo();
/** Returns information about the local server.
Reported through the /crawl API
Controlled through the config section [crawl] server=[0|1]
*/
Json::Value
json() override;
getServerInfo();
/** Returns information about the local server's performance counters.
Reported through the /crawl API
Controlled through the config section [crawl] counts=[0|1]
*/
Json::Value
getServerCounts();
/** Returns information about the local server's UNL.
Reported through the /crawl API
Controlled through the config section [crawl] unl=[0|1]
*/
Json::Value
getUnlInfo();
//--------------------------------------------------------------------------

View File

@@ -485,6 +485,7 @@ JSS ( validated ); // out: NetworkOPs, RPCHelpers, AccountTx*
// Tx
JSS ( validator_list_expires ); // out: NetworkOps, ValidatorList
JSS ( validator_list ); // out: NetworkOps, ValidatorList
JSS ( validators );
JSS ( validated_ledger ); // out: NetworkOPs
JSS ( validated_ledgers ); // out: NetworkOPs
JSS ( validation_key ); // out: ValidationCreate, ValidationSeed

View File

@@ -58,58 +58,50 @@ textTime(std::string& text, UptimeClock::time_point& seconds,
text += "s";
}
// {
// min_count: <number> // optional, defaults to 10
// }
Json::Value doGetCounts (RPC::Context& context)
Json::Value getCountsJson(Application& app, int minObjectCount)
{
int minCount = 10;
auto objectCounts = CountedObjects::getInstance().getCounts(minObjectCount);
if (context.params.isMember (jss::min_count))
minCount = context.params[jss::min_count].asUInt ();
auto objectCounts = CountedObjects::getInstance ().getCounts (minCount);
Json::Value ret (Json::objectValue);
Json::Value ret(Json::objectValue);
for (auto const& it : objectCounts)
{
ret [it.first] = it.second;
}
int dbKB = getKBUsedAll (context.app.getLedgerDB ().getSession ());
int dbKB = getKBUsedAll (app.getLedgerDB ().getSession ());
if (dbKB > 0)
ret[jss::dbKBTotal] = dbKB;
dbKB = getKBUsedDB (context.app.getLedgerDB ().getSession ());
dbKB = getKBUsedDB (app.getLedgerDB ().getSession ());
if (dbKB > 0)
ret[jss::dbKBLedger] = dbKB;
dbKB = getKBUsedDB (context.app.getTxnDB ().getSession ());
dbKB = getKBUsedDB (app.getTxnDB ().getSession ());
if (dbKB > 0)
ret[jss::dbKBTransaction] = dbKB;
{
std::size_t c = context.app.getOPs().getLocalTxCount ();
std::size_t c = app.getOPs().getLocalTxCount ();
if (c > 0)
ret[jss::local_txs] = static_cast<Json::UInt> (c);
}
ret[jss::write_load] = context.app.getNodeStore ().getWriteLoad ();
ret[jss::write_load] = app.getNodeStore ().getWriteLoad ();
ret[jss::historical_perminute] = static_cast<int>(
context.app.getInboundLedgers().fetchRate());
ret[jss::SLE_hit_rate] = context.app.cachedSLEs().rate();
ret[jss::node_hit_rate] = context.app.getNodeStore ().getCacheHitRate ();
ret[jss::ledger_hit_rate] = context.app.getLedgerMaster ().getCacheHitRate ();
ret[jss::AL_hit_rate] = context.app.getAcceptedLedgerCache ().getHitRate ();
app.getInboundLedgers().fetchRate());
ret[jss::SLE_hit_rate] = app.cachedSLEs().rate();
ret[jss::node_hit_rate] = app.getNodeStore ().getCacheHitRate ();
ret[jss::ledger_hit_rate] = app.getLedgerMaster ().getCacheHitRate ();
ret[jss::AL_hit_rate] = app.getAcceptedLedgerCache ().getHitRate ();
ret[jss::fullbelow_size] = static_cast<int>(context.app.family().fullbelow().size());
ret[jss::treenode_cache_size] = context.app.family().treecache().getCacheSize();
ret[jss::treenode_track_size] = context.app.family().treecache().getTrackSize();
ret[jss::fullbelow_size] = static_cast<int>(app.family().fullbelow().size());
ret[jss::treenode_cache_size] = app.family().treecache().getCacheSize();
ret[jss::treenode_track_size] = app.family().treecache().getTrackSize();
std::string uptime;
auto s = UptimeClock::now();
@@ -121,21 +113,21 @@ Json::Value doGetCounts (RPC::Context& context)
textTime (uptime, s, "second", 1s);
ret[jss::uptime] = uptime;
ret[jss::node_writes] = context.app.getNodeStore().getStoreCount();
ret[jss::node_reads_total] = context.app.getNodeStore().getFetchTotalCount();
ret[jss::node_reads_hit] = context.app.getNodeStore().getFetchHitCount();
ret[jss::node_written_bytes] = context.app.getNodeStore().getStoreSize();
ret[jss::node_read_bytes] = context.app.getNodeStore().getFetchSize();
ret[jss::node_writes] = app.getNodeStore().getStoreCount();
ret[jss::node_reads_total] = app.getNodeStore().getFetchTotalCount();
ret[jss::node_reads_hit] = app.getNodeStore().getFetchHitCount();
ret[jss::node_written_bytes] = app.getNodeStore().getStoreSize();
ret[jss::node_read_bytes] = app.getNodeStore().getFetchSize();
if (auto shardStore = context.app.getShardStore())
if (auto shardStore = app.getShardStore())
{
Json::Value& jv = (ret[jss::shards] = Json::objectValue);
jv[jss::fullbelow_size] =
static_cast<int>(context.app.shardFamily()->fullbelow().size());
static_cast<int>(app.shardFamily()->fullbelow().size());
jv[jss::treenode_cache_size] =
context.app.shardFamily()->treecache().getCacheSize();
app.shardFamily()->treecache().getCacheSize();
jv[jss::treenode_track_size] =
context.app.shardFamily()->treecache().getTrackSize();
app.shardFamily()->treecache().getTrackSize();
ret[jss::write_load] = shardStore->getWriteLoad();
ret[jss::node_hit_rate] = shardStore->getCacheHitRate();
jv[jss::node_writes] = shardStore->getStoreCount();
@@ -148,4 +140,17 @@ Json::Value doGetCounts (RPC::Context& context)
return ret;
}
// {
// min_count: <number> // optional, defaults to 10
// }
Json::Value doGetCounts (RPC::Context& context)
{
int minCount = 10;
if (context.params.isMember (jss::min_count))
minCount = context.params[jss::min_count].asUInt ();
return getCountsJson(context.app, minCount);
}
} // ripple

View File

@@ -0,0 +1,32 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012-2019 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.
*/
//==============================================================================
#ifndef RIPPLE_RPC_HANDLERS_GETCOUNTS_H_INCLUDED
#define RIPPLE_RPC_HANDLERS_GETCOUNTS_H_INCLUDED
#include <ripple/json/Object.h>
#include <ripple/app/main/Application.h>
namespace ripple {
Json::Value getCountsJson(Application& app, int minObjectCount);
}
#endif

View File

@@ -396,6 +396,8 @@ private:
{
testcase ("Apply list");
std::string const siteUri = "testApplyList.test";
ManifestCache manifests;
jtx::Env env (*this);
auto trustedKeys = std::make_unique<ValidatorList> (
@@ -437,7 +439,7 @@ private:
BEAST_EXPECT(ListDisposition::stale ==
trustedKeys->applyList (
manifest1, expiredblob, expiredSig, version));
manifest1, expiredblob, expiredSig, version, siteUri));
// apply single list
using namespace std::chrono_literals;
@@ -448,7 +450,7 @@ private:
auto const sig1 = signList (blob1, pubSigningKeys1);
BEAST_EXPECT(ListDisposition::accepted == trustedKeys->applyList (
manifest1, blob1, sig1, version));
manifest1, blob1, sig1, version, siteUri));
for (auto const& val : list1)
{
@@ -463,13 +465,13 @@ private:
pubSigningKeys1.first, pubSigningKeys1.second, 1));
BEAST_EXPECT(ListDisposition::untrusted == trustedKeys->applyList (
untrustedManifest, blob1, sig1, version));
untrustedManifest, blob1, sig1, version, siteUri));
// do not use list with unhandled version
auto const badVersion = 666;
BEAST_EXPECT(ListDisposition::unsupported_version ==
trustedKeys->applyList (
manifest1, blob1, sig1, badVersion));
manifest1, blob1, sig1, badVersion, siteUri));
// apply list with highest sequence number
auto const sequence2 = 2;
@@ -479,7 +481,7 @@ private:
BEAST_EXPECT(ListDisposition::accepted ==
trustedKeys->applyList (
manifest1, blob2, sig2, version));
manifest1, blob2, sig2, version, siteUri));
for (auto const& val : list1)
{
@@ -496,11 +498,11 @@ private:
// do not re-apply lists with past or current sequence numbers
BEAST_EXPECT(ListDisposition::stale ==
trustedKeys->applyList (
manifest1, blob1, sig1, version));
manifest1, blob1, sig1, version, siteUri));
BEAST_EXPECT(ListDisposition::same_sequence ==
trustedKeys->applyList (
manifest1, blob2, sig2, version));
manifest1, blob2, sig2, version, siteUri));
// apply list with new publisher key updated by manifest
auto const pubSigningKeys2 = randomKeyPair(KeyType::secp256k1);
@@ -515,7 +517,7 @@ private:
BEAST_EXPECT(ListDisposition::accepted ==
trustedKeys->applyList (
manifest2, blob3, sig3, version));
manifest2, blob3, sig3, version, siteUri));
auto const sequence4 = 4;
auto const blob4 = makeList (
@@ -523,7 +525,7 @@ private:
auto const badSig = signList (blob4, pubSigningKeys1);
BEAST_EXPECT(ListDisposition::invalid ==
trustedKeys->applyList (
manifest1, blob4, badSig, version));
manifest1, blob4, badSig, version, siteUri));
// do not apply list with revoked publisher key
// applied list is removed due to revoked publisher key
@@ -540,7 +542,7 @@ private:
BEAST_EXPECT(ListDisposition::untrusted ==
trustedKeys->applyList (
maxManifest, blob5, sig5, version));
maxManifest, blob5, sig5, version, siteUri));
BEAST_EXPECT(! trustedKeys->trustedPublisher(publisherPublic));
for (auto const& val : list1)
@@ -555,6 +557,8 @@ private:
{
testcase ("Update trusted");
std::string const siteUri = "testUpdateTrusted.test";
PublicKey emptyLocalKey;
ManifestCache manifests;
jtx::Env env (*this);
@@ -805,7 +809,7 @@ private:
BEAST_EXPECT(ListDisposition::accepted ==
trustedKeys->applyList (
manifest, blob, sig, version));
manifest, blob, sig, version, siteUri));
TrustChanges changes =
trustedKeys->updateTrusted(activeValidators);
@@ -839,7 +843,7 @@ private:
BEAST_EXPECT(ListDisposition::accepted ==
trustedKeys->applyList (
manifest, blob2, sig2, version));
manifest, blob2, sig2, version, siteUri));
changes = trustedKeys->updateTrusted (activeValidators);
BEAST_EXPECT(changes.removed.empty());
@@ -942,7 +946,7 @@ private:
calcNodeID(valKeys.back().masterPublic));
}
auto addPublishedList = [this, &env, &trustedKeys, &valKeys]()
auto addPublishedList = [this, &env, &trustedKeys, &valKeys, &siteUri]()
{
auto const publisherSecret = randomSecretKey();
auto const publisherPublic =
@@ -970,7 +974,7 @@ private:
auto const sig = signList (blob, pubSigningKeys);
BEAST_EXPECT(ListDisposition::accepted == trustedKeys->applyList (
manifest, blob, sig, version));
manifest, blob, sig, version, siteUri));
};
// Apply multiple published lists
@@ -999,6 +1003,8 @@ private:
{
testcase("Expires");
std::string const siteUri = "testExpires.test";
jtx::Env env(*this);
auto toStr = [](PublicKey const& publicKey) {
@@ -1088,7 +1094,7 @@ private:
// Apply first list
BEAST_EXPECT(
ListDisposition::accepted == trustedKeys->applyList(
prep1.manifest, prep1.blob, prep1.sig, prep1.version));
prep1.manifest, prep1.blob, prep1.sig, prep1.version, siteUri));
// One list still hasn't published, so expiration is still unknown
BEAST_EXPECT(trustedKeys->expires() == boost::none);
@@ -1096,7 +1102,7 @@ private:
// Apply second list
BEAST_EXPECT(
ListDisposition::accepted == trustedKeys->applyList(
prep2.manifest, prep2.blob, prep2.sig, prep2.version));
prep2.manifest, prep2.blob, prep2.sig, prep2.version, siteUri));
// We now have loaded both lists, so expiration is known
BEAST_EXPECT(