Propagate validator lists (VLs or UNLs) over the peer network:

* Whenever a node downloads a new VL, send it to all peers that
  haven't already sent or received it. It also saves it to the
  database_dir as a Json text file named "cache." plus the public key of
  the list signer. Any files that exist for public keys provided in
  [validator_list_keys] will be loaded and processed if any download
  from [validator_list_sites] fails or no [validator_list_sites] are
  configured.
* Whenever a node receives a broadcast VL message, it treats it as if
  it had downloaded it on it's own, broadcasting to other peers as
  described above.
* Because nodes normally download the VL once every 5 minutes, a single
  node downloading a VL with an updated sequence number could
  potentially propagate across a large part of a well-connected network
  before any other nodes attempt to download, decreasing the amount of
  time that different parts of the network are using different VLs.
* Send all of our current valid VLs to new peers on connection.
  This is probably the "noisiest" part of this change, but will give
  poorly connected or poorly networked nodes the best chance of syncing
  quickly. Nodes which have no http(s) access configured or available
  can get a VL with no extra effort.
* Requests on the peer port to the /vl/<pubkey> endpoint will return
  that VL in the same JSON format as is used to download now, IF the
  node trusts and has a valid instance of that VL.
* Upgrade protocol version to 2.1. VLs will only be sent to 2.1 and
  higher nodes.
* Resolves #2953
This commit is contained in:
Edward Hennis
2019-04-24 18:43:54 -04:00
committed by Manoj doshi
parent 3753840de5
commit 2c71802e38
26 changed files with 1003 additions and 145 deletions

View File

@@ -1052,7 +1052,7 @@
# [crawl]
#
# List of options to control what data is reported through the /crawl endpoint
# See https://developers.ripple.com/peer-protocol.html#peer-crawler
# See https://xrpl.org/peer-crawler.html
#
# <flag>
#
@@ -1093,6 +1093,16 @@
# counts = 0
# unl = 1
#
# [vl]
#
# Options to control what data is reported through the /vl endpoint
# See [...]
#
# enable = <flag>
#
# Enable or disable access to /vl requests. Default is '1' which
# enables access.
#
#-------------------------------------------------------------------------------
#
# 9. Example Settings

View File

@@ -365,7 +365,7 @@ public:
std::unique_ptr <ServerHandler> serverHandler_;
std::unique_ptr <AmendmentTable> m_amendmentTable;
std::unique_ptr <LoadFeeTrack> mFeeTrack;
std::unique_ptr <HashRouter> mHashRouter;
std::unique_ptr <HashRouter> hashRouter_;
RCLValidations mValidations;
std::unique_ptr <LoadManager> m_loadManager;
std::unique_ptr <TxQ> txQ_;
@@ -377,7 +377,7 @@ public:
std::unique_ptr <DatabaseCon> mTxnDB;
std::unique_ptr <DatabaseCon> mLedgerDB;
std::unique_ptr <DatabaseCon> mWalletDB;
std::unique_ptr <Overlay> m_overlay;
std::unique_ptr <Overlay> overlay_;
std::vector <std::unique_ptr<Stoppable>> websocketServers_;
boost::asio::signal_set m_signals;
@@ -520,7 +520,8 @@ public:
logs_->journal("ManifestCache")))
, validators_ (std::make_unique<ValidatorList> (
*validatorManifests_, *publisherManifests_, *timeKeeper_,
*validatorManifests_, *publisherManifests_,
*timeKeeper_, config_->legacy("database_path"),
logs_->journal("ValidatorList"), config_->VALIDATION_QUORUM))
, validatorSites_ (std::make_unique<ValidatorSite> (*this))
@@ -531,7 +532,7 @@ public:
, mFeeTrack (std::make_unique<LoadFeeTrack>(logs_->journal("LoadManager")))
, mHashRouter (std::make_unique<HashRouter>(
, hashRouter_ (std::make_unique<HashRouter>(
stopwatch(), HashRouter::getDefaultHoldTime (),
HashRouter::getDefaultRecoverLimit ()))
@@ -760,7 +761,7 @@ public:
HashRouter& getHashRouter () override
{
return *mHashRouter;
return *hashRouter_;
}
RCLValidations& getValidations () override
@@ -828,7 +829,8 @@ public:
Overlay& overlay () override
{
return *m_overlay;
assert(overlay_);
return *overlay_;
}
TxQ& getTxQ() override
@@ -1480,10 +1482,10 @@ bool ApplicationImp::setup()
// move the instantiation inside a conditional:
//
// if (!config_.standalone())
m_overlay = make_Overlay (*this, setup_Overlay(*config_), *m_jobQueue,
overlay_ = make_Overlay (*this, setup_Overlay(*config_), *m_jobQueue,
*serverHandler_, *m_resourceManager, *m_resolver, get_io_service(),
*config_, m_collectorManager->collector ());
add (*m_overlay); // add to PropertyStream
add (*overlay_); // add to PropertyStream
if (!config_->standalone())
{
@@ -1675,7 +1677,7 @@ int ApplicationImp::fdRequired() const
int needed = 128;
// 1.5 times the configured peer limit for peer connections:
needed += static_cast<int>(0.5 + (1.5 * m_overlay->limit()));
needed += static_cast<int>(0.5 + (1.5 * overlay_->limit()));
// the number of fds needed by the backend (internally
// doubled if online delete is enabled).
@@ -2131,7 +2133,7 @@ ApplicationImp::journal (std::string const& name)
bool ApplicationImp::nodeToShards()
{
assert(m_overlay);
assert(overlay_);
assert(!config_->standalone());
if (config_->section(ConfigSection::shardDatabase()).empty())
@@ -2152,7 +2154,7 @@ bool ApplicationImp::nodeToShards()
bool ApplicationImp::validateShards()
{
assert(m_overlay);
assert(overlay_);
assert(!config_->standalone());
if (config_->section(ConfigSection::shardDatabase()).empty())

View File

@@ -197,8 +197,10 @@ public:
boost::optional<std::set<PeerShortID>> shouldRelay(uint256 const& key);
/** Determines whether the hashed item should be recovered
from the open ledger into the next open ledger or the transaction
queue.
@return `bool` indicates whether the item should be relayed
@return `bool` indicates whether the item should be recovered
*/
bool shouldRecover(uint256 const& key);

View File

@@ -35,6 +35,10 @@
namespace ripple {
// predeclaration
class Overlay;
class HashRouter;
enum class ListDisposition
{
/// List is valid
@@ -123,11 +127,17 @@ class ValidatorList
std::size_t sequence;
TimeKeeper::time_point expiration;
std::string siteUri;
std::string rawManifest;
std::string rawBlob;
std::string rawSignature;
std::uint32_t rawVersion;
uint256 hash;
};
ManifestCache& validatorManifests_;
ManifestCache& publisherManifests_;
TimeKeeper& timeKeeper_;
boost::filesystem::path const dataPath_;
beast::Journal const j_;
std::shared_timed_mutex mutable mutex_;
@@ -147,16 +157,45 @@ class ValidatorList
// Currently supported version of publisher list format
static constexpr std::uint32_t requiredListVersion = 1;
static const std::string filePrefix_;
public:
ValidatorList (
ManifestCache& validatorManifests,
ManifestCache& publisherManifests,
TimeKeeper& timeKeeper,
std::string const& databasePath,
beast::Journal j,
boost::optional<std::size_t> minimumQuorum = boost::none);
~ValidatorList () = default;
/** Describes the result of processing a Validator List (UNL),
including some of the information from the list which can
be used by the caller to know which list publisher is
involved.
*/
struct PublisherListStats
{
explicit PublisherListStats(ListDisposition d)
: disposition(d)
{
}
PublisherListStats(ListDisposition d, PublicKey key,
bool avail, std::size_t seq)
: disposition(d)
, publisherKey(key)
, available(avail)
, sequence(seq)
{
}
ListDisposition disposition;
boost::optional<PublicKey> publisherKey;
bool available = false;
boost::optional<std::size_t> sequence;
};
/** Load configured trusted keys.
@param localSigningKey This node's validation public key
@@ -180,6 +219,44 @@ public:
std::vector<std::string> const& configKeys,
std::vector<std::string> const& publisherKeys);
/** Apply published list of public keys, then broadcast it to all
peers that have not seen it or sent it.
@param manifest base64-encoded publisher key manifest
@param blob base64-encoded json containing published validator list
@param signature Signature of the decoded blob
@param version Version of published list format
@param siteUri Uri of the site from which the list was validated
@param hash Hash of the data parameters
@param overlay Overlay object which will handle sending the message
@param hashRouter HashRouter object which will determine which
peers not to send to
@return `ListDisposition::accepted`, plus some of the publisher
information, if list was successfully applied
@par Thread Safety
May be called concurrently
*/
PublisherListStats
applyListAndBroadcast (
std::string const& manifest,
std::string const& blob,
std::string const& signature,
std::uint32_t version,
std::string siteUri,
uint256 const& hash,
Overlay& overlay,
HashRouter& hashRouter);
/** Apply published list of public keys
@param manifest base64-encoded publisher key manifest
@@ -190,19 +267,38 @@ public:
@param version Version of published list format
@return `ListDisposition::accepted` if list was successfully applied
@param siteUri Uri of the site from which the list was validated
@param hash Optional hash of the data parameters.
Defaults to uninitialized
@return `ListDisposition::accepted`, plus some of the publisher
information, if list was successfully applied
@par Thread Safety
May be called concurrently
*/
ListDisposition
PublisherListStats
applyList (
std::string const& manifest,
std::string const& blob,
std::string const& signature,
std::uint32_t version,
std::string siteUri);
std::string siteUri,
boost::optional<uint256> const& hash = {});
/* Attempt to read previously stored list files. Expected to only be
called when loading from URL fails.
@return A list of valid file:// URLs, if any.
@par Thread Safety
May be called concurrently
*/
std::vector<std::string>
loadLists();
/** Update trusted nodes
@@ -333,6 +429,47 @@ public:
for_each_listed (
std::function<void(PublicKey const&, bool)> func) const;
/** Invokes the callback once for every available publisher list's raw
data members
@note Undefined behavior results when calling ValidatorList members
from within the callback
The arguments passed into the lambda are:
@li The raw manifest string
@li The raw "blob" string containing the values for the validator list
@li The signature string used to sign the blob
@li The version number
@li The `PublicKey` of the blob signer (matches the value from
[validator_list_keys])
@li The sequence number of the "blob"
@li The precomputed hash of the original / raw elements
@par Thread Safety
May be called concurrently
*/
void
for_each_available (
std::function<void(std::string const& manifest,
std::string const& blob, std::string const& signature,
std::uint32_t version,
PublicKey const& pubKey, std::size_t sequence,
uint256 const& hash)> func) const;
/** Returns the current valid list for the given publisher key,
if available, as a Json object.
*/
boost::optional<Json::Value>
getAvailable(boost::beast::string_view const& pubKey);
/** Return the number of configured validator list sites. */
std::size_t
count() const;
@@ -371,6 +508,17 @@ public:
private:
/** Get the filename used for caching UNLs
*/
boost::filesystem::path
GetCacheFileName(PublicKey const& pubKey);
/** Write a JSON UNL to a cache file
*/
void
CacheValidatorFile(PublicKey const& pubKey,
PublisherList const& publisher);
/** Check response for trusted valid published list
@return `ListDisposition::accepted` if list can be applied

View File

@@ -244,6 +244,11 @@ private:
detail::response_type& res,
std::size_t siteIdx,
std::lock_guard<std::mutex>& lock);
/// If no sites are provided, or a site fails to load,
/// get a list of local cache files from the ValidatorList.
bool
missingSite();
};
} // ripple

View File

@@ -18,11 +18,15 @@
//==============================================================================
#include <ripple/app/misc/ValidatorList.h>
#include <ripple/app/misc/HashRouter.h>
#include <ripple/basics/base64.h>
#include <ripple/basics/FileUtilities.h>
#include <ripple/basics/Slice.h>
#include <ripple/basics/StringUtilities.h>
#include <ripple/json/json_reader.h>
#include <ripple/overlay/Overlay.h>
#include <ripple/protocol/jss.h>
#include <ripple/protocol/messages.h>
#include <boost/regex.hpp>
#include <date/date.h>
@@ -54,15 +58,19 @@ to_string(ListDisposition disposition)
return "unknown";
}
const std::string ValidatorList::filePrefix_ = "cache.";
ValidatorList::ValidatorList (
ManifestCache& validatorManifests,
ManifestCache& publisherManifests,
TimeKeeper& timeKeeper,
std::string const& databasePath,
beast::Journal j,
boost::optional<std::size_t> minimumQuorum)
: validatorManifests_ (validatorManifests)
, publisherManifests_ (publisherManifests)
, timeKeeper_ (timeKeeper)
, dataPath_(databasePath)
, j_ (j)
, quorum_ (minimumQuorum.value_or(1)) // Genesis ledger quorum
, minimumQuorum_ (minimumQuorum)
@@ -192,16 +200,122 @@ ValidatorList::load (
return true;
}
ListDisposition
boost::filesystem::path
ValidatorList::GetCacheFileName(PublicKey const& pubKey)
{
return dataPath_ / (filePrefix_ + strHex(pubKey));
}
void
ValidatorList::CacheValidatorFile(PublicKey const& pubKey,
PublisherList const& publisher)
{
if (dataPath_.empty())
return;
boost::filesystem::path const filename =
GetCacheFileName(pubKey);
boost::system::error_code ec;
Json::Value value(Json::objectValue);
value["manifest"] = publisher.rawManifest;
value["blob"] = publisher.rawBlob;
value["signature"] = publisher.rawSignature;
value["version"] = publisher.rawVersion;
writeFileContents(ec, filename, value.toStyledString());
if (ec)
{
// Log and ignore any file I/O exceptions
JLOG(j_.error()) <<
"Problem writing " <<
filename <<
" " <<
ec.value() <<
": " <<
ec.message();
}
}
ValidatorList::PublisherListStats
ValidatorList::applyListAndBroadcast(
std::string const& manifest,
std::string const& blob,
std::string const& signature,
std::uint32_t version,
std::string siteUri,
uint256 const& hash,
Overlay& overlay,
HashRouter& hashRouter)
{
auto const result = applyList(manifest, blob, signature,
version, std::move(siteUri), hash);
auto const disposition = result.disposition;
bool broadcast = disposition == ListDisposition::accepted ||
disposition == ListDisposition::same_sequence;
if (broadcast)
{
assert(result.available && result.publisherKey && result.sequence);
auto const toSkip = hashRouter.shouldRelay(hash);
if (toSkip)
{
protocol::TMValidatorList msg;
msg.set_manifest(manifest);
msg.set_blob(blob);
msg.set_signature(signature);
msg.set_version(version);
auto const& publisherKey = *result.publisherKey;
auto const sequence = *result.sequence;
// Can't use overlay.foreach here because we need to modify
// the peer, and foreach provides a const&
auto message =
std::make_shared<Message>(msg, protocol::mtVALIDATORLIST);
for (auto& peer : overlay.getActivePeers())
{
if (toSkip->count(peer->id()) == 0 &&
peer->supportsFeature(
ProtocolFeature::ValidatorListPropagation) &&
peer->publisherListSequence(publisherKey) < sequence)
{
peer->send(message);
JLOG(j_.debug())
<< "Sent validator list for " << strHex(publisherKey)
<< " with sequence " << sequence << " to "
<< peer->getRemoteAddress().to_string() << " ("
<< peer->id() << ")";
// Don't send it next time.
hashRouter.addSuppressionPeer(hash, peer->id());
peer->setPublisherListSequence(publisherKey, sequence);
}
}
}
}
return result;
}
ValidatorList::PublisherListStats
ValidatorList::applyList (
std::string const& manifest,
std::string const& blob,
std::string const& signature,
std::uint32_t version,
std::string siteUri)
std::string siteUri,
boost::optional<uint256> const& hash)
{
using namespace std::string_literals;
if (version != requiredListVersion)
return ListDisposition::unsupported_version;
return PublisherListStats{ ListDisposition::unsupported_version };
std::unique_lock<std::shared_timed_mutex> lock{mutex_};
@@ -209,16 +323,37 @@ ValidatorList::applyList (
PublicKey pubKey;
auto const result = verify (list, pubKey, manifest, blob, signature);
if (result != ListDisposition::accepted)
return result;
{
if (result == ListDisposition::same_sequence &&
publisherLists_.count(pubKey))
{
// We've seen this valid list already, so return
// what we know about it.
auto const& publisher = publisherLists_[pubKey];
return PublisherListStats{ result, pubKey,
publisher.available, publisher.sequence };
}
return PublisherListStats{ result };
}
// Update publisher's list
Json::Value const& newList = list["validators"];
publisherLists_[pubKey].available = true;
publisherLists_[pubKey].sequence = list["sequence"].asUInt ();
publisherLists_[pubKey].expiration = TimeKeeper::time_point{
auto& publisher = publisherLists_[pubKey];
publisher.available = true;
publisher.sequence = list["sequence"].asUInt ();
publisher.expiration = TimeKeeper::time_point{
TimeKeeper::duration{list["expiration"].asUInt()}};
publisherLists_[pubKey].siteUri = std::move(siteUri);
std::vector<PublicKey>& publisherList = publisherLists_[pubKey].list;
publisher.siteUri = std::move(siteUri);
publisher.rawManifest = manifest;
publisher.rawBlob = blob;
publisher.rawSignature = signature;
publisher.rawVersion = version;
if(hash)
publisher.hash = *hash;
std::vector<PublicKey>& publisherList = publisher.list;
PublisherListStats const applyResult{ result, pubKey,
publisher.available, publisher.sequence };
std::vector<PublicKey> oldList = publisherList;
publisherList.clear ();
@@ -311,7 +446,65 @@ ValidatorList::applyList (
}
}
return ListDisposition::accepted;
// Cache the validator list in a file
CacheValidatorFile(pubKey, publisher);
return applyResult;
}
std::vector<std::string>
ValidatorList::loadLists()
{
using namespace std::string_literals;
using namespace boost::filesystem;
using namespace boost::system::errc;
std::unique_lock<std::shared_timed_mutex> lock{mutex_};
std::vector<std::string> sites;
sites.reserve(publisherLists_.size());
for (auto const& [pubKey, publisher] : publisherLists_)
{
boost::system::error_code ec;
if (publisher.available)
continue;
boost::filesystem::path const filename =
GetCacheFileName(pubKey);
auto const fullPath{ canonical(filename, ec) };
if (ec)
continue;
auto size = file_size(fullPath, ec);
if (!ec && !size)
{
// Treat an empty file as a missing file, because
// nobody else is going to write it.
ec = make_error_code(no_such_file_or_directory);
}
if (ec)
continue;
std::string const prefix = [&fullPath]() {
#if _MSC_VER // MSVC: Windows paths need a leading / added
{
return fullPath.root_path() == "/"s ?
"file://" : "file:///";
}
#else
{
(void)fullPath;
return "file://";
}
#endif
}();
sites.emplace_back(prefix + fullPath.string());
}
// Then let the ValidatorSites do the rest of the work.
return sites;
}
ListDisposition
@@ -594,6 +787,57 @@ ValidatorList::for_each_listed (
func (v.first, trusted(v.first));
}
void
ValidatorList::for_each_available (
std::function<void(std::string const& manifest,
std::string const& blob, std::string const& signature,
std::uint32_t version,
PublicKey const& pubKey, std::size_t sequence,
uint256 const& hash)> func) const
{
std::shared_lock<std::shared_timed_mutex> read_lock{mutex_};
for (auto const& [key, pl] : publisherLists_)
{
if (!pl.available)
continue;
func(pl.rawManifest, pl.rawBlob, pl.rawSignature, pl.rawVersion,
key, pl.sequence, pl.hash);
}
}
boost::optional<Json::Value>
ValidatorList::getAvailable(boost::beast::string_view const& pubKey)
{
std::shared_lock<std::shared_timed_mutex> read_lock{mutex_};
auto const keyBlob = strViewUnHex (pubKey);
if (! keyBlob || ! publicKeyType(makeSlice(*keyBlob)))
{
JLOG (j_.info()) <<
"Invalid requested validator list publisher key: " << pubKey;
return {};
}
auto id = PublicKey(makeSlice(*keyBlob));
auto iter = publisherLists_.find(id);
if (iter == publisherLists_.end()
|| !iter->second.available)
return {};
Json::Value value(Json::objectValue);
value["manifest"] = iter->second.rawManifest;
value["blob"] = iter->second.rawBlob;
value["signature"] = iter->second.rawSignature;
value["version"] = iter->second.rawVersion;
return value;
}
std::size_t
ValidatorList::calculateQuorum (
std::size_t trusted, std::size_t seen)

View File

@@ -25,6 +25,7 @@
#include <ripple/basics/base64.h>
#include <ripple/basics/Slice.h>
#include <ripple/json/json_reader.h>
#include <ripple/protocol/digest.h>
#include <ripple/protocol/jss.h>
#include <boost/algorithm/clamp.hpp>
#include <boost/regex.hpp>
@@ -116,10 +117,23 @@ ValidatorSite::~ValidatorSite()
}
}
bool
ValidatorSite::missingSite()
{
auto const sites = app_.validators().loadLists();
return sites.empty() || load(sites);
}
bool
ValidatorSite::load (
std::vector<std::string> const& siteURIs)
{
// If no sites are provided, act as if a site failed to load.
if (siteURIs.empty())
{
return missingSite();
}
JLOG (j_.debug()) <<
"Loading configured validator list sites";
@@ -374,54 +388,59 @@ ValidatorSite::parseJsonResponse (
throw std::runtime_error{"missing fields"};
}
auto const disp = app_.validators().applyList (
body["manifest"].asString (),
body["blob"].asString (),
body["signature"].asString(),
body["version"].asUInt(),
sites_[siteIdx].activeResource->uri);
auto const manifest = body["manifest"].asString ();
auto const blob = body["blob"].asString ();
auto const signature = body["signature"].asString();
auto const version = body["version"].asUInt();
auto const& uri = sites_[siteIdx].activeResource->uri;
auto const hash = sha512Half(manifest, blob, signature, version);
auto const applyResult = app_.validators().applyListAndBroadcast (
manifest,
blob,
signature,
version,
uri,
hash,
app_.overlay(),
app_.getHashRouter());
auto const disp = applyResult.disposition;
sites_[siteIdx].lastRefreshStatus.emplace(
Site::Status{clock_type::now(), disp, ""});
if (ListDisposition::accepted == disp)
switch (disp)
{
case ListDisposition::accepted:
JLOG (j_.debug()) <<
"Applied new validator list from " <<
sites_[siteIdx].activeResource->uri;
}
else if (ListDisposition::same_sequence == disp)
{
uri;
break;
case ListDisposition::same_sequence:
JLOG (j_.debug()) <<
"Validator list with current sequence from " <<
sites_[siteIdx].activeResource->uri;
}
else if (ListDisposition::stale == disp)
{
uri;
break;
case ListDisposition::stale:
JLOG (j_.warn()) <<
"Stale validator list from " <<
sites_[siteIdx].activeResource->uri;
}
else if (ListDisposition::untrusted == disp)
{
uri;
break;
case ListDisposition::untrusted:
JLOG (j_.warn()) <<
"Untrusted validator list from " <<
sites_[siteIdx].activeResource->uri;
}
else if (ListDisposition::invalid == disp)
{
uri;
break;
case ListDisposition::invalid:
JLOG (j_.warn()) <<
"Invalid validator list from " <<
sites_[siteIdx].activeResource->uri;
}
else if (ListDisposition::unsupported_version == disp)
{
uri;
break;
case ListDisposition::unsupported_version:
JLOG (j_.warn()) <<
"Unsupported version validator list from " <<
sites_[siteIdx].activeResource->uri;
}
else
{
uri;
break;
default:
BOOST_ASSERT(false);
}
@@ -509,6 +528,10 @@ ValidatorSite::onSiteFetch(
if (retry)
sites_[siteIdx].nextRefresh =
clock_type::now() + error_retry_interval;
// See if there's a copy saved locally from last time we
// saw the list.
missingSite();
};
if (ec)
{
@@ -592,7 +615,7 @@ ValidatorSite::onTextFetch(
sites_[siteIdx].activeResource->uri <<
" " <<
ec.value() <<
":" <<
": " <<
ec.message();
throw std::runtime_error{"fetch error"};
}

View File

@@ -27,13 +27,14 @@
namespace ripple
{
// TODO: Should this one function have its own file, or can it
// be absorbed somewhere else?
std::string getFileContents(boost::system::error_code& ec,
boost::filesystem::path const& sourcePath,
boost::optional<std::size_t> maxSize = boost::none);
void writeFileContents(boost::system::error_code& ec,
boost::filesystem::path const& destPath,
std::string const& contents);
}
#endif

View File

@@ -25,6 +25,7 @@
#include <boost/format.hpp>
#include <boost/optional.hpp>
#include <boost/utility/string_view.hpp>
#include <sstream>
#include <string>
@@ -63,7 +64,60 @@ inline static std::string sqlEscape (Blob const& vecSrc)
uint64_t uintFromHex (std::string const& strSrc);
boost::optional<Blob> strUnHex (std::string const& strSrc);
template <class Iterator>
boost::optional<Blob>
strUnHex(std::size_t strSize, Iterator begin, Iterator end)
{
Blob out;
out.reserve((strSize + 1) / 2);
auto iter = begin;
if (strSize & 1)
{
int c = charUnHex(*iter);
if (c < 0)
return {};
out.push_back(c);
++iter;
}
while (iter != end)
{
int cHigh = charUnHex(*iter);
++iter;
if (cHigh < 0)
return {};
int cLow = charUnHex(*iter);
++iter;
if (cLow < 0)
return {};
out.push_back(static_cast<unsigned char>((cHigh << 4) | cLow));
}
return {std::move(out)};
}
inline
boost::optional<Blob>
strUnHex (std::string const& strSrc)
{
return strUnHex(strSrc.size(), strSrc.cbegin(), strSrc.cend());
}
inline
boost::optional<Blob>
strViewUnHex (boost::string_view const& strSrc)
{
return strUnHex(strSrc.size(), strSrc.cbegin(), strSrc.cend());
}
struct parsedURL
{

View File

@@ -60,4 +60,28 @@ std::string getFileContents(boost::system::error_code& ec,
return result;
}
void writeFileContents(boost::system::error_code& ec,
boost::filesystem::path const& destPath,
std::string const& contents)
{
using namespace boost::filesystem;
using namespace boost::system::errc;
ofstream fileStream(destPath, std::ios::out | std::ios::trunc);
if (!fileStream)
{
ec = make_error_code(static_cast<errc_t>(errno));
return;
}
fileStream << contents;
if (fileStream.bad ())
{
ec = make_error_code(static_cast<errc_t>(errno));
return;
}
}
}

View File

@@ -30,45 +30,6 @@
namespace ripple {
boost::optional<Blob> strUnHex (std::string const& strSrc)
{
Blob out;
out.reserve ((strSrc.size () + 1) / 2);
auto iter = strSrc.cbegin ();
if (strSrc.size () & 1)
{
int c = charUnHex (*iter);
if (c < 0)
return {};
out.push_back(c);
++iter;
}
while (iter != strSrc.cend ())
{
int cHigh = charUnHex (*iter);
++iter;
if (cHigh < 0)
return {};
int cLow = charUnHex (*iter);
++iter;
if (cLow < 0)
return {};
out.push_back (static_cast<unsigned char>((cHigh << 4) | cLow));
}
return {std::move(out)};
}
uint64_t uintFromHex (std::string const& strSrc)
{
uint64_t uValue (0);

View File

@@ -80,6 +80,7 @@ public:
int ipLimit = 0;
std::uint32_t crawlOptions = 0;
boost::optional<std::uint32_t> networkID;
bool vlEnabled = true;
};
using PeerSequence = std::vector <std::shared_ptr<Peer>>;

View File

@@ -35,6 +35,10 @@ class Charge;
// Maximum hops to attempt when crawling shards. cs = crawl shards
static constexpr std::uint32_t csHopLimit = 3;
enum class ProtocolFeature {
ValidatorListPropagation,
};
/** Represents a peer connection in the overlay. */
class Peer
{
@@ -95,6 +99,17 @@ public:
virtual
Json::Value json() = 0;
virtual bool
supportsFeature(ProtocolFeature f) const = 0;
virtual
boost::optional<std::size_t>
publisherListSequence(PublicKey const&) const = 0;
virtual
void
setPublisherListSequence(PublicKey const&, std::size_t const) = 0;
//
// Ledger
//

View File

@@ -1016,7 +1016,7 @@ OverlayImpl::json ()
}
bool
OverlayImpl::processRequest (http_request_type const& req,
OverlayImpl::processCrawl (http_request_type const& req,
Handoff& handoff)
{
if (req.target() != "/crawl" || setup_.crawlOptions == CrawlOptions::Disabled)
@@ -1052,6 +1052,62 @@ OverlayImpl::processRequest (http_request_type const& req,
return true;
}
bool
OverlayImpl::processValidatorList (http_request_type const& req,
Handoff& handoff)
{
// If the target is in the form "/vl/<validator_list_public_key>",
// return the most recent validator list for that key.
if (!req.target().starts_with("/vl/") ||
!setup_.vlEnabled)
return false;
auto key = req.target();
if (key.starts_with("/vl/"))
key.remove_prefix(strlen("/vl/"));
else
key.remove_prefix(strlen("/unl/"));
if(key.empty())
return false;
// find the list
auto vl = app_.validators().getAvailable(key);
boost::beast::http::response<json_body> msg;
msg.version(req.version());
msg.insert("Server", BuildInfo::getFullVersionString());
msg.insert("Content-Type", "application/json");
msg.insert("Connection", "close");
if (!vl)
{
// 404 not found
msg.result(boost::beast::http::status::not_found);
msg.insert("Content-Length", "0");
msg.body() = Json::nullValue;
}
else
{
msg.result(boost::beast::http::status::ok);
msg.body() = *vl;
}
msg.prepare_payload();
handoff.response = std::make_shared<SimpleWriter>(msg);
return true;
}
bool
OverlayImpl::processRequest (http_request_type const& req,
Handoff& handoff)
{
// Take advantage of || short-circuiting
return processCrawl(req, handoff) ||
processValidatorList(req, handoff);
}
Overlay::PeerSequence
OverlayImpl::getActivePeers()
{
@@ -1331,6 +1387,11 @@ setup_Overlay (BasicConfig const& config)
}
}
}
{
auto const& section = config.section("vl");
set(setup.vlEnabled, "enabled", section);
}
try
{

View File

@@ -385,6 +385,30 @@ private:
http_request_type const& request, address_type remote_address,
std::string msg);
/** Handles crawl requests. Crawl returns information about the
node and its peers so crawlers can map the network.
@return true if the request was handled.
*/
bool
processCrawl (http_request_type const& req,
Handoff& handoff);
/** Handles validator list requests.
Using a /vl/<hex-encoded public key> URL, will retrieve the
latest valdiator list (or UNL) that this node has for that
public key, if the node trusts that public key.
@return true if the request was handled.
*/
bool
processValidatorList (http_request_type const& req,
Handoff& handoff);
/** Handles non-peer protocol requests.
@return true if the request was handled.
*/
bool
processRequest (http_request_type const& req,
Handoff& handoff);

View File

@@ -405,6 +405,17 @@ PeerImp::json()
return ret;
}
bool
PeerImp::supportsFeature(ProtocolFeature f) const
{
switch (f)
{
case ProtocolFeature::ValidatorListPropagation:
return protocol_ >= make_protocol(2, 1);
}
return false;
}
//------------------------------------------------------------------------------
bool
@@ -803,6 +814,36 @@ PeerImp::doProtocolStart()
{
onReadMessage(error_code(), 0);
// Send all the validator lists that have been loaded
if (supportsFeature(ProtocolFeature::ValidatorListPropagation))
{
app_.validators().for_each_available(
[&](std::string const& manifest,
std::string const& blob, std::string const& signature,
std::uint32_t version,
PublicKey const& pubKey, std::size_t sequence,
uint256 const& hash)
{
protocol::TMValidatorList vl;
vl.set_manifest(manifest);
vl.set_blob(blob);
vl.set_signature(signature);
vl.set_version(version);
JLOG(p_journal_.debug()) << "Sending validator list for " <<
strHex(pubKey) << " with sequence " <<
sequence << " to " <<
remote_address_.to_string() << " (" << id_ << ")";
auto m = std::make_shared<Message>(vl, protocol::mtVALIDATORLIST);
send(m);
// Don't send it next time.
app_.getHashRouter().addSuppressionPeer(hash, id_);
setPublisherListSequence(pubKey, sequence);
}
);
}
protocol::TMManifests tm;
app_.validatorManifests ().for_each_manifest (
@@ -1965,6 +2006,137 @@ PeerImp::onMessage (std::shared_ptr <protocol::TMHaveTransactionSet> const& m)
}
}
void
PeerImp::onMessage (std::shared_ptr <protocol::TMValidatorList> const& m)
{
try
{
if (!supportsFeature(ProtocolFeature::ValidatorListPropagation))
{
JLOG(p_journal_.debug())
<< "ValidatorList: received validator list from peer using "
<< "protocol version " << to_string(protocol_)
<< " which shouldn't support this feature.";
fee_ = Resource::feeUnwantedData;
return;
}
auto const& manifest = m->manifest();
auto const& blob = m->blob();
auto const& signature = m->signature();
auto const version = m->version();
auto const hash = sha512Half(manifest, blob, signature, version);
JLOG(p_journal_.debug()) << "Received validator list from " <<
remote_address_.to_string() << " (" << id_ << ")";
if (! app_.getHashRouter ().addSuppressionPeer(hash, id_))
{
JLOG(p_journal_.debug()) <<
"ValidatorList: received duplicate validator list";
// Charging this fee here won't hurt the peer in the normal
// course of operation (ie. refresh every 5 minutes), but
// will add up if the peer is misbehaving.
fee_ = Resource::feeUnwantedData;
return;
}
auto const applyResult = app_.validators().applyListAndBroadcast (
manifest,
blob,
signature,
version,
remote_address_.to_string(),
hash,
app_.overlay(),
app_.getHashRouter());
auto const disp = applyResult.disposition;
JLOG(p_journal_.debug()) << "Processed validator list from " <<
(applyResult.publisherKey ? strHex(*applyResult.publisherKey) :
"unknown or invalid publisher") << " from " <<
remote_address_.to_string() << " (" << id_ << ") with result " <<
to_string(disp);
switch (disp)
{
case ListDisposition::accepted:
JLOG (p_journal_.debug()) <<
"Applied new validator list from peer " << remote_address_;
{
std::lock_guard<std::mutex> sl(recentLock_);
assert(applyResult.sequence && applyResult.publisherKey);
auto const& pubKey = *applyResult.publisherKey;
#ifndef NDEBUG
if (auto const iter = publisherListSequences_.find(pubKey);
iter != publisherListSequences_.end())
{
assert(iter->second < *applyResult.sequence);
}
#endif
publisherListSequences_[pubKey] = *applyResult.sequence;
}
break;
case ListDisposition::same_sequence:
JLOG (p_journal_.warn()) <<
"Validator list with current sequence from peer " <<
remote_address_;
// Charging this fee here won't hurt the peer in the normal
// course of operation (ie. refresh every 5 minutes), but
// will add up if the peer is misbehaving.
fee_ = Resource::feeUnwantedData;
#ifndef NDEBUG
{
std::lock_guard<std::mutex> sl(recentLock_);
assert(applyResult.sequence && applyResult.publisherKey);
assert(publisherListSequences_[*applyResult.publisherKey]
== *applyResult.sequence);
}
#endif // !NDEBUG
break;
case ListDisposition::stale:
JLOG (p_journal_.warn()) <<
"Stale validator list from peer " << remote_address_;
// There are very few good reasons for a peer to send an
// old list, particularly more than once.
fee_ = Resource::feeBadData;
break;
case ListDisposition::untrusted:
JLOG (p_journal_.warn()) <<
"Untrusted validator list from peer " << remote_address_;
// Charging this fee here won't hurt the peer in the normal
// course of operation (ie. refresh every 5 minutes), but
// will add up if the peer is misbehaving.
fee_ = Resource::feeUnwantedData;
break;
case ListDisposition::invalid:
JLOG (p_journal_.warn()) <<
"Invalid validator list from peer " << remote_address_;
// This shouldn't ever happen with a well-behaved peer
fee_ = Resource::feeInvalidSignature;
break;
case ListDisposition::unsupported_version:
JLOG (p_journal_.warn()) <<
"Unsupported version validator list from peer " <<
remote_address_;
// During a version transition, this may be legitimate.
// If it happens frequently, that's probably bad.
fee_ = Resource::feeBadData;
break;
default:
assert(false);
}
}
catch (std::exception const& e)
{
JLOG(p_journal_.warn()) <<
"ValidatorList: Exception, " << e.what() <<
" from peer " << remote_address_;
fee_ = Resource::feeBadData;
}
}
void
PeerImp::onMessage (std::shared_ptr <protocol::TMValidation> const& m)
{

View File

@@ -194,6 +194,9 @@ private:
int large_sendq_ = 0;
int no_ping_ = 0;
std::unique_ptr <LoadEvent> load_event_;
// The highest sequence of each PublisherList that has
// been sent to or received from this peer.
hash_map<PublicKey, std::size_t> publisherListSequences_;
std::mutex mutable shardInfoMutex_;
hash_map<PublicKey, ShardInfo> shardInfo_;
@@ -342,6 +345,29 @@ public:
Json::Value
json() override;
bool
supportsFeature(ProtocolFeature f) const override;
boost::optional<std::size_t>
publisherListSequence(PublicKey const& pubKey) const override
{
std::lock_guard<std::mutex> sl(recentLock_);
auto iter = publisherListSequences_.find(pubKey);
if (iter != publisherListSequences_.end())
return iter->second;
return {};
}
void
setPublisherListSequence(PublicKey const& pubKey, std::size_t const seq)
override
{
std::lock_guard<std::mutex> sl(recentLock_);
publisherListSequences_[pubKey] = seq;
}
//
// Ledger
//
@@ -488,6 +514,7 @@ public:
void onMessage (std::shared_ptr <protocol::TMProposeSet> const& m);
void onMessage (std::shared_ptr <protocol::TMStatusChange> const& m);
void onMessage (std::shared_ptr <protocol::TMHaveTransactionSet> const& m);
void onMessage (std::shared_ptr <protocol::TMValidatorList> const& m);
void onMessage (std::shared_ptr <protocol::TMValidation> const& m);
void onMessage (std::shared_ptr <protocol::TMGetObjectByHash> const& m);

View File

@@ -56,6 +56,7 @@ protocolMessageName (int type)
case protocol::mtPROPOSE_LEDGER: return "propose";
case protocol::mtSTATUS_CHANGE: return "status";
case protocol::mtHAVE_SET: return "have_set";
case protocol::mtVALIDATORLIST: return "validator_list";
case protocol::mtVALIDATION: return "validation";
case protocol::mtGET_OBJECTS: return "get_objects";
default:
@@ -230,6 +231,9 @@ invokeProtocolMessage (Buffers const& buffers, Handler& handler)
case protocol::mtVALIDATION:
success = detail::invoke<protocol::TMValidation>(*header, buffers, handler);
break;
case protocol::mtVALIDATORLIST:
success = detail::invoke<protocol::TMValidatorList> (*header, buffers, handler);
break;
case protocol::mtGET_OBJECTS:
success = detail::invoke<protocol::TMGetObjectByHash>(*header, buffers, handler);
break;

View File

@@ -36,7 +36,8 @@ constexpr
ProtocolVersion const supportedProtocolList[]
{
{ 1, 2 },
{ 2, 0 }
{ 2, 0 },
{ 2, 1 }
};
// This ugly construct ensures that supportedProtocolList is sorted in strictly
@@ -130,10 +131,8 @@ parseProtocolVersions(boost::beast::string_view const& value)
}
boost::optional<ProtocolVersion>
negotiateProtocolVersion(boost::beast::string_view const& versions)
negotiateProtocolVersion(std::vector<ProtocolVersion> const& versions)
{
auto const them = parseProtocolVersions(versions);
boost::optional<ProtocolVersion> result;
// The protocol version we want to negotiate is the largest item in the
@@ -148,13 +147,21 @@ negotiateProtocolVersion(boost::beast::string_view const& versions)
};
std::set_intersection(
std::begin(them), std::end(them),
std::begin(versions), std::end(versions),
std::begin(supportedProtocolList), std::end(supportedProtocolList),
boost::make_function_output_iterator(pickVersion));
return result;
}
boost::optional<ProtocolVersion>
negotiateProtocolVersion(boost::beast::string_view const& versions)
{
auto const them = parseProtocolVersions(versions);
return negotiateProtocolVersion(them);
}
std::string const&
supportedProtocolVersions()
{

View File

@@ -37,6 +37,7 @@ namespace ripple {
using ProtocolVersion = std::pair<std::uint16_t, std::uint16_t>;
inline
constexpr
ProtocolVersion
make_protocol(std::uint16_t major, std::uint16_t minor)
{
@@ -62,6 +63,10 @@ to_string(ProtocolVersion const& p);
std::vector<ProtocolVersion>
parseProtocolVersions(boost::beast::string_view const& s);
/** Given a list of supported protocol versions, choose the one we prefer. */
boost::optional<ProtocolVersion>
negotiateProtocolVersion(std::vector<ProtocolVersion> const& versions);
/** Given a list of supported protocol versions, choose the one we prefer. */
boost::optional<ProtocolVersion>
negotiateProtocolVersion(boost::beast::string_view const& versions);

View File

@@ -46,6 +46,9 @@ TrafficCount::category TrafficCount::categorize (
if (type == protocol::mtTRANSACTION)
return TrafficCount::category::transaction;
if (type == protocol::mtVALIDATORLIST)
return TrafficCount::category::validatorlist;
if (type == protocol::mtVALIDATION)
return TrafficCount::category::validation;

View File

@@ -75,6 +75,7 @@ public:
transaction,
proposal,
validation,
validatorlist,
shards, // shard-related traffic
// TMHaveSet message:
@@ -189,6 +190,7 @@ protected:
{"transactions"}, // category::transaction
{"proposals"}, // category::proposal
{"validations"}, // category::validation
{"validator_lists"}, // category::validatorlist
{"shards"}, // category::shards
{"set_get"}, // category::get_set
{"set_share"}, // category::share_set

View File

@@ -22,6 +22,7 @@ enum MessageType
mtSHARD_INFO = 51;
mtGET_PEER_SHARD_INFO = 52;
mtPEER_SHARD_INFO = 53;
mtVALIDATORLIST = 54;
}
// token, iterations, target, challenge = issue demand for proof of work
@@ -197,6 +198,14 @@ message TMHaveTransactionSet
required bytes hash = 2;
}
// Validator list
message TMValidatorList
{
required bytes manifest = 1;
required bytes blob = 2;
required bytes signature = 3;
required uint32 version = 4;
}
// Used to sign a final closed ledger after reprocessing
message TMValidation

View File

@@ -254,8 +254,11 @@ public:
sort (getPopulatedManifests (m)));
jtx::Env env (*this);
auto& app = env.app();
auto unl = std::make_unique<ValidatorList> (
m, m, env.timeKeeper(), env.journal);
m, m, env.timeKeeper(),
app.config().legacy("database_path"),
env.journal);
{
// save should not store untrusted master keys to db

View File

@@ -163,15 +163,20 @@ private:
ManifestCache manifests;
jtx::Env env (*this);
auto& app = env.app();
{
auto trustedKeys = std::make_unique <ValidatorList> (
manifests, manifests, env.timeKeeper(), env.journal);
manifests, manifests, env.timeKeeper(),
app.config().legacy("database_path"),
env.journal);
BEAST_EXPECT(trustedKeys->quorum () == 1);
}
{
std::size_t minQuorum = 0;
auto trustedKeys = std::make_unique <ValidatorList> (
manifests, manifests, env.timeKeeper(), env.journal, minQuorum);
manifests, manifests, env.timeKeeper(),
app.config().legacy("database_path"),
env.journal, minQuorum);
BEAST_EXPECT(trustedKeys->quorum () == minQuorum);
}
}
@@ -182,6 +187,7 @@ private:
testcase ("Config Load");
jtx::Env env (*this);
auto& app = env.app();
PublicKey emptyLocalKey;
std::vector<std::string> const emptyCfgKeys;
std::vector<std::string> const emptyCfgPublishers;
@@ -230,7 +236,9 @@ private:
{
ManifestCache manifests;
auto trustedKeys = std::make_unique <ValidatorList> (
manifests, manifests, env.timeKeeper(), env.journal);
manifests, manifests, env.timeKeeper(),
app.config().legacy("database_path"),
env.journal);
// Correct (empty) configuration
BEAST_EXPECT(trustedKeys->load (
@@ -252,7 +260,9 @@ private:
// load should add validator keys from config
ManifestCache manifests;
auto trustedKeys = std::make_unique <ValidatorList> (
manifests, manifests, env.timeKeeper(), env.journal);
manifests, manifests, env.timeKeeper(),
app.config().legacy("database_path"),
env.journal);
BEAST_EXPECT(trustedKeys->load (
emptyLocalKey, cfgKeys, emptyCfgPublishers));
@@ -290,7 +300,9 @@ private:
// local validator key on config list
ManifestCache manifests;
auto trustedKeys = std::make_unique <ValidatorList> (
manifests, manifests, env.timeKeeper(), env.journal);
manifests, manifests, env.timeKeeper(),
app.config().legacy("database_path"),
env.journal);
auto const localSigningPublic = parseBase58<PublicKey> (
TokenType::NodePublic, cfgKeys.front());
@@ -307,7 +319,9 @@ private:
// local validator key not on config list
ManifestCache manifests;
auto trustedKeys = std::make_unique <ValidatorList> (
manifests, manifests, env.timeKeeper(), env.journal);
manifests, manifests, env.timeKeeper(),
app.config().legacy("database_path"),
env.journal);
auto const localSigningPublic = randomNode();
BEAST_EXPECT(trustedKeys->load (
@@ -322,7 +336,9 @@ private:
// local validator key (with manifest) not on config list
ManifestCache manifests;
auto trustedKeys = std::make_unique <ValidatorList> (
manifests, manifests, env.timeKeeper(), env.journal);
manifests, manifests, env.timeKeeper(),
app.config().legacy("database_path"),
env.journal);
manifests.applyManifest (*deserializeManifest(cfgManifest));
@@ -338,7 +354,9 @@ private:
{
ManifestCache manifests;
auto trustedKeys = std::make_unique <ValidatorList> (
manifests, manifests, env.timeKeeper(), env.journal);
manifests, manifests, env.timeKeeper(),
app.config().legacy("database_path"),
env.journal);
// load should reject invalid validator list signing keys
std::vector<std::string> badPublishers(
@@ -375,7 +393,9 @@ private:
ManifestCache valManifests;
ManifestCache pubManifests;
auto trustedKeys = std::make_unique <ValidatorList> (
valManifests, pubManifests, env.timeKeeper(), env.journal);
valManifests, pubManifests, env.timeKeeper(),
app.config().legacy("database_path"),
env.journal);
auto const pubRevokedSecret = randomSecretKey();
auto const pubRevokedPublic =
@@ -414,8 +434,11 @@ private:
ManifestCache manifests;
jtx::Env env (*this);
auto& app = env.app();
auto trustedKeys = std::make_unique<ValidatorList> (
manifests, manifests, env.app().timeKeeper(), env.journal);
manifests, manifests, env.app().timeKeeper(),
app.config().legacy("database_path"),
env.journal);
auto const publisherSecret = randomSecretKey();
auto const publisherPublic =
@@ -453,7 +476,8 @@ private:
BEAST_EXPECT(ListDisposition::stale ==
trustedKeys->applyList (
manifest1, expiredblob, expiredSig, version, siteUri));
manifest1, expiredblob, expiredSig,
version, siteUri).disposition);
// apply single list
using namespace std::chrono_literals;
@@ -463,8 +487,9 @@ private:
list1, sequence, expiration.time_since_epoch().count());
auto const sig1 = signList (blob1, pubSigningKeys1);
BEAST_EXPECT(ListDisposition::accepted == trustedKeys->applyList (
manifest1, blob1, sig1, version, siteUri));
BEAST_EXPECT(ListDisposition::accepted ==
trustedKeys->applyList ( manifest1, blob1,
sig1, version, siteUri).disposition);
for (auto const& val : list1)
{
@@ -479,13 +504,13 @@ private:
pubSigningKeys1.first, pubSigningKeys1.second, 1));
BEAST_EXPECT(ListDisposition::untrusted == trustedKeys->applyList (
untrustedManifest, blob1, sig1, version, siteUri));
untrustedManifest, blob1, sig1, version, siteUri).disposition);
// do not use list with unhandled version
auto const badVersion = 666;
BEAST_EXPECT(ListDisposition::unsupported_version ==
trustedKeys->applyList (
manifest1, blob1, sig1, badVersion, siteUri));
manifest1, blob1, sig1, badVersion, siteUri).disposition);
// apply list with highest sequence number
auto const sequence2 = 2;
@@ -495,7 +520,7 @@ private:
BEAST_EXPECT(ListDisposition::accepted ==
trustedKeys->applyList (
manifest1, blob2, sig2, version, siteUri));
manifest1, blob2, sig2, version, siteUri).disposition);
for (auto const& val : list1)
{
@@ -512,11 +537,11 @@ private:
// do not re-apply lists with past or current sequence numbers
BEAST_EXPECT(ListDisposition::stale ==
trustedKeys->applyList (
manifest1, blob1, sig1, version, siteUri));
manifest1, blob1, sig1, version, siteUri).disposition);
BEAST_EXPECT(ListDisposition::same_sequence ==
trustedKeys->applyList (
manifest1, blob2, sig2, version, siteUri));
manifest1, blob2, sig2, version, siteUri).disposition);
// apply list with new publisher key updated by manifest
auto const pubSigningKeys2 = randomKeyPair(KeyType::secp256k1);
@@ -531,7 +556,7 @@ private:
BEAST_EXPECT(ListDisposition::accepted ==
trustedKeys->applyList (
manifest2, blob3, sig3, version, siteUri));
manifest2, blob3, sig3, version, siteUri).disposition);
auto const sequence4 = 4;
auto const blob4 = makeList (
@@ -539,7 +564,7 @@ private:
auto const badSig = signList (blob4, pubSigningKeys1);
BEAST_EXPECT(ListDisposition::invalid ==
trustedKeys->applyList (
manifest1, blob4, badSig, version, siteUri));
manifest1, blob4, badSig, version, siteUri).disposition);
// do not apply list with revoked publisher key
// applied list is removed due to revoked publisher key
@@ -554,7 +579,7 @@ private:
BEAST_EXPECT(ListDisposition::untrusted ==
trustedKeys->applyList (
maxManifest, blob5, sig5, version, siteUri));
maxManifest, blob5, sig5, version, siteUri).disposition);
BEAST_EXPECT(! trustedKeys->trustedPublisher(publisherPublic));
for (auto const& val : list1)
@@ -574,8 +599,11 @@ private:
PublicKey emptyLocalKeyOuter;
ManifestCache manifestsOuter;
jtx::Env env (*this);
auto& app = env.app();
auto trustedKeysOuter = std::make_unique <ValidatorList> (
manifestsOuter, manifestsOuter, env.timeKeeper(), env.journal);
manifestsOuter, manifestsOuter, env.timeKeeper(),
app.config().legacy("database_path"),
env.journal);
std::vector<std::string> cfgPublishersOuter;
hash_set<NodeID> activeValidatorsOuter;
@@ -722,7 +750,9 @@ private:
{
// Make quorum unattainable if lists from any publishers are unavailable
auto trustedKeys = std::make_unique <ValidatorList> (
manifestsOuter, manifestsOuter, env.timeKeeper(), env.journal);
manifestsOuter, manifestsOuter, env.timeKeeper(),
app.config().legacy("database_path"),
env.journal);
auto const publisherSecret = randomSecretKey();
auto const publisherPublic =
derivePublicKey(KeyType::ed25519, publisherSecret);
@@ -746,7 +776,9 @@ private:
std::size_t const minQuorum = 1;
ManifestCache manifests;
auto trustedKeys = std::make_unique <ValidatorList> (
manifests, manifests, env.timeKeeper(), env.journal, minQuorum);
manifests, manifests, env.timeKeeper(),
app.config().legacy("database_path"),
env.journal, minQuorum);
std::size_t n = 10;
std::vector<std::string> cfgKeys;
@@ -786,7 +818,9 @@ private:
{
// Remove expired published list
auto trustedKeys = std::make_unique<ValidatorList> (
manifestsOuter, manifestsOuter, env.app().timeKeeper(), env.journal);
manifestsOuter, manifestsOuter, env.app().timeKeeper(),
app.config().legacy("database_path"),
env.journal);
PublicKey emptyLocalKey;
std::vector<std::string> emptyCfgKeys;
@@ -819,7 +853,7 @@ private:
BEAST_EXPECT(ListDisposition::accepted ==
trustedKeys->applyList (
manifest, blob, sig, version, siteUri));
manifest, blob, sig, version, siteUri).disposition);
TrustChanges changes =
trustedKeys->updateTrusted(activeValidators);
@@ -853,7 +887,7 @@ private:
BEAST_EXPECT(ListDisposition::accepted ==
trustedKeys->applyList (
manifest, blob2, sig2, version, siteUri));
manifest, blob2, sig2, version, siteUri).disposition);
changes = trustedKeys->updateTrusted (activeValidators);
BEAST_EXPECT(changes.removed.empty());
@@ -872,7 +906,9 @@ private:
{
// Test 1-9 configured validators
auto trustedKeys = std::make_unique <ValidatorList> (
manifestsOuter, manifestsOuter, env.timeKeeper(), env.journal);
manifestsOuter, manifestsOuter, env.timeKeeper(),
app.config().legacy("database_path"),
env.journal);
std::vector<std::string> cfgPublishers;
hash_set<NodeID> activeValidators;
@@ -903,7 +939,9 @@ private:
{
// Test 2-9 configured validators as validator
auto trustedKeys = std::make_unique <ValidatorList> (
manifestsOuter, manifestsOuter, env.timeKeeper(), env.journal);
manifestsOuter, manifestsOuter, env.timeKeeper(),
app.config().legacy("database_path"),
env.journal);
auto const localKey = randomNode();
std::vector<std::string> cfgPublishers;
@@ -943,7 +981,9 @@ private:
// Trusted set should include all validators from multiple lists
ManifestCache manifests;
auto trustedKeys = std::make_unique <ValidatorList> (
manifests, manifests, env.timeKeeper(), env.journal);
manifests, manifests, env.timeKeeper(),
app.config().legacy("database_path"),
env.journal);
hash_set<NodeID> activeValidators;
std::vector<Validator> valKeys;
@@ -983,8 +1023,9 @@ private:
valKeys, sequence, expiration.time_since_epoch().count());
auto const sig = signList (blob, pubSigningKeys);
BEAST_EXPECT(ListDisposition::accepted == trustedKeys->applyList (
manifest, blob, sig, version, siteUri));
BEAST_EXPECT(ListDisposition::accepted ==
trustedKeys->applyList (manifest, blob, sig, version,
siteUri).disposition);
};
// Apply multiple published lists
@@ -1016,6 +1057,7 @@ private:
std::string const siteUri = "testExpires.test";
jtx::Env env(*this);
auto& app = env.app();
auto toStr = [](PublicKey const& publicKey) {
return toBase58(TokenType::NodePublic, publicKey);
@@ -1025,7 +1067,9 @@ private:
{
ManifestCache manifests;
auto trustedKeys = std::make_unique<ValidatorList>(
manifests, manifests, env.timeKeeper(), env.journal);
manifests, manifests, env.timeKeeper(),
app.config().legacy("database_path"),
env.journal);
// Empty list has no expiration
BEAST_EXPECT(trustedKeys->expires() == boost::none);
@@ -1044,7 +1088,9 @@ private:
{
ManifestCache manifests;
auto trustedKeys = std::make_unique<ValidatorList>(
manifests, manifests, env.app().timeKeeper(), env.journal);
manifests, manifests, env.app().timeKeeper(),
app.config().legacy("database_path"),
env.journal);
std::vector<Validator> validators = {randomValidator()};
hash_set<NodeID> activeValidators;
@@ -1104,7 +1150,8 @@ private:
// Apply first list
BEAST_EXPECT(
ListDisposition::accepted == trustedKeys->applyList(
prep1.manifest, prep1.blob, prep1.sig, prep1.version, siteUri));
prep1.manifest, prep1.blob, prep1.sig,
prep1.version, siteUri).disposition);
// One list still hasn't published, so expiration is still unknown
BEAST_EXPECT(trustedKeys->expires() == boost::none);
@@ -1112,7 +1159,8 @@ private:
// Apply second list
BEAST_EXPECT(
ListDisposition::accepted == trustedKeys->applyList(
prep2.manifest, prep2.blob, prep2.sig, prep2.version, siteUri));
prep2.manifest, prep2.blob, prep2.sig,
prep2.version, siteUri).disposition);
// We now have loaded both lists, so expiration is known
BEAST_EXPECT(

View File

@@ -36,11 +36,14 @@ public:
"This file is very short. That's all we need.";
FileDirGuard file(*this, "test_file", "test.txt",
expectedContents);
"This is temporary text that should get overwritten");
error_code ec;
auto const path = file.file();
writeFileContents(ec, path, expectedContents);
BEAST_EXPECT(!ec);
{
// Test with no max
auto const good = getFileContents(ec, path);