mirror of
https://github.com/Xahau/xahaud.git
synced 2025-11-20 10:35:50 +00:00
Fetch validator lists from remote sites:
Validator lists from configured remote sites are fetched at a regular interval. Fetched lists are expected to be in JSON format and contain the following fields: * "manifest": Base64-encoded serialization of a manifest containing the validator publisher's master and signing public keys. * "blob": Base64-encoded JSON string containing a "sequence", "expiration" and "validators" field. "expiration" contains the Ripple timestamp (seconds since January 1st, 2000 (00:00 UTC)) for when the list expires. "validators" contains an array of objects with a "validation_public_key" field. * "signature": Hex-encoded signature of the blob using the publisher's signing key. * "version": 1 * "refreshInterval" (optional)
This commit is contained in:
@@ -1035,6 +1035,14 @@
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ripple\app\misc\CanonicalTXSet.h">
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\app\misc\detail\Work.h">
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\app\misc\detail\WorkBase.h">
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\app\misc\detail\WorkPlain.h">
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\app\misc\detail\WorkSSL.h">
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\app\misc\FeeVote.h">
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ripple\app\misc\FeeVoteImpl.cpp">
|
||||
@@ -1077,6 +1085,10 @@
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\app\misc\impl\ValidatorSite.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ripple\app\misc\LoadFeeTrack.h">
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\app\misc\Manifest.h">
|
||||
@@ -1109,6 +1121,8 @@
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\app\misc\ValidatorList.h">
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\app\misc\ValidatorSite.h">
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ripple\app\paths\AccountCurrencies.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
@@ -4275,6 +4289,10 @@
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\test\app\ValidatorSite_test.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\test\basics\base_uint_test.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
|
||||
@@ -121,6 +121,9 @@
|
||||
<Filter Include="ripple\app\misc">
|
||||
<UniqueIdentifier>{5A1509B2-871B-A7AC-1E60-544D3F398741}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="ripple\app\misc\detail">
|
||||
<UniqueIdentifier>{2919FCCC-A707-22B8-FFB4-89494A8AC070}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="ripple\app\misc\impl">
|
||||
<UniqueIdentifier>{C4BDB9F8-7DB7-E304-D286-098085D5D16E}</UniqueIdentifier>
|
||||
</Filter>
|
||||
@@ -1545,6 +1548,18 @@
|
||||
<ClInclude Include="..\..\src\ripple\app\misc\CanonicalTXSet.h">
|
||||
<Filter>ripple\app\misc</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\app\misc\detail\Work.h">
|
||||
<Filter>ripple\app\misc\detail</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\app\misc\detail\WorkBase.h">
|
||||
<Filter>ripple\app\misc\detail</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\app\misc\detail\WorkPlain.h">
|
||||
<Filter>ripple\app\misc\detail</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\app\misc\detail\WorkSSL.h">
|
||||
<Filter>ripple\app\misc\detail</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\app\misc\FeeVote.h">
|
||||
<Filter>ripple\app\misc</Filter>
|
||||
</ClInclude>
|
||||
@@ -1581,6 +1596,9 @@
|
||||
<ClCompile Include="..\..\src\ripple\app\misc\impl\ValidatorList.cpp">
|
||||
<Filter>ripple\app\misc\impl</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\app\misc\impl\ValidatorSite.cpp">
|
||||
<Filter>ripple\app\misc\impl</Filter>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ripple\app\misc\LoadFeeTrack.h">
|
||||
<Filter>ripple\app\misc</Filter>
|
||||
</ClInclude>
|
||||
@@ -1620,6 +1638,9 @@
|
||||
<ClInclude Include="..\..\src\ripple\app\misc\ValidatorList.h">
|
||||
<Filter>ripple\app\misc</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\app\misc\ValidatorSite.h">
|
||||
<Filter>ripple\app\misc</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ripple\app\paths\AccountCurrencies.cpp">
|
||||
<Filter>ripple\app\paths</Filter>
|
||||
</ClCompile>
|
||||
@@ -5040,6 +5061,9 @@
|
||||
<ClCompile Include="..\..\src\test\app\ValidatorList_test.cpp">
|
||||
<Filter>test\app</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\test\app\ValidatorSite_test.cpp">
|
||||
<Filter>test\app</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\test\basics\base_uint_test.cpp">
|
||||
<Filter>test\basics</Filter>
|
||||
</ClCompile>
|
||||
|
||||
@@ -607,13 +607,15 @@
|
||||
# needed to accept consensus.
|
||||
#
|
||||
# The contents of the file should include a [validators] and/or
|
||||
# [validator_list_keys] entries.
|
||||
# [validator_list_sites] and [validator_list_keys] entries.
|
||||
# [validators] should be followed by a list of validation public keys of
|
||||
# nodes, one per line.
|
||||
# [validator_list_sites] should be followed by a list of URIs each serving a
|
||||
# list of recommended validators.
|
||||
# [validator_list_keys] should be followed by a list of keys belonging to
|
||||
# trusted validator list publishers. Validator lists will only be
|
||||
# considered if the list is accompanied by a valid signature from a trusted
|
||||
# publisher key.
|
||||
# trusted validator list publishers. Validator lists fetched from configured
|
||||
# sites will only be considered if the list is accompanied by a valid
|
||||
# signature from a trusted publisher key.
|
||||
#
|
||||
# Specify the file by its name or path.
|
||||
# Unless an absolute path is specified, it will be considered relative to
|
||||
|
||||
@@ -23,13 +23,20 @@
|
||||
# n9KorY8QtTdRx7TVDpwnG9NvyxsDwHUKUEeDLY3AkiGncVaSXZi5
|
||||
# n9MqiExBcoG19UXwoLjBJnhsxEhAZMuWwJDRdkyDz1EkEkwzQTNt
|
||||
#
|
||||
# [validator_list_sites]
|
||||
#
|
||||
# List of URIs serving lists of recommended validators.
|
||||
#
|
||||
# Examples:
|
||||
# https://ripple.com/validators
|
||||
# http://127.0.0.1:8000
|
||||
#
|
||||
# [validator_list_keys]
|
||||
#
|
||||
# List of keys belonging to trusted validator list publishers.
|
||||
# Validator lists will only be considered if the list is accompanied by a
|
||||
# valid signature from a trusted publisher key.
|
||||
# Validator lists fetched from configured sites will only be considered
|
||||
# if the list is accompanied by a valid signature from a trusted
|
||||
# publisher key.
|
||||
# Validator list keys should be hex-encoded.
|
||||
#
|
||||
# Examples:
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
#include <ripple/app/misc/TxQ.h>
|
||||
#include <ripple/app/misc/Validations.h>
|
||||
#include <ripple/app/misc/ValidatorList.h>
|
||||
#include <ripple/app/misc/ValidatorSite.h>
|
||||
#include <ripple/app/paths/Pathfinder.h>
|
||||
#include <ripple/app/paths/PathRequests.h>
|
||||
#include <ripple/app/tx/apply.h>
|
||||
@@ -351,6 +352,7 @@ public:
|
||||
std::unique_ptr <ManifestCache> validatorManifests_;
|
||||
std::unique_ptr <ManifestCache> publisherManifests_;
|
||||
std::unique_ptr <ValidatorList> validators_;
|
||||
std::unique_ptr <ValidatorSite> validatorSites_;
|
||||
std::unique_ptr <ServerHandler> serverHandler_;
|
||||
std::unique_ptr <AmendmentTable> m_amendmentTable;
|
||||
std::unique_ptr <LoadFeeTrack> mFeeTrack;
|
||||
@@ -486,6 +488,9 @@ public:
|
||||
*validatorManifests_, *publisherManifests_, *timeKeeper_,
|
||||
logs_->journal("ValidatorList"), config_->VALIDATION_QUORUM))
|
||||
|
||||
, validatorSites_ (std::make_unique<ValidatorSite> (
|
||||
get_io_service (), *validators_, logs_->journal("ValidatorSite")))
|
||||
|
||||
, serverHandler_ (make_ServerHandler (*this, *m_networkOPs, get_io_service (),
|
||||
*m_jobQueue, *m_networkOPs, *m_resourceManager, *m_collectorManager))
|
||||
|
||||
@@ -701,6 +706,11 @@ public:
|
||||
return *validators_;
|
||||
}
|
||||
|
||||
ValidatorSite& validatorSites () override
|
||||
{
|
||||
return *validatorSites_;
|
||||
}
|
||||
|
||||
ManifestCache& validatorManifests() override
|
||||
{
|
||||
return *validatorManifests_;
|
||||
@@ -866,6 +876,8 @@ public:
|
||||
|
||||
mValidations->flush ();
|
||||
|
||||
validatorSites_->stop ();
|
||||
|
||||
// TODO Store manifests in manifests.sqlite instead of wallet.db
|
||||
validatorManifests_->save (getWalletDB (), "ValidatorManifests",
|
||||
[this](PublicKey const& pubKey)
|
||||
@@ -1112,6 +1124,14 @@ bool ApplicationImp::setup()
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!validatorSites_->load (
|
||||
config().section (SECTION_VALIDATOR_LIST_SITES).values ()))
|
||||
{
|
||||
JLOG(m_journal.fatal()) <<
|
||||
"Invalid entry in [" << SECTION_VALIDATOR_LIST_SITES << "]";
|
||||
return false;
|
||||
}
|
||||
|
||||
m_nodeStore->tune (config_->getSize (siNodeCacheSize), config_->getSize (siNodeCacheAge));
|
||||
m_ledgerMaster->tune (config_->getSize (siLedgerSize), config_->getSize (siLedgerAge));
|
||||
family().treecache().setTargetSize (config_->getSize (siTreeCacheSize));
|
||||
@@ -1133,6 +1153,8 @@ bool ApplicationImp::setup()
|
||||
*config_);
|
||||
add (*m_overlay); // add to PropertyStream
|
||||
|
||||
validatorSites_->start ();
|
||||
|
||||
// start first consensus round
|
||||
if (! m_networkOPs->beginConsensus(m_ledgerMaster->getClosedLedger()->info().hash))
|
||||
{
|
||||
|
||||
@@ -64,6 +64,7 @@ class TransactionMaster;
|
||||
class TxQ;
|
||||
class Validations;
|
||||
class ValidatorList;
|
||||
class ValidatorSite;
|
||||
class Cluster;
|
||||
|
||||
class DatabaseCon;
|
||||
@@ -121,6 +122,7 @@ public:
|
||||
virtual Overlay& overlay () = 0;
|
||||
virtual TxQ& getTxQ() = 0;
|
||||
virtual ValidatorList& validators () = 0;
|
||||
virtual ValidatorSite& validatorSites () = 0;
|
||||
virtual ManifestCache& validatorManifests () = 0;
|
||||
virtual ManifestCache& publisherManifests () = 0;
|
||||
virtual Cluster& cluster () = 0;
|
||||
|
||||
169
src/ripple/app/misc/ValidatorSite.h
Normal file
169
src/ripple/app/misc/ValidatorSite.h
Normal file
@@ -0,0 +1,169 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2016 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_APP_MISC_VALIDATORSITE_H_INCLUDED
|
||||
#define RIPPLE_APP_MISC_VALIDATORSITE_H_INCLUDED
|
||||
|
||||
#include <ripple/app/misc/ValidatorList.h>
|
||||
#include <ripple/app/misc/detail/Work.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/basics/StringUtilities.h>
|
||||
#include <boost/asio.hpp>
|
||||
#include <mutex>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
/**
|
||||
Validator Sites
|
||||
---------------
|
||||
|
||||
This class manages the set of configured remote sites used to fetch the
|
||||
latest published recommended validator lists.
|
||||
|
||||
Lists are fetched at a regular interval.
|
||||
Fetched lists are expected to be in JSON format and contain the following
|
||||
fields:
|
||||
|
||||
@li @c "blob": Base64-encoded JSON string containing a @c "sequence", @c
|
||||
"expiration", and @c "validators" field. @c "expiration" contains the
|
||||
Ripple timestamp (seconds since January 1st, 2000 (00:00 UTC)) for when
|
||||
the list expires. @c "validators" contains an array of objects with a
|
||||
@c "validation_public_key" field.
|
||||
@c "validation_public_key" should be the hex-encoded master public key.
|
||||
|
||||
@li @c "manifest": Base64-encoded serialization of a manifest containing the
|
||||
publisher's master and signing public keys.
|
||||
|
||||
@li @c "signature": Hex-encoded signature of the blob using the publisher's
|
||||
signing key.
|
||||
|
||||
@li @c "version": 1
|
||||
|
||||
@li @c "refreshInterval" (optional)
|
||||
*/
|
||||
class ValidatorSite
|
||||
{
|
||||
friend class Work;
|
||||
|
||||
private:
|
||||
using error_code = boost::system::error_code;
|
||||
using clock_type = std::chrono::system_clock;
|
||||
|
||||
struct Site
|
||||
{
|
||||
std::string uri;
|
||||
parsedURL pUrl;
|
||||
std::chrono::minutes refreshInterval;
|
||||
clock_type::time_point nextRefresh;
|
||||
};
|
||||
|
||||
boost::asio::io_service& ios_;
|
||||
ValidatorList& validators_;
|
||||
beast::Journal j_;
|
||||
std::mutex mutable sites_mutex_;
|
||||
std::mutex mutable state_mutex_;
|
||||
|
||||
std::condition_variable cv_;
|
||||
std::weak_ptr<detail::Work> work_;
|
||||
boost::asio::basic_waitable_timer<clock_type> timer_;
|
||||
|
||||
// A list is currently being fetched from a site
|
||||
std::atomic<bool> fetching_;
|
||||
|
||||
// One or more lists are due to be fetched
|
||||
std::atomic<bool> pending_;
|
||||
std::atomic<bool> stopping_;
|
||||
|
||||
// The configured list of URIs for fetching lists
|
||||
std::vector<Site> sites_;
|
||||
|
||||
public:
|
||||
ValidatorSite (
|
||||
boost::asio::io_service& ios,
|
||||
ValidatorList& validators,
|
||||
beast::Journal j);
|
||||
~ValidatorSite ();
|
||||
|
||||
/** Load configured site URIs.
|
||||
|
||||
@param siteURIs List of URIs to fetch published validator lists
|
||||
|
||||
@par Thread Safety
|
||||
|
||||
May be called concurrently
|
||||
|
||||
@return `false` if an entry is invalid or unparsable
|
||||
*/
|
||||
bool
|
||||
load (
|
||||
std::vector<std::string> const& siteURIs);
|
||||
|
||||
/** Start fetching lists from sites
|
||||
|
||||
This does nothing if list fetching has already started
|
||||
|
||||
@par Thread Safety
|
||||
|
||||
May be called concurrently
|
||||
*/
|
||||
void
|
||||
start ();
|
||||
|
||||
/** Wait for current fetches from sites to complete
|
||||
|
||||
@par Thread Safety
|
||||
|
||||
May be called concurrently
|
||||
*/
|
||||
void
|
||||
join ();
|
||||
|
||||
/** Stop fetching lists from sites
|
||||
|
||||
This blocks until list fetching has stopped
|
||||
|
||||
@par Thread Safety
|
||||
|
||||
May be called concurrently
|
||||
*/
|
||||
void
|
||||
stop ();
|
||||
|
||||
private:
|
||||
/// Queue next site to be fetched
|
||||
void
|
||||
setTimer ();
|
||||
|
||||
/// Fetch site whose time has come
|
||||
void
|
||||
onTimer (
|
||||
std::size_t siteIdx,
|
||||
error_code const& ec);
|
||||
|
||||
/// Store latest list fetched from site
|
||||
void
|
||||
onSiteFetch (
|
||||
boost::system::error_code const& ec,
|
||||
detail::response_type&& res,
|
||||
std::size_t siteIdx);
|
||||
};
|
||||
|
||||
} // ripple
|
||||
|
||||
#endif
|
||||
47
src/ripple/app/misc/detail/Work.h
Normal file
47
src/ripple/app/misc/detail/Work.h
Normal file
@@ -0,0 +1,47 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2016 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_APP_MISC_DETAIL_WORK_H_INCLUDED
|
||||
#define RIPPLE_APP_MISC_DETAIL_WORK_H_INCLUDED
|
||||
|
||||
#include <beast/http/message.hpp>
|
||||
#include <beast/http/string_body.hpp>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
namespace detail {
|
||||
|
||||
using response_type =
|
||||
beast::http::response<beast::http::string_body>;
|
||||
|
||||
class Work
|
||||
{
|
||||
public:
|
||||
virtual ~Work() = default;
|
||||
|
||||
virtual void run() = 0;
|
||||
|
||||
virtual void cancel() = 0;
|
||||
};
|
||||
|
||||
} // detail
|
||||
|
||||
} // ripple
|
||||
|
||||
#endif
|
||||
222
src/ripple/app/misc/detail/WorkBase.h
Normal file
222
src/ripple/app/misc/detail/WorkBase.h
Normal file
@@ -0,0 +1,222 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2016 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_APP_MISC_DETAIL_WORKBASE_H_INCLUDED
|
||||
#define RIPPLE_APP_MISC_DETAIL_WORKBASE_H_INCLUDED
|
||||
|
||||
#include <ripple/app/misc/detail/Work.h>
|
||||
#include <ripple/protocol/BuildInfo.h>
|
||||
#include <beast/core/placeholders.hpp>
|
||||
#include <beast/core/streambuf.hpp>
|
||||
#include <beast/http/empty_body.hpp>
|
||||
#include <beast/http/read.hpp>
|
||||
#include <beast/http/write.hpp>
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <class Impl>
|
||||
class WorkBase
|
||||
: public Work
|
||||
{
|
||||
protected:
|
||||
using error_code = boost::system::error_code;
|
||||
|
||||
public:
|
||||
using callback_type =
|
||||
std::function<void(error_code const&, response_type&&)>;
|
||||
protected:
|
||||
using socket_type = boost::asio::ip::tcp::socket;
|
||||
using endpoint_type = boost::asio::ip::tcp::endpoint;
|
||||
using resolver_type = boost::asio::ip::tcp::resolver;
|
||||
using query_type = resolver_type::query;
|
||||
using request_type =
|
||||
beast::http::request<beast::http::empty_body>;
|
||||
|
||||
std::string host_;
|
||||
std::string path_;
|
||||
std::string port_;
|
||||
callback_type cb_;
|
||||
boost::asio::io_service& ios_;
|
||||
boost::asio::io_service::strand strand_;
|
||||
resolver_type resolver_;
|
||||
socket_type socket_;
|
||||
request_type req_;
|
||||
response_type res_;
|
||||
beast::streambuf read_buf_;
|
||||
|
||||
public:
|
||||
WorkBase(
|
||||
std::string const& host, std::string const& path,
|
||||
std::string const& port,
|
||||
boost::asio::io_service& ios, callback_type cb);
|
||||
~WorkBase();
|
||||
|
||||
Impl&
|
||||
impl()
|
||||
{
|
||||
return *static_cast<Impl*>(this);
|
||||
}
|
||||
|
||||
void run() override;
|
||||
|
||||
void cancel() override;
|
||||
|
||||
void
|
||||
fail(error_code const& ec);
|
||||
|
||||
void
|
||||
onResolve(error_code const& ec, resolver_type::iterator it);
|
||||
|
||||
void
|
||||
onStart();
|
||||
|
||||
void
|
||||
onRequest(error_code const& ec);
|
||||
|
||||
void
|
||||
onResponse(error_code const& ec);
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template<class Impl>
|
||||
WorkBase<Impl>::WorkBase(std::string const& host,
|
||||
std::string const& path, std::string const& port,
|
||||
boost::asio::io_service& ios, callback_type cb)
|
||||
: host_(host)
|
||||
, path_(path)
|
||||
, port_(port)
|
||||
, cb_(std::move(cb))
|
||||
, ios_(ios)
|
||||
, strand_(ios)
|
||||
, resolver_(ios)
|
||||
, socket_(ios)
|
||||
{
|
||||
}
|
||||
|
||||
template<class Impl>
|
||||
WorkBase<Impl>::~WorkBase()
|
||||
{
|
||||
if (cb_)
|
||||
cb_ (make_error_code(boost::system::errc::not_a_socket),
|
||||
std::move(res_));
|
||||
}
|
||||
|
||||
template<class Impl>
|
||||
void
|
||||
WorkBase<Impl>::run()
|
||||
{
|
||||
if (! strand_.running_in_this_thread())
|
||||
return ios_.post(strand_.wrap (std::bind(
|
||||
&WorkBase::run, impl().shared_from_this())));
|
||||
|
||||
resolver_.async_resolve(
|
||||
query_type{host_, port_},
|
||||
strand_.wrap (std::bind(&WorkBase::onResolve, impl().shared_from_this(),
|
||||
beast::asio::placeholders::error,
|
||||
beast::asio::placeholders::iterator)));
|
||||
}
|
||||
|
||||
template<class Impl>
|
||||
void
|
||||
WorkBase<Impl>::cancel()
|
||||
{
|
||||
if (! strand_.running_in_this_thread())
|
||||
{
|
||||
return ios_.post(strand_.wrap (std::bind(
|
||||
&WorkBase::cancel, impl().shared_from_this())));
|
||||
}
|
||||
|
||||
error_code ec;
|
||||
resolver_.cancel();
|
||||
socket_.cancel (ec);
|
||||
}
|
||||
|
||||
template<class Impl>
|
||||
void
|
||||
WorkBase<Impl>::fail(error_code const& ec)
|
||||
{
|
||||
if (cb_)
|
||||
{
|
||||
cb_(ec, std::move(res_));
|
||||
cb_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template<class Impl>
|
||||
void
|
||||
WorkBase<Impl>::onResolve(error_code const& ec, resolver_type::iterator it)
|
||||
{
|
||||
if (ec)
|
||||
return fail(ec);
|
||||
|
||||
socket_.async_connect(*it,
|
||||
strand_.wrap (std::bind(&Impl::onConnect, impl().shared_from_this(),
|
||||
beast::asio::placeholders::error)));
|
||||
}
|
||||
|
||||
template<class Impl>
|
||||
void
|
||||
WorkBase<Impl>::onStart()
|
||||
{
|
||||
req_.method = "GET";
|
||||
req_.url = path_.empty() ? "/" : path_;
|
||||
req_.version = 11;
|
||||
req_.fields.replace (
|
||||
"Host", host_ + ":" + port_);
|
||||
req_.fields.replace ("User-Agent", BuildInfo::getFullVersionString());
|
||||
beast::http::prepare (req_);
|
||||
|
||||
beast::http::async_write(impl().stream(), req_,
|
||||
strand_.wrap (std::bind (&WorkBase::onRequest,
|
||||
impl().shared_from_this(), beast::asio::placeholders::error)));
|
||||
}
|
||||
|
||||
template<class Impl>
|
||||
void
|
||||
WorkBase<Impl>::onRequest(error_code const& ec)
|
||||
{
|
||||
if (ec)
|
||||
return fail(ec);
|
||||
|
||||
beast::http::async_read (impl().stream(), read_buf_, res_,
|
||||
strand_.wrap (std::bind (&WorkBase::onResponse,
|
||||
impl().shared_from_this(), beast::asio::placeholders::error)));
|
||||
}
|
||||
|
||||
template<class Impl>
|
||||
void
|
||||
WorkBase<Impl>::onResponse(error_code const& ec)
|
||||
{
|
||||
if (ec)
|
||||
return fail(ec);
|
||||
|
||||
assert(cb_);
|
||||
cb_(ec, std::move(res_));
|
||||
cb_ = nullptr;
|
||||
}
|
||||
|
||||
} // detail
|
||||
|
||||
} // ripple
|
||||
|
||||
#endif
|
||||
76
src/ripple/app/misc/detail/WorkPlain.h
Normal file
76
src/ripple/app/misc/detail/WorkPlain.h
Normal file
@@ -0,0 +1,76 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2016 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_APP_MISC_DETAIL_WORKPLAIN_H_INCLUDED
|
||||
#define RIPPLE_APP_MISC_DETAIL_WORKPLAIN_H_INCLUDED
|
||||
|
||||
#include <ripple/app/misc/detail/WorkBase.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
namespace detail {
|
||||
|
||||
// Work over TCP/IP
|
||||
class WorkPlain : public WorkBase<WorkPlain>
|
||||
, public std::enable_shared_from_this<WorkPlain>
|
||||
{
|
||||
friend class WorkBase<WorkPlain>;
|
||||
|
||||
public:
|
||||
WorkPlain(
|
||||
std::string const& host,
|
||||
std::string const& path, std::string const& port,
|
||||
boost::asio::io_service& ios, callback_type cb);
|
||||
~WorkPlain() = default;
|
||||
|
||||
private:
|
||||
void
|
||||
onConnect(error_code const& ec);
|
||||
|
||||
socket_type&
|
||||
stream()
|
||||
{
|
||||
return socket_;
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
WorkPlain::WorkPlain(
|
||||
std::string const& host,
|
||||
std::string const& path, std::string const& port,
|
||||
boost::asio::io_service& ios, callback_type cb)
|
||||
: WorkBase (host, path, port, ios, cb)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
WorkPlain::onConnect(error_code const& ec)
|
||||
{
|
||||
if (ec)
|
||||
return fail(ec);
|
||||
|
||||
onStart ();
|
||||
}
|
||||
|
||||
} // detail
|
||||
|
||||
} // ripple
|
||||
|
||||
#endif
|
||||
137
src/ripple/app/misc/detail/WorkSSL.h
Normal file
137
src/ripple/app/misc/detail/WorkSSL.h
Normal file
@@ -0,0 +1,137 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2016 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_APP_MISC_DETAIL_WORKSSL_H_INCLUDED
|
||||
#define RIPPLE_APP_MISC_DETAIL_WORKSSL_H_INCLUDED
|
||||
|
||||
#include <ripple/app/misc/detail/WorkBase.h>
|
||||
#include <ripple/basics/contract.h>
|
||||
#include <boost/asio/ssl.hpp>
|
||||
#include <boost/bind.hpp>
|
||||
#include <boost/format.hpp>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
namespace detail {
|
||||
|
||||
class SSLContext : public boost::asio::ssl::context
|
||||
{
|
||||
public:
|
||||
SSLContext()
|
||||
: boost::asio::ssl::context(boost::asio::ssl::context::sslv23)
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
set_default_verify_paths (ec);
|
||||
|
||||
if (ec)
|
||||
{
|
||||
Throw<std::runtime_error> (
|
||||
boost::str (boost::format (
|
||||
"Failed to set_default_verify_paths: %s") %
|
||||
ec.message ()));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Work over SSL
|
||||
class WorkSSL : public WorkBase<WorkSSL>
|
||||
, public std::enable_shared_from_this<WorkSSL>
|
||||
{
|
||||
friend class WorkBase<WorkSSL>;
|
||||
|
||||
private:
|
||||
using stream_type = boost::asio::ssl::stream<socket_type&>;
|
||||
|
||||
SSLContext context_;
|
||||
stream_type stream_;
|
||||
|
||||
public:
|
||||
WorkSSL(
|
||||
std::string const& host,
|
||||
std::string const& path, std::string const& port,
|
||||
boost::asio::io_service& ios, callback_type cb);
|
||||
~WorkSSL() = default;
|
||||
|
||||
private:
|
||||
stream_type&
|
||||
stream()
|
||||
{
|
||||
return stream_;
|
||||
}
|
||||
|
||||
void
|
||||
onConnect(error_code const& ec);
|
||||
|
||||
void
|
||||
onHandshake(error_code const& ec);
|
||||
|
||||
static bool
|
||||
rfc2818_verify (
|
||||
std::string const& domain,
|
||||
bool preverified,
|
||||
boost::asio::ssl::verify_context& ctx)
|
||||
{
|
||||
return
|
||||
boost::asio::ssl::rfc2818_verification (domain) (preverified, ctx);
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
WorkSSL::WorkSSL(
|
||||
std::string const& host,
|
||||
std::string const& path, std::string const& port,
|
||||
boost::asio::io_service& ios, callback_type cb)
|
||||
: WorkBase (host, path, port, ios, cb)
|
||||
, context_()
|
||||
, stream_ (socket_, context_)
|
||||
{
|
||||
stream_.set_verify_mode (boost::asio::ssl::verify_peer);
|
||||
stream_.set_verify_callback (
|
||||
std::bind (
|
||||
&WorkSSL::rfc2818_verify, host_,
|
||||
std::placeholders::_1, std::placeholders::_2));
|
||||
}
|
||||
|
||||
void
|
||||
WorkSSL::onConnect(error_code const& ec)
|
||||
{
|
||||
if (ec)
|
||||
return fail(ec);
|
||||
|
||||
stream_.async_handshake(
|
||||
boost::asio::ssl::stream_base::client,
|
||||
strand_.wrap (boost::bind(&WorkSSL::onHandshake, shared_from_this(),
|
||||
boost::asio::placeholders::error)));
|
||||
}
|
||||
|
||||
void
|
||||
WorkSSL::onHandshake(error_code const& ec)
|
||||
{
|
||||
if (ec)
|
||||
return fail(ec);
|
||||
|
||||
onStart ();
|
||||
}
|
||||
|
||||
} // detail
|
||||
|
||||
} // ripple
|
||||
|
||||
#endif
|
||||
280
src/ripple/app/misc/impl/ValidatorSite.cpp
Normal file
280
src/ripple/app/misc/impl/ValidatorSite.cpp
Normal file
@@ -0,0 +1,280 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2016 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <ripple/app/misc/detail/WorkPlain.h>
|
||||
#include <ripple/app/misc/detail/WorkSSL.h>
|
||||
#include <ripple/app/misc/ValidatorList.h>
|
||||
#include <ripple/app/misc/ValidatorSite.h>
|
||||
#include <ripple/basics/Slice.h>
|
||||
#include <ripple/json/json_reader.h>
|
||||
#include <beast/core/detail/base64.hpp>
|
||||
#include <boost/regex.hpp>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
// default site query frequency - 5 minutes
|
||||
auto constexpr DEFAULT_REFRESH_INTERVAL = std::chrono::minutes{5};
|
||||
|
||||
ValidatorSite::ValidatorSite (
|
||||
boost::asio::io_service& ios,
|
||||
ValidatorList& validators,
|
||||
beast::Journal j)
|
||||
: ios_ (ios)
|
||||
, validators_ (validators)
|
||||
, j_ (j)
|
||||
, timer_ (ios_)
|
||||
, fetching_ (false)
|
||||
, pending_ (false)
|
||||
, stopping_ (false)
|
||||
{
|
||||
}
|
||||
|
||||
ValidatorSite::~ValidatorSite()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock{state_mutex_};
|
||||
if (timer_.expires_at().time_since_epoch().count())
|
||||
{
|
||||
if (! stopping_)
|
||||
{
|
||||
lock.unlock();
|
||||
stop();
|
||||
}
|
||||
else
|
||||
{
|
||||
cv_.wait(lock, [&]{ return ! fetching_; });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
ValidatorSite::load (
|
||||
std::vector<std::string> const& siteURIs)
|
||||
{
|
||||
JLOG (j_.debug()) <<
|
||||
"Loading configured validator list sites";
|
||||
|
||||
std::lock_guard <std::mutex> lock{sites_mutex_};
|
||||
|
||||
for (auto uri : siteURIs)
|
||||
{
|
||||
parsedURL pUrl;
|
||||
if (! parseUrl (pUrl, uri) ||
|
||||
(pUrl.scheme != "http" && pUrl.scheme != "https"))
|
||||
{
|
||||
JLOG (j_.error()) <<
|
||||
"Invalid validator site uri: " << uri;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! pUrl.port)
|
||||
pUrl.port = (pUrl.scheme == "https") ? 443 : 80;
|
||||
|
||||
sites_.push_back ({
|
||||
uri, pUrl, DEFAULT_REFRESH_INTERVAL, clock_type::now()});
|
||||
}
|
||||
|
||||
JLOG (j_.debug()) <<
|
||||
"Loaded " << siteURIs.size() << " sites";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
ValidatorSite::start ()
|
||||
{
|
||||
std::lock_guard <std::mutex> lock{state_mutex_};
|
||||
if (! timer_.expires_at().time_since_epoch().count())
|
||||
setTimer ();
|
||||
}
|
||||
|
||||
void
|
||||
ValidatorSite::join ()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock{state_mutex_};
|
||||
cv_.wait(lock, [&]{ return ! pending_; });
|
||||
}
|
||||
|
||||
void
|
||||
ValidatorSite::stop()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock{state_mutex_};
|
||||
stopping_ = true;
|
||||
cv_.wait(lock, [&]{ return ! fetching_; });
|
||||
|
||||
if(auto sp = work_.lock())
|
||||
sp->cancel();
|
||||
|
||||
error_code ec;
|
||||
timer_.cancel(ec);
|
||||
stopping_ = false;
|
||||
pending_ = false;
|
||||
cv_.notify_all();
|
||||
}
|
||||
|
||||
void
|
||||
ValidatorSite::setTimer ()
|
||||
{
|
||||
std::lock_guard <std::mutex> lock{sites_mutex_};
|
||||
auto next = sites_.end();
|
||||
|
||||
for (auto it = sites_.begin (); it != sites_.end (); ++it)
|
||||
if (next == sites_.end () || it->nextRefresh < next->nextRefresh)
|
||||
next = it;
|
||||
|
||||
if (next != sites_.end ())
|
||||
{
|
||||
pending_ = next->nextRefresh <= clock_type::now();
|
||||
cv_.notify_all();
|
||||
timer_.expires_at (next->nextRefresh);
|
||||
timer_.async_wait (std::bind (&ValidatorSite::onTimer, this,
|
||||
std::distance (sites_.begin (), next),
|
||||
beast::asio::placeholders::error));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ValidatorSite::onTimer (
|
||||
std::size_t siteIdx,
|
||||
error_code const& ec)
|
||||
{
|
||||
if (ec == boost::asio::error::operation_aborted)
|
||||
return;
|
||||
if (ec)
|
||||
{
|
||||
JLOG(j_.error()) <<
|
||||
"ValidatorSite::onTimer: " << ec.message();
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard <std::mutex> lock{sites_mutex_};
|
||||
sites_[siteIdx].nextRefresh =
|
||||
clock_type::now() + DEFAULT_REFRESH_INTERVAL;
|
||||
|
||||
assert(! fetching_);
|
||||
fetching_ = true;
|
||||
|
||||
std::shared_ptr<detail::Work> sp;
|
||||
if (sites_[siteIdx].pUrl.scheme == "https")
|
||||
{
|
||||
sp = std::make_shared<detail::WorkSSL>(
|
||||
sites_[siteIdx].pUrl.domain,
|
||||
sites_[siteIdx].pUrl.path,
|
||||
std::to_string(*sites_[siteIdx].pUrl.port),
|
||||
ios_,
|
||||
[this, siteIdx](error_code const& err, detail::response_type&& resp)
|
||||
{
|
||||
onSiteFetch (err, std::move(resp), siteIdx);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
sp = std::make_shared<detail::WorkPlain>(
|
||||
sites_[siteIdx].pUrl.domain,
|
||||
sites_[siteIdx].pUrl.path,
|
||||
std::to_string(*sites_[siteIdx].pUrl.port),
|
||||
ios_,
|
||||
[this, siteIdx](error_code const& err, detail::response_type&& resp)
|
||||
{
|
||||
onSiteFetch (err, std::move(resp), siteIdx);
|
||||
});
|
||||
}
|
||||
|
||||
work_ = sp;
|
||||
sp->run ();
|
||||
}
|
||||
|
||||
void
|
||||
ValidatorSite::onSiteFetch(
|
||||
boost::system::error_code const& ec,
|
||||
detail::response_type&& res,
|
||||
std::size_t siteIdx)
|
||||
{
|
||||
if (! ec && res.status != 200)
|
||||
{
|
||||
std::lock_guard <std::mutex> lock{sites_mutex_};
|
||||
JLOG (j_.warn()) <<
|
||||
"Request for validator list at " <<
|
||||
sites_[siteIdx].uri << " returned " << res.status;
|
||||
}
|
||||
else if (! ec)
|
||||
{
|
||||
std::lock_guard <std::mutex> lock{sites_mutex_};
|
||||
Json::Reader r;
|
||||
Json::Value body;
|
||||
if (r.parse(res.body.data(), body) &&
|
||||
body.isObject () &&
|
||||
body.isMember("blob") && body["blob"].isString () &&
|
||||
body.isMember("manifest") && body["manifest"].isString () &&
|
||||
body.isMember("signature") && body["signature"].isString() &&
|
||||
body.isMember("version") && body["version"].isInt())
|
||||
{
|
||||
|
||||
auto const disp = validators_.applyList (
|
||||
body["manifest"].asString (),
|
||||
body["blob"].asString (),
|
||||
body["signature"].asString(),
|
||||
body["version"].asUInt());
|
||||
|
||||
if (ListDisposition::accepted == disp)
|
||||
{
|
||||
JLOG (j_.debug()) <<
|
||||
"Applied new validator list from " <<
|
||||
sites_[siteIdx].uri;
|
||||
}
|
||||
else if (ListDisposition::stale == disp)
|
||||
{
|
||||
JLOG (j_.warn()) <<
|
||||
"Stale validator list from " << sites_[siteIdx].uri;
|
||||
}
|
||||
else if (ListDisposition::untrusted == disp)
|
||||
{
|
||||
JLOG (j_.warn()) <<
|
||||
"Untrusted validator list from " <<
|
||||
sites_[siteIdx].uri;
|
||||
}
|
||||
else if (ListDisposition::invalid == disp)
|
||||
{
|
||||
JLOG (j_.warn()) <<
|
||||
"Invalid validator list from " <<
|
||||
sites_[siteIdx].uri;
|
||||
}
|
||||
|
||||
if (body.isMember ("refresh_interval") &&
|
||||
body["refresh_interval"].isNumeric ())
|
||||
{
|
||||
sites_[siteIdx].refreshInterval =
|
||||
std::chrono::minutes{body["refresh_interval"].asUInt ()};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
JLOG (j_.warn()) <<
|
||||
"Unable to parse JSON response from " <<
|
||||
sites_[siteIdx].uri;
|
||||
}
|
||||
}
|
||||
|
||||
std::lock_guard <std::mutex> lock{state_mutex_};
|
||||
fetching_ = false;
|
||||
if (! stopping_)
|
||||
setTimer ();
|
||||
cv_.notify_all();
|
||||
}
|
||||
|
||||
} // ripple
|
||||
@@ -64,6 +64,7 @@ struct ConfigSection
|
||||
#define SECTION_WEBSOCKET_PING_FREQ "websocket_ping_frequency"
|
||||
#define SECTION_VALIDATOR_KEYS "validator_keys"
|
||||
#define SECTION_VALIDATOR_LIST_KEYS "validator_list_keys"
|
||||
#define SECTION_VALIDATOR_LIST_SITES "validator_list_sites"
|
||||
#define SECTION_VALIDATORS "validators"
|
||||
#define SECTION_VALIDATION_MANIFEST "validation_manifest"
|
||||
#define SECTION_VETO_AMENDMENTS "veto_amendments"
|
||||
|
||||
@@ -503,6 +503,13 @@ void Config::loadFromString (std::string const& fileContents)
|
||||
if (valKeyEntries)
|
||||
section (SECTION_VALIDATOR_KEYS).append (*valKeyEntries);
|
||||
|
||||
auto valSiteEntries = getIniFileSection(
|
||||
iniFile,
|
||||
SECTION_VALIDATOR_LIST_SITES);
|
||||
|
||||
if (valSiteEntries)
|
||||
section (SECTION_VALIDATOR_LIST_SITES).append (*valSiteEntries);
|
||||
|
||||
auto valListKeys = getIniFileSection(
|
||||
iniFile,
|
||||
SECTION_VALIDATOR_LIST_KEYS);
|
||||
@@ -523,6 +530,14 @@ void Config::loadFromString (std::string const& fileContents)
|
||||
// Consolidate [validator_keys] and [validators]
|
||||
section (SECTION_VALIDATORS).append (
|
||||
section (SECTION_VALIDATOR_KEYS).lines ());
|
||||
|
||||
if (! section (SECTION_VALIDATOR_LIST_SITES).lines().empty() &&
|
||||
section (SECTION_VALIDATOR_LIST_KEYS).lines().empty())
|
||||
{
|
||||
Throw<std::runtime_error> (
|
||||
"[" + std::string(SECTION_VALIDATOR_LIST_KEYS) +
|
||||
"] config section is missing");
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -33,3 +33,4 @@
|
||||
#include <ripple/app/misc/impl/Transaction.cpp>
|
||||
#include <ripple/app/misc/impl/TxQ.cpp>
|
||||
#include <ripple/app/misc/impl/ValidatorList.cpp>
|
||||
#include <ripple/app/misc/impl/ValidatorSite.cpp>
|
||||
|
||||
392
src/test/app/ValidatorSite_test.cpp
Normal file
392
src/test/app/ValidatorSite_test.cpp
Normal file
@@ -0,0 +1,392 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright 2016 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <beast/core/placeholders.hpp>
|
||||
#include <beast/core/detail/base64.hpp>
|
||||
#include <beast/http.hpp>
|
||||
#include <ripple/app/misc/ValidatorSite.h>
|
||||
#include <ripple/basics/Slice.h>
|
||||
#include <ripple/basics/strHex.h>
|
||||
#include <ripple/protocol/digest.h>
|
||||
#include <ripple/protocol/HashPrefix.h>
|
||||
#include <ripple/protocol/PublicKey.h>
|
||||
#include <ripple/protocol/SecretKey.h>
|
||||
#include <ripple/protocol/Sign.h>
|
||||
#include <test/jtx.h>
|
||||
#include <boost/utility/in_place_factory.hpp>
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
class http_sync_server
|
||||
{
|
||||
using endpoint_type = boost::asio::ip::tcp::endpoint;
|
||||
using address_type = boost::asio::ip::address;
|
||||
using socket_type = boost::asio::ip::tcp::socket;
|
||||
|
||||
using req_type = beast::http::request<beast::http::string_body>;
|
||||
using resp_type = beast::http::response<beast::http::string_body>;
|
||||
using error_code = boost::system::error_code;
|
||||
|
||||
socket_type sock_;
|
||||
boost::asio::ip::tcp::acceptor acceptor_;
|
||||
|
||||
std::string list_;
|
||||
|
||||
public:
|
||||
http_sync_server(endpoint_type const& ep,
|
||||
boost::asio::io_service& ios,
|
||||
std::pair<PublicKey, SecretKey> keys,
|
||||
std::string const& manifest,
|
||||
int sequence,
|
||||
std::size_t expiration,
|
||||
int version,
|
||||
std::vector <PublicKey> const& validators)
|
||||
: sock_(ios)
|
||||
, acceptor_(ios)
|
||||
{
|
||||
std::string data =
|
||||
"{\"sequence\":" + std::to_string(sequence) +
|
||||
",\"expiration\":" + std::to_string(expiration) +
|
||||
",\"validators\":[";
|
||||
|
||||
for (auto const& val : validators)
|
||||
{
|
||||
data += "{\"validation_public_key\":\"" + strHex (val) + "\"},";
|
||||
}
|
||||
data.pop_back();
|
||||
data += "]}";
|
||||
std::string blob = beast::detail::base64_encode(data);
|
||||
|
||||
list_ = "{\"blob\":\"" + blob + "\"";
|
||||
|
||||
auto const sig = sign(keys.first, keys.second, makeSlice(data));
|
||||
|
||||
list_ += ",\"signature\":\"" + strHex(sig) + "\"";
|
||||
list_ += ",\"manifest\":\"" + manifest + "\"";
|
||||
list_ += ",\"version\":" + std::to_string(version) + '}';
|
||||
|
||||
acceptor_.open(ep.protocol());
|
||||
error_code ec;
|
||||
acceptor_.set_option(
|
||||
boost::asio::ip::tcp::acceptor::reuse_address(true), ec);
|
||||
acceptor_.bind(ep);
|
||||
acceptor_.listen(boost::asio::socket_base::max_connections);
|
||||
acceptor_.async_accept(sock_,
|
||||
std::bind(&http_sync_server::on_accept, this,
|
||||
beast::asio::placeholders::error));
|
||||
}
|
||||
|
||||
~http_sync_server()
|
||||
{
|
||||
error_code ec;
|
||||
acceptor_.close(ec);
|
||||
}
|
||||
|
||||
private:
|
||||
struct lambda
|
||||
{
|
||||
int id;
|
||||
http_sync_server& self;
|
||||
socket_type sock;
|
||||
boost::asio::io_service::work work;
|
||||
|
||||
lambda(int id_, http_sync_server& self_,
|
||||
socket_type&& sock_)
|
||||
: id(id_)
|
||||
, self(self_)
|
||||
, sock(std::move(sock_))
|
||||
, work(sock.get_io_service())
|
||||
{
|
||||
}
|
||||
|
||||
void operator()()
|
||||
{
|
||||
self.do_peer(id, std::move(sock));
|
||||
}
|
||||
};
|
||||
|
||||
void
|
||||
on_accept(error_code ec)
|
||||
{
|
||||
if(! acceptor_.is_open())
|
||||
return;
|
||||
if(ec)
|
||||
return;
|
||||
static int id_ = 0;
|
||||
std::thread{lambda{++id_, *this, std::move(sock_)}}.detach();
|
||||
acceptor_.async_accept(sock_,
|
||||
std::bind(&http_sync_server::on_accept, this,
|
||||
beast::asio::placeholders::error));
|
||||
}
|
||||
|
||||
void
|
||||
do_peer(int id, socket_type&& sock0)
|
||||
{
|
||||
socket_type sock(std::move(sock0));
|
||||
beast::streambuf sb;
|
||||
error_code ec;
|
||||
for(;;)
|
||||
{
|
||||
req_type req;
|
||||
beast::http::read(sock, sb, req, ec);
|
||||
if(ec)
|
||||
break;
|
||||
auto path = req.url;
|
||||
if(path != "/validators")
|
||||
{
|
||||
resp_type res;
|
||||
res.status = 404;
|
||||
res.reason = "Not Found";
|
||||
res.version = req.version;
|
||||
res.fields.insert("Server", "http_sync_server");
|
||||
res.fields.insert("Content-Type", "text/html");
|
||||
res.body = "The file '" + path + "' was not found";
|
||||
prepare(res);
|
||||
write(sock, res, ec);
|
||||
if(ec)
|
||||
break;
|
||||
}
|
||||
resp_type res;
|
||||
res.status = 200;
|
||||
res.reason = "OK";
|
||||
res.version = req.version;
|
||||
res.fields.insert("Server", "http_sync_server");
|
||||
res.fields.insert("Content-Type", "application/json");
|
||||
|
||||
res.body = list_;
|
||||
try
|
||||
{
|
||||
prepare(res);
|
||||
}
|
||||
catch(std::exception const& e)
|
||||
{
|
||||
res = {};
|
||||
res.status = 500;
|
||||
res.reason = "Internal Error";
|
||||
res.version = req.version;
|
||||
res.fields.insert("Server", "http_sync_server");
|
||||
res.fields.insert("Content-Type", "text/html");
|
||||
res.body =
|
||||
std::string{"An internal error occurred"} + e.what();
|
||||
prepare(res);
|
||||
}
|
||||
write(sock, res, ec);
|
||||
if(ec)
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class ValidatorSite_test : public beast::unit_test::suite
|
||||
{
|
||||
private:
|
||||
static
|
||||
PublicKey
|
||||
randomNode ()
|
||||
{
|
||||
return derivePublicKey (KeyType::secp256k1, randomSecretKey());
|
||||
}
|
||||
|
||||
std::string
|
||||
makeManifestString (
|
||||
PublicKey const& pk,
|
||||
SecretKey const& sk,
|
||||
PublicKey const& spk,
|
||||
SecretKey const& ssk,
|
||||
int seq)
|
||||
{
|
||||
STObject st(sfGeneric);
|
||||
st[sfSequence] = seq;
|
||||
st[sfPublicKey] = pk;
|
||||
st[sfSigningPubKey] = spk;
|
||||
|
||||
sign(st, HashPrefix::manifest, *publicKeyType(spk), ssk);
|
||||
sign(st, HashPrefix::manifest, *publicKeyType(pk), sk,
|
||||
sfMasterSignature);
|
||||
|
||||
Serializer s;
|
||||
st.add(s);
|
||||
|
||||
return beast::detail::base64_encode (std::string(
|
||||
static_cast<char const*> (s.data()), s.size()));
|
||||
}
|
||||
|
||||
void
|
||||
testConfigLoad ()
|
||||
{
|
||||
testcase ("Config Load");
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
Env env (*this);
|
||||
auto trustedSites = std::make_unique<ValidatorSite> (
|
||||
env.app().getIOService(), env.app().validators(), beast::Journal());
|
||||
|
||||
// load should accept empty sites list
|
||||
std::vector<std::string> emptyCfgSites;
|
||||
BEAST_EXPECT(trustedSites->load (emptyCfgSites));
|
||||
|
||||
// load should accept valid validator site uris
|
||||
std::vector<std::string> cfgSites({
|
||||
"http://ripple.com/",
|
||||
"http://ripple.com/validators",
|
||||
"http://ripple.com:8080/validators",
|
||||
"http://207.261.33.37/validators",
|
||||
"http://207.261.33.37:8080/validators",
|
||||
"https://ripple.com/validators",
|
||||
"https://ripple.com:443/validators"});
|
||||
BEAST_EXPECT(trustedSites->load (cfgSites));
|
||||
|
||||
// load should reject validator site uris with invalid schemes
|
||||
std::vector<std::string> badSites(
|
||||
{"ftp://ripple.com/validators"});
|
||||
BEAST_EXPECT(!trustedSites->load (badSites));
|
||||
|
||||
badSites[0] = "wss://ripple.com/validators";
|
||||
BEAST_EXPECT(!trustedSites->load (badSites));
|
||||
|
||||
badSites[0] = "ripple.com/validators";
|
||||
BEAST_EXPECT(!trustedSites->load (badSites));
|
||||
}
|
||||
|
||||
void
|
||||
testFetchList ()
|
||||
{
|
||||
testcase ("Fetch list");
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
Env env (*this);
|
||||
auto& ioService = env.app ().getIOService ();
|
||||
auto& trustedKeys = env.app ().validators ();
|
||||
|
||||
beast::Journal journal;
|
||||
|
||||
PublicKey emptyLocalKey;
|
||||
std::vector<std::string> emptyCfgKeys;
|
||||
|
||||
auto const publisherSecret1 = randomSecretKey();
|
||||
auto const publisherPublic1 =
|
||||
derivePublicKey(KeyType::ed25519, publisherSecret1);
|
||||
auto const pubSigningKeys1 = randomKeyPair(KeyType::secp256k1);
|
||||
|
||||
auto const manifest1 = makeManifestString (
|
||||
publisherPublic1, publisherSecret1,
|
||||
pubSigningKeys1.first, pubSigningKeys1.second, 1);
|
||||
|
||||
auto const publisherSecret2 = randomSecretKey();
|
||||
auto const publisherPublic2 =
|
||||
derivePublicKey(KeyType::ed25519, publisherSecret2);
|
||||
auto const pubSigningKeys2 = randomKeyPair(KeyType::secp256k1);
|
||||
|
||||
auto const manifest2 = makeManifestString (
|
||||
publisherPublic2, publisherSecret2,
|
||||
pubSigningKeys2.first, pubSigningKeys2.second, 1);
|
||||
|
||||
std::vector<std::string> cfgPublishers({
|
||||
strHex(publisherPublic1),
|
||||
strHex(publisherPublic2)});
|
||||
|
||||
BEAST_EXPECT(trustedKeys.load (
|
||||
emptyLocalKey, emptyCfgKeys, cfgPublishers));
|
||||
|
||||
auto constexpr listSize = 20;
|
||||
std::vector<PublicKey> list1;
|
||||
list1.reserve (listSize);
|
||||
while (list1.size () < listSize)
|
||||
list1.push_back (randomNode());
|
||||
|
||||
std::vector<PublicKey> list2;
|
||||
list2.reserve (listSize);
|
||||
while (list2.size () < listSize)
|
||||
list2.push_back (randomNode());
|
||||
|
||||
std::uint16_t constexpr port1 = 7475;
|
||||
std::uint16_t constexpr port2 = 7476;
|
||||
|
||||
using endpoint_type = boost::asio::ip::tcp::endpoint;
|
||||
using address_type = boost::asio::ip::address;
|
||||
|
||||
endpoint_type ep1{address_type::from_string("127.0.0.1"), port1};
|
||||
endpoint_type ep2{address_type::from_string("127.0.0.1"), port2};
|
||||
|
||||
auto const sequence = 1;
|
||||
auto const version = 1;
|
||||
NetClock::time_point const expiration =
|
||||
env.timeKeeper().now() + 3600s;
|
||||
|
||||
http_sync_server server1(
|
||||
ep1, ioService, pubSigningKeys1, manifest1, sequence,
|
||||
expiration.time_since_epoch().count(), version, list1);
|
||||
|
||||
http_sync_server server2(
|
||||
ep2, ioService, pubSigningKeys2, manifest2, sequence,
|
||||
expiration.time_since_epoch().count(), version, list2);
|
||||
|
||||
{
|
||||
// fetch single site
|
||||
std::vector<std::string> cfgSites(
|
||||
{"http://127.0.0.1:" + std::to_string(port1) + "/validators"});
|
||||
|
||||
auto sites = std::make_unique<ValidatorSite> (
|
||||
env.app().getIOService(), env.app().validators(), journal);
|
||||
|
||||
sites->load (cfgSites);
|
||||
sites->start();
|
||||
sites->join();
|
||||
|
||||
for (auto const& val : list1)
|
||||
BEAST_EXPECT(trustedKeys.listed (val));
|
||||
}
|
||||
{
|
||||
// fetch multiple sites
|
||||
std::vector<std::string> cfgSites({
|
||||
"http://127.0.0.1:" + std::to_string(port1) + "/validators",
|
||||
"http://127.0.0.1:" + std::to_string(port2) + "/validators"});
|
||||
|
||||
auto sites = std::make_unique<ValidatorSite> (
|
||||
env.app().getIOService(), env.app().validators(), journal);
|
||||
|
||||
sites->load (cfgSites);
|
||||
sites->start();
|
||||
sites->join();
|
||||
|
||||
for (auto const& val : list1)
|
||||
BEAST_EXPECT(trustedKeys.listed (val));
|
||||
|
||||
for (auto const& val : list2)
|
||||
BEAST_EXPECT(trustedKeys.listed (val));
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testConfigLoad ();
|
||||
testFetchList ();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(ValidatorSite, app, ripple);
|
||||
|
||||
} // test
|
||||
} // ripple
|
||||
@@ -300,6 +300,10 @@ nHUhG1PgAG8H8myUENypM35JgfqXAKNQvRVVAFDRzJrny5eZN8d5
|
||||
nHBu9PTL9dn2GuZtdW4U2WzBwffyX9qsQCd9CNU4Z5YG3PQfViM8
|
||||
nHUPDdcdb2Y5DZAJne4c2iabFuAP3F34xZUgYQT2NH7qfkdapgnz
|
||||
|
||||
[validator_list_sites]
|
||||
recommendedripplevalidators.com
|
||||
moreripplevalidators.net
|
||||
|
||||
[validator_list_keys]
|
||||
03E74EE14CB525AFBB9F1B7D86CD58ECC4B91452294B42AB4E78F260BD905C091D
|
||||
030775A669685BD6ABCEBD80385921C7851783D991A8055FD21D2F3966C96F1B56
|
||||
@@ -527,19 +531,50 @@ nHBu9PTL9dn2GuZtdW4U2WzBwffyX9qsQCd9CNU4Z5YG3PQfViM8
|
||||
BEAST_EXPECT(c.section (SECTION_VALIDATORS).values ().size () == 5);
|
||||
}
|
||||
{
|
||||
// load validator list keys from config
|
||||
// load validator list sites and keys from config
|
||||
Config c;
|
||||
std::string toLoad(R"rippleConfig(
|
||||
[validator_list_sites]
|
||||
ripplevalidators.com
|
||||
trustthesevalidators.gov
|
||||
|
||||
[validator_list_keys]
|
||||
021A99A537FDEBC34E4FCA03B39BEADD04299BB19E85097EC92B15A3518801E566
|
||||
)rippleConfig");
|
||||
c.loadFromString (toLoad);
|
||||
BEAST_EXPECT(
|
||||
c.section (SECTION_VALIDATOR_LIST_SITES).values ().size () == 2);
|
||||
BEAST_EXPECT(
|
||||
c.section (SECTION_VALIDATOR_LIST_SITES).values ()[0] ==
|
||||
"ripplevalidators.com");
|
||||
BEAST_EXPECT(
|
||||
c.section (SECTION_VALIDATOR_LIST_SITES).values ()[1] ==
|
||||
"trustthesevalidators.gov");
|
||||
BEAST_EXPECT(
|
||||
c.section (SECTION_VALIDATOR_LIST_KEYS).values ().size () == 1);
|
||||
BEAST_EXPECT(
|
||||
c.section (SECTION_VALIDATOR_LIST_KEYS).values ()[0] ==
|
||||
"021A99A537FDEBC34E4FCA03B39BEADD04299BB19E85097EC92B15A3518801E566");
|
||||
}
|
||||
{
|
||||
// load should throw if [validator_list_sites] is configured but
|
||||
// [validator_list_keys] is not
|
||||
Config c;
|
||||
std::string toLoad(R"rippleConfig(
|
||||
[validator_list_sites]
|
||||
ripplevalidators.com
|
||||
trustthesevalidators.gov
|
||||
)rippleConfig");
|
||||
std::string error;
|
||||
auto const expectedError =
|
||||
"[validator_list_keys] config section is missing";
|
||||
try {
|
||||
c.loadFromString (toLoad);
|
||||
} catch (std::runtime_error& e) {
|
||||
error = e.what();
|
||||
}
|
||||
BEAST_EXPECT(error == expectedError);
|
||||
}
|
||||
{
|
||||
// load from specified [validators_file] absolute path
|
||||
detail::ValidatorsTxtGuard const vtg (
|
||||
@@ -550,6 +585,8 @@ nHBu9PTL9dn2GuZtdW4U2WzBwffyX9qsQCd9CNU4Z5YG3PQfViM8
|
||||
c.loadFromString (boost::str (cc % vtg.validatorsFile ()));
|
||||
BEAST_EXPECT(c.legacy ("validators_file") == vtg.validatorsFile ());
|
||||
BEAST_EXPECT(c.section (SECTION_VALIDATORS).values ().size () == 8);
|
||||
BEAST_EXPECT(
|
||||
c.section (SECTION_VALIDATOR_LIST_SITES).values ().size () == 2);
|
||||
BEAST_EXPECT(
|
||||
c.section (SECTION_VALIDATOR_LIST_KEYS).values ().size () == 2);
|
||||
}
|
||||
@@ -566,6 +603,8 @@ nHBu9PTL9dn2GuZtdW4U2WzBwffyX9qsQCd9CNU4Z5YG3PQfViM8
|
||||
auto const& c (rcg.config ());
|
||||
BEAST_EXPECT(c.legacy ("validators_file") == valFileName);
|
||||
BEAST_EXPECT(c.section (SECTION_VALIDATORS).values ().size () == 8);
|
||||
BEAST_EXPECT(
|
||||
c.section (SECTION_VALIDATOR_LIST_SITES).values ().size () == 2);
|
||||
BEAST_EXPECT(
|
||||
c.section (SECTION_VALIDATOR_LIST_KEYS).values ().size () == 2);
|
||||
}
|
||||
@@ -582,6 +621,8 @@ nHBu9PTL9dn2GuZtdW4U2WzBwffyX9qsQCd9CNU4Z5YG3PQfViM8
|
||||
auto const& c (rcg.config ());
|
||||
BEAST_EXPECT(c.legacy ("validators_file") == valFilePath);
|
||||
BEAST_EXPECT(c.section (SECTION_VALIDATORS).values ().size () == 8);
|
||||
BEAST_EXPECT(
|
||||
c.section (SECTION_VALIDATOR_LIST_SITES).values ().size () == 2);
|
||||
BEAST_EXPECT(
|
||||
c.section (SECTION_VALIDATOR_LIST_KEYS).values ().size () == 2);
|
||||
}
|
||||
@@ -596,6 +637,8 @@ nHBu9PTL9dn2GuZtdW4U2WzBwffyX9qsQCd9CNU4Z5YG3PQfViM8
|
||||
auto const& c (rcg.config ());
|
||||
BEAST_EXPECT(c.legacy ("validators_file").empty ());
|
||||
BEAST_EXPECT(c.section (SECTION_VALIDATORS).values ().size () == 8);
|
||||
BEAST_EXPECT(
|
||||
c.section (SECTION_VALIDATOR_LIST_SITES).values ().size () == 2);
|
||||
BEAST_EXPECT(
|
||||
c.section (SECTION_VALIDATOR_LIST_KEYS).values ().size () == 2);
|
||||
}
|
||||
@@ -614,6 +657,8 @@ nHBu9PTL9dn2GuZtdW4U2WzBwffyX9qsQCd9CNU4Z5YG3PQfViM8
|
||||
auto const& c (rcg.config ());
|
||||
BEAST_EXPECT(c.legacy ("validators_file") == vtg.validatorsFile ());
|
||||
BEAST_EXPECT(c.section (SECTION_VALIDATORS).values ().size () == 8);
|
||||
BEAST_EXPECT(
|
||||
c.section (SECTION_VALIDATOR_LIST_SITES).values ().size () == 2);
|
||||
BEAST_EXPECT(
|
||||
c.section (SECTION_VALIDATOR_LIST_KEYS).values ().size () == 2);
|
||||
}
|
||||
@@ -635,6 +680,10 @@ n9LdgEtkmGB9E2h3K4Vp7iGUaKuq23Zr32ehxiU8FWY7xoxbWTSA
|
||||
nHB1X37qrniVugfQcuBTAjswphC1drx7QjFFojJPZwKHHnt8kU7v
|
||||
nHUkAWDR4cB8AgPg7VXMX6et8xRTQb2KJfgv1aBEXozwrawRKgMB
|
||||
|
||||
[validator_list_sites]
|
||||
ripplevalidators.com
|
||||
trustthesevalidators.gov
|
||||
|
||||
[validator_list_keys]
|
||||
021A99A537FDEBC34E4FCA03B39BEADD04299BB19E85097EC92B15A3518801E566
|
||||
)rippleConfig");
|
||||
@@ -645,6 +694,8 @@ nHUkAWDR4cB8AgPg7VXMX6et8xRTQb2KJfgv1aBEXozwrawRKgMB
|
||||
c.loadFromString (boost::str (cc % vtg.validatorsFile ()));
|
||||
BEAST_EXPECT(c.legacy ("validators_file") == vtg.validatorsFile ());
|
||||
BEAST_EXPECT(c.section (SECTION_VALIDATORS).values ().size () == 15);
|
||||
BEAST_EXPECT(
|
||||
c.section (SECTION_VALIDATOR_LIST_SITES).values ().size () == 4);
|
||||
BEAST_EXPECT(
|
||||
c.section (SECTION_VALIDATOR_LIST_KEYS).values ().size () == 3);
|
||||
}
|
||||
|
||||
@@ -45,5 +45,6 @@
|
||||
#include <test/app/TrustAndBalance_test.cpp>
|
||||
#include <test/app/TxQ_test.cpp>
|
||||
#include <test/app/ValidatorList_test.cpp>
|
||||
#include <test/app/ValidatorSite_test.cpp>
|
||||
#include <test/app/SetTrust_test.cpp>
|
||||
#include <test/app/Ticket_test.cpp>
|
||||
|
||||
Reference in New Issue
Block a user