mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-24 04:55:52 +00:00
Dynamize trusted validator list and quorum (RIPD-1220):
Instead of specifying a static list of trusted validators in the config or validators file, the configuration can now include trusted validator list publisher keys. The trusted validator list and quorum are now reset each consensus round using the latest validator lists and the list of recent validations seen. The minimum validation quorum is now only configurable via the command line.
This commit is contained in:
@@ -1061,6 +1061,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\Manifest.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\app\misc\impl\Transaction.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
@@ -1075,6 +1079,8 @@
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ripple\app\misc\LoadFeeTrack.h">
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\app\misc\Manifest.h">
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ripple\app\misc\NetworkOPs.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
@@ -2297,12 +2303,6 @@
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ripple\overlay\impl\ConnectAttempt.h">
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ripple\overlay\impl\Manifest.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ripple\overlay\impl\Manifest.h">
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ripple\overlay\impl\Message.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
@@ -4199,6 +4199,10 @@
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\test\app\Manifest_test.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\test\app\MultiSign_test.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
@@ -4715,10 +4719,6 @@
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\test\overlay\manifest_test.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\test\overlay\short_read_test.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
|
||||
@@ -1569,6 +1569,9 @@
|
||||
<ClCompile Include="..\..\src\ripple\app\misc\impl\LoadFeeTrack.cpp">
|
||||
<Filter>ripple\app\misc\impl</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\app\misc\impl\Manifest.cpp">
|
||||
<Filter>ripple\app\misc\impl</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\app\misc\impl\Transaction.cpp">
|
||||
<Filter>ripple\app\misc\impl</Filter>
|
||||
</ClCompile>
|
||||
@@ -1581,6 +1584,9 @@
|
||||
<ClInclude Include="..\..\src\ripple\app\misc\LoadFeeTrack.h">
|
||||
<Filter>ripple\app\misc</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\app\misc\Manifest.h">
|
||||
<Filter>ripple\app\misc</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ripple\app\misc\NetworkOPs.cpp">
|
||||
<Filter>ripple\app\misc</Filter>
|
||||
</ClCompile>
|
||||
@@ -2880,12 +2886,6 @@
|
||||
<ClInclude Include="..\..\src\ripple\overlay\impl\ConnectAttempt.h">
|
||||
<Filter>ripple\overlay\impl</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ripple\overlay\impl\Manifest.cpp">
|
||||
<Filter>ripple\overlay\impl</Filter>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ripple\overlay\impl\Manifest.h">
|
||||
<Filter>ripple\overlay\impl</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ripple\overlay\impl\Message.cpp">
|
||||
<Filter>ripple\overlay\impl</Filter>
|
||||
</ClCompile>
|
||||
@@ -4983,6 +4983,9 @@
|
||||
<ClCompile Include="..\..\src\test\app\LoadFeeTrack_test.cpp">
|
||||
<Filter>test\app</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\test\app\Manifest_test.cpp">
|
||||
<Filter>test\app</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\test\app\MultiSign_test.cpp">
|
||||
<Filter>test\app</Filter>
|
||||
</ClCompile>
|
||||
@@ -5427,9 +5430,6 @@
|
||||
<ClCompile Include="..\..\src\test\overlay\cluster_test.cpp">
|
||||
<Filter>test\overlay</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\test\overlay\manifest_test.cpp">
|
||||
<Filter>test\overlay</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\test\overlay\short_read_test.cpp">
|
||||
<Filter>test\overlay</Filter>
|
||||
</ClCompile>
|
||||
|
||||
@@ -104,9 +104,6 @@ n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C RL3
|
||||
n9KiYM9CgngLvtRCQHZwgC2gjpdaZcCcbt3VboxiNFcKuwFVujzS RL4
|
||||
n9LdgEtkmGB9E2h3K4Vp7iGUaKuq23Zr32ehxiU8FWY7xoxbWTSA RL5
|
||||
|
||||
[validation_quorum]
|
||||
3
|
||||
|
||||
[validation_seed]
|
||||
{validation_seed}
|
||||
#vaidation_public_key: {validation_public_key}
|
||||
|
||||
@@ -45,7 +45,6 @@ RESULT = {
|
||||
'websocket_public_port': '5206',
|
||||
'peer_ip': '0.0.0.0',
|
||||
'rpc_port': '5205',
|
||||
'validation_quorum': '3',
|
||||
'websocket_ip': '127.0.0.1'}
|
||||
|
||||
FULL = """
|
||||
@@ -131,10 +130,6 @@ n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C RL3
|
||||
n9KiYM9CgngLvtRCQHZwgC2gjpdaZcCcbt3VboxiNFcKuwFVujzS RL4
|
||||
n9LdgEtkmGB9E2h3K4Vp7iGUaKuq23Zr32ehxiU8FWY7xoxbWTSA RL5
|
||||
|
||||
# Ditto.
|
||||
[validation_quorum]
|
||||
3
|
||||
|
||||
[validation_seed]
|
||||
sh1T8T9yGuV7Jb6DPhqSzdU2s5LcV
|
||||
|
||||
|
||||
@@ -546,8 +546,7 @@
|
||||
#
|
||||
# These settings affect the behavior of the server instance with respect
|
||||
# to Ripple payment protocol level activities such as validating and
|
||||
# closing ledgers, establishing a quorum, or adjusting fees in response
|
||||
# to server overloads.
|
||||
# closing ledgers or adjusting fees in response to server overloads.
|
||||
#
|
||||
#
|
||||
#
|
||||
@@ -607,11 +606,14 @@
|
||||
# to always accept as validators as well as the minimum number of validators
|
||||
# needed to accept consensus.
|
||||
#
|
||||
# The contents of the file should include a [validators] and a
|
||||
# [validation_quorum] entry. [validators] should be followed by
|
||||
# a list of validation public keys of nodes, one per line, optionally
|
||||
# followed by a comment separated by whitespace.
|
||||
# [validation_quorum] should be followed by a number.
|
||||
# The contents of the file should include a [validators] and/or
|
||||
# [validator_list_keys] entries.
|
||||
# [validators] should be followed by a list of validation public keys of
|
||||
# nodes, one per line.
|
||||
# [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.
|
||||
#
|
||||
# Specify the file by its name or path.
|
||||
# Unless an absolute path is specified, it will be considered relative to
|
||||
@@ -623,14 +625,11 @@
|
||||
#
|
||||
# Example content:
|
||||
# [validators]
|
||||
# n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7 RL1
|
||||
# n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj RL2
|
||||
# n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C RL3
|
||||
# n9KiYM9CgngLvtRCQHZwgC2gjpdaZcCcbt3VboxiNFcKuwFVujzS RL4
|
||||
# n9LdgEtkmGB9E2h3K4Vp7iGUaKuq23Zr32ehxiU8FWY7xoxbWTSA RL5
|
||||
#
|
||||
# [validation_quorum]
|
||||
# 3
|
||||
# n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7
|
||||
# n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj
|
||||
# n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C
|
||||
# n9KiYM9CgngLvtRCQHZwgC2gjpdaZcCcbt3VboxiNFcKuwFVujzS
|
||||
# n9LdgEtkmGB9E2h3K4Vp7iGUaKuq23Zr32ehxiU8FWY7xoxbWTSA
|
||||
#
|
||||
#
|
||||
# [path_search]
|
||||
@@ -1021,7 +1020,7 @@ pool.ntp.org
|
||||
[ips]
|
||||
r.ripple.com 51235
|
||||
|
||||
# File containing validation quorum and trusted validator keys.
|
||||
# File containing trusted validator keys or validator list publishers.
|
||||
# Unless an absolute path is specified, it will be considered relative to the
|
||||
# folder in which the rippled.cfg file is located.
|
||||
[validators_file]
|
||||
|
||||
@@ -13,8 +13,6 @@
|
||||
# [validators]
|
||||
#
|
||||
# List of the validation public keys of nodes to always accept as validators.
|
||||
# A comment may, optionally, be associated with each entry, separated by
|
||||
# whitespace from the validation public key.
|
||||
#
|
||||
# The latest list of recommended validators can be obtained from
|
||||
# https://ripple.com/ripple.txt
|
||||
@@ -23,26 +21,27 @@
|
||||
#
|
||||
# Examples:
|
||||
# n9KorY8QtTdRx7TVDpwnG9NvyxsDwHUKUEeDLY3AkiGncVaSXZi5
|
||||
# n9MqiExBcoG19UXwoLjBJnhsxEhAZMuWwJDRdkyDz1EkEkwzQTNt John Doe
|
||||
# n9MqiExBcoG19UXwoLjBJnhsxEhAZMuWwJDRdkyDz1EkEkwzQTNt
|
||||
#
|
||||
#
|
||||
#
|
||||
# [validation_quorum]
|
||||
# [validator_list_keys]
|
||||
#
|
||||
# Sets the minimum number of trusted validations a ledger must have before
|
||||
# the server considers it fully validated. Note that if you are validating,
|
||||
# your validation counts.
|
||||
# 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 list keys should be hex-encoded.
|
||||
#
|
||||
# Examples:
|
||||
# ed499d732bded01504a7407c224412ef550cc1ade638a4de4eb88af7c36cb8b282
|
||||
# 0202d3f36a801349f3be534e3f64cfa77dede6e1b6310a0b48f40f20f955cec945
|
||||
# 02dd8b7075f64d77d9d2bdb88da364f29fcd975f9ea6f21894abcc7564efda8054
|
||||
#
|
||||
|
||||
# Public keys of the validators that this rippled instance trusts.
|
||||
[validators]
|
||||
n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7 RL1
|
||||
n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj RL2
|
||||
n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C RL3
|
||||
n9KiYM9CgngLvtRCQHZwgC2gjpdaZcCcbt3VboxiNFcKuwFVujzS RL4
|
||||
n9LdgEtkmGB9E2h3K4Vp7iGUaKuq23Zr32ehxiU8FWY7xoxbWTSA RL5
|
||||
|
||||
# The number of validators rippled needs to accept a consensus.
|
||||
# Don't change this unless you know what you're doing.
|
||||
[validation_quorum]
|
||||
3
|
||||
n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7
|
||||
n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj
|
||||
n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C
|
||||
n9KiYM9CgngLvtRCQHZwgC2gjpdaZcCcbt3VboxiNFcKuwFVujzS
|
||||
n9LdgEtkmGB9E2h3K4Vp7iGUaKuq23Zr32ehxiU8FWY7xoxbWTSA
|
||||
|
||||
@@ -113,10 +113,6 @@ public:
|
||||
std::chrono::seconds getValidatedLedgerAge ();
|
||||
bool isCaughtUp(std::string& reason);
|
||||
|
||||
int getMinValidations ();
|
||||
|
||||
void setMinValidations (int v, bool strict);
|
||||
|
||||
std::uint32_t getEarliestFetch ();
|
||||
|
||||
bool storeLedger (std::shared_ptr<Ledger const> ledger);
|
||||
@@ -257,7 +253,7 @@ private:
|
||||
|
||||
void getFetchPack(LedgerHash missingHash, LedgerIndex missingIndex);
|
||||
boost::optional<LedgerHash> getLedgerHashForHistory(LedgerIndex index);
|
||||
int getNeededValidations();
|
||||
std::size_t getNeededValidations();
|
||||
void advanceThread();
|
||||
// Try to publish ledgers, acquire missing ledgers
|
||||
void doAdvance();
|
||||
@@ -313,8 +309,6 @@ private:
|
||||
|
||||
std::unique_ptr <detail::LedgerCleaner> mLedgerCleaner;
|
||||
|
||||
int mMinValidations; // The minimum validations to publish a ledger.
|
||||
bool mStrictValCount; // Don't raise the minimum
|
||||
uint256 mLastValidateHash;
|
||||
std::uint32_t mLastValidateSeq;
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include <ripple/app/misc/NetworkOPs.h>
|
||||
#include <ripple/app/misc/TxQ.h>
|
||||
#include <ripple/app/misc/Validations.h>
|
||||
#include <ripple/app/misc/ValidatorList.h>
|
||||
#include <ripple/app/tx/apply.h>
|
||||
#include <ripple/basics/contract.h>
|
||||
#include <ripple/basics/CountedObject.h>
|
||||
@@ -1267,14 +1268,14 @@ LedgerConsensusImp<Traits>::makeInitialPosition () ->
|
||||
app_.getValidations().getValidations (
|
||||
previousLedger_->info().parentHash);
|
||||
|
||||
auto const count = std::count_if (
|
||||
std::size_t const count = std::count_if (
|
||||
validations.begin(), validations.end(),
|
||||
[](auto const& v)
|
||||
{
|
||||
return v.second->isTrusted();
|
||||
});
|
||||
|
||||
if (count >= ledgerMaster_.getMinValidations())
|
||||
if (count >= app_.validators ().quorum ())
|
||||
{
|
||||
feeVote_.doVoting (
|
||||
previousLedger_,
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include <ripple/app/misc/Transaction.h>
|
||||
#include <ripple/app/misc/TxQ.h>
|
||||
#include <ripple/app/misc/Validations.h>
|
||||
#include <ripple/app/misc/ValidatorList.h>
|
||||
#include <ripple/app/paths/PathRequests.h>
|
||||
#include <ripple/basics/contract.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
@@ -53,9 +54,6 @@ namespace ripple {
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
// 150/256ths of validations of previous ledger
|
||||
#define MIN_VALIDATION_RATIO 150
|
||||
|
||||
// Don't catch up more than 100 ledgers (cannot exceed 256)
|
||||
#define MAX_LEDGER_GAP 100
|
||||
|
||||
@@ -73,8 +71,6 @@ LedgerMaster::LedgerMaster (Application& app, Stopwatch& stopwatch,
|
||||
, mHeldTransactions (uint256 ())
|
||||
, mLedgerCleaner (detail::make_LedgerCleaner (
|
||||
app, *this, app_.journal("LedgerCleaner")))
|
||||
, mMinValidations (0)
|
||||
, mStrictValCount (false)
|
||||
, mLastValidateSeq (0)
|
||||
, mAdvanceThread (false)
|
||||
, mAdvanceWork (false)
|
||||
@@ -115,13 +111,6 @@ LedgerMaster::isCompatible (
|
||||
beast::Journal::Stream s,
|
||||
char const* reason)
|
||||
{
|
||||
if (mStrictValCount)
|
||||
{
|
||||
// If we're only using validation count, then we can't
|
||||
// reject a ledger even if it's incompatible
|
||||
return true;
|
||||
}
|
||||
|
||||
auto validLedger = getValidatedLedger();
|
||||
|
||||
if (validLedger &&
|
||||
@@ -217,7 +206,7 @@ LedgerMaster::setValidLedger(
|
||||
|
||||
NetClock::time_point signTime;
|
||||
|
||||
if (! times.empty () && times.size() >= mMinValidations)
|
||||
if (! times.empty () && times.size() >= app_.validators ().quorum ())
|
||||
{
|
||||
// Calculate the sample median
|
||||
std::sort (times.begin (), times.end ());
|
||||
@@ -702,7 +691,7 @@ LedgerMaster::failedSave(std::uint32_t seq, uint256 const& hash)
|
||||
void
|
||||
LedgerMaster::checkAccept (uint256 const& hash, std::uint32_t seq)
|
||||
{
|
||||
int valCount = 0;
|
||||
std::size_t valCount = 0;
|
||||
|
||||
if (seq != 0)
|
||||
{
|
||||
@@ -713,18 +702,11 @@ LedgerMaster::checkAccept (uint256 const& hash, std::uint32_t seq)
|
||||
valCount =
|
||||
app_.getValidations().getTrustedValidationCount (hash);
|
||||
|
||||
if (valCount >= mMinValidations)
|
||||
if (valCount >= app_.validators ().quorum ())
|
||||
{
|
||||
ScopedLockType ml (m_mutex);
|
||||
if (seq > mLastValidLedger.second)
|
||||
mLastValidLedger = std::make_pair (hash, seq);
|
||||
|
||||
if (!mStrictValCount && (mMinValidations < (valCount/2 + 1)))
|
||||
{
|
||||
mMinValidations = (valCount/2 + 1);
|
||||
JLOG (m_journal.info())
|
||||
<< "Raising minimum validations to " << mMinValidations;
|
||||
}
|
||||
}
|
||||
|
||||
if (seq == mValidLedgerSeq)
|
||||
@@ -742,7 +724,7 @@ LedgerMaster::checkAccept (uint256 const& hash, std::uint32_t seq)
|
||||
if ((seq != 0) && (getValidLedgerIndex() == 0))
|
||||
{
|
||||
// Set peers sane early if we can
|
||||
if (valCount >= mMinValidations)
|
||||
if (valCount >= app_.validators ().quorum ())
|
||||
app_.overlay().checkSanity (seq);
|
||||
}
|
||||
|
||||
@@ -757,30 +739,14 @@ LedgerMaster::checkAccept (uint256 const& hash, std::uint32_t seq)
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines how many validations are needed to fully-validated a ledger
|
||||
* Determines how many validations are needed to fully validate a ledger
|
||||
*
|
||||
* @return Number of validations needed
|
||||
*/
|
||||
int
|
||||
std::size_t
|
||||
LedgerMaster::getNeededValidations ()
|
||||
{
|
||||
if (standalone_)
|
||||
return 0;
|
||||
|
||||
int minVal = mMinValidations;
|
||||
|
||||
if (mLastValidateHash.isNonZero ())
|
||||
{
|
||||
int val = app_.getValidations ().getTrustedValidationCount (
|
||||
mLastValidateHash);
|
||||
val *= MIN_VALIDATION_RATIO;
|
||||
val /= 256;
|
||||
|
||||
if (val > minVal)
|
||||
minVal = val;
|
||||
}
|
||||
|
||||
return minVal;
|
||||
return standalone_ ? 0 : app_.validators().quorum ();
|
||||
}
|
||||
|
||||
void
|
||||
@@ -904,7 +870,7 @@ LedgerMaster::consensusBuilt(
|
||||
ledgerSeq_ = seq;
|
||||
}
|
||||
|
||||
int valCount_;
|
||||
std::size_t valCount_;
|
||||
LedgerIndex ledgerSeq_;
|
||||
};
|
||||
|
||||
@@ -916,7 +882,7 @@ LedgerMaster::consensusBuilt(
|
||||
vs.mergeValidation (v->getFieldU32 (sfLedgerSequence));
|
||||
}
|
||||
|
||||
auto neededValidations = getNeededValidations ();
|
||||
auto const neededValidations = getNeededValidations ();
|
||||
auto maxSeq = mValidLedgerSeq.load();
|
||||
auto maxLedger = ledger->info().hash;
|
||||
|
||||
@@ -1313,21 +1279,6 @@ LedgerMaster::getPublishedLedger ()
|
||||
return mPubLedger;
|
||||
}
|
||||
|
||||
int
|
||||
LedgerMaster::getMinValidations ()
|
||||
{
|
||||
return mMinValidations;
|
||||
}
|
||||
|
||||
void
|
||||
LedgerMaster::setMinValidations (int v, bool strict)
|
||||
{
|
||||
JLOG (m_journal.info()) << "Validation quorum: " << v
|
||||
<< (strict ? " strict" : "");
|
||||
mMinValidations = v;
|
||||
mStrictValCount = strict;
|
||||
}
|
||||
|
||||
std::string
|
||||
LedgerMaster::getCompleteLedgers ()
|
||||
{
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
#include <ripple/app/misc/AmendmentTable.h>
|
||||
#include <ripple/app/misc/HashRouter.h>
|
||||
#include <ripple/app/misc/LoadFeeTrack.h>
|
||||
#include <ripple/app/misc/Manifest.h>
|
||||
#include <ripple/app/misc/NetworkOPs.h>
|
||||
#include <ripple/app/misc/SHAMapStore.h>
|
||||
#include <ripple/app/misc/TxQ.h>
|
||||
@@ -347,6 +348,8 @@ public:
|
||||
TaggedCache <uint256, AcceptedLedger> m_acceptedLedgerCache;
|
||||
std::unique_ptr <NetworkOPs> m_networkOPs;
|
||||
std::unique_ptr <Cluster> cluster_;
|
||||
std::unique_ptr <ManifestCache> validatorManifests_;
|
||||
std::unique_ptr <ManifestCache> publisherManifests_;
|
||||
std::unique_ptr <ValidatorList> validators_;
|
||||
std::unique_ptr <ServerHandler> serverHandler_;
|
||||
std::unique_ptr <AmendmentTable> m_amendmentTable;
|
||||
@@ -473,8 +476,15 @@ public:
|
||||
, cluster_ (std::make_unique<Cluster> (
|
||||
logs_->journal("Overlay")))
|
||||
|
||||
, validatorManifests_ (std::make_unique<ManifestCache> (
|
||||
logs_->journal("ManifestCache")))
|
||||
|
||||
, publisherManifests_ (std::make_unique<ManifestCache> (
|
||||
logs_->journal("ManifestCache")))
|
||||
|
||||
, validators_ (std::make_unique<ValidatorList> (
|
||||
logs_->journal("UniqueNodeList")))
|
||||
*validatorManifests_, *publisherManifests_, *timeKeeper_,
|
||||
logs_->journal("ValidatorList"), config_->VALIDATION_QUORUM))
|
||||
|
||||
, serverHandler_ (make_ServerHandler (*this, *m_networkOPs, get_io_service (),
|
||||
*m_jobQueue, *m_networkOPs, *m_resourceManager, *m_collectorManager))
|
||||
@@ -691,6 +701,16 @@ public:
|
||||
return *validators_;
|
||||
}
|
||||
|
||||
ManifestCache& validatorManifests() override
|
||||
{
|
||||
return *validatorManifests_;
|
||||
}
|
||||
|
||||
ManifestCache& publisherManifests() override
|
||||
{
|
||||
return *publisherManifests_;
|
||||
}
|
||||
|
||||
Cluster& cluster () override
|
||||
{
|
||||
return *cluster_;
|
||||
@@ -846,7 +866,18 @@ public:
|
||||
|
||||
mValidations->flush ();
|
||||
|
||||
m_overlay->saveValidatorKeyManifests (getWalletDB ());
|
||||
// TODO Store manifests in manifests.sqlite instead of wallet.db
|
||||
validatorManifests_->save (getWalletDB (), "ValidatorManifests",
|
||||
[this](PublicKey const& pubKey)
|
||||
{
|
||||
return validators().listed (pubKey);
|
||||
});
|
||||
|
||||
publisherManifests_->save (getWalletDB (), "PublisherManifests",
|
||||
[this](PublicKey const& pubKey)
|
||||
{
|
||||
return validators().trustedPublisher (pubKey);
|
||||
});
|
||||
|
||||
stopped ();
|
||||
}
|
||||
@@ -1013,9 +1044,6 @@ bool ApplicationImp::setup()
|
||||
|
||||
Pathfinder::initPathTable();
|
||||
|
||||
m_ledgerMaster->setMinValidations (
|
||||
config_->VALIDATION_QUORUM, config_->LOCK_QUORUM);
|
||||
|
||||
auto const startUp = config_->START_UP;
|
||||
if (startUp == Config::FRESH)
|
||||
{
|
||||
@@ -1062,15 +1090,26 @@ bool ApplicationImp::setup()
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!validators_->load (config().section (SECTION_VALIDATORS)))
|
||||
if (!validatorManifests_->load (
|
||||
getWalletDB (), "ValidatorManifests",
|
||||
config().section (SECTION_VALIDATION_MANIFEST).lines()))
|
||||
{
|
||||
JLOG(m_journal.fatal()) << "Invalid entry in validator configuration.";
|
||||
JLOG(m_journal.fatal()) << "Invalid configured validator manifest.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (validators_->size () == 0 && !config_->standalone())
|
||||
publisherManifests_->load (
|
||||
getWalletDB (), "PublisherManifests");
|
||||
|
||||
// Setup trusted validators
|
||||
if (!validators_->load (
|
||||
config_->VALIDATION_PUB,
|
||||
config().section (SECTION_VALIDATORS).values (),
|
||||
config().section (SECTION_VALIDATOR_LIST_KEYS).values ()))
|
||||
{
|
||||
JLOG(m_journal.warn()) << "No validators are configured.";
|
||||
JLOG(m_journal.fatal()) <<
|
||||
"Invalid entry in validator configuration.";
|
||||
return false;
|
||||
}
|
||||
|
||||
m_nodeStore->tune (config_->getSize (siNodeCacheSize), config_->getSize (siNodeCacheAge));
|
||||
@@ -1101,8 +1140,6 @@ bool ApplicationImp::setup()
|
||||
return false;
|
||||
}
|
||||
|
||||
m_overlay->setupValidatorKeyManifests (*config_, getWalletDB ());
|
||||
|
||||
{
|
||||
auto setup = setup_ServerHandler(
|
||||
*config_,
|
||||
|
||||
@@ -50,6 +50,7 @@ class InboundTransactions;
|
||||
class AcceptedLedger;
|
||||
class LedgerMaster;
|
||||
class LoadManager;
|
||||
class ManifestCache;
|
||||
class NetworkOPs;
|
||||
class OpenLedger;
|
||||
class OrderBookDB;
|
||||
@@ -120,6 +121,8 @@ public:
|
||||
virtual Overlay& overlay () = 0;
|
||||
virtual TxQ& getTxQ() = 0;
|
||||
virtual ValidatorList& validators () = 0;
|
||||
virtual ManifestCache& validatorManifests () = 0;
|
||||
virtual ManifestCache& publisherManifests () = 0;
|
||||
virtual Cluster& cluster () = 0;
|
||||
virtual Validations& getValidations () = 0;
|
||||
virtual NodeStore::Database& getNodeStore () = 0;
|
||||
|
||||
@@ -135,6 +135,10 @@ const char* WalletDBInit[] =
|
||||
RawData BLOB NOT NULL \
|
||||
);",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS PublisherManifests ( \
|
||||
RawData BLOB NOT NULL \
|
||||
);",
|
||||
|
||||
// Old tables that were present in wallet.db and we
|
||||
// no longer need or use.
|
||||
"DROP INDEX IF EXISTS SeedNodeNext;",
|
||||
|
||||
@@ -218,7 +218,7 @@ int run (int argc, char** argv)
|
||||
("unittest-arg", po::value <std::string> ()->implicit_value (""), "Supplies argument to unit tests.")
|
||||
("parameters", po::value< vector<string> > (), "Specify comma separated parameters.")
|
||||
("quiet,q", "Reduce diagnotics.")
|
||||
("quorum", po::value <int> (), "Set the validation quorum.")
|
||||
("quorum", po::value <std::size_t> (), "Override the minimum validation quorum.")
|
||||
("silent", "No output to the console after startup.")
|
||||
("verbose,v", "Verbose logging.")
|
||||
("load", "Load the current ledger from the local DB.")
|
||||
@@ -338,9 +338,6 @@ int run (int argc, char** argv)
|
||||
}
|
||||
|
||||
config->START_UP = Config::NETWORK;
|
||||
|
||||
if (config->VALIDATION_QUORUM < 2)
|
||||
config->VALIDATION_QUORUM = 2;
|
||||
}
|
||||
|
||||
// Override the RPC destination IP address. This must
|
||||
@@ -385,11 +382,7 @@ int run (int argc, char** argv)
|
||||
{
|
||||
try
|
||||
{
|
||||
config->VALIDATION_QUORUM = vm["quorum"].as <int> ();
|
||||
config->LOCK_QUORUM = true;
|
||||
|
||||
if (config->VALIDATION_QUORUM < 0)
|
||||
Throw<std::domain_error> ("");
|
||||
config->VALIDATION_QUORUM = vm["quorum"].as <std::size_t> ();
|
||||
}
|
||||
catch(std::exception const&)
|
||||
{
|
||||
|
||||
@@ -17,14 +17,11 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_OVERLAY_MANIFEST_H_INCLUDED
|
||||
#define RIPPLE_OVERLAY_MANIFEST_H_INCLUDED
|
||||
#ifndef RIPPLE_APP_MISC_MANIFEST_H_INCLUDED
|
||||
#define RIPPLE_APP_MISC_MANIFEST_H_INCLUDED
|
||||
|
||||
#include <ripple/app/misc/ValidatorList.h>
|
||||
#include <ripple/basics/BasicConfig.h>
|
||||
#include <ripple/basics/UnorderedContainers.h>
|
||||
#include <ripple/protocol/PublicKey.h>
|
||||
#include <ripple/protocol/STExchange.h>
|
||||
#include <ripple/beast/utility/Journal.h>
|
||||
#include <boost/optional.hpp>
|
||||
#include <string>
|
||||
@@ -57,13 +54,10 @@ namespace ripple {
|
||||
There are two stores of information within rippled related to manifests.
|
||||
An instance of ManifestCache stores, for each trusted validator, (a) its
|
||||
master public key, and (b) the most senior of all valid manifests it has
|
||||
seen for that validator, if any. On startup, the [validator_keys] config
|
||||
entries are used to prime the manifest cache with the trusted master keys.
|
||||
At this point, the manifest cache has all the entries it will ever have,
|
||||
but none of them have manifests. The [validation_manifest] config entry
|
||||
(which is the manifest for this validator) is then decoded and added to
|
||||
the manifest cache. Other manifests are added as "gossip" is received
|
||||
from rippled peers.
|
||||
seen for that validator, if any. On startup, the [validation_manifest]
|
||||
config entry (which is the manifest for this validator) is decoded and
|
||||
added to the manifest cache. Other manifests are added as "gossip" is
|
||||
received from rippled peers.
|
||||
|
||||
The other data store (which does not involve manifests per se) contains
|
||||
the set of active ephemeral validator keys. Keys are added to the set
|
||||
@@ -97,127 +91,219 @@ struct Manifest
|
||||
Manifest(Manifest&& other) = default;
|
||||
Manifest& operator=(Manifest&& other) = default;
|
||||
|
||||
inline bool
|
||||
operator==(Manifest const& rhs) const
|
||||
{
|
||||
return sequence == rhs.sequence && masterKey == rhs.masterKey &&
|
||||
signingKey == rhs.signingKey && serialized == rhs.serialized;
|
||||
}
|
||||
|
||||
inline bool
|
||||
operator!=(Manifest const& rhs) const
|
||||
{
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
/** Constructs Manifest from serialized string
|
||||
|
||||
@param s Serialized manifest string
|
||||
|
||||
@return `boost::none` if string is invalid
|
||||
*/
|
||||
static boost::optional<Manifest> make_Manifest(std::string s);
|
||||
|
||||
/// Returns `true` if manifest signature is valid
|
||||
bool verify () const;
|
||||
|
||||
/// Returns hash of serialized manifest data
|
||||
uint256 hash () const;
|
||||
|
||||
/// Returns `true` if manifest revokes master key
|
||||
bool revoked () const;
|
||||
|
||||
/// Returns manifest signature
|
||||
Blob getSignature () const;
|
||||
|
||||
/// Returns manifest master key signature
|
||||
Blob getMasterSignature () const;
|
||||
};
|
||||
|
||||
boost::optional<Manifest> make_Manifest(std::string s);
|
||||
|
||||
inline bool operator==(Manifest const& lhs, Manifest const& rhs)
|
||||
{
|
||||
return lhs.serialized == rhs.serialized && lhs.masterKey == rhs.masterKey &&
|
||||
lhs.signingKey == rhs.signingKey && lhs.sequence == rhs.sequence;
|
||||
}
|
||||
|
||||
inline bool operator!=(Manifest const& lhs, Manifest const& rhs)
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
enum class ManifestDisposition
|
||||
{
|
||||
accepted = 0, // everything checked out
|
||||
/// Manifest is valid
|
||||
accepted = 0,
|
||||
|
||||
untrusted, // manifest declares a master key we don't trust
|
||||
stale, // trusted master key, but seq is too old
|
||||
invalid, // trusted and timely, but invalid signature
|
||||
/// Sequence is too old
|
||||
stale,
|
||||
|
||||
/// Timely, but invalid signature
|
||||
invalid
|
||||
};
|
||||
|
||||
class DatabaseCon;
|
||||
|
||||
/** Remembers manifests with the highest sequence number. */
|
||||
class ManifestCache
|
||||
{
|
||||
private:
|
||||
struct MappedType
|
||||
{
|
||||
MappedType() = default;
|
||||
MappedType(MappedType&&) = default;
|
||||
MappedType& operator=(MappedType&&) = default;
|
||||
beast::Journal mutable j_;
|
||||
std::mutex apply_mutex_;
|
||||
std::mutex mutable read_mutex_;
|
||||
|
||||
MappedType(std::string comment,
|
||||
std::string serialized,
|
||||
PublicKey pk, PublicKey spk, std::uint32_t seq)
|
||||
:comment (std::move(comment))
|
||||
{
|
||||
m.emplace (std::move(serialized), std::move(pk), std::move(spk),
|
||||
seq);
|
||||
}
|
||||
/** Active manifests stored by master public key. */
|
||||
hash_map <PublicKey, Manifest> map_;
|
||||
|
||||
std::string comment;
|
||||
boost::optional<Manifest> m;
|
||||
};
|
||||
|
||||
using MapType = hash_map<PublicKey, MappedType>;
|
||||
|
||||
mutable std::mutex mutex_;
|
||||
MapType map_;
|
||||
|
||||
ManifestDisposition
|
||||
canApply (PublicKey const& pk, std::uint32_t seq,
|
||||
beast::Journal journal) const;
|
||||
/** Master public keys stored by current ephemeral public key. */
|
||||
hash_map <PublicKey, PublicKey> signingToMasterKeys_;
|
||||
|
||||
public:
|
||||
ManifestCache() = default;
|
||||
ManifestCache (ManifestCache const&) = delete;
|
||||
ManifestCache& operator= (ManifestCache const&) = delete;
|
||||
~ManifestCache() = default;
|
||||
explicit
|
||||
ManifestCache (beast::Journal j = beast::Journal())
|
||||
: j_ (j)
|
||||
{
|
||||
};
|
||||
|
||||
bool loadValidatorKeys(
|
||||
Section const& keys,
|
||||
beast::Journal journal);
|
||||
/** Returns master key's current signing key.
|
||||
|
||||
void configManifest (Manifest m, ValidatorList& unl, beast::Journal journal);
|
||||
@param pk Master public key
|
||||
|
||||
/** Determines whether a node is in the trusted master key list */
|
||||
@return pk if no known signing key from a manifest
|
||||
|
||||
@par Thread Safety
|
||||
|
||||
May be called concurrently
|
||||
*/
|
||||
PublicKey
|
||||
getSigningKey (PublicKey const& pk) const;
|
||||
|
||||
/** Returns ephemeral signing key's master public key.
|
||||
|
||||
@param pk Ephemeral signing public key
|
||||
|
||||
@return pk if signing key is not in a valid manifest
|
||||
|
||||
@par Thread Safety
|
||||
|
||||
May be called concurrently
|
||||
*/
|
||||
PublicKey
|
||||
getMasterKey (PublicKey const& pk) const;
|
||||
|
||||
/** Returns `true` if master key has been revoked in a manifest.
|
||||
|
||||
@param pk Master public key
|
||||
|
||||
@par Thread Safety
|
||||
|
||||
May be called concurrently
|
||||
*/
|
||||
bool
|
||||
trusted (
|
||||
PublicKey const& identity) const;
|
||||
revoked (PublicKey const& pk) const;
|
||||
|
||||
void addTrustedKey (PublicKey const& pk, std::string comment);
|
||||
/** Add manifest to cache.
|
||||
|
||||
/** The number of installed trusted master keys */
|
||||
std::size_t
|
||||
size () const;
|
||||
@param m Manifest to add
|
||||
|
||||
@return `ManifestDisposition::accepted` if successful, or
|
||||
`stale` or `invalid` otherwise
|
||||
|
||||
@par Thread Safety
|
||||
|
||||
May be called concurrently
|
||||
*/
|
||||
ManifestDisposition
|
||||
applyManifest (
|
||||
Manifest m, ValidatorList& unl, beast::Journal journal);
|
||||
Manifest m);
|
||||
|
||||
/** Populate manifest cache with manifests in database and config.
|
||||
|
||||
@param dbCon Database connection with dbTable
|
||||
|
||||
@param dbTable Database table
|
||||
|
||||
@param configManifest Base64 encoded manifest for local node's
|
||||
validator keys
|
||||
|
||||
@par Thread Safety
|
||||
|
||||
May be called concurrently
|
||||
*/
|
||||
bool load (
|
||||
DatabaseCon& dbCon, std::string const& dbTable,
|
||||
std::vector<std::string> const& configManifest);
|
||||
|
||||
/** Populate manifest cache with manifests in database.
|
||||
|
||||
@param dbCon Database connection with dbTable
|
||||
|
||||
@param dbTable Database table
|
||||
|
||||
@par Thread Safety
|
||||
|
||||
May be called concurrently
|
||||
*/
|
||||
void load (
|
||||
DatabaseCon& dbCon, ValidatorList& unl, beast::Journal journal);
|
||||
void save (DatabaseCon& dbCon) const;
|
||||
DatabaseCon& dbCon, std::string const& dbTable);
|
||||
|
||||
// A "for_each" for populated manifests only
|
||||
/** Save cached manifests to database.
|
||||
|
||||
@param dbCon Database connection with `ValidatorManifests` table
|
||||
|
||||
@param isTrusted Function that returns true if manifest is trusted
|
||||
|
||||
@par Thread Safety
|
||||
|
||||
May be called concurrently
|
||||
*/
|
||||
void save (
|
||||
DatabaseCon& dbCon, std::string const& dbTable,
|
||||
std::function <bool (PublicKey const&)> isTrusted);
|
||||
|
||||
/** Invokes the callback once for every populated manifest.
|
||||
|
||||
@note Undefined behavior results when calling ManifestCache members from
|
||||
within the callback
|
||||
|
||||
@param f Function called for each manifest
|
||||
|
||||
@par Thread Safety
|
||||
|
||||
May be called concurrently
|
||||
*/
|
||||
template <class Function>
|
||||
void
|
||||
for_each_manifest(Function&& f) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock (mutex_);
|
||||
for (auto const& e : map_)
|
||||
std::lock_guard<std::mutex> lock{read_mutex_};
|
||||
for (auto const& m : map_)
|
||||
{
|
||||
if (auto const& m = e.second.m)
|
||||
f(*m);
|
||||
f(m.second);
|
||||
}
|
||||
}
|
||||
|
||||
// A "for_each" for populated manifests only
|
||||
// The PreFun is called with the maximum number of
|
||||
// times EachFun will be called (useful for memory allocations)
|
||||
/** Invokes the callback once for every populated manifest.
|
||||
|
||||
@note Undefined behavior results when calling ManifestCache members from
|
||||
within the callback
|
||||
|
||||
@param pf Pre-function called with the maximum number of times f will be
|
||||
called (useful for memory allocations)
|
||||
|
||||
@param f Function called for each manifest
|
||||
|
||||
@par Thread Safety
|
||||
|
||||
May be called concurrently
|
||||
*/
|
||||
template <class PreFun, class EachFun>
|
||||
void
|
||||
for_each_manifest(PreFun&& pf, EachFun&& f) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock (mutex_);
|
||||
std::lock_guard<std::mutex> lock{read_mutex_};
|
||||
pf(map_.size ());
|
||||
for (auto const& e : map_)
|
||||
for (auto const& m : map_)
|
||||
{
|
||||
if (auto const& m = e.second.m)
|
||||
f(*m);
|
||||
f(m.second);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1202,8 +1202,8 @@ public:
|
||||
if (nodesUsing > v.nodesUsing)
|
||||
return true;
|
||||
|
||||
if (nodesUsing < v.nodesUsing) return
|
||||
false;
|
||||
if (nodesUsing < v.nodesUsing)
|
||||
return false;
|
||||
|
||||
return highNodeUsing > v.highNodeUsing;
|
||||
}
|
||||
@@ -1485,6 +1485,9 @@ bool NetworkOPsImp::beginConsensus (uint256 const& networkClosed)
|
||||
assert (closingInfo.parentHash ==
|
||||
m_ledgerMaster.getClosedLedger()->info().hash);
|
||||
|
||||
app_.validators().onConsensusStart (
|
||||
app_.getValidations().getCurrentPublicKeys ());
|
||||
|
||||
mConsensus->startRound (
|
||||
*mLedgerConsensus,
|
||||
networkClosed,
|
||||
@@ -2032,7 +2035,8 @@ Json::Value NetworkOPsImp::getServerInfo (bool human, bool admin)
|
||||
if (mNeedNetworkLedger)
|
||||
info[jss::network_ledger] = "waiting";
|
||||
|
||||
info[jss::validation_quorum] = m_ledgerMaster.getMinValidations ();
|
||||
info[jss::validation_quorum] = static_cast<Json::UInt>(
|
||||
app_.validators ().quorum ());
|
||||
|
||||
info[jss::io_latency_ms] = static_cast<Json::UInt> (
|
||||
app_.getIOLatency().count());
|
||||
@@ -2050,7 +2054,7 @@ Json::Value NetworkOPsImp::getServerInfo (bool human, bool admin)
|
||||
s.reserve (Manifest::textLength);
|
||||
for (auto const& line : validation_manifest.lines())
|
||||
s += beast::rfc2616::trim(line);
|
||||
if (auto mo = make_Manifest (beast::detail::base64_decode(s)))
|
||||
if (auto mo = Manifest::make_Manifest (beast::detail::base64_decode(s)))
|
||||
{
|
||||
Json::Value valManifest = Json::objectValue;
|
||||
valManifest [jss::master_key] = toBase58 (
|
||||
|
||||
@@ -88,9 +88,12 @@ private:
|
||||
bool addValidation (STValidation::ref val, std::string const& source) override
|
||||
{
|
||||
auto signer = val->getSignerPublic ();
|
||||
auto hash = val->getLedgerHash ();
|
||||
bool isCurrent = current (val);
|
||||
|
||||
if (!val->isTrusted() && app_.validators().trusted (signer))
|
||||
auto pubKey = app_.validators ().getTrustedKey (signer);
|
||||
|
||||
if (!val->isTrusted() && pubKey)
|
||||
val->setTrusted();
|
||||
|
||||
if (!val->isTrusted ())
|
||||
@@ -98,36 +101,61 @@ private:
|
||||
JLOG (j_.trace()) <<
|
||||
"Node " << toBase58 (TokenType::TOKEN_NODE_PUBLIC, signer) <<
|
||||
" not in UNL st=" << val->getSignTime().time_since_epoch().count() <<
|
||||
", hash=" << val->getLedgerHash () <<
|
||||
", hash=" << hash <<
|
||||
", shash=" << val->getSigningHash () <<
|
||||
" src=" << source;
|
||||
}
|
||||
|
||||
auto hash = val->getLedgerHash ();
|
||||
auto node = val->getNodeID ();
|
||||
if (! pubKey)
|
||||
pubKey = app_.validators ().getListedKey (signer);
|
||||
|
||||
if (val->isTrusted () && isCurrent)
|
||||
if (isCurrent &&
|
||||
(val->isTrusted () || pubKey))
|
||||
{
|
||||
ScopedLockType sl (mLock);
|
||||
|
||||
if (!findCreateSet (hash)->insert (std::make_pair (node, val)).second)
|
||||
if (!findCreateSet (hash)->insert (
|
||||
std::make_pair (*pubKey, val)).second)
|
||||
return false;
|
||||
|
||||
auto it = mCurrentValidations.find (node);
|
||||
auto it = mCurrentValidations.find (*pubKey);
|
||||
|
||||
if (it == mCurrentValidations.end ())
|
||||
{
|
||||
// No previous validation from this validator
|
||||
mCurrentValidations.emplace (node, val);
|
||||
mCurrentValidations.emplace (*pubKey, val);
|
||||
}
|
||||
else if (!it->second)
|
||||
{
|
||||
// Previous validation has expired
|
||||
it->second = val;
|
||||
}
|
||||
else if (val->getSignTime () > it->second->getSignTime ())
|
||||
else
|
||||
{
|
||||
// This is a newer validation
|
||||
auto const oldSeq = (*it->second)[~sfLedgerSequence];
|
||||
auto const newSeq = (*val)[~sfLedgerSequence];
|
||||
|
||||
if (oldSeq && newSeq && *oldSeq == *newSeq)
|
||||
{
|
||||
JLOG (j_.warn()) <<
|
||||
"Trusted node " <<
|
||||
toBase58 (TokenType::TOKEN_NODE_PUBLIC, *pubKey) <<
|
||||
" published multiple validations for ledger " <<
|
||||
*oldSeq;
|
||||
|
||||
// Remove current validation for the revoked signing key
|
||||
if (signer != it->second->getSignerPublic())
|
||||
{
|
||||
auto set = findSet (it->second->getLedgerHash ());
|
||||
if (set)
|
||||
set->erase (*pubKey);
|
||||
}
|
||||
}
|
||||
|
||||
if (val->getSignTime () > it->second->getSignTime () ||
|
||||
signer != it->second->getSignerPublic())
|
||||
{
|
||||
// This is either a newer validation or a new signing key
|
||||
val->setPreviousHash (it->second->getLedgerHash ());
|
||||
mStaleValidations.push_back (it->second);
|
||||
it->second = val;
|
||||
@@ -139,6 +167,7 @@ private:
|
||||
isCurrent = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JLOG (j_.debug()) <<
|
||||
"Val for " << hash <<
|
||||
@@ -185,9 +214,10 @@ private:
|
||||
(val->getSeenTime() < (now + VALIDATION_VALID_LOCAL)));
|
||||
}
|
||||
|
||||
int getTrustedValidationCount (uint256 const& ledger) override
|
||||
std::size_t
|
||||
getTrustedValidationCount (uint256 const& ledger) override
|
||||
{
|
||||
int trusted = 0;
|
||||
std::size_t trusted = 0;
|
||||
ScopedLockType sl (mLock);
|
||||
auto set = findSet (ledger);
|
||||
|
||||
@@ -292,6 +322,37 @@ private:
|
||||
return ret;
|
||||
}
|
||||
|
||||
hash_set<PublicKey> getCurrentPublicKeys () override
|
||||
{
|
||||
hash_set<PublicKey> ret;
|
||||
|
||||
ScopedLockType sl (mLock);
|
||||
auto it = mCurrentValidations.begin ();
|
||||
|
||||
while (it != mCurrentValidations.end ())
|
||||
{
|
||||
if (!it->second) // contains no record
|
||||
it = mCurrentValidations.erase (it);
|
||||
else if (! current (it->second))
|
||||
{
|
||||
// contains a stale record
|
||||
mStaleValidations.push_back (it->second);
|
||||
it->second.reset ();
|
||||
condWrite ();
|
||||
it = mCurrentValidations.erase (it);
|
||||
}
|
||||
else
|
||||
{
|
||||
// contains a live record
|
||||
ret.insert (it->first);
|
||||
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
LedgerToValidationCounter getCurrentValidations (
|
||||
uint256 currentLedger,
|
||||
uint256 priorLedger,
|
||||
@@ -317,6 +378,8 @@ private:
|
||||
condWrite ();
|
||||
it = mCurrentValidations.erase (it);
|
||||
}
|
||||
else if (! it->second->isTrusted())
|
||||
++it;
|
||||
else if (! it->second->isFieldPresent (sfLedgerSequence) ||
|
||||
(it->second->getFieldU32 (sfLedgerSequence) >= cutoffBefore))
|
||||
{
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace ripple {
|
||||
// VFALCO TODO rename and move these type aliases into the Validations interface
|
||||
|
||||
// nodes validating and highest node ID validating
|
||||
using ValidationSet = hash_map<NodeID, STValidation::pointer>;
|
||||
using ValidationSet = hash_map<PublicKey, STValidation::pointer>;
|
||||
|
||||
using ValidationCounter = std::pair<int, NodeID>;
|
||||
using LedgerToValidationCounter = hash_map<uint256, ValidationCounter>;
|
||||
@@ -47,7 +47,7 @@ public:
|
||||
|
||||
virtual ValidationSet getValidations (uint256 const& ledger) = 0;
|
||||
|
||||
virtual int getTrustedValidationCount (uint256 const& ledger) = 0;
|
||||
virtual std::size_t getTrustedValidationCount (uint256 const& ledger) = 0;
|
||||
|
||||
/** Returns fees reported by trusted validators in the given ledger. */
|
||||
virtual
|
||||
@@ -57,6 +57,8 @@ public:
|
||||
virtual int getNodesAfter (uint256 const& ledger) = 0;
|
||||
virtual int getLoadRatio (bool overLoaded) = 0;
|
||||
|
||||
virtual hash_set<PublicKey> getCurrentPublicKeys () = 0;
|
||||
|
||||
// VFALCO TODO make a type alias for this ugly return value!
|
||||
virtual LedgerToValidationCounter getCurrentValidations (
|
||||
uint256 currentLedger, uint256 previousLedger,
|
||||
|
||||
@@ -20,102 +20,437 @@
|
||||
#ifndef RIPPLE_APP_MISC_VALIDATORLIST_H_INCLUDED
|
||||
#define RIPPLE_APP_MISC_VALIDATORLIST_H_INCLUDED
|
||||
|
||||
#include <ripple/basics/BasicConfig.h>
|
||||
#include <ripple/app/misc/Manifest.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/basics/UnorderedContainers.h>
|
||||
#include <ripple/core/TimeKeeper.h>
|
||||
#include <ripple/crypto/csprng.h>
|
||||
#include <ripple/protocol/PublicKey.h>
|
||||
#include <boost/optional.hpp>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <boost/iterator/counting_iterator.hpp>
|
||||
#include <boost/range/adaptors.hpp>
|
||||
#include <boost/thread/locks.hpp>
|
||||
#include <boost/thread/shared_mutex.hpp>
|
||||
#include <mutex>
|
||||
#include <numeric>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
enum class ListDisposition
|
||||
{
|
||||
/// List is valid
|
||||
accepted = 0,
|
||||
|
||||
/// List version is not supported
|
||||
unsupported_version,
|
||||
|
||||
/// List signed by untrusted publisher key
|
||||
untrusted,
|
||||
|
||||
/// Trusted publisher key, but seq is too old
|
||||
stale,
|
||||
|
||||
/// Invalid format or signature
|
||||
invalid,
|
||||
};
|
||||
|
||||
/**
|
||||
Trusted Validators List
|
||||
-----------------------
|
||||
|
||||
Rippled accepts ledger proposals and validations from trusted validator
|
||||
nodes. A ledger is considered fully-validated once the number of received
|
||||
trusted validations for a ledger meets or exceeds a quorum value.
|
||||
|
||||
This class manages the set of validation public keys the local rippled node
|
||||
trusts. The list of trusted keys is populated using the keys listed in the
|
||||
configuration file as well as lists signed by trusted publishers. The
|
||||
trusted publisher public keys are specified in the config.
|
||||
|
||||
New lists are expected to include the following data:
|
||||
|
||||
@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
|
||||
|
||||
Individual validator lists are stored separately by publisher. The number of
|
||||
lists on which a validator's public key appears is also tracked.
|
||||
|
||||
The list of trusted validation public keys is reset at the start of each
|
||||
consensus round to take into account the latest known lists as well as the
|
||||
set of validators from whom validations are being received. Listed
|
||||
validation public keys are shuffled and then sorted by the number of lists
|
||||
they appear on. (The shuffling makes the order/rank of validators with the
|
||||
same number of listings non-deterministic.) A quorum value is calculated for
|
||||
the new trusted validator list. If there is only one list, all listed keys
|
||||
are trusted. Otherwise, the trusted list size is set to 125% of the quorum.
|
||||
*/
|
||||
class ValidatorList
|
||||
{
|
||||
private:
|
||||
/** The non-ephemeral public keys from the configuration file. */
|
||||
hash_map<PublicKey, std::string> permanent_;
|
||||
struct PublisherList
|
||||
{
|
||||
bool available;
|
||||
std::vector<PublicKey> list;
|
||||
std::size_t sequence;
|
||||
std::size_t expiration;
|
||||
};
|
||||
|
||||
/** The ephemeral public keys from manifests. */
|
||||
hash_map<PublicKey, std::string> ephemeral_;
|
||||
ManifestCache& validatorManifests_;
|
||||
ManifestCache& publisherManifests_;
|
||||
TimeKeeper& timeKeeper_;
|
||||
beast::Journal j_;
|
||||
boost::shared_mutex mutable mutex_;
|
||||
|
||||
std::mutex mutable mutex_;
|
||||
beast::Journal mutable j_;
|
||||
std::atomic<std::size_t> quorum_;
|
||||
boost::optional<std::size_t> minimumQuorum_;
|
||||
|
||||
// Published lists stored by publisher master public key
|
||||
hash_map<PublicKey, PublisherList> publisherLists_;
|
||||
|
||||
// Listed master public keys with the number of lists they appear on
|
||||
hash_map<PublicKey, std::size_t> keyListings_;
|
||||
|
||||
// The current list of trusted master keys
|
||||
hash_set<PublicKey> trustedKeys_;
|
||||
|
||||
PublicKey localPubKey_;
|
||||
|
||||
public:
|
||||
explicit
|
||||
ValidatorList (beast::Journal j);
|
||||
ValidatorList (
|
||||
ManifestCache& validatorManifests,
|
||||
ManifestCache& publisherManifests,
|
||||
TimeKeeper& timeKeeper,
|
||||
beast::Journal j,
|
||||
boost::optional<std::size_t> minimumQuorum = boost::none);
|
||||
~ValidatorList ();
|
||||
|
||||
/** Determines whether a node is in the UNL
|
||||
@return boost::none if the node isn't a member,
|
||||
otherwise, the comment associated with the
|
||||
node (which may be an empty string).
|
||||
/** Load configured trusted keys.
|
||||
|
||||
@param localSigningKey This node's validation public key
|
||||
|
||||
@param configKeys List of trusted keys from config. Each entry consists
|
||||
of a base58 encoded validation public key, optionally followed by a
|
||||
comment.
|
||||
|
||||
@param publisherKeys List of trusted publisher public keys. Each entry
|
||||
contains a base58 encoded account public key.
|
||||
|
||||
@par Thread Safety
|
||||
|
||||
May be called concurrently
|
||||
|
||||
@return `false` if an entry is invalid or unparsable
|
||||
*/
|
||||
boost::optional<std::string>
|
||||
member (
|
||||
PublicKey const& identity) const;
|
||||
bool
|
||||
load (
|
||||
PublicKey const& localSigningKey,
|
||||
std::vector<std::string> const& configKeys,
|
||||
std::vector<std::string> const& publisherKeys);
|
||||
|
||||
/** Determines whether a node is in the UNL */
|
||||
/** Apply published list of public keys
|
||||
|
||||
@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
|
||||
|
||||
@return `ListDisposition::accepted` if list was successfully applied
|
||||
|
||||
@par Thread Safety
|
||||
|
||||
May be called concurrently
|
||||
*/
|
||||
ListDisposition
|
||||
applyList (
|
||||
std::string const& manifest,
|
||||
std::string const& blob,
|
||||
std::string const& signature,
|
||||
std::uint32_t version);
|
||||
|
||||
/** Update trusted keys
|
||||
|
||||
Reset the trusted keys based on latest manifests, received validations,
|
||||
and lists.
|
||||
|
||||
@param seenValidators Set of public keys used to sign recently
|
||||
received validations
|
||||
|
||||
@par Thread Safety
|
||||
|
||||
May be called concurrently
|
||||
*/
|
||||
template<class KeySet>
|
||||
void
|
||||
onConsensusStart (
|
||||
KeySet const& seenValidators);
|
||||
|
||||
/** Get quorum value for current trusted key set
|
||||
|
||||
The quorum is the minimum number of validations needed for a ledger to
|
||||
be fully validated. It can change when the set of trusted validation
|
||||
keys is updated (at the start of each consensus round) and primarily
|
||||
depends on the number of trusted keys.
|
||||
|
||||
@par Thread Safety
|
||||
|
||||
May be called concurrently
|
||||
|
||||
@return quorum value
|
||||
*/
|
||||
std::size_t
|
||||
quorum () const
|
||||
{
|
||||
return quorum_;
|
||||
};
|
||||
|
||||
/** Returns `true` if public key is trusted
|
||||
|
||||
@param identity Validation public key
|
||||
|
||||
@par Thread Safety
|
||||
|
||||
May be called concurrently
|
||||
*/
|
||||
bool
|
||||
trusted (
|
||||
PublicKey const& identity) const;
|
||||
|
||||
/** Insert a short-term validator key published in a manifest. */
|
||||
/** Returns `true` if public key is included on any lists
|
||||
|
||||
@param identity Validation public key
|
||||
|
||||
@par Thread Safety
|
||||
|
||||
May be called concurrently
|
||||
*/
|
||||
bool
|
||||
insertEphemeralKey (
|
||||
PublicKey const& identity,
|
||||
std::string const& comment);
|
||||
listed (
|
||||
PublicKey const& identity) const;
|
||||
|
||||
/** Remove a short-term validator revoked in a manifest. */
|
||||
/** Returns master public key if public key is trusted
|
||||
|
||||
@param identity Validation public key
|
||||
|
||||
@return `boost::none` if key is not trusted
|
||||
|
||||
@par Thread Safety
|
||||
|
||||
May be called concurrently
|
||||
*/
|
||||
boost::optional<PublicKey>
|
||||
getTrustedKey (
|
||||
PublicKey const& identity) const;
|
||||
|
||||
/** Returns listed master public if public key is included on any lists
|
||||
|
||||
@param identity Validation public key
|
||||
|
||||
@return `boost::none` if key is not listed
|
||||
|
||||
@par Thread Safety
|
||||
|
||||
May be called concurrently
|
||||
*/
|
||||
boost::optional<PublicKey>
|
||||
getListedKey (
|
||||
PublicKey const& identity) const;
|
||||
|
||||
/** Returns `true` if public key is a trusted publisher
|
||||
|
||||
@param identity Publisher public key
|
||||
|
||||
@par Thread Safety
|
||||
|
||||
May be called concurrently
|
||||
*/
|
||||
bool
|
||||
removeEphemeralKey (
|
||||
PublicKey const& identity);
|
||||
trustedPublisher (
|
||||
PublicKey const& identity) const;
|
||||
|
||||
/** Insert a long-term validator key. */
|
||||
bool
|
||||
insertPermanentKey (
|
||||
PublicKey const& identity,
|
||||
std::string const& comment);
|
||||
/** Invokes the callback once for every listed validation public key.
|
||||
|
||||
/** Remove a long-term validator key. */
|
||||
bool
|
||||
removePermanentKey (
|
||||
PublicKey const& identity);
|
||||
|
||||
/** The number of installed permanent and ephemeral keys */
|
||||
std::size_t
|
||||
size () const;
|
||||
|
||||
/** Invokes the callback once for every node in the UNL.
|
||||
@note You are not allowed to insert or remove any
|
||||
nodes in the UNL from within the callback.
|
||||
@note Undefined behavior results when calling ValidatorList members from
|
||||
within the callback
|
||||
|
||||
The arguments passed into the lambda are:
|
||||
- The public key of the validator;
|
||||
- A (possibly empty) comment.
|
||||
- A boolean indicating whether this is a
|
||||
permanent or ephemeral key;
|
||||
|
||||
@li The validation public key
|
||||
|
||||
@li A boolean indicating whether this is a trusted key
|
||||
|
||||
@par Thread Safety
|
||||
|
||||
May be called concurrently
|
||||
*/
|
||||
void
|
||||
for_each (
|
||||
std::function<void(PublicKey const&, std::string const&, bool)> func) const;
|
||||
for_each_listed (
|
||||
std::function<void(PublicKey const&, bool)> func) const;
|
||||
|
||||
/** Load the list of trusted validators.
|
||||
private:
|
||||
/** Check response for trusted valid published list
|
||||
|
||||
The section contains entries consisting of a base58
|
||||
encoded validator public key, optionally followed by
|
||||
a comment.
|
||||
@return `ListDisposition::accepted` if list can be applied
|
||||
|
||||
@return false if an entry could not be parsed or
|
||||
contained an invalid validator public key,
|
||||
true otherwise.
|
||||
@par Thread Safety
|
||||
|
||||
Calling public member function is expected to lock mutex
|
||||
*/
|
||||
ListDisposition
|
||||
verify (
|
||||
Json::Value& list,
|
||||
PublicKey& pubKey,
|
||||
std::string const& manifest,
|
||||
std::string const& blob,
|
||||
std::string const& signature);
|
||||
|
||||
/** Stop trusting publisher's list of keys.
|
||||
|
||||
@param publisherKey Publisher public key
|
||||
|
||||
@return `false` if key was not trusted
|
||||
|
||||
@par Thread Safety
|
||||
|
||||
Calling public member function is expected to lock mutex
|
||||
*/
|
||||
bool
|
||||
load (
|
||||
Section const& validators);
|
||||
removePublisherList (PublicKey const& publisherKey);
|
||||
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template<class KeySet>
|
||||
void
|
||||
ValidatorList::onConsensusStart (
|
||||
KeySet const& seenValidators)
|
||||
{
|
||||
boost::unique_lock<boost::shared_mutex> lock{mutex_};
|
||||
|
||||
// Check that lists from all configured publishers are available
|
||||
bool allListsAvailable = true;
|
||||
|
||||
for (auto const& list : publisherLists_)
|
||||
{
|
||||
// Remove any expired published lists
|
||||
if (list.second.expiration &&
|
||||
list.second.expiration <=
|
||||
timeKeeper_.now().time_since_epoch().count())
|
||||
removePublisherList (list.first);
|
||||
else if (! list.second.available)
|
||||
allListsAvailable = false;
|
||||
}
|
||||
|
||||
std::multimap<std::size_t, PublicKey> rankedKeys;
|
||||
bool localKeyListed = false;
|
||||
|
||||
// "Iterate" the listed keys in random order so that the rank of multiple
|
||||
// keys with the same number of listings is not deterministic
|
||||
std::vector<std::size_t> indexes (keyListings_.size());
|
||||
std::iota (indexes.begin(), indexes.end(), 0);
|
||||
std::shuffle (indexes.begin(), indexes.end(), crypto_prng());
|
||||
|
||||
for (auto const& index : indexes)
|
||||
{
|
||||
auto const& val = std::next (keyListings_.begin(), index);
|
||||
|
||||
if (validatorManifests_.revoked (val->first))
|
||||
continue;
|
||||
|
||||
if (val->first == localPubKey_)
|
||||
{
|
||||
localKeyListed = val->second > 1;
|
||||
rankedKeys.insert (
|
||||
std::pair<std::size_t,PublicKey>(
|
||||
std::numeric_limits<std::size_t>::max(), localPubKey_));
|
||||
}
|
||||
// If no validations are being received, use all validators.
|
||||
// Otherwise, do not use validators whose validations aren't being received
|
||||
else if (seenValidators.empty() ||
|
||||
seenValidators.find (val->first) != seenValidators.end ())
|
||||
{
|
||||
rankedKeys.insert (
|
||||
std::pair<std::size_t,PublicKey>(val->second, val->first));
|
||||
}
|
||||
}
|
||||
|
||||
// This quorum guarantees sufficient overlap with the trusted sets of other
|
||||
// nodes using the same set of published lists.
|
||||
std::size_t quorum = keyListings_.size()/2 + 1;
|
||||
|
||||
// Increment the quorum to prevent two unlisted validators using the same
|
||||
// even number of listed validators from forking.
|
||||
if (localPubKey_.size() && ! localKeyListed &&
|
||||
rankedKeys.size () > 1 && keyListings_.size () % 2 != 0)
|
||||
++quorum;
|
||||
|
||||
JLOG (j_.debug()) <<
|
||||
rankedKeys.size() << " of " << keyListings_.size() <<
|
||||
" listed validators eligible for inclusion in the trusted set";
|
||||
|
||||
auto size = rankedKeys.size();
|
||||
|
||||
// Use all eligible keys if there is only one trusted list.
|
||||
if (publisherLists_.size() == 1)
|
||||
{
|
||||
// Raise the quorum to 80% of the trusted set
|
||||
std::size_t const targetQuorum = std::ceil (size * 0.8);
|
||||
if (targetQuorum > quorum)
|
||||
quorum = targetQuorum;
|
||||
}
|
||||
else
|
||||
{
|
||||
// reduce the trusted set size so that the quorum represents
|
||||
// at least 80%
|
||||
size = quorum * 1.25;
|
||||
}
|
||||
|
||||
if (minimumQuorum_ && (seenValidators.empty() ||
|
||||
rankedKeys.size() < quorum))
|
||||
quorum = *minimumQuorum_;
|
||||
|
||||
// Do not use achievable quorum until lists from all configured
|
||||
// publishers are available
|
||||
else if (! allListsAvailable)
|
||||
quorum = std::numeric_limits<std::size_t>::max();
|
||||
|
||||
trustedKeys_.clear();
|
||||
quorum_ = quorum;
|
||||
|
||||
for (auto const& val : boost::adaptors::reverse (rankedKeys))
|
||||
{
|
||||
if (size <= trustedKeys_.size())
|
||||
break;
|
||||
|
||||
trustedKeys_.insert (val.second);
|
||||
}
|
||||
|
||||
JLOG (j_.debug()) <<
|
||||
"Using quorum of " << quorum_ << " for new set of " <<
|
||||
trustedKeys_.size() << " trusted validators";
|
||||
|
||||
if (trustedKeys_.size() < quorum_)
|
||||
{
|
||||
JLOG (j_.warn()) <<
|
||||
"New quorum of " << quorum_ <<
|
||||
" exceeds the number of trusted validators (" <<
|
||||
trustedKeys_.size() << ")";
|
||||
}
|
||||
}
|
||||
|
||||
} // ripple
|
||||
|
||||
#endif
|
||||
|
||||
373
src/ripple/app/misc/impl/Manifest.cpp
Normal file
373
src/ripple/app/misc/impl/Manifest.cpp
Normal file
@@ -0,0 +1,373 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <ripple/app/misc/Manifest.h>
|
||||
#include <ripple/basics/contract.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/beast/rfc2616.h>
|
||||
#include <ripple/core/DatabaseCon.h>
|
||||
#include <ripple/protocol/PublicKey.h>
|
||||
#include <ripple/protocol/Sign.h>
|
||||
#include <beast/core/detail/base64.hpp>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
boost::optional<Manifest>
|
||||
Manifest::make_Manifest (std::string s)
|
||||
{
|
||||
try
|
||||
{
|
||||
STObject st (sfGeneric);
|
||||
SerialIter sit (s.data (), s.size ());
|
||||
st.set (sit);
|
||||
auto const opt_pk = get<PublicKey>(st, sfPublicKey);
|
||||
auto const opt_spk = get<PublicKey>(st, sfSigningPubKey);
|
||||
auto const opt_seq = get (st, sfSequence);
|
||||
auto const opt_sig = get (st, sfSignature);
|
||||
auto const opt_msig = get (st, sfMasterSignature);
|
||||
if (!opt_pk || !opt_spk || !opt_seq || !opt_sig || !opt_msig)
|
||||
{
|
||||
return boost::none;
|
||||
}
|
||||
return Manifest (std::move (s), *opt_pk, *opt_spk, *opt_seq);
|
||||
}
|
||||
catch (std::exception const&)
|
||||
{
|
||||
return boost::none;
|
||||
}
|
||||
}
|
||||
|
||||
template<class Stream>
|
||||
Stream&
|
||||
logMftAct (
|
||||
Stream& s,
|
||||
std::string const& action,
|
||||
PublicKey const& pk,
|
||||
std::uint32_t seq)
|
||||
{
|
||||
s << "Manifest: " << action <<
|
||||
";Pk: " << toBase58 (TokenType::TOKEN_NODE_PUBLIC, pk) <<
|
||||
";Seq: " << seq << ";";
|
||||
return s;
|
||||
}
|
||||
|
||||
template<class Stream>
|
||||
Stream& logMftAct (
|
||||
Stream& s,
|
||||
std::string const& action,
|
||||
PublicKey const& pk,
|
||||
std::uint32_t seq,
|
||||
std::uint32_t oldSeq)
|
||||
{
|
||||
s << "Manifest: " << action <<
|
||||
";Pk: " << toBase58 (TokenType::TOKEN_NODE_PUBLIC, pk) <<
|
||||
";Seq: " << seq <<
|
||||
";OldSeq: " << oldSeq << ";";
|
||||
return s;
|
||||
}
|
||||
|
||||
Manifest::Manifest (std::string s,
|
||||
PublicKey pk,
|
||||
PublicKey spk,
|
||||
std::uint32_t seq)
|
||||
: serialized (std::move (s))
|
||||
, masterKey (std::move (pk))
|
||||
, signingKey (std::move (spk))
|
||||
, sequence (seq)
|
||||
{
|
||||
}
|
||||
|
||||
bool Manifest::verify () const
|
||||
{
|
||||
STObject st (sfGeneric);
|
||||
SerialIter sit (serialized.data (), serialized.size ());
|
||||
st.set (sit);
|
||||
if (! ripple::verify (st, HashPrefix::manifest, signingKey))
|
||||
return false;
|
||||
|
||||
return ripple::verify (
|
||||
st, HashPrefix::manifest, masterKey, sfMasterSignature);
|
||||
}
|
||||
|
||||
uint256 Manifest::hash () const
|
||||
{
|
||||
STObject st (sfGeneric);
|
||||
SerialIter sit (serialized.data (), serialized.size ());
|
||||
st.set (sit);
|
||||
return st.getHash (HashPrefix::manifest);
|
||||
}
|
||||
|
||||
bool Manifest::revoked () const
|
||||
{
|
||||
/*
|
||||
The maximum possible sequence number means that the master key
|
||||
has been revoked.
|
||||
*/
|
||||
return sequence == std::numeric_limits<std::uint32_t>::max ();
|
||||
}
|
||||
|
||||
Blob Manifest::getSignature () const
|
||||
{
|
||||
STObject st (sfGeneric);
|
||||
SerialIter sit (serialized.data (), serialized.size ());
|
||||
st.set (sit);
|
||||
return st.getFieldVL (sfSignature);
|
||||
}
|
||||
|
||||
Blob Manifest::getMasterSignature () const
|
||||
{
|
||||
STObject st (sfGeneric);
|
||||
SerialIter sit (serialized.data (), serialized.size ());
|
||||
st.set (sit);
|
||||
return st.getFieldVL (sfMasterSignature);
|
||||
}
|
||||
|
||||
PublicKey
|
||||
ManifestCache::getSigningKey (PublicKey const& pk) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock{read_mutex_};
|
||||
auto const iter = map_.find (pk);
|
||||
|
||||
if (iter != map_.end () && !iter->second.revoked ())
|
||||
return iter->second.signingKey;
|
||||
|
||||
return pk;
|
||||
}
|
||||
|
||||
PublicKey
|
||||
ManifestCache::getMasterKey (PublicKey const& pk) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock{read_mutex_};
|
||||
auto const iter = signingToMasterKeys_.find (pk);
|
||||
|
||||
if (iter != signingToMasterKeys_.end ())
|
||||
return iter->second;
|
||||
|
||||
return pk;
|
||||
}
|
||||
|
||||
bool
|
||||
ManifestCache::revoked (PublicKey const& pk) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock{read_mutex_};
|
||||
auto const iter = map_.find (pk);
|
||||
|
||||
if (iter != map_.end ())
|
||||
return iter->second.revoked ();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ManifestDisposition
|
||||
ManifestCache::applyManifest (Manifest m)
|
||||
{
|
||||
std::lock_guard<std::mutex> applyLock{apply_mutex_};
|
||||
|
||||
/*
|
||||
before we spend time checking the signature, make sure the
|
||||
sequence number is newer than any we have.
|
||||
*/
|
||||
auto const iter = map_.find(m.masterKey);
|
||||
|
||||
if (iter != map_.end() &&
|
||||
m.sequence <= iter->second.sequence)
|
||||
{
|
||||
/*
|
||||
A manifest was received for a validator we're tracking, but
|
||||
its sequence number is not higher than the one already stored.
|
||||
This will happen normally when a peer without the latest gossip
|
||||
connects.
|
||||
*/
|
||||
if (auto stream = j_.debug())
|
||||
logMftAct(stream, "Stale", m.masterKey, m.sequence, iter->second.sequence);
|
||||
return ManifestDisposition::stale; // not a newer manifest, ignore
|
||||
}
|
||||
|
||||
if (! m.verify())
|
||||
{
|
||||
/*
|
||||
A manifest's signature is invalid.
|
||||
This shouldn't happen normally.
|
||||
*/
|
||||
if (auto stream = j_.warn())
|
||||
logMftAct(stream, "Invalid", m.masterKey, m.sequence);
|
||||
return ManifestDisposition::invalid;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> readLock{read_mutex_};
|
||||
|
||||
bool const revoked = m.revoked();
|
||||
|
||||
if (iter == map_.end ())
|
||||
{
|
||||
/*
|
||||
This is the first received manifest for a trusted master key
|
||||
(possibly our own). This only happens once per validator per
|
||||
run.
|
||||
*/
|
||||
if (auto stream = j_.info())
|
||||
logMftAct(stream, "AcceptedNew", m.masterKey, m.sequence);
|
||||
|
||||
if (! revoked)
|
||||
signingToMasterKeys_[m.signingKey] = m.masterKey;
|
||||
|
||||
map_.emplace (std::make_pair(m.masterKey, std::move (m)));
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
An ephemeral key was revoked and superseded by a new key.
|
||||
This is expected, but should happen infrequently.
|
||||
*/
|
||||
if (auto stream = j_.info())
|
||||
logMftAct(stream, "AcceptedUpdate",
|
||||
m.masterKey, m.sequence, iter->second.sequence);
|
||||
|
||||
signingToMasterKeys_.erase (iter->second.signingKey);
|
||||
|
||||
if (! revoked)
|
||||
signingToMasterKeys_[m.signingKey] = m.masterKey;
|
||||
|
||||
iter->second = std::move (m);
|
||||
}
|
||||
|
||||
if (revoked)
|
||||
{
|
||||
/*
|
||||
A validator master key has been compromised, so its manifests
|
||||
are now untrustworthy. In order to prevent us from accepting
|
||||
a forged manifest signed by the compromised master key, store
|
||||
this manifest, which has the highest possible sequence number
|
||||
and therefore can't be superseded by a forged one.
|
||||
*/
|
||||
if (auto stream = j_.warn())
|
||||
logMftAct(stream, "Revoked", m.masterKey, m.sequence);
|
||||
}
|
||||
|
||||
return ManifestDisposition::accepted;
|
||||
}
|
||||
|
||||
void
|
||||
ManifestCache::load (
|
||||
DatabaseCon& dbCon, std::string const& dbTable)
|
||||
{
|
||||
// Load manifests stored in database
|
||||
std::string const sql =
|
||||
"SELECT RawData FROM " + dbTable + ";";
|
||||
auto db = dbCon.checkoutDb ();
|
||||
soci::blob sociRawData (*db);
|
||||
soci::statement st =
|
||||
(db->prepare << sql,
|
||||
soci::into (sociRawData));
|
||||
st.execute ();
|
||||
while (st.fetch ())
|
||||
{
|
||||
std::string serialized;
|
||||
convert (sociRawData, serialized);
|
||||
if (auto mo = Manifest::make_Manifest (std::move (serialized)))
|
||||
{
|
||||
if (!mo->verify())
|
||||
{
|
||||
JLOG(j_.warn())
|
||||
<< "Unverifiable manifest in db";
|
||||
continue;
|
||||
}
|
||||
|
||||
applyManifest (std::move(*mo));
|
||||
}
|
||||
else
|
||||
{
|
||||
JLOG(j_.warn())
|
||||
<< "Malformed manifest in database";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
ManifestCache::load (
|
||||
DatabaseCon& dbCon, std::string const& dbTable,
|
||||
std::vector<std::string> const& configManifest)
|
||||
{
|
||||
load (dbCon, dbTable);
|
||||
|
||||
if (! configManifest.empty())
|
||||
{
|
||||
std::string s;
|
||||
s.reserve (Manifest::textLength);
|
||||
for (auto const& line : configManifest)
|
||||
s += beast::rfc2616::trim(line);
|
||||
|
||||
auto mo = Manifest::make_Manifest (beast::detail::base64_decode(s));
|
||||
if (! mo)
|
||||
{
|
||||
JLOG (j_.error()) << "Malformed manifest in config";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mo->revoked())
|
||||
{
|
||||
JLOG (j_.warn()) <<
|
||||
"Configured manifest revokes public key";
|
||||
}
|
||||
|
||||
if (applyManifest (std::move(*mo)) ==
|
||||
ManifestDisposition::invalid)
|
||||
{
|
||||
JLOG (j_.error()) << "Manifest in config was rejected";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ManifestCache::save (
|
||||
DatabaseCon& dbCon, std::string const& dbTable,
|
||||
std::function <bool (PublicKey const&)> isTrusted)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock{apply_mutex_};
|
||||
|
||||
auto db = dbCon.checkoutDb ();
|
||||
|
||||
soci::transaction tr(*db);
|
||||
*db << "DELETE FROM " << dbTable;
|
||||
std::string const sql =
|
||||
"INSERT INTO " + dbTable + " (RawData) VALUES (:rawData);";
|
||||
for (auto const& v : map_)
|
||||
{
|
||||
if (! isTrusted (v.second.masterKey))
|
||||
{
|
||||
|
||||
JLOG(j_.info())
|
||||
<< "Untrusted manifest in cache not saved to db";
|
||||
continue;
|
||||
}
|
||||
|
||||
// soci does not support bulk insertion of blob data
|
||||
// Do not reuse blob because manifest ecdsa signatures vary in length
|
||||
// but blob write length is expected to be >= the last write
|
||||
soci::blob rawData(*db);
|
||||
convert (v.second.serialized, rawData);
|
||||
*db << sql,
|
||||
soci::use (rawData);
|
||||
}
|
||||
tr.commit ();
|
||||
}
|
||||
}
|
||||
@@ -17,117 +17,39 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <BeastConfig.h>
|
||||
#include <ripple/app/misc/ValidatorList.h>
|
||||
#include <ripple/basics/Slice.h>
|
||||
#include <ripple/basics/StringUtilities.h>
|
||||
#include <ripple/json/json_reader.h>
|
||||
#include <beast/core/detail/base64.hpp>
|
||||
#include <boost/regex.hpp>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
ValidatorList::ValidatorList (beast::Journal j)
|
||||
: j_ (j)
|
||||
ValidatorList::ValidatorList (
|
||||
ManifestCache& validatorManifests,
|
||||
ManifestCache& publisherManifests,
|
||||
TimeKeeper& timeKeeper,
|
||||
beast::Journal j,
|
||||
boost::optional<std::size_t> minimumQuorum)
|
||||
: validatorManifests_ (validatorManifests)
|
||||
, publisherManifests_ (publisherManifests)
|
||||
, timeKeeper_ (timeKeeper)
|
||||
, j_ (j)
|
||||
, quorum_ (minimumQuorum ? *minimumQuorum : 1) // Genesis ledger quorum
|
||||
, minimumQuorum_ (minimumQuorum)
|
||||
{
|
||||
}
|
||||
|
||||
boost::optional<std::string>
|
||||
ValidatorList::member (PublicKey const& identity) const
|
||||
ValidatorList::~ValidatorList()
|
||||
{
|
||||
std::lock_guard <std::mutex> sl (mutex_);
|
||||
|
||||
auto ret = ephemeral_.find (identity);
|
||||
|
||||
if (ret != ephemeral_.end())
|
||||
return ret->second;
|
||||
|
||||
ret = permanent_.find (identity);
|
||||
|
||||
if (ret != permanent_.end())
|
||||
return ret->second;
|
||||
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
bool
|
||||
ValidatorList::trusted (PublicKey const& identity) const
|
||||
{
|
||||
return static_cast<bool> (member(identity));
|
||||
}
|
||||
|
||||
bool
|
||||
ValidatorList::insertEphemeralKey (
|
||||
PublicKey const& identity,
|
||||
std::string const& comment)
|
||||
{
|
||||
std::lock_guard <std::mutex> sl (mutex_);
|
||||
|
||||
if (permanent_.find (identity) != permanent_.end())
|
||||
{
|
||||
JLOG (j_.error()) <<
|
||||
toBase58 (TokenType::TOKEN_NODE_PUBLIC, identity) <<
|
||||
": ephemeral key exists in permanent table!";
|
||||
return false;
|
||||
}
|
||||
|
||||
return ephemeral_.emplace (identity, comment).second;
|
||||
}
|
||||
|
||||
bool
|
||||
ValidatorList::removeEphemeralKey (
|
||||
PublicKey const& identity)
|
||||
{
|
||||
std::lock_guard <std::mutex> sl (mutex_);
|
||||
return ephemeral_.erase (identity);
|
||||
}
|
||||
|
||||
bool
|
||||
ValidatorList::insertPermanentKey (
|
||||
PublicKey const& identity,
|
||||
std::string const& comment)
|
||||
{
|
||||
std::lock_guard <std::mutex> sl (mutex_);
|
||||
|
||||
if (ephemeral_.find (identity) != ephemeral_.end())
|
||||
{
|
||||
JLOG (j_.error()) <<
|
||||
toBase58 (TokenType::TOKEN_NODE_PUBLIC, identity) <<
|
||||
": permanent key exists in ephemeral table!";
|
||||
return false;
|
||||
}
|
||||
|
||||
return permanent_.emplace (identity, comment).second;
|
||||
}
|
||||
|
||||
bool
|
||||
ValidatorList::removePermanentKey (
|
||||
PublicKey const& identity)
|
||||
{
|
||||
std::lock_guard <std::mutex> sl (mutex_);
|
||||
return permanent_.erase (identity);
|
||||
}
|
||||
|
||||
std::size_t
|
||||
ValidatorList::size () const
|
||||
{
|
||||
std::lock_guard <std::mutex> sl (mutex_);
|
||||
return permanent_.size () + ephemeral_.size ();
|
||||
}
|
||||
|
||||
void
|
||||
ValidatorList::for_each (
|
||||
std::function<void(PublicKey const&, std::string const&, bool)> func) const
|
||||
{
|
||||
std::lock_guard <std::mutex> sl (mutex_);
|
||||
|
||||
for (auto const& v : permanent_)
|
||||
func (v.first, v.second, false);
|
||||
for (auto const& v : ephemeral_)
|
||||
func (v.first, v.second, true);
|
||||
}
|
||||
|
||||
bool
|
||||
ValidatorList::load (
|
||||
Section const& validators)
|
||||
PublicKey const& localSigningKey,
|
||||
std::vector<std::string> const& configKeys,
|
||||
std::vector<std::string> const& publisherKeys)
|
||||
{
|
||||
static boost::regex const re (
|
||||
"[[:space:]]*" // skip leading whitespace
|
||||
@@ -141,12 +63,61 @@ ValidatorList::load (
|
||||
")?" // end optional comment block
|
||||
);
|
||||
|
||||
boost::unique_lock<boost::shared_mutex> read_lock{mutex_};
|
||||
|
||||
JLOG (j_.debug()) <<
|
||||
"Loading configured validators";
|
||||
"Loading configured trusted validator list publisher keys";
|
||||
|
||||
std::size_t count = 0;
|
||||
for (auto key : publisherKeys)
|
||||
{
|
||||
JLOG (j_.trace()) <<
|
||||
"Processing '" << key << "'";
|
||||
|
||||
for (auto const& n : validators.values ())
|
||||
auto const ret = strUnHex (key);
|
||||
|
||||
if (! ret.second || ! ret.first.size ())
|
||||
{
|
||||
JLOG (j_.error()) <<
|
||||
"Invalid validator list publisher key: " << key;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto id = PublicKey(Slice{ ret.first.data (), ret.first.size() });
|
||||
|
||||
if (validatorManifests_.revoked (id))
|
||||
{
|
||||
JLOG (j_.warn()) <<
|
||||
"Configured validator list publisher key is revoked: " << key;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (publisherLists_.count(id))
|
||||
{
|
||||
JLOG (j_.warn()) <<
|
||||
"Duplicate validator list publisher key: " << key;
|
||||
continue;
|
||||
}
|
||||
|
||||
publisherLists_[id].available = false;
|
||||
++count;
|
||||
}
|
||||
|
||||
JLOG (j_.debug()) <<
|
||||
"Loaded " << count << " keys";
|
||||
|
||||
localPubKey_ = validatorManifests_.getMasterKey (localSigningKey);
|
||||
|
||||
// Treat local validator key as though it was listed in the config
|
||||
if (localPubKey_.size())
|
||||
keyListings_.insert ({ localPubKey_, 1 });
|
||||
|
||||
JLOG (j_.debug()) <<
|
||||
"Loading configured validator keys";
|
||||
|
||||
count = 0;
|
||||
PublicKey local;
|
||||
for (auto const& n : configKeys)
|
||||
{
|
||||
JLOG (j_.trace()) <<
|
||||
"Processing '" << n << "'";
|
||||
@@ -165,19 +136,22 @@ ValidatorList::load (
|
||||
|
||||
if (!id)
|
||||
{
|
||||
JLOG (j_.error()) <<
|
||||
"Invalid node identity: " << match[1];
|
||||
JLOG (j_.error()) << "Invalid node identity: " << match[1];
|
||||
return false;
|
||||
}
|
||||
|
||||
if (trusted (*id))
|
||||
// Skip local key which was already added
|
||||
if (*id == localPubKey_ || *id == localSigningKey)
|
||||
continue;
|
||||
|
||||
auto ret = keyListings_.insert ({*id, 1});
|
||||
if (! ret.second)
|
||||
{
|
||||
JLOG (j_.warn()) <<
|
||||
"Duplicate node identity: " << match[1];
|
||||
JLOG (j_.warn()) << "Duplicate node identity: " << match[1];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (insertPermanentKey(*id, trim_whitespace (match[2])))
|
||||
publisherLists_[local].list.emplace_back (std::move(*id));
|
||||
publisherLists_[local].available = true;
|
||||
++count;
|
||||
}
|
||||
|
||||
@@ -187,4 +161,244 @@ ValidatorList::load (
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
ListDisposition
|
||||
ValidatorList::applyList (
|
||||
std::string const& manifest,
|
||||
std::string const& blob,
|
||||
std::string const& signature,
|
||||
std::uint32_t version)
|
||||
{
|
||||
if (version != 1)
|
||||
return ListDisposition::unsupported_version;
|
||||
|
||||
boost::unique_lock<boost::shared_mutex> lock{mutex_};
|
||||
|
||||
Json::Value list;
|
||||
PublicKey pubKey;
|
||||
auto const result = verify (list, pubKey, manifest, blob, signature);
|
||||
if (result != ListDisposition::accepted)
|
||||
return result;
|
||||
|
||||
// Update publisher's list
|
||||
Json::Value const& newList = list["validators"];
|
||||
publisherLists_[pubKey].available = true;
|
||||
publisherLists_[pubKey].sequence = list["sequence"].asUInt ();
|
||||
publisherLists_[pubKey].expiration = list["expiration"].asUInt ();
|
||||
std::vector<PublicKey>& publisherList = publisherLists_[pubKey].list;
|
||||
|
||||
std::vector<PublicKey> oldList = publisherList;
|
||||
publisherList.clear ();
|
||||
publisherList.reserve (newList.size ());
|
||||
for (auto const& val : newList)
|
||||
{
|
||||
if (val.isObject () &&
|
||||
val.isMember ("validation_public_key") &&
|
||||
val["validation_public_key"].isString ())
|
||||
{
|
||||
std::pair<Blob, bool> ret (strUnHex (
|
||||
val["validation_public_key"].asString ()));
|
||||
|
||||
if (! ret.second || ! ret.first.size ())
|
||||
{
|
||||
JLOG (j_.error()) <<
|
||||
"Invalid node identity: " <<
|
||||
val["validation_public_key"].asString ();
|
||||
}
|
||||
else
|
||||
{
|
||||
publisherList.push_back (
|
||||
PublicKey(Slice{ ret.first.data (), ret.first.size() }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update keyListings_ for added and removed keys
|
||||
std::sort (
|
||||
publisherList.begin (),
|
||||
publisherList.end ());
|
||||
|
||||
auto iNew = publisherList.begin ();
|
||||
auto iOld = oldList.begin ();
|
||||
while (iNew != publisherList.end () ||
|
||||
iOld != oldList.end ())
|
||||
{
|
||||
if (iOld == oldList.end () ||
|
||||
(iNew != publisherList.end () &&
|
||||
*iNew < *iOld))
|
||||
{
|
||||
// Increment list count for added keys
|
||||
++keyListings_[*iNew];
|
||||
++iNew;
|
||||
}
|
||||
else if (iNew == publisherList.end () ||
|
||||
(iOld != oldList.end () && *iOld < *iNew))
|
||||
{
|
||||
// Decrement list count for removed keys
|
||||
if (keyListings_[*iOld] == 1)
|
||||
keyListings_.erase (*iOld);
|
||||
else
|
||||
--keyListings_[*iOld];
|
||||
++iOld;
|
||||
}
|
||||
else
|
||||
{
|
||||
++iNew;
|
||||
++iOld;
|
||||
}
|
||||
}
|
||||
|
||||
if (publisherList.empty())
|
||||
{
|
||||
JLOG (j_.warn()) <<
|
||||
"No validator keys included in valid list";
|
||||
}
|
||||
|
||||
return ListDisposition::accepted;
|
||||
}
|
||||
|
||||
ListDisposition
|
||||
ValidatorList::verify (
|
||||
Json::Value& list,
|
||||
PublicKey& pubKey,
|
||||
std::string const& manifest,
|
||||
std::string const& blob,
|
||||
std::string const& signature)
|
||||
{
|
||||
auto m = Manifest::make_Manifest (beast::detail::base64_decode(manifest));
|
||||
|
||||
if (! m || ! publisherLists_.count (m->masterKey))
|
||||
return ListDisposition::untrusted;
|
||||
|
||||
pubKey = m->masterKey;
|
||||
auto const revoked = m->revoked();
|
||||
|
||||
auto const result = publisherManifests_.applyManifest (
|
||||
std::move(*m));
|
||||
|
||||
if (revoked && result == ManifestDisposition::accepted)
|
||||
{
|
||||
removePublisherList (pubKey);
|
||||
publisherLists_.erase (pubKey);
|
||||
}
|
||||
|
||||
if (revoked || result == ManifestDisposition::invalid)
|
||||
return ListDisposition::untrusted;
|
||||
|
||||
auto const sig = strUnHex(signature);
|
||||
auto const data = beast::detail::base64_decode (blob);
|
||||
if (! sig.second ||
|
||||
! ripple::verify (
|
||||
publisherManifests_.getSigningKey(pubKey),
|
||||
makeSlice(data),
|
||||
makeSlice(sig.first)))
|
||||
return ListDisposition::invalid;
|
||||
|
||||
Json::Reader r;
|
||||
if (! r.parse (data, list))
|
||||
return ListDisposition::invalid;
|
||||
|
||||
if (list.isMember("sequence") && list["sequence"].isInt() &&
|
||||
list.isMember("expiration") && list["expiration"].isInt() &&
|
||||
list.isMember("validators") && list["validators"].isArray())
|
||||
{
|
||||
auto const sequence = list["sequence"].asUInt ();
|
||||
auto const expiration = list["expiration"].asUInt ();
|
||||
if (sequence <= publisherLists_[pubKey].sequence ||
|
||||
expiration <= timeKeeper_.now().time_since_epoch().count())
|
||||
return ListDisposition::stale;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ListDisposition::invalid;
|
||||
}
|
||||
|
||||
return ListDisposition::accepted;
|
||||
}
|
||||
|
||||
bool
|
||||
ValidatorList::listed (
|
||||
PublicKey const& identity) const
|
||||
{
|
||||
boost::shared_lock<boost::shared_mutex> read_lock{mutex_};
|
||||
|
||||
auto const pubKey = validatorManifests_.getMasterKey (identity);
|
||||
return keyListings_.find (pubKey) != keyListings_.end ();
|
||||
}
|
||||
|
||||
bool
|
||||
ValidatorList::trusted (PublicKey const& identity) const
|
||||
{
|
||||
boost::shared_lock<boost::shared_mutex> read_lock{mutex_};
|
||||
|
||||
auto const pubKey = validatorManifests_.getMasterKey (identity);
|
||||
return trustedKeys_.find (pubKey) != trustedKeys_.end();
|
||||
}
|
||||
|
||||
boost::optional<PublicKey>
|
||||
ValidatorList::getListedKey (
|
||||
PublicKey const& identity) const
|
||||
{
|
||||
boost::shared_lock<boost::shared_mutex> read_lock{mutex_};
|
||||
|
||||
auto const pubKey = validatorManifests_.getMasterKey (identity);
|
||||
if (keyListings_.find (pubKey) != keyListings_.end ())
|
||||
return pubKey;
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
boost::optional<PublicKey>
|
||||
ValidatorList::getTrustedKey (PublicKey const& identity) const
|
||||
{
|
||||
boost::shared_lock<boost::shared_mutex> read_lock{mutex_};
|
||||
|
||||
auto const pubKey = validatorManifests_.getMasterKey (identity);
|
||||
if (trustedKeys_.find (pubKey) != trustedKeys_.end())
|
||||
return pubKey;
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
bool
|
||||
ValidatorList::trustedPublisher (PublicKey const& identity) const
|
||||
{
|
||||
boost::shared_lock<boost::shared_mutex> read_lock{mutex_};
|
||||
return identity.size() && publisherLists_.count (identity);
|
||||
}
|
||||
|
||||
bool
|
||||
ValidatorList::removePublisherList (PublicKey const& publisherKey)
|
||||
{
|
||||
auto const iList = publisherLists_.find (publisherKey);
|
||||
if (iList == publisherLists_.end ())
|
||||
return false;
|
||||
|
||||
JLOG (j_.debug()) <<
|
||||
"Removing validator list for revoked publisher " <<
|
||||
toBase58(TokenType::TOKEN_NODE_PUBLIC, publisherKey);
|
||||
|
||||
for (auto const& val : iList->second.list)
|
||||
{
|
||||
auto const& iVal = keyListings_.find (val);
|
||||
if (iVal == keyListings_.end())
|
||||
continue;
|
||||
|
||||
if (iVal->second <= 1)
|
||||
keyListings_.erase (iVal);
|
||||
else
|
||||
--iVal->second;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
ValidatorList::for_each_listed (
|
||||
std::function<void(PublicKey const&, bool)> func) const
|
||||
{
|
||||
boost::shared_lock<boost::shared_mutex> read_lock{mutex_};
|
||||
|
||||
for (auto const& v : keyListings_)
|
||||
func (v.first, trusted(v.first));
|
||||
}
|
||||
|
||||
} // ripple
|
||||
|
||||
@@ -138,8 +138,6 @@ public:
|
||||
|
||||
// Note: The following parameters do not relate to the UNL or trust at all
|
||||
std::size_t NETWORK_QUORUM = 0; // Minimum number of nodes to consider the network present
|
||||
int VALIDATION_QUORUM = 1; // Minimum validations to consider ledger authoritative
|
||||
bool LOCK_QUORUM = false; // Do not raise the quorum
|
||||
|
||||
// Peer networking parameters
|
||||
bool PEER_PRIVATE = false; // True to ask peers not to relay current IP.
|
||||
@@ -156,6 +154,7 @@ public:
|
||||
// Validation
|
||||
PublicKey VALIDATION_PUB;
|
||||
SecretKey VALIDATION_PRIV;
|
||||
boost::optional<std::size_t> VALIDATION_QUORUM; // Minimum validations to consider ledger authoritative
|
||||
|
||||
// Node Identity
|
||||
std::string NODE_SEED;
|
||||
|
||||
@@ -60,11 +60,11 @@ struct ConfigSection
|
||||
#define SECTION_SSL_VERIFY_FILE "ssl_verify_file"
|
||||
#define SECTION_SSL_VERIFY_DIR "ssl_verify_dir"
|
||||
#define SECTION_VALIDATORS_FILE "validators_file"
|
||||
#define SECTION_VALIDATION_QUORUM "validation_quorum"
|
||||
#define SECTION_VALIDATION_SEED "validation_seed"
|
||||
#define SECTION_WEBSOCKET_PING_FREQ "websocket_ping_frequency"
|
||||
#define SECTION_VALIDATORS "validators"
|
||||
#define SECTION_VALIDATOR_KEYS "validator_keys"
|
||||
#define SECTION_VALIDATOR_LIST_KEYS "validator_list_keys"
|
||||
#define SECTION_VALIDATORS "validators"
|
||||
#define SECTION_VALIDATION_MANIFEST "validation_manifest"
|
||||
#define SECTION_VETO_AMENDMENTS "veto_amendments"
|
||||
|
||||
|
||||
@@ -48,7 +48,6 @@ enum JobType
|
||||
jtUPDATE_PF, // Update pathfinding requests
|
||||
jtTRANSACTION, // A transaction received from the network
|
||||
jtBATCH, // Apply batched transactions
|
||||
jtUNL, // A Score or Fetch of the UNL (DEPRECATED)
|
||||
jtADVANCE, // Advance validated/acquired ledgers
|
||||
jtPUBLEDGER, // Publish a fully-accepted ledger
|
||||
jtTXN_DATA, // Fetch a proposed set
|
||||
|
||||
@@ -50,7 +50,6 @@ add( jtRPC, "RPC", maxLimit, false, 0, 0);
|
||||
add( jtUPDATE_PF, "updatePaths", maxLimit, false, 0, 0);
|
||||
add( jtTRANSACTION, "transaction", maxLimit, false, 250, 1000);
|
||||
add( jtBATCH, "batch", maxLimit, false, 250, 1000);
|
||||
add( jtUNL, "unl", 1, false, 0, 0);
|
||||
add( jtADVANCE, "advanceLedger", maxLimit, false, 0, 0);
|
||||
add( jtPUBLEDGER, "publishNewLedger", maxLimit, false, 3000, 4500);
|
||||
add( jtTXN_DATA, "fetchTxnData", 1, false, 0, 0);
|
||||
|
||||
@@ -378,9 +378,6 @@ void Config::loadFromString (std::string const& fileContents)
|
||||
if (getSingleSection (secConfig, SECTION_NETWORK_QUORUM, strTemp, j_))
|
||||
NETWORK_QUORUM = beast::lexicalCastThrow <std::size_t> (strTemp);
|
||||
|
||||
if (getSingleSection (secConfig, SECTION_VALIDATION_QUORUM, strTemp, j_))
|
||||
VALIDATION_QUORUM = std::max (0, beast::lexicalCastThrow <int> (strTemp));
|
||||
|
||||
if (getSingleSection (secConfig, SECTION_FEE_ACCOUNT_RESERVE, strTemp, j_))
|
||||
FEE_ACCOUNT_RESERVE = beast::lexicalCastThrow <std::uint64_t> (strTemp);
|
||||
|
||||
@@ -425,6 +422,12 @@ void Config::loadFromString (std::string const& fileContents)
|
||||
if (getSingleSection (secConfig, SECTION_PATH_SEARCH_MAX, strTemp, j_))
|
||||
PATH_SEARCH_MAX = beast::lexicalCastThrow <int> (strTemp);
|
||||
|
||||
if (getSingleSection (secConfig, SECTION_DEBUG_LOGFILE, strTemp, j_))
|
||||
DEBUG_LOGFILE = strTemp;
|
||||
|
||||
// Do not load trusted validator configuration for standalone mode
|
||||
if (! RUN_STANDALONE)
|
||||
{
|
||||
// If a file was explicitly specified, then throw if the
|
||||
// path is malformed or if the file does not exist or is
|
||||
// not a file.
|
||||
@@ -500,31 +503,27 @@ void Config::loadFromString (std::string const& fileContents)
|
||||
if (valKeyEntries)
|
||||
section (SECTION_VALIDATOR_KEYS).append (*valKeyEntries);
|
||||
|
||||
if (!entries && !valKeyEntries)
|
||||
auto valListKeys = getIniFileSection(
|
||||
iniFile,
|
||||
SECTION_VALIDATOR_LIST_KEYS);
|
||||
|
||||
if (valListKeys)
|
||||
section (SECTION_VALIDATOR_LIST_KEYS).append (*valListKeys);
|
||||
|
||||
if (!entries && !valKeyEntries && !valListKeys)
|
||||
Throw<std::runtime_error> (
|
||||
"The file specified in [" SECTION_VALIDATORS_FILE "] "
|
||||
"does not contain a [" SECTION_VALIDATORS "] or "
|
||||
"[" SECTION_VALIDATOR_KEYS "] section: " +
|
||||
"does not contain a [" SECTION_VALIDATORS "], "
|
||||
"[" SECTION_VALIDATOR_KEYS "] or "
|
||||
"[" SECTION_VALIDATOR_LIST_KEYS "]"
|
||||
" section: " +
|
||||
validatorsFile.string());
|
||||
|
||||
// Look for [validation_quorum] in the validators file
|
||||
// if it was not in the config
|
||||
if (!getIniFileSection (secConfig, SECTION_VALIDATION_QUORUM))
|
||||
{
|
||||
if (!getSingleSection (
|
||||
iniFile, SECTION_VALIDATION_QUORUM, strTemp, j_))
|
||||
Throw<std::runtime_error> (
|
||||
"The file specified in [" SECTION_VALIDATORS_FILE "] "
|
||||
"does not contain a [" SECTION_VALIDATION_QUORUM "] "
|
||||
"section: " + validatorsFile.string());
|
||||
else
|
||||
VALIDATION_QUORUM = std::max (
|
||||
0, beast::lexicalCastThrow <int> (strTemp));
|
||||
}
|
||||
}
|
||||
|
||||
if (getSingleSection (secConfig, SECTION_DEBUG_LOGFILE, strTemp, j_))
|
||||
DEBUG_LOGFILE = strTemp;
|
||||
// Consolidate [validator_keys] and [validators]
|
||||
section (SECTION_VALIDATORS).append (
|
||||
section (SECTION_VALIDATOR_KEYS).lines ());
|
||||
}
|
||||
|
||||
{
|
||||
auto const part = section("features");
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
#include <ripple/basics/CountedObject.h>
|
||||
#include <ripple/json/json_value.h>
|
||||
#include <ripple/overlay/impl/Manifest.h>
|
||||
#include <ripple/app/misc/Manifest.h>
|
||||
#include <ripple/resource/Consumer.h>
|
||||
#include <ripple/protocol/Book.h>
|
||||
#include <ripple/core/Stoppable.h>
|
||||
|
||||
@@ -38,9 +38,6 @@ namespace boost { namespace asio { namespace ssl { class context; } } }
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class DatabaseCon;
|
||||
class BasicConfig;
|
||||
|
||||
/** Manages the set of connected peers. */
|
||||
class Overlay
|
||||
: public Stoppable
|
||||
@@ -164,15 +161,6 @@ public:
|
||||
relay (protocol::TMValidation& m,
|
||||
uint256 const& uid) = 0;
|
||||
|
||||
virtual
|
||||
void
|
||||
setupValidatorKeyManifests (BasicConfig const& config,
|
||||
DatabaseCon& db) = 0;
|
||||
|
||||
virtual
|
||||
void
|
||||
saveValidatorKeyManifests (DatabaseCon& db) const = 0;
|
||||
|
||||
/** Visit every active peer and return a value
|
||||
The functor must:
|
||||
- Be callable as:
|
||||
|
||||
@@ -1,477 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012, 2013 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/main/Application.h>
|
||||
#include <ripple/basics/contract.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/app/misc/ValidatorList.h>
|
||||
#include <ripple/core/DatabaseCon.h>
|
||||
#include <ripple/overlay/impl/Manifest.h>
|
||||
#include <ripple/protocol/PublicKey.h>
|
||||
#include <ripple/protocol/Sign.h>
|
||||
#include <boost/regex.hpp>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
boost::optional<Manifest>
|
||||
make_Manifest (std::string s)
|
||||
{
|
||||
try
|
||||
{
|
||||
STObject st (sfGeneric);
|
||||
SerialIter sit (s.data (), s.size ());
|
||||
st.set (sit);
|
||||
auto const opt_pk = get<PublicKey>(st, sfPublicKey);
|
||||
auto const opt_spk = get<PublicKey>(st, sfSigningPubKey);
|
||||
auto const opt_seq = get (st, sfSequence);
|
||||
auto const opt_sig = get (st, sfSignature);
|
||||
auto const opt_msig = get (st, sfMasterSignature);
|
||||
if (!opt_pk || !opt_spk || !opt_seq || !opt_sig || !opt_msig)
|
||||
{
|
||||
return boost::none;
|
||||
}
|
||||
return Manifest (std::move (s), *opt_pk, *opt_spk, *opt_seq);
|
||||
}
|
||||
catch (std::exception const&)
|
||||
{
|
||||
return boost::none;
|
||||
}
|
||||
}
|
||||
|
||||
template<class Stream>
|
||||
Stream&
|
||||
logMftAct (
|
||||
Stream& s,
|
||||
std::string const& action,
|
||||
PublicKey const& pk,
|
||||
std::uint32_t seq)
|
||||
{
|
||||
s << "Manifest: " << action <<
|
||||
";Pk: " << toBase58 (TokenType::TOKEN_NODE_PUBLIC, pk) <<
|
||||
";Seq: " << seq << ";";
|
||||
return s;
|
||||
}
|
||||
|
||||
template<class Stream>
|
||||
Stream& logMftAct (
|
||||
Stream& s,
|
||||
std::string const& action,
|
||||
PublicKey const& pk,
|
||||
std::uint32_t seq,
|
||||
std::uint32_t oldSeq)
|
||||
{
|
||||
s << "Manifest: " << action <<
|
||||
";Pk: " << toBase58 (TokenType::TOKEN_NODE_PUBLIC, pk) <<
|
||||
";Seq: " << seq <<
|
||||
";OldSeq: " << oldSeq << ";";
|
||||
return s;
|
||||
}
|
||||
|
||||
Manifest::Manifest (std::string s,
|
||||
PublicKey pk,
|
||||
PublicKey spk,
|
||||
std::uint32_t seq)
|
||||
: serialized (std::move (s))
|
||||
, masterKey (std::move (pk))
|
||||
, signingKey (std::move (spk))
|
||||
, sequence (seq)
|
||||
{
|
||||
}
|
||||
|
||||
bool Manifest::verify () const
|
||||
{
|
||||
STObject st (sfGeneric);
|
||||
SerialIter sit (serialized.data (), serialized.size ());
|
||||
st.set (sit);
|
||||
if (! ripple::verify (st, HashPrefix::manifest, signingKey))
|
||||
return false;
|
||||
|
||||
return ripple::verify (
|
||||
st, HashPrefix::manifest, masterKey, sfMasterSignature);
|
||||
}
|
||||
|
||||
uint256 Manifest::hash () const
|
||||
{
|
||||
STObject st (sfGeneric);
|
||||
SerialIter sit (serialized.data (), serialized.size ());
|
||||
st.set (sit);
|
||||
return st.getHash (HashPrefix::manifest);
|
||||
}
|
||||
|
||||
bool Manifest::revoked () const
|
||||
{
|
||||
/*
|
||||
The maximum possible sequence number means that the master key
|
||||
has been revoked.
|
||||
*/
|
||||
return sequence == std::numeric_limits<std::uint32_t>::max ();
|
||||
}
|
||||
|
||||
Blob Manifest::getSignature () const
|
||||
{
|
||||
STObject st (sfGeneric);
|
||||
SerialIter sit (serialized.data (), serialized.size ());
|
||||
st.set (sit);
|
||||
return st.getFieldVL (sfSignature);
|
||||
}
|
||||
|
||||
Blob Manifest::getMasterSignature () const
|
||||
{
|
||||
STObject st (sfGeneric);
|
||||
SerialIter sit (serialized.data (), serialized.size ());
|
||||
st.set (sit);
|
||||
return st.getFieldVL (sfMasterSignature);
|
||||
}
|
||||
|
||||
bool
|
||||
ManifestCache::loadValidatorKeys(
|
||||
Section const& keys,
|
||||
beast::Journal journal)
|
||||
{
|
||||
static boost::regex const re (
|
||||
"[[:space:]]*" // skip leading whitespace
|
||||
"([[:alnum:]]+)" // node identity
|
||||
"(?:" // begin optional comment block
|
||||
"[[:space:]]+" // (skip all leading whitespace)
|
||||
"(?:" // begin optional comment
|
||||
"(.*[^[:space:]]+)" // the comment
|
||||
"[[:space:]]*" // (skip all trailing whitespace)
|
||||
")?" // end optional comment
|
||||
")?" // end optional comment block
|
||||
);
|
||||
|
||||
JLOG (journal.debug()) <<
|
||||
"Loading configured validator keys";
|
||||
|
||||
std::size_t count = 0;
|
||||
|
||||
for (auto const& line : keys.lines())
|
||||
{
|
||||
boost::smatch match;
|
||||
|
||||
if (!boost::regex_match (line, match, re))
|
||||
{
|
||||
JLOG (journal.error()) <<
|
||||
"Malformed entry: '" << line << "'";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const key = parseBase58<PublicKey>(
|
||||
TokenType::TOKEN_NODE_PUBLIC, match[1]);
|
||||
|
||||
if (!key)
|
||||
{
|
||||
JLOG (journal.error()) <<
|
||||
"Error decoding validator key: " << match[1];
|
||||
return false;
|
||||
}
|
||||
|
||||
if (publicKeyType(*key) != KeyType::ed25519)
|
||||
{
|
||||
JLOG (journal.error()) <<
|
||||
"Validator key not using Ed25519: " << match[1];
|
||||
return false;
|
||||
}
|
||||
|
||||
JLOG (journal.debug()) << "Loaded key: " << match[1];
|
||||
|
||||
addTrustedKey (*key, match[2]);
|
||||
++count;
|
||||
}
|
||||
|
||||
JLOG (journal.debug()) <<
|
||||
"Loaded " << count << " entries";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
ManifestCache::configManifest (
|
||||
Manifest m, ValidatorList& unl, beast::Journal journal)
|
||||
{
|
||||
if (! m.verify())
|
||||
{
|
||||
Throw<std::runtime_error> ("Unverifiable manifest in config");
|
||||
}
|
||||
|
||||
// Trust our own master public key
|
||||
if (!trusted(m.masterKey) && !unl.trusted (m.masterKey))
|
||||
{
|
||||
addTrustedKey (m.masterKey, "");
|
||||
}
|
||||
|
||||
auto const result = applyManifest (std::move(m), unl, journal);
|
||||
|
||||
if (result != ManifestDisposition::accepted)
|
||||
{
|
||||
Throw<std::runtime_error> ("Our own validation manifest was not accepted");
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
ManifestCache::trusted (PublicKey const& identity) const
|
||||
{
|
||||
return map_.count(identity);
|
||||
}
|
||||
|
||||
void
|
||||
ManifestCache::addTrustedKey (PublicKey const& pk, std::string comment)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock (mutex_);
|
||||
|
||||
auto& value = map_[pk];
|
||||
|
||||
if (value.m)
|
||||
{
|
||||
Throw<std::runtime_error> (
|
||||
"New trusted validator key already has a manifest");
|
||||
}
|
||||
|
||||
value.comment = std::move(comment);
|
||||
}
|
||||
|
||||
std::size_t
|
||||
ManifestCache::size () const
|
||||
{
|
||||
std::lock_guard <std::mutex> lock (mutex_);
|
||||
return map_.size ();
|
||||
}
|
||||
|
||||
ManifestDisposition
|
||||
ManifestCache::canApply (PublicKey const& pk, std::uint32_t seq,
|
||||
beast::Journal journal) const
|
||||
{
|
||||
auto const iter = map_.find(pk);
|
||||
|
||||
if (iter == map_.end())
|
||||
{
|
||||
/*
|
||||
A manifest was received whose master key we don't trust.
|
||||
Since rippled always sends all of its current manifests,
|
||||
this will happen normally any time a peer connects.
|
||||
*/
|
||||
if (auto stream = journal.debug())
|
||||
logMftAct(stream, "Untrusted", pk, seq);
|
||||
return ManifestDisposition::untrusted;
|
||||
}
|
||||
|
||||
auto& old = iter->second.m;
|
||||
|
||||
if (old && seq <= old->sequence)
|
||||
{
|
||||
/*
|
||||
A manifest was received for a validator we're tracking, but
|
||||
its sequence number is no higher than the one already stored.
|
||||
This will happen normally when a peer without the latest gossip
|
||||
connects.
|
||||
*/
|
||||
if (auto stream = journal.debug())
|
||||
logMftAct(stream, "Stale", pk, seq, old->sequence);
|
||||
return ManifestDisposition::stale; // not a newer manifest, ignore
|
||||
}
|
||||
|
||||
return ManifestDisposition::accepted;
|
||||
}
|
||||
|
||||
|
||||
ManifestDisposition
|
||||
ManifestCache::applyManifest (
|
||||
Manifest m, ValidatorList& unl, beast::Journal journal)
|
||||
{
|
||||
/*
|
||||
Move master public key from permanent trusted key list
|
||||
to manifest cache.
|
||||
*/
|
||||
if (auto unlComment = unl.member (m.masterKey))
|
||||
{
|
||||
addTrustedKey (m.masterKey, *unlComment);
|
||||
unl.removePermanentKey (m.masterKey);
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock (mutex_);
|
||||
|
||||
/*
|
||||
before we spend time checking the signature, make sure we trust the
|
||||
master key and the sequence number is newer than any we have.
|
||||
*/
|
||||
auto const chk = canApply(m.masterKey, m.sequence, journal);
|
||||
|
||||
if (chk != ManifestDisposition::accepted)
|
||||
{
|
||||
return chk;
|
||||
}
|
||||
}
|
||||
|
||||
if (! m.verify())
|
||||
{
|
||||
/*
|
||||
A manifest's signature is invalid.
|
||||
This shouldn't happen normally.
|
||||
*/
|
||||
if (auto stream = journal.warn())
|
||||
logMftAct(stream, "Invalid", m.masterKey, m.sequence);
|
||||
return ManifestDisposition::invalid;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock (mutex_);
|
||||
|
||||
/*
|
||||
We released the lock above, so we have to check again, in case
|
||||
another thread accepted a newer manifest.
|
||||
*/
|
||||
auto const chk = canApply(m.masterKey, m.sequence, journal);
|
||||
|
||||
if (chk != ManifestDisposition::accepted)
|
||||
{
|
||||
return chk;
|
||||
}
|
||||
|
||||
auto const iter = map_.find(m.masterKey);
|
||||
|
||||
auto& old = iter->second.m;
|
||||
|
||||
if (! old)
|
||||
{
|
||||
/*
|
||||
This is the first received manifest for a trusted master key
|
||||
(possibly our own). This only happens once per validator per
|
||||
run (and possibly not at all, if there's an obsolete entry in
|
||||
[validator_keys] for a validator that no longer exists).
|
||||
*/
|
||||
if (auto stream = journal.info())
|
||||
logMftAct(stream, "AcceptedNew", m.masterKey, m.sequence);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m.revoked ())
|
||||
{
|
||||
/*
|
||||
The MASTER key for this validator was revoked. This is
|
||||
expected, but should happen at most *very* rarely.
|
||||
*/
|
||||
if (auto stream = journal.info())
|
||||
logMftAct(stream, "Revoked",
|
||||
m.masterKey, m.sequence, old->sequence);
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
An ephemeral key was revoked and superseded by a new key.
|
||||
This is expected, but should happen infrequently.
|
||||
*/
|
||||
if (auto stream = journal.info())
|
||||
logMftAct(stream, "AcceptedUpdate",
|
||||
m.masterKey, m.sequence, old->sequence);
|
||||
}
|
||||
|
||||
unl.removeEphemeralKey (old->signingKey);
|
||||
}
|
||||
|
||||
if (m.revoked ())
|
||||
{
|
||||
// The master key is revoked -- don't insert the signing key
|
||||
if (auto stream = journal.warn())
|
||||
logMftAct(stream, "Revoked", m.masterKey, m.sequence);
|
||||
|
||||
/*
|
||||
A validator master key has been compromised, so its manifests
|
||||
are now untrustworthy. In order to prevent us from accepting
|
||||
a forged manifest signed by the compromised master key, store
|
||||
this manifest, which has the highest possible sequence number
|
||||
and therefore can't be superseded by a forged one.
|
||||
*/
|
||||
}
|
||||
else
|
||||
{
|
||||
unl.insertEphemeralKey (m.signingKey, iter->second.comment);
|
||||
}
|
||||
|
||||
old = std::move(m);
|
||||
|
||||
return ManifestDisposition::accepted;
|
||||
}
|
||||
|
||||
void ManifestCache::load (
|
||||
DatabaseCon& dbCon, ValidatorList& unl, beast::Journal journal)
|
||||
{
|
||||
static const char* const sql =
|
||||
"SELECT RawData FROM ValidatorManifests;";
|
||||
auto db = dbCon.checkoutDb ();
|
||||
soci::blob sociRawData (*db);
|
||||
soci::statement st =
|
||||
(db->prepare << sql,
|
||||
soci::into (sociRawData));
|
||||
st.execute ();
|
||||
while (st.fetch ())
|
||||
{
|
||||
std::string serialized;
|
||||
convert (sociRawData, serialized);
|
||||
if (auto mo = make_Manifest (std::move (serialized)))
|
||||
{
|
||||
if (!mo->verify())
|
||||
{
|
||||
JLOG(journal.warn())
|
||||
<< "Unverifiable manifest in db";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (trusted(mo->masterKey) || unl.trusted(mo->masterKey))
|
||||
{
|
||||
applyManifest (std::move(*mo), unl, journal);
|
||||
}
|
||||
else
|
||||
{
|
||||
JLOG(journal.info())
|
||||
<< "Manifest in db is no longer trusted";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
JLOG(journal.warn())
|
||||
<< "Malformed manifest in database";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ManifestCache::save (DatabaseCon& dbCon) const
|
||||
{
|
||||
auto db = dbCon.checkoutDb ();
|
||||
|
||||
soci::transaction tr(*db);
|
||||
*db << "DELETE FROM ValidatorManifests";
|
||||
static const char* const sql =
|
||||
"INSERT INTO ValidatorManifests (RawData) VALUES (:rawData);";
|
||||
for (auto const& v : map_)
|
||||
{
|
||||
if (!v.second.m)
|
||||
continue;
|
||||
|
||||
// soci does not support bulk insertion of blob data
|
||||
// Do not reuse blob because manifest ecdsa signatures vary in length
|
||||
// but blob write length is expected to be >= the last write
|
||||
soci::blob rawData(*db);
|
||||
convert (v.second.m->serialized, rawData);
|
||||
*db << sql,
|
||||
soci::use (rawData);
|
||||
}
|
||||
tr.commit ();
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@
|
||||
#include <BeastConfig.h>
|
||||
#include <ripple/app/misc/HashRouter.h>
|
||||
#include <ripple/app/misc/NetworkOPs.h>
|
||||
#include <ripple/core/ConfigSections.h>
|
||||
#include <ripple/app/misc/ValidatorList.h>
|
||||
#include <ripple/core/DatabaseCon.h>
|
||||
#include <ripple/basics/contract.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
@@ -479,57 +479,6 @@ OverlayImpl::checkStopped ()
|
||||
stopped();
|
||||
}
|
||||
|
||||
void
|
||||
OverlayImpl::setupValidatorKeyManifests (BasicConfig const& config,
|
||||
DatabaseCon& db)
|
||||
{
|
||||
auto const loaded = manifestCache_.loadValidatorKeys (
|
||||
config.section (SECTION_VALIDATOR_KEYS),
|
||||
journal_);
|
||||
|
||||
if (!loaded)
|
||||
Throw<std::runtime_error> (
|
||||
"Unable to load keys from [" SECTION_VALIDATOR_KEYS "]");
|
||||
|
||||
auto const& validation_manifest =
|
||||
config.section (SECTION_VALIDATION_MANIFEST);
|
||||
|
||||
if (! validation_manifest.lines().empty())
|
||||
{
|
||||
std::string s;
|
||||
s.reserve (Manifest::textLength);
|
||||
for (auto const& line : validation_manifest.lines())
|
||||
s += beast::rfc2616::trim(line);
|
||||
if (auto mo = make_Manifest (beast::detail::base64_decode(s)))
|
||||
{
|
||||
manifestCache_.configManifest (
|
||||
std::move (*mo),
|
||||
app_.validators(),
|
||||
journal_);
|
||||
}
|
||||
else
|
||||
{
|
||||
Throw<std::runtime_error> ("Malformed manifest in config");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
JLOG(journal_.debug()) << "No [" SECTION_VALIDATION_MANIFEST <<
|
||||
"] section in config";
|
||||
}
|
||||
|
||||
manifestCache_.load (
|
||||
db,
|
||||
app_.validators(),
|
||||
journal_);
|
||||
}
|
||||
|
||||
void
|
||||
OverlayImpl::saveValidatorKeyManifests (DatabaseCon& db) const
|
||||
{
|
||||
manifestCache_.save (db);
|
||||
}
|
||||
|
||||
void
|
||||
OverlayImpl::onPrepare()
|
||||
{
|
||||
@@ -713,21 +662,29 @@ OverlayImpl::onManifests (
|
||||
{
|
||||
auto& s = m->list ().Get (i).stobject ();
|
||||
|
||||
if (auto mo = make_Manifest (s))
|
||||
if (auto mo = Manifest::make_Manifest (s))
|
||||
{
|
||||
uint256 const hash = mo->hash ();
|
||||
if (!hashRouter.addSuppressionPeer (hash, from->id ()))
|
||||
continue;
|
||||
|
||||
auto const serialized = mo->serialized;
|
||||
auto const result = manifestCache_.applyManifest (
|
||||
std::move(*mo),
|
||||
app_.validators(),
|
||||
journal);
|
||||
if (! app_.validators().listed (mo->masterKey))
|
||||
{
|
||||
JLOG(journal.info()) << "Untrusted manifest #" << i + 1;
|
||||
app_.getOPs().pubManifest (*mo);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (result == ManifestDisposition::accepted ||
|
||||
result == ManifestDisposition::untrusted)
|
||||
app_.getOPs().pubManifest (*make_Manifest(serialized));
|
||||
auto const serialized = mo->serialized;
|
||||
|
||||
auto const result = app_.validatorManifests ().applyManifest (
|
||||
std::move(*mo));
|
||||
|
||||
if (result == ManifestDisposition::accepted)
|
||||
{
|
||||
app_.getOPs().pubManifest (
|
||||
*Manifest::make_Manifest(serialized));
|
||||
}
|
||||
|
||||
if (result == ManifestDisposition::accepted)
|
||||
{
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
#include <ripple/app/main/Application.h>
|
||||
#include <ripple/core/Job.h>
|
||||
#include <ripple/overlay/Overlay.h>
|
||||
#include <ripple/overlay/impl/Manifest.h>
|
||||
#include <ripple/overlay/impl/TrafficCount.h>
|
||||
#include <ripple/server/Handoff.h>
|
||||
#include <ripple/rpc/ServerHandler.h>
|
||||
@@ -118,7 +117,6 @@ private:
|
||||
hash_map<Peer::id_t, std::weak_ptr<PeerImp>> ids_;
|
||||
Resolver& m_resolver;
|
||||
std::atomic <Peer::id_t> next_id_;
|
||||
ManifestCache manifestCache_;
|
||||
int timer_count_;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -152,12 +150,6 @@ public:
|
||||
return serverHandler_;
|
||||
}
|
||||
|
||||
ManifestCache const&
|
||||
manifestCache() const
|
||||
{
|
||||
return manifestCache_;
|
||||
}
|
||||
|
||||
Setup const&
|
||||
setup() const
|
||||
{
|
||||
@@ -195,15 +187,6 @@ public:
|
||||
relay (protocol::TMValidation& m,
|
||||
uint256 const& uid) override;
|
||||
|
||||
virtual
|
||||
void
|
||||
setupValidatorKeyManifests (BasicConfig const& config,
|
||||
DatabaseCon& db) override;
|
||||
|
||||
virtual
|
||||
void
|
||||
saveValidatorKeyManifests (DatabaseCon& db) const override;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
//
|
||||
// OverlayImpl
|
||||
|
||||
@@ -703,8 +703,8 @@ PeerImp::doProtocolStart()
|
||||
|
||||
protocol::TMManifests tm;
|
||||
|
||||
overlay_.manifestCache ().for_each_manifest (
|
||||
[&tm](size_t s){tm.mutable_list()->Reserve(s);},
|
||||
app_.validatorManifests ().for_each_manifest (
|
||||
[&tm](std::size_t s){tm.mutable_list()->Reserve(s);},
|
||||
[&tm](Manifest const& manifest)
|
||||
{
|
||||
auto const& s = manifest.serialized;
|
||||
|
||||
@@ -116,10 +116,9 @@ JSS ( close_time_resolution ); // in: Application; out: LedgerToJson
|
||||
JSS ( closed ); // out: NetworkOPs, LedgerToJson,
|
||||
// handlers/Ledger
|
||||
JSS ( closed_ledger ); // out: NetworkOPs
|
||||
JSS ( cluster ); // out: UniqueNodeList, PeerImp
|
||||
JSS ( cluster ); // out: PeerImp
|
||||
JSS ( code ); // out: errors
|
||||
JSS ( command ); // in: RPCHandler
|
||||
JSS ( comment ); // out: UnlList
|
||||
JSS ( complete ); // out: NetworkOPs, InboundLedger
|
||||
JSS ( complete_ledgers ); // out: NetworkOPs, PeerImp
|
||||
JSS ( consensus ); // out: NetworkOPs, LedgerConsensus
|
||||
@@ -156,7 +155,6 @@ JSS ( enabled ); // out: AmendmentTable
|
||||
JSS ( engine_result ); // out: NetworkOPs, TransactionSign, Submit
|
||||
JSS ( engine_result_code ); // out: NetworkOPs, TransactionSign, Submit
|
||||
JSS ( engine_result_message ); // out: NetworkOPs, TransactionSign, Submit
|
||||
JSS ( ephemeral ); // out: UnlList
|
||||
JSS ( error ); // out: error
|
||||
JSS ( error_code ); // out: error
|
||||
JSS ( error_exception ); // out: Submit
|
||||
@@ -416,6 +414,7 @@ JSS ( transactions ); // out: LedgerToJson,
|
||||
JSS ( transitions ); // out: NetworkOPs
|
||||
JSS ( treenode_cache_size ); // out: GetCounts
|
||||
JSS ( treenode_track_size ); // out: GetCounts
|
||||
JSS ( trusted ); // out: UnlList
|
||||
JSS ( tx ); // out: STTx, AccountTx*
|
||||
JSS ( tx_blob ); // in/out: Submit,
|
||||
// in: TransactionSign, AccountTx*
|
||||
|
||||
@@ -31,19 +31,16 @@ Json::Value doUnlList (RPC::Context& context)
|
||||
auto lock = make_lock(context.app.getMasterMutex());
|
||||
Json::Value obj (Json::objectValue);
|
||||
|
||||
context.app.validators().for_each (
|
||||
context.app.validators().for_each_listed (
|
||||
[&unl = obj[jss::unl]](
|
||||
PublicKey const& publicKey,
|
||||
std::string const& comment,
|
||||
bool ephemeral)
|
||||
bool trusted)
|
||||
{
|
||||
Json::Value node (Json::objectValue);
|
||||
|
||||
node[jss::pubkey_validator] = toBase58(
|
||||
TokenType::TOKEN_NODE_PUBLIC, publicKey);
|
||||
node[jss::ephemeral] = ephemeral;
|
||||
if (!comment.empty())
|
||||
node[jss::comment] = comment;
|
||||
node[jss::trusted] = trusted;
|
||||
|
||||
unl.append (node);
|
||||
});
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <ripple/app/misc/impl/AccountTxPaging.cpp>
|
||||
#include <ripple/app/misc/impl/AmendmentTable.cpp>
|
||||
#include <ripple/app/misc/impl/LoadFeeTrack.cpp>
|
||||
#include <ripple/app/misc/impl/Manifest.cpp>
|
||||
#include <ripple/app/misc/impl/Transaction.cpp>
|
||||
#include <ripple/app/misc/impl/TxQ.cpp>
|
||||
#include <ripple/app/misc/impl/ValidatorList.cpp>
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
|
||||
#include <ripple/overlay/impl/ConnectAttempt.cpp>
|
||||
#include <ripple/overlay/impl/Cluster.cpp>
|
||||
#include <ripple/overlay/impl/Manifest.cpp>
|
||||
#include <ripple/overlay/impl/Message.cpp>
|
||||
#include <ripple/overlay/impl/OverlayImpl.cpp>
|
||||
#include <ripple/overlay/impl/PeerImp.cpp>
|
||||
|
||||
@@ -402,7 +402,7 @@ public:
|
||||
v->setFieldV256 (sfAmendments, field);
|
||||
|
||||
v->setTrusted();
|
||||
validations [calcNodeID(val)] = v;
|
||||
validations [val] = v;
|
||||
}
|
||||
|
||||
ourVotes = table.doValidation (enabled);
|
||||
|
||||
@@ -17,23 +17,25 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <BeastConfig.h>
|
||||
#include <ripple/app/misc/Manifest.h>
|
||||
#include <ripple/app/misc/ValidatorList.h>
|
||||
#include <ripple/basics/contract.h>
|
||||
#include <ripple/basics/StringUtilities.h>
|
||||
#include <test/jtx/TestSuite.h>
|
||||
#include <ripple/overlay/impl/Manifest.h>
|
||||
#include <test/jtx.h>
|
||||
#include <ripple/core/DatabaseCon.h>
|
||||
#include <ripple/app/main/DBInit.h>
|
||||
#include <ripple/protocol/SecretKey.h>
|
||||
#include <ripple/protocol/Sign.h>
|
||||
#include <ripple/protocol/STExchange.h>
|
||||
#include <beast/core/detail/base64.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/utility/in_place_factory.hpp>
|
||||
|
||||
namespace ripple {
|
||||
namespace tests {
|
||||
namespace test {
|
||||
|
||||
class manifest_test : public ripple::TestSuite
|
||||
class Manifest_test : public beast::unit_test::suite
|
||||
{
|
||||
private:
|
||||
static PublicKey randomNode ()
|
||||
@@ -78,8 +80,9 @@ private:
|
||||
{
|
||||
return boost::filesystem::current_path () / "manifest_test_databases";
|
||||
}
|
||||
|
||||
public:
|
||||
manifest_test ()
|
||||
Manifest_test ()
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -89,7 +92,7 @@ public:
|
||||
{
|
||||
}
|
||||
}
|
||||
~manifest_test ()
|
||||
~Manifest_test ()
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -100,6 +103,30 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
std::string
|
||||
makeManifestString (
|
||||
PublicKey const& pk,
|
||||
SecretKey const& sk,
|
||||
PublicKey const& spk,
|
||||
SecretKey const& ssk,
|
||||
int seq)
|
||||
{
|
||||
STObject st(sfGeneric);
|
||||
st[sfSequence] = seq;
|
||||
st[sfPublicKey] = pk;
|
||||
st[sfSigningPubKey] = spk;
|
||||
|
||||
sign(st, HashPrefix::manifest, *publicKeyType(spk), ssk);
|
||||
sign(st, HashPrefix::manifest, *publicKeyType(pk), sk,
|
||||
sfMasterSignature);
|
||||
|
||||
Serializer s;
|
||||
st.add(s);
|
||||
|
||||
return beast::detail::base64_encode (std::string(
|
||||
static_cast<char const*> (s.data()), s.size()));
|
||||
}
|
||||
|
||||
Manifest
|
||||
make_Manifest
|
||||
(SecretKey const& sk, KeyType type, SecretKey const& ssk, KeyType stype,
|
||||
@@ -129,10 +156,10 @@ public:
|
||||
st.add(s);
|
||||
|
||||
std::string const m (static_cast<char const*> (s.data()), s.size());
|
||||
if (auto r = ripple::make_Manifest (std::move (m)))
|
||||
if (auto r = Manifest::make_Manifest (std::move (m)))
|
||||
return std::move (*r);
|
||||
Throw<std::runtime_error> ("Could not create a manifest");
|
||||
return *ripple::make_Manifest(std::move(m)); // Silence compiler warning.
|
||||
return *Manifest::make_Manifest(std::move(m)); // Silence compiler warning.
|
||||
}
|
||||
|
||||
Manifest
|
||||
@@ -141,105 +168,7 @@ public:
|
||||
return Manifest (m.serialized, m.masterKey, m.signingKey, m.sequence);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
testConfigLoad ()
|
||||
{
|
||||
testcase ("Config Load");
|
||||
|
||||
ManifestCache cache;
|
||||
beast::Journal journal;
|
||||
|
||||
std::vector<PublicKey> network;
|
||||
network.reserve(8);
|
||||
|
||||
while (network.size () != 8)
|
||||
network.push_back (randomMasterKey());
|
||||
|
||||
auto format = [](
|
||||
PublicKey const &publicKey,
|
||||
char const* comment = nullptr)
|
||||
{
|
||||
auto ret = toBase58(
|
||||
TokenType::TOKEN_NODE_PUBLIC,
|
||||
publicKey);
|
||||
|
||||
if (comment)
|
||||
ret += comment;
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
Section s1;
|
||||
|
||||
// Correct (empty) configuration
|
||||
BEAST_EXPECT(cache.loadValidatorKeys (s1, journal));
|
||||
BEAST_EXPECT(cache.size() == 0);
|
||||
|
||||
// Correct configuration
|
||||
s1.append (format (network[0]));
|
||||
s1.append (format (network[1], " Comment"));
|
||||
s1.append (format (network[2], " Multi Word Comment"));
|
||||
s1.append (format (network[3], " Leading Whitespace"));
|
||||
s1.append (format (network[4], " Trailing Whitespace "));
|
||||
s1.append (format (network[5], " Leading & Trailing Whitespace "));
|
||||
s1.append (format (network[6], " Leading, Trailing & Internal Whitespace "));
|
||||
s1.append (format (network[7], " "));
|
||||
|
||||
BEAST_EXPECT(cache.loadValidatorKeys (s1, journal));
|
||||
|
||||
for (auto const& n : network)
|
||||
BEAST_EXPECT(cache.trusted (n));
|
||||
|
||||
// Incorrect configurations:
|
||||
Section s2;
|
||||
s2.append ("NotAPublicKey");
|
||||
BEAST_EXPECT(!cache.loadValidatorKeys (s2, journal));
|
||||
|
||||
Section s3;
|
||||
s3.append (format (network[0], "!"));
|
||||
BEAST_EXPECT(!cache.loadValidatorKeys (s3, journal));
|
||||
|
||||
Section s4;
|
||||
s4.append (format (network[0], "! Comment"));
|
||||
BEAST_EXPECT(!cache.loadValidatorKeys (s4, journal));
|
||||
|
||||
// Check if we properly terminate when we encounter
|
||||
// a malformed or unparseable entry:
|
||||
auto const masterKey1 = randomMasterKey();
|
||||
auto const masterKey2 = randomMasterKey ();
|
||||
|
||||
Section s5;
|
||||
s5.append (format (masterKey1, "XXX"));
|
||||
s5.append (format (masterKey2));
|
||||
BEAST_EXPECT(!cache.loadValidatorKeys (s5, journal));
|
||||
BEAST_EXPECT(!cache.trusted (masterKey1));
|
||||
BEAST_EXPECT(!cache.trusted (masterKey2));
|
||||
|
||||
// Reject secp256k1 permanent validator keys
|
||||
auto const node1 = randomNode ();
|
||||
auto const node2 = randomNode ();
|
||||
|
||||
Section s6;
|
||||
s6.append (format (node1));
|
||||
s6.append (format (node2, " Comment"));
|
||||
BEAST_EXPECT(!cache.loadValidatorKeys (s6, journal));
|
||||
BEAST_EXPECT(!cache.trusted (node1));
|
||||
BEAST_EXPECT(!cache.trusted (node2));
|
||||
|
||||
// Trust our own master public key from configured manifest
|
||||
auto unl = std::make_unique<ValidatorList> (journal);
|
||||
|
||||
auto const sk = randomSecretKey();
|
||||
auto const kp = randomKeyPair(KeyType::secp256k1);
|
||||
auto const m = make_Manifest (
|
||||
sk, KeyType::ed25519, kp.second, KeyType::secp256k1, 0);
|
||||
|
||||
cache.configManifest (clone (m), *unl, journal);
|
||||
BEAST_EXPECT(cache.trusted (m.masterKey));
|
||||
}
|
||||
|
||||
void testLoadStore (ManifestCache const& m, ValidatorList& unl)
|
||||
void testLoadStore (ManifestCache& m)
|
||||
{
|
||||
testcase ("load/store");
|
||||
|
||||
@@ -251,13 +180,6 @@ public:
|
||||
setup.dataDir = getDatabasePath ();
|
||||
DatabaseCon dbCon(setup, dbName, WalletDBInit, WalletDBCount);
|
||||
|
||||
if (!m.size ())
|
||||
fail ();
|
||||
|
||||
m.save (dbCon);
|
||||
|
||||
beast::Journal journal;
|
||||
|
||||
auto getPopulatedManifests =
|
||||
[](ManifestCache const& cache) -> std::vector<Manifest const*>
|
||||
{
|
||||
@@ -279,21 +201,45 @@ public:
|
||||
};
|
||||
std::vector<Manifest const*> const inManifests (
|
||||
sort (getPopulatedManifests (m)));
|
||||
|
||||
beast::Journal journal;
|
||||
jtx::Env env (*this);
|
||||
auto unl = std::make_unique<ValidatorList> (
|
||||
m, m, env.timeKeeper(), journal);
|
||||
|
||||
{
|
||||
// load should not load untrusted master keys from db
|
||||
// save should not store untrusted master keys to db
|
||||
m.save (dbCon, "ValidatorManifests",
|
||||
[&unl](PublicKey const& pubKey)
|
||||
{
|
||||
return unl->listed (pubKey);
|
||||
});
|
||||
|
||||
ManifestCache loaded;
|
||||
|
||||
loaded.load (dbCon, unl, journal);
|
||||
BEAST_EXPECT(loaded.size() == 0);
|
||||
loaded.load (dbCon, "ValidatorManifests");
|
||||
for (auto const& man : inManifests)
|
||||
BEAST_EXPECT(
|
||||
loaded.getSigningKey (man->masterKey) == man->masterKey);
|
||||
}
|
||||
{
|
||||
// load should load all trusted master keys from db
|
||||
ManifestCache loaded;
|
||||
|
||||
// save should store all trusted master keys to db
|
||||
PublicKey emptyLocalKey;
|
||||
std::vector<std::string> s1;
|
||||
std::vector<std::string> keys;
|
||||
std::vector<std::string> cfgManifest;
|
||||
for (auto const& man : inManifests)
|
||||
loaded.addTrustedKey (man->masterKey, "");
|
||||
s1.push_back (toBase58(
|
||||
TokenType::TOKEN_NODE_PUBLIC, man->masterKey));
|
||||
unl->load (emptyLocalKey, s1, keys);
|
||||
|
||||
loaded.load (dbCon, unl, journal);
|
||||
m.save (dbCon, "ValidatorManifests",
|
||||
[&unl](PublicKey const& pubKey)
|
||||
{
|
||||
return unl->listed (pubKey);
|
||||
});
|
||||
ManifestCache loaded;
|
||||
loaded.load (dbCon, "ValidatorManifests");
|
||||
|
||||
std::vector<Manifest const*> const loadedManifests (
|
||||
sort (getPopulatedManifests (loaded)));
|
||||
@@ -312,18 +258,23 @@ public:
|
||||
}
|
||||
}
|
||||
{
|
||||
// load should remove master key from permanent key list
|
||||
ManifestCache loaded;
|
||||
auto const iMan = inManifests.begin();
|
||||
// load config manifest
|
||||
std::vector<std::string> const badManifest ({"bad manifest"});
|
||||
|
||||
if (!*iMan)
|
||||
fail ();
|
||||
BEAST_EXPECT(m.trusted((*iMan)->masterKey));
|
||||
BEAST_EXPECT(unl.insertPermanentKey((*iMan)->masterKey, "trusted key"));
|
||||
BEAST_EXPECT(unl.trusted((*iMan)->masterKey));
|
||||
loaded.load (dbCon, unl, journal);
|
||||
BEAST_EXPECT(!unl.trusted((*iMan)->masterKey));
|
||||
BEAST_EXPECT(loaded.trusted((*iMan)->masterKey));
|
||||
ManifestCache loaded;
|
||||
BEAST_EXPECT(! loaded.load (
|
||||
dbCon, "ValidatorManifests", badManifest));
|
||||
|
||||
auto const sk = randomSecretKey();
|
||||
auto const pk = derivePublicKey(KeyType::ed25519, sk);
|
||||
auto const kp = randomKeyPair(KeyType::secp256k1);
|
||||
|
||||
std::vector<std::string> const cfgManifest ({
|
||||
makeManifestString (pk, sk, kp.first, kp.second, 0)
|
||||
});
|
||||
|
||||
BEAST_EXPECT(loaded.load (
|
||||
dbCon, "ValidatorManifests", cfgManifest));
|
||||
}
|
||||
}
|
||||
boost::filesystem::remove (getDatabasePath () /
|
||||
@@ -353,16 +304,76 @@ public:
|
||||
BEAST_EXPECT(strHex(masterSig) == strHex(m.getMasterSignature()));
|
||||
}
|
||||
|
||||
void testGetKeys()
|
||||
{
|
||||
testcase ("getKeys");
|
||||
|
||||
ManifestCache cache;
|
||||
auto const sk = randomSecretKey();
|
||||
auto const pk = derivePublicKey(KeyType::ed25519, sk);
|
||||
|
||||
// getSigningKey should return same key if there is no manifest
|
||||
BEAST_EXPECT(cache.getSigningKey(pk) == pk);
|
||||
|
||||
// getSigningKey should return the ephemeral public key
|
||||
// for the listed validator master public key
|
||||
// getMasterKey should return the listed validator master key
|
||||
// for that ephemeral public key
|
||||
auto const kp0 = randomKeyPair(KeyType::secp256k1);
|
||||
auto const m0 = make_Manifest (
|
||||
sk, KeyType::ed25519, kp0.second, KeyType::secp256k1, 0);
|
||||
BEAST_EXPECT(cache.applyManifest(clone (m0)) ==
|
||||
ManifestDisposition::accepted);
|
||||
BEAST_EXPECT(cache.getSigningKey(pk) == kp0.first);
|
||||
BEAST_EXPECT(cache.getMasterKey(kp0.first) == pk);
|
||||
|
||||
// getSigningKey should return the latest ephemeral public key
|
||||
// for the listed validator master public key
|
||||
// getMasterKey should only return a master key for the latest
|
||||
// ephemeral public key
|
||||
auto const kp1 = randomKeyPair(KeyType::secp256k1);
|
||||
auto const m1 = make_Manifest (
|
||||
sk, KeyType::ed25519, kp1.second, KeyType::secp256k1, 1);
|
||||
BEAST_EXPECT(cache.applyManifest(clone (m1)) ==
|
||||
ManifestDisposition::accepted);
|
||||
BEAST_EXPECT(cache.getSigningKey(pk) == kp1.first);
|
||||
BEAST_EXPECT(cache.getMasterKey(kp1.first) == pk);
|
||||
BEAST_EXPECT(cache.getMasterKey(kp0.first) == kp0.first);
|
||||
|
||||
// getSigningKey and getMasterKey should return the same keys if
|
||||
// a new manifest is applied with the same signing key but a higher
|
||||
// sequence
|
||||
auto const m2 = make_Manifest (
|
||||
sk, KeyType::ed25519, kp1.second, KeyType::secp256k1, 2);
|
||||
BEAST_EXPECT(cache.applyManifest(clone (m2)) ==
|
||||
ManifestDisposition::accepted);
|
||||
BEAST_EXPECT(cache.getSigningKey(pk) == kp1.first);
|
||||
BEAST_EXPECT(cache.getMasterKey(kp1.first) == pk);
|
||||
BEAST_EXPECT(cache.getMasterKey(kp0.first) == kp0.first);
|
||||
|
||||
// getSigningKey should return boost::none for a
|
||||
// revoked master public key
|
||||
// getMasterKey should return boost::none for an ephemeral public key
|
||||
// from a revoked master public key
|
||||
auto const kpMax = randomKeyPair(KeyType::secp256k1);
|
||||
auto const mMax = make_Manifest (
|
||||
sk, KeyType::ed25519, kpMax.second, KeyType::secp256k1,
|
||||
std::numeric_limits<std::uint32_t>::max ());
|
||||
BEAST_EXPECT(cache.applyManifest(clone (mMax)) ==
|
||||
ManifestDisposition::accepted);
|
||||
BEAST_EXPECT(cache.revoked(pk));
|
||||
BEAST_EXPECT(cache.getSigningKey(pk) == pk);
|
||||
BEAST_EXPECT(cache.getMasterKey(kpMax.first) == kpMax.first);
|
||||
BEAST_EXPECT(cache.getMasterKey(kp1.first) == kp1.first);
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
ManifestCache cache;
|
||||
beast::Journal journal;
|
||||
auto unl = std::make_unique<ValidatorList> (journal);
|
||||
{
|
||||
testcase ("apply");
|
||||
auto const accepted = ManifestDisposition::accepted;
|
||||
auto const untrusted = ManifestDisposition::untrusted;
|
||||
auto const stale = ManifestDisposition::stale;
|
||||
auto const invalid = ManifestDisposition::invalid;
|
||||
|
||||
@@ -373,9 +384,11 @@ public:
|
||||
sk_a, KeyType::ed25519, kp_a.second, KeyType::secp256k1, 0);
|
||||
auto const s_a1 = make_Manifest (
|
||||
sk_a, KeyType::ed25519, kp_a.second, KeyType::secp256k1, 1);
|
||||
auto const s_aMax = make_Manifest (
|
||||
sk_a, KeyType::ed25519, kp_a.second, KeyType::secp256k1,
|
||||
std::numeric_limits<std::uint32_t>::max ());
|
||||
|
||||
auto const sk_b = randomSecretKey();
|
||||
auto const pk_b = derivePublicKey(KeyType::ed25519, sk_b);
|
||||
auto const kp_b = randomKeyPair(KeyType::secp256k1);
|
||||
auto const s_b0 = make_Manifest (
|
||||
sk_b, KeyType::ed25519, kp_b.second, KeyType::secp256k1, 0);
|
||||
@@ -386,45 +399,39 @@ public:
|
||||
true); // broken
|
||||
auto const fake = s_b1.serialized + '\0';
|
||||
|
||||
BEAST_EXPECT(cache.applyManifest (clone (s_a0), *unl, journal) == untrusted);
|
||||
// applyManifest should accept new manifests with
|
||||
// higher sequence numbers
|
||||
BEAST_EXPECT(cache.applyManifest (clone (s_a0)) == accepted);
|
||||
BEAST_EXPECT(cache.applyManifest (clone (s_a0)) == stale);
|
||||
|
||||
cache.addTrustedKey (pk_a, "a");
|
||||
cache.addTrustedKey (pk_b, "b");
|
||||
BEAST_EXPECT(cache.applyManifest (clone (s_a1)) == accepted);
|
||||
BEAST_EXPECT(cache.applyManifest (clone (s_a1)) == stale);
|
||||
BEAST_EXPECT(cache.applyManifest (clone (s_a0)) == stale);
|
||||
|
||||
BEAST_EXPECT(cache.applyManifest (clone (s_a0), *unl, journal) == accepted);
|
||||
BEAST_EXPECT(cache.applyManifest (clone (s_a0), *unl, journal) == stale);
|
||||
// applyManifest should accept manifests with max sequence numbers
|
||||
// that revoke the master public key
|
||||
BEAST_EXPECT(!cache.revoked (pk_a));
|
||||
BEAST_EXPECT(s_aMax.revoked ());
|
||||
BEAST_EXPECT(cache.applyManifest (clone (s_aMax)) == accepted);
|
||||
BEAST_EXPECT(cache.applyManifest (clone (s_aMax)) == stale);
|
||||
BEAST_EXPECT(cache.applyManifest (clone (s_a1)) == stale);
|
||||
BEAST_EXPECT(cache.applyManifest (clone (s_a0)) == stale);
|
||||
BEAST_EXPECT(cache.revoked (pk_a));
|
||||
|
||||
BEAST_EXPECT(cache.applyManifest (clone (s_a1), *unl, journal) == accepted);
|
||||
BEAST_EXPECT(cache.applyManifest (clone (s_a1), *unl, journal) == stale);
|
||||
BEAST_EXPECT(cache.applyManifest (clone (s_a0), *unl, journal) == stale);
|
||||
// applyManifest should reject manifests with invalid signatures
|
||||
BEAST_EXPECT(cache.applyManifest (clone (s_b0)) == accepted);
|
||||
BEAST_EXPECT(cache.applyManifest (clone (s_b0)) == stale);
|
||||
|
||||
BEAST_EXPECT(cache.applyManifest (clone (s_b0), *unl, journal) == accepted);
|
||||
BEAST_EXPECT(cache.applyManifest (clone (s_b0), *unl, journal) == stale);
|
||||
|
||||
BEAST_EXPECT(!ripple::make_Manifest(fake));
|
||||
BEAST_EXPECT(cache.applyManifest (clone (s_b2), *unl, journal) == invalid);
|
||||
|
||||
// When trusted permanent key is found as manifest master key
|
||||
// move to manifest cache
|
||||
auto const sk_c = randomSecretKey();
|
||||
auto const pk_c = derivePublicKey(KeyType::ed25519, sk_c);
|
||||
auto const kp_c = randomKeyPair(KeyType::secp256k1);
|
||||
auto const s_c0 = make_Manifest (
|
||||
sk_c, KeyType::ed25519, kp_c.second, KeyType::secp256k1, 0);
|
||||
BEAST_EXPECT(unl->insertPermanentKey(pk_c, "trusted key"));
|
||||
BEAST_EXPECT(unl->trusted(pk_c));
|
||||
BEAST_EXPECT(!cache.trusted(pk_c));
|
||||
BEAST_EXPECT(cache.applyManifest(clone (s_c0), *unl, journal) == accepted);
|
||||
BEAST_EXPECT(!unl->trusted(pk_c));
|
||||
BEAST_EXPECT(cache.trusted(pk_c));
|
||||
BEAST_EXPECT(!Manifest::make_Manifest(fake));
|
||||
BEAST_EXPECT(cache.applyManifest (clone (s_b2)) == invalid);
|
||||
}
|
||||
testConfigLoad();
|
||||
testLoadStore (cache, *unl);
|
||||
testLoadStore (cache);
|
||||
testGetSignature ();
|
||||
testGetKeys ();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(manifest,overlay,ripple);
|
||||
BEAST_DEFINE_TESTSUITE(Manifest,app,ripple);
|
||||
|
||||
} // tests
|
||||
} // test
|
||||
} // ripple
|
||||
@@ -17,48 +17,111 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <BeastConfig.h>
|
||||
#include <beast/core/detail/base64.hpp>
|
||||
#include <ripple/basics/Slice.h>
|
||||
#include <test/jtx/TestSuite.h>
|
||||
#include <ripple/basics/strHex.h>
|
||||
#include <ripple/app/misc/ValidatorList.h>
|
||||
#include <test/jtx.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>
|
||||
|
||||
|
||||
namespace ripple {
|
||||
namespace tests {
|
||||
namespace test {
|
||||
|
||||
class ValidatorList_test : public ripple::TestSuite
|
||||
class ValidatorList_test : public beast::unit_test::suite
|
||||
{
|
||||
private:
|
||||
static
|
||||
PublicKey
|
||||
randomNode ()
|
||||
{
|
||||
return derivePublicKey (
|
||||
KeyType::secp256k1,
|
||||
randomSecretKey());
|
||||
return derivePublicKey (KeyType::secp256k1, randomSecretKey());
|
||||
}
|
||||
|
||||
static
|
||||
PublicKey
|
||||
randomMasterKey ()
|
||||
{
|
||||
return derivePublicKey (
|
||||
KeyType::ed25519,
|
||||
randomSecretKey());
|
||||
return derivePublicKey (KeyType::ed25519, randomSecretKey());
|
||||
}
|
||||
|
||||
static
|
||||
bool
|
||||
isPresent (
|
||||
std::vector<PublicKey> container,
|
||||
PublicKey const& item)
|
||||
std::string
|
||||
makeManifestString (
|
||||
PublicKey const& pk,
|
||||
SecretKey const& sk,
|
||||
PublicKey const& spk,
|
||||
SecretKey const& ssk,
|
||||
int seq)
|
||||
{
|
||||
auto found = std::find (
|
||||
std::begin (container),
|
||||
std::end (container),
|
||||
item);
|
||||
STObject st(sfGeneric);
|
||||
st[sfSequence] = seq;
|
||||
st[sfPublicKey] = pk;
|
||||
st[sfSigningPubKey] = spk;
|
||||
|
||||
return (found != std::end (container));
|
||||
sign(st, HashPrefix::manifest, *publicKeyType(spk), ssk);
|
||||
sign(st, HashPrefix::manifest, *publicKeyType(pk), sk,
|
||||
sfMasterSignature);
|
||||
|
||||
Serializer s;
|
||||
st.add(s);
|
||||
|
||||
return std::string(static_cast<char const*> (s.data()), s.size());
|
||||
}
|
||||
|
||||
std::string
|
||||
makeList (
|
||||
std::vector <PublicKey> const& validators,
|
||||
std::size_t sequence,
|
||||
std::size_t expiration)
|
||||
{
|
||||
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 += "]}";
|
||||
return beast::detail::base64_encode(data);
|
||||
}
|
||||
|
||||
std::string
|
||||
signList (
|
||||
std::string const& blob,
|
||||
std::pair<PublicKey, SecretKey> const& keys)
|
||||
{
|
||||
auto const data = beast::detail::base64_decode (blob);
|
||||
return strHex(sign(
|
||||
keys.first, keys.second, makeSlice(data)));
|
||||
}
|
||||
|
||||
void
|
||||
testGenesisQuorum ()
|
||||
{
|
||||
testcase ("Genesis Quorum");
|
||||
|
||||
beast::Journal journal;
|
||||
ManifestCache manifests;
|
||||
jtx::Env env (*this);
|
||||
{
|
||||
auto trustedKeys = std::make_unique <ValidatorList> (
|
||||
manifests, manifests, env.timeKeeper(), journal);
|
||||
BEAST_EXPECT(trustedKeys->quorum () == 1);
|
||||
}
|
||||
{
|
||||
std::size_t minQuorum = 0;
|
||||
auto trustedKeys = std::make_unique <ValidatorList> (
|
||||
manifests, manifests, env.timeKeeper(), journal, minQuorum);
|
||||
BEAST_EXPECT(trustedKeys->quorum () == minQuorum);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -66,21 +129,28 @@ private:
|
||||
{
|
||||
testcase ("Config Load");
|
||||
|
||||
auto validators = std::make_unique <ValidatorList> (beast::Journal ());
|
||||
beast::Journal journal;
|
||||
jtx::Env env (*this);
|
||||
PublicKey emptyLocalKey;
|
||||
std::vector<std::string> emptyCfgKeys;
|
||||
std::vector<std::string> emptyCfgPublishers;
|
||||
|
||||
std::vector<PublicKey> network;
|
||||
network.reserve(8);
|
||||
auto const localSigningKeys = randomKeyPair(KeyType::secp256k1);
|
||||
auto const localSigningPublic = localSigningKeys.first;
|
||||
auto const localSigningSecret = localSigningKeys.second;
|
||||
auto const localMasterSecret = randomSecretKey();
|
||||
auto const localMasterPublic = derivePublicKey(
|
||||
KeyType::ed25519, localMasterSecret);
|
||||
|
||||
while (network.size () != 8)
|
||||
network.push_back (randomNode());
|
||||
auto cfgManifest = makeManifestString (
|
||||
localMasterPublic, localMasterSecret,
|
||||
localSigningPublic, localSigningSecret, 1);
|
||||
|
||||
auto format = [](
|
||||
PublicKey const &publicKey,
|
||||
char const* comment = nullptr)
|
||||
{
|
||||
auto ret = toBase58(
|
||||
TokenType::TOKEN_NODE_PUBLIC,
|
||||
publicKey);
|
||||
auto ret = toBase58 (TokenType::TOKEN_NODE_PUBLIC, publicKey);
|
||||
|
||||
if (comment)
|
||||
ret += comment;
|
||||
@@ -88,213 +158,592 @@ private:
|
||||
return ret;
|
||||
};
|
||||
|
||||
Section s1;
|
||||
std::vector<PublicKey> configList;
|
||||
configList.reserve(8);
|
||||
|
||||
// Correct (empty) configuration
|
||||
BEAST_EXPECT(validators->load (s1));
|
||||
BEAST_EXPECT(validators->size() == 0);
|
||||
while (configList.size () != 8)
|
||||
configList.push_back (randomNode());
|
||||
|
||||
// Correct configuration
|
||||
s1.append (format (network[0]));
|
||||
s1.append (format (network[1], " Comment"));
|
||||
s1.append (format (network[2], " Multi Word Comment"));
|
||||
s1.append (format (network[3], " Leading Whitespace"));
|
||||
s1.append (format (network[4], " Trailing Whitespace "));
|
||||
s1.append (format (network[5], " Leading & Trailing Whitespace "));
|
||||
s1.append (format (network[6], " Leading, Trailing & Internal Whitespace "));
|
||||
s1.append (format (network[7], " "));
|
||||
std::vector<std::string> cfgKeys ({
|
||||
format (configList[0]),
|
||||
format (configList[1], " Comment"),
|
||||
format (configList[2], " Multi Word Comment"),
|
||||
format (configList[3], " Leading Whitespace"),
|
||||
format (configList[4], " Trailing Whitespace "),
|
||||
format (configList[5], " Leading & Trailing Whitespace "),
|
||||
format (configList[6], " Leading, Trailing & Internal Whitespace "),
|
||||
format (configList[7], " ")
|
||||
});
|
||||
|
||||
BEAST_EXPECT(validators->load (s1));
|
||||
{
|
||||
ManifestCache manifests;
|
||||
auto trustedKeys = std::make_unique <ValidatorList> (
|
||||
manifests, manifests, env.timeKeeper(), journal);
|
||||
|
||||
for (auto const& n : network)
|
||||
BEAST_EXPECT(validators->trusted (n));
|
||||
// Correct (empty) configuration
|
||||
BEAST_EXPECT(trustedKeys->load (
|
||||
emptyLocalKey, emptyCfgKeys, emptyCfgPublishers));
|
||||
|
||||
// Incorrect configurations:
|
||||
Section s2;
|
||||
s2.append ("NotAPublicKey");
|
||||
BEAST_EXPECT(!validators->load (s2));
|
||||
// load local validator key with or without manifest
|
||||
BEAST_EXPECT(trustedKeys->load (
|
||||
localSigningPublic, emptyCfgKeys, emptyCfgPublishers));
|
||||
BEAST_EXPECT(trustedKeys->listed (localSigningPublic));
|
||||
|
||||
Section s3;
|
||||
s3.append (format (network[0], "!"));
|
||||
BEAST_EXPECT(!validators->load (s3));
|
||||
manifests.applyManifest (*Manifest::make_Manifest(cfgManifest));
|
||||
BEAST_EXPECT(trustedKeys->load (
|
||||
localSigningPublic, emptyCfgKeys, emptyCfgPublishers));
|
||||
|
||||
Section s4;
|
||||
s4.append (format (network[0], "! Comment"));
|
||||
BEAST_EXPECT(!validators->load (s4));
|
||||
BEAST_EXPECT(trustedKeys->listed (localMasterPublic));
|
||||
BEAST_EXPECT(trustedKeys->listed (localSigningPublic));
|
||||
}
|
||||
{
|
||||
// load should add validator keys from config
|
||||
ManifestCache manifests;
|
||||
auto trustedKeys = std::make_unique <ValidatorList> (
|
||||
manifests, manifests, env.timeKeeper(), journal);
|
||||
|
||||
// Check if we properly terminate when we encounter
|
||||
// a malformed or unparseable entry:
|
||||
auto const node1 = randomNode();
|
||||
auto const node2 = randomNode ();
|
||||
BEAST_EXPECT(trustedKeys->load (
|
||||
emptyLocalKey, cfgKeys, emptyCfgPublishers));
|
||||
|
||||
Section s5;
|
||||
s5.append (format (node1, "XXX"));
|
||||
s5.append (format (node2));
|
||||
BEAST_EXPECT(!validators->load (s5));
|
||||
BEAST_EXPECT(!validators->trusted (node1));
|
||||
BEAST_EXPECT(!validators->trusted (node2));
|
||||
for (auto const& n : configList)
|
||||
BEAST_EXPECT(trustedKeys->listed (n));
|
||||
|
||||
// Add Ed25519 master public keys to permanent validators list
|
||||
// load should accept Ed25519 master public keys
|
||||
auto const masterNode1 = randomMasterKey ();
|
||||
auto const masterNode2 = randomMasterKey ();
|
||||
|
||||
Section s6;
|
||||
s6.append (format (masterNode1));
|
||||
s6.append (format (masterNode2, " Comment"));
|
||||
BEAST_EXPECT(validators->load (s6));
|
||||
BEAST_EXPECT(validators->trusted (masterNode1));
|
||||
BEAST_EXPECT(validators->trusted (masterNode2));
|
||||
std::vector<std::string> cfgMasterKeys({
|
||||
format (masterNode1),
|
||||
format (masterNode2, " Comment")
|
||||
});
|
||||
BEAST_EXPECT(trustedKeys->load (
|
||||
emptyLocalKey, cfgMasterKeys, emptyCfgPublishers));
|
||||
BEAST_EXPECT(trustedKeys->listed (masterNode1));
|
||||
BEAST_EXPECT(trustedKeys->listed (masterNode2));
|
||||
|
||||
// load should reject invalid config keys
|
||||
std::vector<std::string> badKeys({"NotAPublicKey"});
|
||||
BEAST_EXPECT(!trustedKeys->load (
|
||||
emptyLocalKey, badKeys, emptyCfgPublishers));
|
||||
|
||||
badKeys[0] = format (randomNode(), "!");
|
||||
BEAST_EXPECT(!trustedKeys->load (
|
||||
emptyLocalKey, badKeys, emptyCfgPublishers));
|
||||
|
||||
badKeys[0] = format (randomNode(), "! Comment");
|
||||
BEAST_EXPECT(!trustedKeys->load (
|
||||
emptyLocalKey, badKeys, emptyCfgPublishers));
|
||||
|
||||
// load terminates when encountering an invalid entry
|
||||
auto const goodKey = randomNode();
|
||||
badKeys.push_back (format (goodKey));
|
||||
BEAST_EXPECT(!trustedKeys->load (
|
||||
emptyLocalKey, badKeys, emptyCfgPublishers));
|
||||
BEAST_EXPECT(!trustedKeys->listed (goodKey));
|
||||
}
|
||||
|
||||
void
|
||||
testMembership ()
|
||||
{
|
||||
// The servers on the permanentValidators
|
||||
std::vector<PublicKey> permanentValidators;
|
||||
std::vector<PublicKey> ephemeralValidators;
|
||||
// local validator key on config list
|
||||
ManifestCache manifests;
|
||||
auto trustedKeys = std::make_unique <ValidatorList> (
|
||||
manifests, manifests, env.timeKeeper(), journal);
|
||||
|
||||
while (permanentValidators.size () != 64)
|
||||
permanentValidators.push_back (randomNode());
|
||||
auto const localSigningPublic = parseBase58<PublicKey> (
|
||||
TokenType::TOKEN_NODE_PUBLIC, cfgKeys.front());
|
||||
|
||||
while (ephemeralValidators.size () != 64)
|
||||
ephemeralValidators.push_back (randomNode());
|
||||
BEAST_EXPECT(trustedKeys->load (
|
||||
*localSigningPublic, cfgKeys, emptyCfgPublishers));
|
||||
|
||||
{
|
||||
testcase ("Membership: No Validators");
|
||||
|
||||
auto vl = std::make_unique <ValidatorList> (beast::Journal ());
|
||||
|
||||
for (auto const& v : permanentValidators)
|
||||
BEAST_EXPECT(!vl->trusted (v));
|
||||
|
||||
for (auto const& v : ephemeralValidators)
|
||||
BEAST_EXPECT(!vl->trusted (v));
|
||||
BEAST_EXPECT(trustedKeys->listed (*localSigningPublic));
|
||||
for (auto const& n : configList)
|
||||
BEAST_EXPECT(trustedKeys->listed (n));
|
||||
}
|
||||
|
||||
{
|
||||
testcase ("Membership: Non-Empty, Some Present, Some Not Present");
|
||||
// local validator key not on config list
|
||||
ManifestCache manifests;
|
||||
auto trustedKeys = std::make_unique <ValidatorList> (
|
||||
manifests, manifests, env.timeKeeper(), journal);
|
||||
|
||||
std::vector<PublicKey> p (
|
||||
permanentValidators.begin (),
|
||||
permanentValidators.begin () + 16);
|
||||
auto const localSigningPublic = randomNode();
|
||||
BEAST_EXPECT(trustedKeys->load (
|
||||
localSigningPublic, cfgKeys, emptyCfgPublishers));
|
||||
|
||||
while (p.size () != 32)
|
||||
p.push_back (randomNode());
|
||||
BEAST_EXPECT(trustedKeys->listed (localSigningPublic));
|
||||
for (auto const& n : configList)
|
||||
BEAST_EXPECT(trustedKeys->listed (n));
|
||||
}
|
||||
{
|
||||
// local validator key (with manifest) not on config list
|
||||
ManifestCache manifests;
|
||||
auto trustedKeys = std::make_unique <ValidatorList> (
|
||||
manifests, manifests, env.timeKeeper(), journal);
|
||||
|
||||
std::vector<PublicKey> e (
|
||||
ephemeralValidators.begin (),
|
||||
ephemeralValidators.begin () + 16);
|
||||
manifests.applyManifest (*Manifest::make_Manifest(cfgManifest));
|
||||
|
||||
while (e.size () != 32)
|
||||
e.push_back (randomNode());
|
||||
BEAST_EXPECT(trustedKeys->load (
|
||||
localSigningPublic, cfgKeys, emptyCfgPublishers));
|
||||
|
||||
auto vl = std::make_unique <ValidatorList> (beast::Journal ());
|
||||
BEAST_EXPECT(trustedKeys->listed (localSigningPublic));
|
||||
BEAST_EXPECT(trustedKeys->listed (localMasterPublic));
|
||||
for (auto const& n : configList)
|
||||
BEAST_EXPECT(trustedKeys->listed (n));
|
||||
}
|
||||
{
|
||||
ManifestCache manifests;
|
||||
auto trustedKeys = std::make_unique <ValidatorList> (
|
||||
manifests, manifests, env.timeKeeper(), journal);
|
||||
|
||||
for (auto const& v : p)
|
||||
vl->insertPermanentKey (v, "");
|
||||
// load should reject invalid validator list signing keys
|
||||
std::vector<std::string> badPublishers(
|
||||
{"NotASigningKey"});
|
||||
BEAST_EXPECT(!trustedKeys->load (
|
||||
emptyLocalKey, emptyCfgKeys, badPublishers));
|
||||
|
||||
for (auto const& v : e)
|
||||
vl->insertEphemeralKey (v, "");
|
||||
// load should reject validator list signing keys with invalid encoding
|
||||
std::vector<PublicKey> keys ({
|
||||
randomMasterKey(), randomMasterKey(), randomMasterKey()});
|
||||
badPublishers.clear();
|
||||
for (auto const& key : keys)
|
||||
badPublishers.push_back (
|
||||
toBase58 (TokenType::TOKEN_NODE_PUBLIC, key));
|
||||
|
||||
for (auto const& v : p)
|
||||
BEAST_EXPECT(vl->trusted (v));
|
||||
BEAST_EXPECT(! trustedKeys->load (
|
||||
emptyLocalKey, emptyCfgKeys, badPublishers));
|
||||
for (auto const& key : keys)
|
||||
BEAST_EXPECT(!trustedKeys->trustedPublisher (key));
|
||||
|
||||
for (auto const& v : e)
|
||||
BEAST_EXPECT(vl->trusted (v));
|
||||
// load should accept valid validator list publisher keys
|
||||
std::vector<std::string> cfgPublishers;
|
||||
for (auto const& key : keys)
|
||||
cfgPublishers.push_back (strHex(key));
|
||||
|
||||
for (auto const& v : permanentValidators)
|
||||
BEAST_EXPECT(static_cast<bool>(vl->trusted (v)) == isPresent (p, v));
|
||||
|
||||
for (auto const& v : ephemeralValidators)
|
||||
BEAST_EXPECT(static_cast<bool>(vl->trusted (v)) == isPresent (e, v));
|
||||
BEAST_EXPECT(trustedKeys->load (
|
||||
emptyLocalKey, emptyCfgKeys, cfgPublishers));
|
||||
for (auto const& key : keys)
|
||||
BEAST_EXPECT(trustedKeys->trustedPublisher (key));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testModification ()
|
||||
testApplyList ()
|
||||
{
|
||||
testcase ("Insertion and Removal");
|
||||
testcase ("Apply list");
|
||||
|
||||
auto vl = std::make_unique <ValidatorList> (beast::Journal ());
|
||||
beast::Journal journal;
|
||||
ManifestCache manifests;
|
||||
jtx::Env env (*this);
|
||||
auto trustedKeys = std::make_unique<ValidatorList> (
|
||||
manifests, manifests, env.app().timeKeeper(), journal);
|
||||
|
||||
auto const v = randomNode ();
|
||||
auto const publisherSecret = randomSecretKey();
|
||||
auto const publisherPublic =
|
||||
derivePublicKey(KeyType::ed25519, publisherSecret);
|
||||
auto const pubSigningKeys1 = randomKeyPair(KeyType::secp256k1);
|
||||
auto const manifest1 = beast::detail::base64_encode(makeManifestString (
|
||||
publisherPublic, publisherSecret,
|
||||
pubSigningKeys1.first, pubSigningKeys1.second, 1));
|
||||
|
||||
// Inserting a new permanent key succeeds
|
||||
BEAST_EXPECT(vl->insertPermanentKey (v, "Permanent"));
|
||||
{
|
||||
auto member = vl->member (v);
|
||||
BEAST_EXPECT(static_cast<bool>(member));
|
||||
BEAST_EXPECT(member->compare("Permanent") == 0);
|
||||
}
|
||||
// Inserting the same permanent key fails:
|
||||
BEAST_EXPECT(!vl->insertPermanentKey (v, ""));
|
||||
{
|
||||
auto member = vl->member (v);
|
||||
BEAST_EXPECT(static_cast<bool>(member));
|
||||
BEAST_EXPECT(member->compare("Permanent") == 0);
|
||||
}
|
||||
// Inserting the same key as ephemeral fails:
|
||||
BEAST_EXPECT(!vl->insertEphemeralKey (v, "Ephemeral"));
|
||||
{
|
||||
auto member = vl->member (v);
|
||||
BEAST_EXPECT(static_cast<bool>(member));
|
||||
BEAST_EXPECT(member->compare("Permanent") == 0);
|
||||
}
|
||||
// Removing the key as ephemeral fails:
|
||||
BEAST_EXPECT(!vl->removeEphemeralKey (v));
|
||||
{
|
||||
auto member = vl->member (v);
|
||||
BEAST_EXPECT(static_cast<bool>(member));
|
||||
BEAST_EXPECT(member->compare("Permanent") == 0);
|
||||
}
|
||||
// Deleting the key as permanent succeeds:
|
||||
BEAST_EXPECT(vl->removePermanentKey (v));
|
||||
BEAST_EXPECT(!static_cast<bool>(vl->trusted (v)));
|
||||
std::vector<std::string> cfgKeys1({
|
||||
strHex(publisherPublic)});
|
||||
PublicKey emptyLocalKey;
|
||||
std::vector<std::string> emptyCfgKeys;
|
||||
|
||||
// Insert an ephemeral validator key
|
||||
BEAST_EXPECT(vl->insertEphemeralKey (v, "Ephemeral"));
|
||||
{
|
||||
auto member = vl->member (v);
|
||||
BEAST_EXPECT(static_cast<bool>(member));
|
||||
BEAST_EXPECT(member->compare("Ephemeral") == 0);
|
||||
BEAST_EXPECT(trustedKeys->load (
|
||||
emptyLocalKey, emptyCfgKeys, cfgKeys1));
|
||||
|
||||
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());
|
||||
|
||||
// do not apply expired list
|
||||
auto const version = 1;
|
||||
auto const sequence = 1;
|
||||
auto const expiredblob = makeList (
|
||||
list1, sequence, env.timeKeeper().now().time_since_epoch().count());
|
||||
auto const expiredSig = signList (expiredblob, pubSigningKeys1);
|
||||
|
||||
BEAST_EXPECT(ListDisposition::stale ==
|
||||
trustedKeys->applyList (
|
||||
manifest1, expiredblob, expiredSig, version));
|
||||
|
||||
// apply single list
|
||||
NetClock::time_point const expiration =
|
||||
env.timeKeeper().now() + 3600s;
|
||||
auto const blob1 = makeList (
|
||||
list1, sequence, expiration.time_since_epoch().count());
|
||||
auto const sig1 = signList (blob1, pubSigningKeys1);
|
||||
|
||||
BEAST_EXPECT(ListDisposition::accepted == trustedKeys->applyList (
|
||||
manifest1, blob1, sig1, version));
|
||||
|
||||
for (auto const& val : list1)
|
||||
BEAST_EXPECT(trustedKeys->listed (val));
|
||||
|
||||
// do not use list from untrusted publisher
|
||||
auto const untrustedManifest = beast::detail::base64_encode(
|
||||
makeManifestString (
|
||||
randomMasterKey(), publisherSecret,
|
||||
pubSigningKeys1.first, pubSigningKeys1.second, 1));
|
||||
|
||||
BEAST_EXPECT(ListDisposition::untrusted == trustedKeys->applyList (
|
||||
untrustedManifest, blob1, sig1, version));
|
||||
|
||||
// do not use list with unhandled version
|
||||
auto const badVersion = 666;
|
||||
BEAST_EXPECT(ListDisposition::unsupported_version ==
|
||||
trustedKeys->applyList (
|
||||
manifest1, blob1, sig1, badVersion));
|
||||
|
||||
// apply list with highest sequence number
|
||||
auto const sequence2 = 2;
|
||||
auto const blob2 = makeList (
|
||||
list2, sequence2, expiration.time_since_epoch().count());
|
||||
auto const sig2 = signList (blob2, pubSigningKeys1);
|
||||
|
||||
BEAST_EXPECT(ListDisposition::accepted ==
|
||||
trustedKeys->applyList (
|
||||
manifest1, blob2, sig2, version));
|
||||
|
||||
for (auto const& val : list1)
|
||||
BEAST_EXPECT(! trustedKeys->listed (val));
|
||||
|
||||
for (auto const& val : list2)
|
||||
BEAST_EXPECT(trustedKeys->listed (val));
|
||||
|
||||
// do not re-apply lists with past or current sequence numbers
|
||||
BEAST_EXPECT(ListDisposition::stale ==
|
||||
trustedKeys->applyList (
|
||||
manifest1, blob1, sig1, version));
|
||||
|
||||
BEAST_EXPECT(ListDisposition::stale ==
|
||||
trustedKeys->applyList (
|
||||
manifest1, blob2, sig2, version));
|
||||
|
||||
// apply list with new publisher key updated by manifest
|
||||
auto const pubSigningKeys2 = randomKeyPair(KeyType::secp256k1);
|
||||
auto manifest2 = beast::detail::base64_encode(makeManifestString (
|
||||
publisherPublic, publisherSecret,
|
||||
pubSigningKeys2.first, pubSigningKeys2.second, 2));
|
||||
|
||||
auto const sequence3 = 3;
|
||||
auto const blob3 = makeList (
|
||||
list1, sequence3, expiration.time_since_epoch().count());
|
||||
auto const sig3 = signList (blob3, pubSigningKeys2);
|
||||
|
||||
BEAST_EXPECT(ListDisposition::accepted ==
|
||||
trustedKeys->applyList (
|
||||
manifest2, blob3, sig3, version));
|
||||
|
||||
auto const sequence4 = 4;
|
||||
auto const blob4 = makeList (
|
||||
list1, sequence4, expiration.time_since_epoch().count());
|
||||
auto const badSig = signList (blob4, pubSigningKeys1);
|
||||
BEAST_EXPECT(ListDisposition::invalid ==
|
||||
trustedKeys->applyList (
|
||||
manifest1, blob4, badSig, version));
|
||||
|
||||
// do not apply list with revoked publisher key
|
||||
// applied list is removed due to revoked publisher key
|
||||
auto const signingKeysMax = randomKeyPair(KeyType::secp256k1);
|
||||
auto maxManifest = beast::detail::base64_encode(makeManifestString (
|
||||
publisherPublic, publisherSecret,
|
||||
pubSigningKeys2.first, pubSigningKeys2.second,
|
||||
std::numeric_limits<std::uint32_t>::max ()));
|
||||
|
||||
auto const sequence5 = 5;
|
||||
auto const blob5 = makeList (
|
||||
list1, sequence5, expiration.time_since_epoch().count());
|
||||
auto const sig5 = signList (blob5, signingKeysMax);
|
||||
|
||||
BEAST_EXPECT(ListDisposition::untrusted ==
|
||||
trustedKeys->applyList (
|
||||
maxManifest, blob5, sig5, version));
|
||||
|
||||
BEAST_EXPECT(! trustedKeys->trustedPublisher(publisherPublic));
|
||||
for (auto const& val : list1)
|
||||
BEAST_EXPECT(! trustedKeys->listed (val));
|
||||
}
|
||||
// Inserting the same ephemeral key fails
|
||||
BEAST_EXPECT(!vl->insertEphemeralKey (v, ""));
|
||||
|
||||
void
|
||||
testUpdate ()
|
||||
{
|
||||
auto member = vl->member (v);
|
||||
BEAST_EXPECT(static_cast<bool>(member));
|
||||
BEAST_EXPECT(member->compare("Ephemeral") == 0);
|
||||
}
|
||||
// Inserting the same key as permanent fails:
|
||||
BEAST_EXPECT(!vl->insertPermanentKey (v, "Permanent"));
|
||||
testcase ("Update");
|
||||
|
||||
PublicKey emptyLocalKey;
|
||||
ManifestCache manifests;
|
||||
jtx::Env env (*this);
|
||||
auto trustedKeys = std::make_unique <ValidatorList> (
|
||||
manifests, manifests, env.timeKeeper(), beast::Journal ());
|
||||
|
||||
std::vector<std::string> cfgPublishers;
|
||||
hash_set<PublicKey> activeValidators;
|
||||
|
||||
{
|
||||
auto member = vl->member (v);
|
||||
BEAST_EXPECT(static_cast<bool>(member));
|
||||
BEAST_EXPECT(member->compare("Ephemeral") == 0);
|
||||
}
|
||||
// Deleting the key as permanent fails:
|
||||
BEAST_EXPECT(!vl->removePermanentKey (v));
|
||||
std::vector<std::string> cfgKeys;
|
||||
cfgKeys.reserve(20);
|
||||
|
||||
while (cfgKeys.size () != 20)
|
||||
{
|
||||
auto member = vl->member (v);
|
||||
BEAST_EXPECT(static_cast<bool>(member));
|
||||
BEAST_EXPECT(member->compare("Ephemeral") == 0);
|
||||
auto const valKey = randomNode();
|
||||
cfgKeys.push_back (toBase58(
|
||||
TokenType::TOKEN_NODE_PUBLIC, valKey));
|
||||
if (cfgKeys.size () <= 15)
|
||||
activeValidators.emplace (valKey);
|
||||
}
|
||||
|
||||
BEAST_EXPECT(trustedKeys->load (
|
||||
emptyLocalKey, cfgKeys, cfgPublishers));
|
||||
|
||||
// onConsensusStart should make all available configured
|
||||
// validators trusted
|
||||
trustedKeys->onConsensusStart (activeValidators);
|
||||
BEAST_EXPECT(trustedKeys->quorum () == 12);
|
||||
std::size_t i = 0;
|
||||
for (auto const& val : cfgKeys)
|
||||
{
|
||||
if (auto const valKey = parseBase58<PublicKey>(
|
||||
TokenType::TOKEN_NODE_PUBLIC, val))
|
||||
{
|
||||
BEAST_EXPECT(trustedKeys->listed (*valKey));
|
||||
if (i++ < activeValidators.size ())
|
||||
BEAST_EXPECT(trustedKeys->trusted (*valKey));
|
||||
else
|
||||
BEAST_EXPECT(!trustedKeys->trusted (*valKey));
|
||||
}
|
||||
else
|
||||
fail ();
|
||||
}
|
||||
}
|
||||
{
|
||||
// update with manifests
|
||||
auto const masterPrivate = randomSecretKey();
|
||||
auto const masterPublic =
|
||||
derivePublicKey(KeyType::ed25519, masterPrivate);
|
||||
|
||||
std::vector<std::string> cfgKeys ({
|
||||
toBase58 (TokenType::TOKEN_NODE_PUBLIC, masterPublic)});
|
||||
|
||||
BEAST_EXPECT(trustedKeys->load (
|
||||
emptyLocalKey, cfgKeys, cfgPublishers));
|
||||
|
||||
auto const signingKeys1 = randomKeyPair(KeyType::secp256k1);
|
||||
auto const signingPublic1 = signingKeys1.first;
|
||||
activeValidators.emplace (masterPublic);
|
||||
|
||||
// Should not trust ephemeral signing key if there is no manifest
|
||||
trustedKeys->onConsensusStart (activeValidators);
|
||||
BEAST_EXPECT(trustedKeys->listed (masterPublic));
|
||||
BEAST_EXPECT(trustedKeys->trusted (masterPublic));
|
||||
BEAST_EXPECT(!trustedKeys->listed (signingPublic1));
|
||||
BEAST_EXPECT(!trustedKeys->trusted (signingPublic1));
|
||||
|
||||
// Should trust the ephemeral signing key from the applied manifest
|
||||
auto m1 = Manifest::make_Manifest (makeManifestString (
|
||||
masterPublic, masterPrivate,
|
||||
signingPublic1, signingKeys1.second, 1));
|
||||
|
||||
BEAST_EXPECT(
|
||||
manifests.applyManifest(std::move (*m1)) ==
|
||||
ManifestDisposition::accepted);
|
||||
trustedKeys->onConsensusStart (activeValidators);
|
||||
BEAST_EXPECT(trustedKeys->quorum () == 13);
|
||||
BEAST_EXPECT(trustedKeys->listed (masterPublic));
|
||||
BEAST_EXPECT(trustedKeys->trusted (masterPublic));
|
||||
BEAST_EXPECT(trustedKeys->listed (signingPublic1));
|
||||
BEAST_EXPECT(trustedKeys->trusted (signingPublic1));
|
||||
|
||||
// Should only trust the ephemeral signing key
|
||||
// from the newest applied manifest
|
||||
auto const signingKeys2 = randomKeyPair(KeyType::secp256k1);
|
||||
auto const signingPublic2 = signingKeys2.first;
|
||||
auto m2 = Manifest::make_Manifest (makeManifestString (
|
||||
masterPublic, masterPrivate,
|
||||
signingPublic2, signingKeys2.second, 2));
|
||||
|
||||
BEAST_EXPECT(
|
||||
manifests.applyManifest(std::move (*m2)) ==
|
||||
ManifestDisposition::accepted);
|
||||
trustedKeys->onConsensusStart (activeValidators);
|
||||
BEAST_EXPECT(trustedKeys->quorum () == 13);
|
||||
BEAST_EXPECT(trustedKeys->listed (masterPublic));
|
||||
BEAST_EXPECT(trustedKeys->trusted (masterPublic));
|
||||
BEAST_EXPECT(trustedKeys->listed (signingPublic2));
|
||||
BEAST_EXPECT(trustedKeys->trusted (signingPublic2));
|
||||
BEAST_EXPECT(!trustedKeys->listed (signingPublic1));
|
||||
BEAST_EXPECT(!trustedKeys->trusted (signingPublic1));
|
||||
|
||||
// Should not trust keys from revoked master public key
|
||||
auto const signingKeysMax = randomKeyPair(KeyType::secp256k1);
|
||||
auto const signingPublicMax = signingKeysMax.first;
|
||||
activeValidators.emplace (signingPublicMax);
|
||||
auto mMax = Manifest::make_Manifest (makeManifestString (
|
||||
masterPublic, masterPrivate,
|
||||
signingPublicMax, signingKeysMax.second,
|
||||
std::numeric_limits<std::uint32_t>::max ()));
|
||||
|
||||
BEAST_EXPECT(mMax->revoked ());
|
||||
BEAST_EXPECT(
|
||||
manifests.applyManifest(std::move (*mMax)) ==
|
||||
ManifestDisposition::accepted);
|
||||
BEAST_EXPECT(manifests.getSigningKey (masterPublic) == masterPublic);
|
||||
BEAST_EXPECT(manifests.revoked (masterPublic));
|
||||
trustedKeys->onConsensusStart (activeValidators);
|
||||
BEAST_EXPECT(trustedKeys->quorum () == 12);
|
||||
BEAST_EXPECT(trustedKeys->listed (masterPublic));
|
||||
BEAST_EXPECT(!trustedKeys->trusted (masterPublic));
|
||||
BEAST_EXPECT(!trustedKeys->listed (signingPublicMax));
|
||||
BEAST_EXPECT(!trustedKeys->trusted (signingPublicMax));
|
||||
BEAST_EXPECT(!trustedKeys->listed (signingPublic2));
|
||||
BEAST_EXPECT(!trustedKeys->trusted (signingPublic2));
|
||||
BEAST_EXPECT(!trustedKeys->listed (signingPublic1));
|
||||
BEAST_EXPECT(!trustedKeys->trusted (signingPublic1));
|
||||
}
|
||||
{
|
||||
// Make quorum unattainable if lists from any publishers are unavailable
|
||||
auto trustedKeys = std::make_unique <ValidatorList> (
|
||||
manifests, manifests, env.timeKeeper(), beast::Journal ());
|
||||
auto const publisherSecret = randomSecretKey();
|
||||
auto const publisherPublic =
|
||||
derivePublicKey(KeyType::ed25519, publisherSecret);
|
||||
|
||||
std::vector<std::string> cfgPublishers({
|
||||
strHex(publisherPublic)});
|
||||
std::vector<std::string> emptyCfgKeys;
|
||||
|
||||
BEAST_EXPECT(trustedKeys->load (
|
||||
emptyLocalKey, emptyCfgKeys, cfgPublishers));
|
||||
|
||||
trustedKeys->onConsensusStart (activeValidators);
|
||||
BEAST_EXPECT(trustedKeys->quorum () ==
|
||||
std::numeric_limits<std::size_t>::max());
|
||||
}
|
||||
{
|
||||
// Trust all listed validators if none are active
|
||||
auto trustedKeys = std::make_unique <ValidatorList> (
|
||||
manifests, manifests, env.timeKeeper(), beast::Journal ());
|
||||
|
||||
std::vector<PublicKey> keys ({ randomNode (), randomNode () });
|
||||
hash_set<PublicKey> activeValidators;
|
||||
std::vector<std::string> cfgKeys ({
|
||||
toBase58 (TokenType::TOKEN_NODE_PUBLIC, keys[0]),
|
||||
toBase58 (TokenType::TOKEN_NODE_PUBLIC, keys[1])});
|
||||
|
||||
BEAST_EXPECT(trustedKeys->load (
|
||||
emptyLocalKey, cfgKeys, cfgPublishers));
|
||||
|
||||
trustedKeys->onConsensusStart (activeValidators);
|
||||
|
||||
BEAST_EXPECT(trustedKeys->quorum () == 2);
|
||||
for (auto const& key : keys)
|
||||
BEAST_EXPECT(trustedKeys->trusted (key));
|
||||
}
|
||||
{
|
||||
// Should use custom minimum quorum
|
||||
std::size_t const minQuorum = 0;
|
||||
ManifestCache manifests;
|
||||
auto trustedKeys = std::make_unique <ValidatorList> (
|
||||
manifests, manifests, env.timeKeeper(), beast::Journal (), minQuorum);
|
||||
|
||||
auto const node = randomNode ();
|
||||
std::vector<std::string> cfgKeys ({
|
||||
toBase58 (TokenType::TOKEN_NODE_PUBLIC, node)});
|
||||
hash_set<PublicKey> activeValidators;
|
||||
|
||||
BEAST_EXPECT(trustedKeys->load (
|
||||
emptyLocalKey, cfgKeys, cfgPublishers));
|
||||
|
||||
trustedKeys->onConsensusStart (activeValidators);
|
||||
BEAST_EXPECT(trustedKeys->quorum () == minQuorum);
|
||||
|
||||
activeValidators.emplace (node);
|
||||
trustedKeys->onConsensusStart (activeValidators);
|
||||
BEAST_EXPECT(trustedKeys->quorum () == 1);
|
||||
}
|
||||
{
|
||||
// Increase quorum when running as an unlisted validator
|
||||
auto trustedKeys = std::make_unique <ValidatorList> (
|
||||
manifests, manifests, env.timeKeeper(), beast::Journal ());
|
||||
|
||||
std::vector<PublicKey> keys ({ randomNode (), randomNode () });
|
||||
hash_set<PublicKey> activeValidators ({ keys[0] });
|
||||
std::vector<std::string> cfgKeys ({
|
||||
toBase58 (TokenType::TOKEN_NODE_PUBLIC, keys[0]),
|
||||
toBase58 (TokenType::TOKEN_NODE_PUBLIC, keys[1])});
|
||||
|
||||
auto const localKey = randomNode ();
|
||||
BEAST_EXPECT(trustedKeys->load (
|
||||
localKey, cfgKeys, cfgPublishers));
|
||||
|
||||
trustedKeys->onConsensusStart (activeValidators);
|
||||
BEAST_EXPECT(trustedKeys->quorum () == 3);
|
||||
|
||||
// local validator key is always trusted
|
||||
BEAST_EXPECT(trustedKeys->trusted (localKey));
|
||||
}
|
||||
{
|
||||
// Remove expired published list
|
||||
auto trustedKeys = std::make_unique<ValidatorList> (
|
||||
manifests, manifests, env.app().timeKeeper(), beast::Journal ());
|
||||
|
||||
PublicKey emptyLocalKey;
|
||||
std::vector<std::string> emptyCfgKeys;
|
||||
auto const publisherKeys = randomKeyPair(KeyType::secp256k1);
|
||||
auto const pubSigningKeys = randomKeyPair(KeyType::secp256k1);
|
||||
auto const manifest = beast::detail::base64_encode (
|
||||
makeManifestString (
|
||||
publisherKeys.first, publisherKeys.second,
|
||||
pubSigningKeys.first, pubSigningKeys.second, 1));
|
||||
|
||||
std::vector<std::string> cfgKeys ({
|
||||
strHex(publisherKeys.first)});
|
||||
|
||||
BEAST_EXPECT(trustedKeys->load (
|
||||
emptyLocalKey, emptyCfgKeys, cfgKeys));
|
||||
|
||||
std::vector<PublicKey> list ({randomNode()});
|
||||
hash_set<PublicKey> activeValidators ({ list[0] });
|
||||
|
||||
// do not apply expired list
|
||||
auto const version = 1;
|
||||
auto const sequence = 1;
|
||||
NetClock::time_point const expiration =
|
||||
env.timeKeeper().now() + 60s;
|
||||
auto const blob = makeList (
|
||||
list, sequence, expiration.time_since_epoch().count());
|
||||
auto const sig = signList (blob, pubSigningKeys);
|
||||
|
||||
BEAST_EXPECT(ListDisposition::accepted ==
|
||||
trustedKeys->applyList (
|
||||
manifest, blob, sig, version));
|
||||
|
||||
trustedKeys->onConsensusStart (activeValidators);
|
||||
BEAST_EXPECT(trustedKeys->trusted (list[0]));
|
||||
|
||||
env.timeKeeper().set(expiration);
|
||||
trustedKeys->onConsensusStart (activeValidators);
|
||||
BEAST_EXPECT(! trustedKeys->trusted (list[0]));
|
||||
}
|
||||
// Deleting the key as ephemeral succeeds:
|
||||
BEAST_EXPECT(vl->removeEphemeralKey (v));
|
||||
BEAST_EXPECT(!vl->trusted(v));
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testConfigLoad();
|
||||
testMembership ();
|
||||
testModification ();
|
||||
testGenesisQuorum ();
|
||||
testConfigLoad ();
|
||||
testApplyList ();
|
||||
testUpdate ();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(ValidatorList, app, ripple);
|
||||
|
||||
} // tests
|
||||
} // test
|
||||
} // ripple
|
||||
|
||||
@@ -285,9 +285,9 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
std::string valFileContents (boost::optional<int> const& quorum)
|
||||
std::string valFileContents ()
|
||||
{
|
||||
static boost::format configContentsTemplate (R"rippleConfig(
|
||||
std::string configContents (R"rippleConfig(
|
||||
[validators]
|
||||
n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7
|
||||
n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj
|
||||
@@ -300,14 +300,11 @@ nHUhG1PgAG8H8myUENypM35JgfqXAKNQvRVVAFDRzJrny5eZN8d5
|
||||
nHBu9PTL9dn2GuZtdW4U2WzBwffyX9qsQCd9CNU4Z5YG3PQfViM8
|
||||
nHUPDdcdb2Y5DZAJne4c2iabFuAP3F34xZUgYQT2NH7qfkdapgnz
|
||||
|
||||
%1%
|
||||
|
||||
[validator_list_keys]
|
||||
03E74EE14CB525AFBB9F1B7D86CD58ECC4B91452294B42AB4E78F260BD905C091D
|
||||
030775A669685BD6ABCEBD80385921C7851783D991A8055FD21D2F3966C96F1B56
|
||||
)rippleConfig");
|
||||
|
||||
std::string quorumSection =
|
||||
quorum ? "[validation_quorum]\n" + to_string(*quorum) : "";
|
||||
return boost::str (
|
||||
configContentsTemplate % quorumSection);
|
||||
return configContents;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -321,7 +318,7 @@ private:
|
||||
public:
|
||||
ValidatorsTxtGuard (beast::unit_test::suite& test,
|
||||
path subDir, path const& validatorsFileName,
|
||||
boost::optional<int> const& quorum, bool useCounter = true)
|
||||
bool useCounter = true)
|
||||
: ConfigGuard (test, std::move (subDir), useCounter)
|
||||
{
|
||||
using namespace boost::filesystem;
|
||||
@@ -332,7 +329,7 @@ public:
|
||||
if (!exists (validatorsFile_))
|
||||
{
|
||||
std::ofstream o (validatorsFile_.string ());
|
||||
o << valFileContents (quorum);
|
||||
o << valFileContents ();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -392,9 +389,6 @@ port_wss_admin
|
||||
|
||||
[ssl_verify]
|
||||
0
|
||||
|
||||
[validation_quorum]
|
||||
3
|
||||
)rippleConfig");
|
||||
|
||||
c.loadFromString (toLoad);
|
||||
@@ -499,9 +493,8 @@ port_wss_admin
|
||||
}
|
||||
{
|
||||
// load should throw for invalid [validators_file]
|
||||
int const quorum = 3;
|
||||
detail::ValidatorsTxtGuard const vtg (
|
||||
*this, "test_cfg", "validators.cfg", quorum);
|
||||
*this, "test_cfg", "validators.cfg");
|
||||
path const invalidFile = current_path () / vtg.subdir ();
|
||||
boost::format cc ("[validators_file]\n%1%\n");
|
||||
std::string error;
|
||||
@@ -517,7 +510,7 @@ port_wss_admin
|
||||
BEAST_EXPECT(error == expectedError);
|
||||
}
|
||||
{
|
||||
// load validators and quorum from config
|
||||
// load validators from config into single section
|
||||
Config c;
|
||||
std::string toLoad(R"rippleConfig(
|
||||
[validators]
|
||||
@@ -528,53 +521,59 @@ n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C
|
||||
[validator_keys]
|
||||
nHUhG1PgAG8H8myUENypM35JgfqXAKNQvRVVAFDRzJrny5eZN8d5
|
||||
nHBu9PTL9dn2GuZtdW4U2WzBwffyX9qsQCd9CNU4Z5YG3PQfViM8
|
||||
|
||||
[validation_quorum]
|
||||
4
|
||||
)rippleConfig");
|
||||
c.loadFromString (toLoad);
|
||||
BEAST_EXPECT(c.legacy ("validators_file").empty ());
|
||||
BEAST_EXPECT(c.section (SECTION_VALIDATORS).values ().size () == 3);
|
||||
BEAST_EXPECT(c.section (SECTION_VALIDATOR_KEYS).values ().size () == 2);
|
||||
BEAST_EXPECT(c.VALIDATION_QUORUM == 4);
|
||||
BEAST_EXPECT(c.section (SECTION_VALIDATORS).values ().size () == 5);
|
||||
}
|
||||
{
|
||||
// load validator list keys from config
|
||||
Config c;
|
||||
std::string toLoad(R"rippleConfig(
|
||||
[validator_list_keys]
|
||||
021A99A537FDEBC34E4FCA03B39BEADD04299BB19E85097EC92B15A3518801E566
|
||||
)rippleConfig");
|
||||
c.loadFromString (toLoad);
|
||||
BEAST_EXPECT(
|
||||
c.section (SECTION_VALIDATOR_LIST_KEYS).values ().size () == 1);
|
||||
BEAST_EXPECT(
|
||||
c.section (SECTION_VALIDATOR_LIST_KEYS).values ()[0] ==
|
||||
"021A99A537FDEBC34E4FCA03B39BEADD04299BB19E85097EC92B15A3518801E566");
|
||||
}
|
||||
{
|
||||
// load from specified [validators_file] absolute path
|
||||
int const quorum = 3;
|
||||
detail::ValidatorsTxtGuard const vtg (
|
||||
*this, "test_cfg", "validators.cfg", quorum);
|
||||
*this, "test_cfg", "validators.cfg");
|
||||
BEAST_EXPECT(vtg.validatorsFileExists ());
|
||||
Config c;
|
||||
boost::format cc ("[validators_file]\n%1%\n");
|
||||
c.loadFromString (boost::str (cc % vtg.validatorsFile ()));
|
||||
BEAST_EXPECT(c.legacy ("validators_file") == vtg.validatorsFile ());
|
||||
BEAST_EXPECT(c.section (SECTION_VALIDATORS).values ().size () == 5);
|
||||
BEAST_EXPECT(c.section (SECTION_VALIDATOR_KEYS).values ().size () == 3);
|
||||
BEAST_EXPECT(c.VALIDATION_QUORUM == quorum);
|
||||
BEAST_EXPECT(c.section (SECTION_VALIDATORS).values ().size () == 8);
|
||||
BEAST_EXPECT(
|
||||
c.section (SECTION_VALIDATOR_LIST_KEYS).values ().size () == 2);
|
||||
}
|
||||
{
|
||||
// load from specified [validators_file] file name
|
||||
// in config directory
|
||||
int const quorum = 3;
|
||||
std::string const valFileName = "validators.txt";
|
||||
detail::ValidatorsTxtGuard const vtg (
|
||||
*this, "test_cfg", valFileName, quorum);
|
||||
*this, "test_cfg", valFileName);
|
||||
detail::RippledCfgGuard const rcg (
|
||||
*this, vtg.subdir (), "", valFileName, false);
|
||||
BEAST_EXPECT(vtg.validatorsFileExists ());
|
||||
BEAST_EXPECT(rcg.configFileExists ());
|
||||
auto const& c (rcg.config ());
|
||||
BEAST_EXPECT(c.legacy ("validators_file") == valFileName);
|
||||
BEAST_EXPECT(c.section (SECTION_VALIDATORS).values ().size () == 5);
|
||||
BEAST_EXPECT(c.section (SECTION_VALIDATOR_KEYS).values ().size () == 3);
|
||||
BEAST_EXPECT(c.VALIDATION_QUORUM == quorum);
|
||||
BEAST_EXPECT(c.section (SECTION_VALIDATORS).values ().size () == 8);
|
||||
BEAST_EXPECT(
|
||||
c.section (SECTION_VALIDATOR_LIST_KEYS).values ().size () == 2);
|
||||
}
|
||||
{
|
||||
// load from specified [validators_file] relative path
|
||||
// to config directory
|
||||
int const quorum = 3;
|
||||
detail::ValidatorsTxtGuard const vtg (
|
||||
*this, "test_cfg", "validators.txt", quorum);
|
||||
*this, "test_cfg", "validators.txt");
|
||||
auto const valFilePath = ".." / vtg.subdir() / "validators.txt";
|
||||
detail::RippledCfgGuard const rcg (
|
||||
*this, vtg.subdir (), "", valFilePath, false);
|
||||
@@ -582,63 +581,41 @@ nHBu9PTL9dn2GuZtdW4U2WzBwffyX9qsQCd9CNU4Z5YG3PQfViM8
|
||||
BEAST_EXPECT(rcg.configFileExists ());
|
||||
auto const& c (rcg.config ());
|
||||
BEAST_EXPECT(c.legacy ("validators_file") == valFilePath);
|
||||
BEAST_EXPECT(c.section (SECTION_VALIDATORS).values ().size () == 5);
|
||||
BEAST_EXPECT(c.section (SECTION_VALIDATOR_KEYS).values ().size () == 3);
|
||||
BEAST_EXPECT(c.VALIDATION_QUORUM == quorum);
|
||||
BEAST_EXPECT(c.section (SECTION_VALIDATORS).values ().size () == 8);
|
||||
BEAST_EXPECT(
|
||||
c.section (SECTION_VALIDATOR_LIST_KEYS).values ().size () == 2);
|
||||
}
|
||||
{
|
||||
// load from validators file in default location
|
||||
int const quorum = 3;
|
||||
detail::ValidatorsTxtGuard const vtg (
|
||||
*this, "test_cfg", "validators.txt", quorum);
|
||||
*this, "test_cfg", "validators.txt");
|
||||
detail::RippledCfgGuard const rcg (*this, vtg.subdir (),
|
||||
"", "", false);
|
||||
BEAST_EXPECT(vtg.validatorsFileExists ());
|
||||
BEAST_EXPECT(rcg.configFileExists ());
|
||||
auto const& c (rcg.config ());
|
||||
BEAST_EXPECT(c.legacy ("validators_file").empty ());
|
||||
BEAST_EXPECT(c.section (SECTION_VALIDATORS).values ().size () == 5);
|
||||
BEAST_EXPECT(c.section (SECTION_VALIDATOR_KEYS).values ().size () == 3);
|
||||
BEAST_EXPECT(c.VALIDATION_QUORUM == quorum);
|
||||
BEAST_EXPECT(c.section (SECTION_VALIDATORS).values ().size () == 8);
|
||||
BEAST_EXPECT(
|
||||
c.section (SECTION_VALIDATOR_LIST_KEYS).values ().size () == 2);
|
||||
}
|
||||
{
|
||||
// load from specified [validators_file] instead
|
||||
// of default location
|
||||
int const quorum = 3;
|
||||
detail::ValidatorsTxtGuard const vtg (
|
||||
*this, "test_cfg", "validators.cfg", quorum);
|
||||
*this, "test_cfg", "validators.cfg");
|
||||
BEAST_EXPECT(vtg.validatorsFileExists ());
|
||||
detail::ValidatorsTxtGuard const vtgDefault (
|
||||
*this, vtg.subdir (), "validators.txt", 4, false);
|
||||
*this, vtg.subdir (), "validators.txt", false);
|
||||
BEAST_EXPECT(vtgDefault.validatorsFileExists ());
|
||||
detail::RippledCfgGuard const rcg (
|
||||
*this, vtg.subdir (), "", vtg.validatorsFile (), false);
|
||||
BEAST_EXPECT(rcg.configFileExists ());
|
||||
auto const& c (rcg.config ());
|
||||
BEAST_EXPECT(c.legacy ("validators_file") == vtg.validatorsFile ());
|
||||
BEAST_EXPECT(c.section (SECTION_VALIDATORS).values ().size () == 5);
|
||||
BEAST_EXPECT(c.section (SECTION_VALIDATOR_KEYS).values ().size () == 3);
|
||||
BEAST_EXPECT(c.VALIDATION_QUORUM == quorum);
|
||||
}
|
||||
{
|
||||
// do not load quorum from validators file if in config
|
||||
boost::format cc (R"rippleConfig(
|
||||
[validators_file]
|
||||
%1%
|
||||
|
||||
[validation_quorum]
|
||||
4
|
||||
)rippleConfig");
|
||||
int const quorum = 3;
|
||||
detail::ValidatorsTxtGuard const vtg (
|
||||
*this, "test_cfg", "validators.cfg", quorum);
|
||||
BEAST_EXPECT(vtg.validatorsFileExists ());
|
||||
Config c;
|
||||
c.loadFromString (boost::str (cc % vtg.validatorsFile ()));
|
||||
BEAST_EXPECT(c.legacy ("validators_file") == vtg.validatorsFile ());
|
||||
BEAST_EXPECT(c.section (SECTION_VALIDATORS).values ().size () == 5);
|
||||
BEAST_EXPECT(c.section (SECTION_VALIDATOR_KEYS).values ().size () == 3);
|
||||
BEAST_EXPECT(c.VALIDATION_QUORUM == 4);
|
||||
BEAST_EXPECT(c.section (SECTION_VALIDATORS).values ().size () == 8);
|
||||
BEAST_EXPECT(
|
||||
c.section (SECTION_VALIDATOR_LIST_KEYS).values ().size () == 2);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -658,51 +635,34 @@ n9LdgEtkmGB9E2h3K4Vp7iGUaKuq23Zr32ehxiU8FWY7xoxbWTSA
|
||||
nHB1X37qrniVugfQcuBTAjswphC1drx7QjFFojJPZwKHHnt8kU7v
|
||||
nHUkAWDR4cB8AgPg7VXMX6et8xRTQb2KJfgv1aBEXozwrawRKgMB
|
||||
|
||||
[validator_list_keys]
|
||||
021A99A537FDEBC34E4FCA03B39BEADD04299BB19E85097EC92B15A3518801E566
|
||||
)rippleConfig");
|
||||
int const quorum = 3;
|
||||
detail::ValidatorsTxtGuard const vtg (
|
||||
*this, "test_cfg", "validators.cfg", quorum);
|
||||
*this, "test_cfg", "validators.cfg");
|
||||
BEAST_EXPECT(vtg.validatorsFileExists ());
|
||||
Config c;
|
||||
c.loadFromString (boost::str (cc % vtg.validatorsFile ()));
|
||||
BEAST_EXPECT(c.legacy ("validators_file") == vtg.validatorsFile ());
|
||||
BEAST_EXPECT(c.section (SECTION_VALIDATORS).values ().size () == 10);
|
||||
BEAST_EXPECT(c.section (SECTION_VALIDATOR_KEYS).values ().size () == 5);
|
||||
BEAST_EXPECT(c.VALIDATION_QUORUM == quorum);
|
||||
BEAST_EXPECT(c.section (SECTION_VALIDATORS).values ().size () == 15);
|
||||
BEAST_EXPECT(
|
||||
c.section (SECTION_VALIDATOR_LIST_KEYS).values ().size () == 3);
|
||||
}
|
||||
{
|
||||
// load should throw if [validators] and [validator_keys] are
|
||||
// missing from rippled cfg and validators file
|
||||
// load should throw if [validators], [validator_keys] and
|
||||
// [validator_list_keys] are missing from rippled cfg and
|
||||
// validators file
|
||||
Config c;
|
||||
boost::format cc ("[validators_file]\n%1%\n");
|
||||
std::string error;
|
||||
detail::ValidatorsTxtGuard const vtg (
|
||||
*this, "test_cfg", "validators.cfg", boost::none);
|
||||
*this, "test_cfg", "validators.cfg");
|
||||
BEAST_EXPECT(vtg.validatorsFileExists ());
|
||||
auto const expectedError =
|
||||
"The file specified in [validators_file] does not contain a "
|
||||
"[validators] or [validator_keys] section: " +
|
||||
"[validators], [validator_keys] or [validator_list_keys] section: " +
|
||||
vtg.validatorsFile ();
|
||||
std::ofstream o (vtg.validatorsFile ());
|
||||
o << "[validation_quorum]\n3\n";
|
||||
try {
|
||||
Config c;
|
||||
c.loadFromString (boost::str (cc % vtg.validatorsFile ()));
|
||||
} catch (std::runtime_error& e) {
|
||||
error = e.what();
|
||||
}
|
||||
BEAST_EXPECT(error == expectedError);
|
||||
}
|
||||
{
|
||||
// load should throw if [validation_quorum] is
|
||||
// missing from rippled cfg and validators file
|
||||
boost::format cc ("[validators_file]\n%1%\n");
|
||||
std::string error;
|
||||
detail::ValidatorsTxtGuard const vtg (
|
||||
*this, "test_cfg", "validators.cfg", boost::none);
|
||||
BEAST_EXPECT(vtg.validatorsFileExists ());
|
||||
auto const expectedError =
|
||||
"The file specified in [validators_file] does not contain a "
|
||||
"[validation_quorum] section: " + vtg.validatorsFile ();
|
||||
try {
|
||||
Config c;
|
||||
c.loadFromString (boost::str (cc % vtg.validatorsFile ()));
|
||||
|
||||
@@ -57,10 +57,13 @@ public:
|
||||
|
||||
[validation_seed]
|
||||
%2%
|
||||
|
||||
[validators]
|
||||
%3%
|
||||
)rippleConfig");
|
||||
|
||||
p->loadFromString (boost::str (
|
||||
toLoad % validator::manifest % validator::seed));
|
||||
toLoad % validator::manifest % validator::seed % validator::master_key));
|
||||
|
||||
setupConfigForUnitTests(*p);
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <test/app/HashRouter_test.cpp>
|
||||
#include <test/app/LedgerLoad_test.cpp>
|
||||
#include <test/app/LoadFeeTrack_test.cpp>
|
||||
#include <test/app/Manifest_test.cpp>
|
||||
#include <test/app/MultiSign_test.cpp>
|
||||
#include <test/app/OfferStream_test.cpp>
|
||||
#include <test/app/Offer_test.cpp>
|
||||
|
||||
@@ -19,6 +19,5 @@
|
||||
//==============================================================================
|
||||
|
||||
#include <test/overlay/cluster_test.cpp>
|
||||
#include <test/overlay/manifest_test.cpp>
|
||||
#include <test/overlay/short_read_test.cpp>
|
||||
#include <test/overlay/TMHello_test.cpp>
|
||||
Reference in New Issue
Block a user