diff --git a/Builds/VisualStudio2015/RippleD.vcxproj b/Builds/VisualStudio2015/RippleD.vcxproj
index 75f46490a..6c57577cf 100644
--- a/Builds/VisualStudio2015/RippleD.vcxproj
+++ b/Builds/VisualStudio2015/RippleD.vcxproj
@@ -1061,6 +1061,10 @@
True
True
+
+ True
+ True
+
True
True
@@ -1075,6 +1079,8 @@
+
+
True
True
@@ -2297,12 +2303,6 @@
-
- True
- True
-
-
-
True
True
@@ -4199,6 +4199,10 @@
True
True
+
+ True
+ True
+
True
True
@@ -4715,10 +4719,6 @@
True
True
-
- True
- True
-
True
True
diff --git a/Builds/VisualStudio2015/RippleD.vcxproj.filters b/Builds/VisualStudio2015/RippleD.vcxproj.filters
index 063b12fe2..2c99d99fb 100644
--- a/Builds/VisualStudio2015/RippleD.vcxproj.filters
+++ b/Builds/VisualStudio2015/RippleD.vcxproj.filters
@@ -1569,6 +1569,9 @@
ripple\app\misc\impl
+
+ ripple\app\misc\impl
+
ripple\app\misc\impl
@@ -1581,6 +1584,9 @@
ripple\app\misc
+
+ ripple\app\misc
+
ripple\app\misc
@@ -2880,12 +2886,6 @@
ripple\overlay\impl
-
- ripple\overlay\impl
-
-
- ripple\overlay\impl
-
ripple\overlay\impl
@@ -4983,6 +4983,9 @@
test\app
+
+ test\app
+
test\app
@@ -5427,9 +5430,6 @@
test\overlay
-
- test\overlay
-
test\overlay
diff --git a/bin/python/ripple/util/ValidatorManifestTest.py b/bin/python/ripple/util/ValidatorManifestTest.py
index 3907fb80f..1be45f755 100755
--- a/bin/python/ripple/util/ValidatorManifestTest.py
+++ b/bin/python/ripple/util/ValidatorManifestTest.py
@@ -104,9 +104,6 @@ n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C RL3
n9KiYM9CgngLvtRCQHZwgC2gjpdaZcCcbt3VboxiNFcKuwFVujzS RL4
n9LdgEtkmGB9E2h3K4Vp7iGUaKuq23Zr32ehxiU8FWY7xoxbWTSA RL5
-[validation_quorum]
-3
-
[validation_seed]
{validation_seed}
#vaidation_public_key: {validation_public_key}
diff --git a/bin/python/ripple/util/test_ConfigFile.py b/bin/python/ripple/util/test_ConfigFile.py
index 8b6d04050..8aa1b22b4 100644
--- a/bin/python/ripple/util/test_ConfigFile.py
+++ b/bin/python/ripple/util/test_ConfigFile.py
@@ -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
diff --git a/doc/rippled-example.cfg b/doc/rippled-example.cfg
index 54c311e84..552daf1c4 100644
--- a/doc/rippled-example.cfg
+++ b/doc/rippled-example.cfg
@@ -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]
diff --git a/doc/validators-example.txt b/doc/validators-example.txt
index 6d4941343..885b3a536 100644
--- a/doc/validators-example.txt
+++ b/doc/validators-example.txt
@@ -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
diff --git a/src/ripple/app/ledger/LedgerMaster.h b/src/ripple/app/ledger/LedgerMaster.h
index 3faf3137b..d3217073c 100644
--- a/src/ripple/app/ledger/LedgerMaster.h
+++ b/src/ripple/app/ledger/LedgerMaster.h
@@ -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);
@@ -257,7 +253,7 @@ private:
void getFetchPack(LedgerHash missingHash, LedgerIndex missingIndex);
boost::optional 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 mLedgerCleaner;
- int mMinValidations; // The minimum validations to publish a ledger.
- bool mStrictValCount; // Don't raise the minimum
uint256 mLastValidateHash;
std::uint32_t mLastValidateSeq;
diff --git a/src/ripple/app/ledger/impl/LedgerConsensusImp.cpp b/src/ripple/app/ledger/impl/LedgerConsensusImp.cpp
index ced613b7c..d4904dcdc 100644
--- a/src/ripple/app/ledger/impl/LedgerConsensusImp.cpp
+++ b/src/ripple/app/ledger/impl/LedgerConsensusImp.cpp
@@ -35,6 +35,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -1267,14 +1268,14 @@ LedgerConsensusImp::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_,
diff --git a/src/ripple/app/ledger/impl/LedgerMaster.cpp b/src/ripple/app/ledger/impl/LedgerMaster.cpp
index 4873010cd..2b16d24eb 100644
--- a/src/ripple/app/ledger/impl/LedgerMaster.cpp
+++ b/src/ripple/app/ledger/impl/LedgerMaster.cpp
@@ -33,6 +33,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -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 ()
{
diff --git a/src/ripple/app/main/Application.cpp b/src/ripple/app/main/Application.cpp
index 6976233a1..1ebd5d363 100644
--- a/src/ripple/app/main/Application.cpp
+++ b/src/ripple/app/main/Application.cpp
@@ -39,6 +39,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -347,6 +348,8 @@ public:
TaggedCache m_acceptedLedgerCache;
std::unique_ptr m_networkOPs;
std::unique_ptr cluster_;
+ std::unique_ptr validatorManifests_;
+ std::unique_ptr publisherManifests_;
std::unique_ptr validators_;
std::unique_ptr serverHandler_;
std::unique_ptr m_amendmentTable;
@@ -473,8 +476,15 @@ public:
, cluster_ (std::make_unique (
logs_->journal("Overlay")))
+ , validatorManifests_ (std::make_unique (
+ logs_->journal("ManifestCache")))
+
+ , publisherManifests_ (std::make_unique (
+ logs_->journal("ManifestCache")))
+
, validators_ (std::make_unique (
- 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_,
diff --git a/src/ripple/app/main/Application.h b/src/ripple/app/main/Application.h
index 551457cf1..8f6d4ee5a 100644
--- a/src/ripple/app/main/Application.h
+++ b/src/ripple/app/main/Application.h
@@ -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;
diff --git a/src/ripple/app/main/DBInit.cpp b/src/ripple/app/main/DBInit.cpp
index faedc6a9d..eb8170c20 100644
--- a/src/ripple/app/main/DBInit.cpp
+++ b/src/ripple/app/main/DBInit.cpp
@@ -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;",
diff --git a/src/ripple/app/main/Main.cpp b/src/ripple/app/main/Main.cpp
index 6ad534b40..46fa8aaa4 100644
--- a/src/ripple/app/main/Main.cpp
+++ b/src/ripple/app/main/Main.cpp
@@ -218,7 +218,7 @@ int run (int argc, char** argv)
("unittest-arg", po::value ()->implicit_value (""), "Supplies argument to unit tests.")
("parameters", po::value< vector > (), "Specify comma separated parameters.")
("quiet,q", "Reduce diagnotics.")
- ("quorum", po::value (), "Set the validation quorum.")
+ ("quorum", po::value (), "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 ();
- config->LOCK_QUORUM = true;
-
- if (config->VALIDATION_QUORUM < 0)
- Throw ("");
+ config->VALIDATION_QUORUM = vm["quorum"].as ();
}
catch(std::exception const&)
{
diff --git a/src/ripple/overlay/impl/Manifest.h b/src/ripple/app/misc/Manifest.h
similarity index 50%
rename from src/ripple/overlay/impl/Manifest.h
rename to src/ripple/app/misc/Manifest.h
index 3d9bce89e..8e2e122be 100644
--- a/src/ripple/overlay/impl/Manifest.h
+++ b/src/ripple/app/misc/Manifest.h
@@ -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
-#include
#include
#include
-#include
#include
#include
#include
@@ -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 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 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 map_;
- std::string comment;
- boost::optional m;
- };
-
- using MapType = hash_map;
-
- 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 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 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 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
void
for_each_manifest(Function&& f) const
{
- std::lock_guard lock (mutex_);
- for (auto const& e : map_)
+ std::lock_guard 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
void
for_each_manifest(PreFun&& pf, EachFun&& f) const
{
- std::lock_guard lock (mutex_);
+ std::lock_guard 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);
}
}
};
diff --git a/src/ripple/app/misc/NetworkOPs.cpp b/src/ripple/app/misc/NetworkOPs.cpp
index f597a7815..ca6c9bc46 100644
--- a/src/ripple/app/misc/NetworkOPs.cpp
+++ b/src/ripple/app/misc/NetworkOPs.cpp
@@ -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(
+ app_.validators ().quorum ());
info[jss::io_latency_ms] = static_cast (
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 (
diff --git a/src/ripple/app/misc/Validations.cpp b/src/ripple/app/misc/Validations.cpp
index 2b477baac..5bb708be6 100644
--- a/src/ripple/app/misc/Validations.cpp
+++ b/src/ripple/app/misc/Validations.cpp
@@ -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,45 +101,71 @@ 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 ())
- {
- // This is a newer validation
- val->setPreviousHash (it->second->getLedgerHash ());
- mStaleValidations.push_back (it->second);
- it->second = val;
- condWrite ();
- }
else
{
- // We already have a newer validation from this source
- isCurrent = false;
+ 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;
+ condWrite ();
+ }
+ else
+ {
+ // We already have a newer validation from this source
+ isCurrent = false;
+ }
}
}
@@ -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 getCurrentPublicKeys () override
+ {
+ hash_set 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))
{
diff --git a/src/ripple/app/misc/Validations.h b/src/ripple/app/misc/Validations.h
index 47af858b5..af6285d31 100644
--- a/src/ripple/app/misc/Validations.h
+++ b/src/ripple/app/misc/Validations.h
@@ -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;
+using ValidationSet = hash_map;
using ValidationCounter = std::pair;
using LedgerToValidationCounter = hash_map;
@@ -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 getCurrentPublicKeys () = 0;
+
// VFALCO TODO make a type alias for this ugly return value!
virtual LedgerToValidationCounter getCurrentValidations (
uint256 currentLedger, uint256 previousLedger,
diff --git a/src/ripple/app/misc/ValidatorList.h b/src/ripple/app/misc/ValidatorList.h
index e8eee389c..2ca4212e5 100644
--- a/src/ripple/app/misc/ValidatorList.h
+++ b/src/ripple/app/misc/ValidatorList.h
@@ -20,102 +20,437 @@
#ifndef RIPPLE_APP_MISC_VALIDATORLIST_H_INCLUDED
#define RIPPLE_APP_MISC_VALIDATORLIST_H_INCLUDED
-#include
+#include
#include
#include
+#include
+#include
#include
-#include
-#include
-#include
+#include
+#include
+#include
+#include
#include
+#include
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 permanent_;
+ struct PublisherList
+ {
+ bool available;
+ std::vector list;
+ std::size_t sequence;
+ std::size_t expiration;
+ };
- /** The ephemeral public keys from manifests. */
- hash_map 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 quorum_;
+ boost::optional minimumQuorum_;
+
+ // Published lists stored by publisher master public key
+ hash_map publisherLists_;
+
+ // Listed master public keys with the number of lists they appear on
+ hash_map keyListings_;
+
+ // The current list of trusted master keys
+ hash_set trustedKeys_;
+
+ PublicKey localPubKey_;
public:
- explicit
- ValidatorList (beast::Journal j);
+ ValidatorList (
+ ManifestCache& validatorManifests,
+ ManifestCache& publisherManifests,
+ TimeKeeper& timeKeeper,
+ beast::Journal j,
+ boost::optional 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
- member (
- PublicKey const& identity) const;
+ bool
+ load (
+ PublicKey const& localSigningKey,
+ std::vector const& configKeys,
+ std::vector 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
+ 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
+ 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
+ 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 func) const;
+ for_each_listed (
+ std::function 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
+void
+ValidatorList::onConsensusStart (
+ KeySet const& seenValidators)
+{
+ boost::unique_lock 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 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 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::numeric_limits::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(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::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
diff --git a/src/ripple/app/misc/impl/Manifest.cpp b/src/ripple/app/misc/impl/Manifest.cpp
new file mode 100644
index 000000000..61ce669e4
--- /dev/null
+++ b/src/ripple/app/misc/impl/Manifest.cpp
@@ -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
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+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(st, sfPublicKey);
+ auto const opt_spk = get(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
+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
+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::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 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 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 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 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 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 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 isTrusted)
+{
+ std::lock_guard 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 ();
+}
+}
diff --git a/src/ripple/app/misc/impl/ValidatorList.cpp b/src/ripple/app/misc/impl/ValidatorList.cpp
index 077c7aeaa..4b6fe7209 100644
--- a/src/ripple/app/misc/impl/ValidatorList.cpp
+++ b/src/ripple/app/misc/impl/ValidatorList.cpp
@@ -17,117 +17,39 @@
*/
//==============================================================================
-#include
#include
#include
#include
+#include
+#include
#include
namespace ripple {
-ValidatorList::ValidatorList (beast::Journal j)
- : j_ (j)
+ValidatorList::ValidatorList (
+ ManifestCache& validatorManifests,
+ ManifestCache& publisherManifests,
+ TimeKeeper& timeKeeper,
+ beast::Journal j,
+ boost::optional minimumQuorum)
+ : validatorManifests_ (validatorManifests)
+ , publisherManifests_ (publisherManifests)
+ , timeKeeper_ (timeKeeper)
+ , j_ (j)
+ , quorum_ (minimumQuorum ? *minimumQuorum : 1) // Genesis ledger quorum
+ , minimumQuorum_ (minimumQuorum)
{
}
-boost::optional
-ValidatorList::member (PublicKey const& identity) const
+ValidatorList::~ValidatorList()
{
- std::lock_guard 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 (member(identity));
-}
-
-bool
-ValidatorList::insertEphemeralKey (
- PublicKey const& identity,
- std::string const& comment)
-{
- std::lock_guard 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 sl (mutex_);
- return ephemeral_.erase (identity);
-}
-
-bool
-ValidatorList::insertPermanentKey (
- PublicKey const& identity,
- std::string const& comment)
-{
- std::lock_guard 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 sl (mutex_);
- return permanent_.erase (identity);
-}
-
-std::size_t
-ValidatorList::size () const
-{
- std::lock_guard sl (mutex_);
- return permanent_.size () + ephemeral_.size ();
-}
-
-void
-ValidatorList::for_each (
- std::function func) const
-{
- std::lock_guard 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 const& configKeys,
+ std::vector const& publisherKeys)
{
static boost::regex const re (
"[[:space:]]*" // skip leading whitespace
@@ -141,12 +63,61 @@ ValidatorList::load (
")?" // end optional comment block
);
+ boost::unique_lock 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,20 +136,23 @@ 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])))
- ++count;
+ publisherLists_[local].list.emplace_back (std::move(*id));
+ publisherLists_[local].available = true;
+ ++count;
}
JLOG (j_.debug()) <<
@@ -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 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& publisherList = publisherLists_[pubKey].list;
+
+ std::vector 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 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 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 read_lock{mutex_};
+
+ auto const pubKey = validatorManifests_.getMasterKey (identity);
+ return trustedKeys_.find (pubKey) != trustedKeys_.end();
+}
+
+boost::optional
+ValidatorList::getListedKey (
+ PublicKey const& identity) const
+{
+ boost::shared_lock read_lock{mutex_};
+
+ auto const pubKey = validatorManifests_.getMasterKey (identity);
+ if (keyListings_.find (pubKey) != keyListings_.end ())
+ return pubKey;
+ return boost::none;
+}
+
+boost::optional
+ValidatorList::getTrustedKey (PublicKey const& identity) const
+{
+ boost::shared_lock 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 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 func) const
+{
+ boost::shared_lock read_lock{mutex_};
+
+ for (auto const& v : keyListings_)
+ func (v.first, trusted(v.first));
+}
+
+} // ripple
diff --git a/src/ripple/core/Config.h b/src/ripple/core/Config.h
index d55e8eab1..48754487b 100644
--- a/src/ripple/core/Config.h
+++ b/src/ripple/core/Config.h
@@ -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 VALIDATION_QUORUM; // Minimum validations to consider ledger authoritative
// Node Identity
std::string NODE_SEED;
diff --git a/src/ripple/core/ConfigSections.h b/src/ripple/core/ConfigSections.h
index ee2c219d6..95108ced7 100644
--- a/src/ripple/core/ConfigSections.h
+++ b/src/ripple/core/ConfigSections.h
@@ -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"
diff --git a/src/ripple/core/Job.h b/src/ripple/core/Job.h
index 937ab2cbf..e9ea70c70 100644
--- a/src/ripple/core/Job.h
+++ b/src/ripple/core/Job.h
@@ -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
diff --git a/src/ripple/core/JobTypes.h b/src/ripple/core/JobTypes.h
index 97de34e8f..6943412e4 100644
--- a/src/ripple/core/JobTypes.h
+++ b/src/ripple/core/JobTypes.h
@@ -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);
diff --git a/src/ripple/core/impl/Config.cpp b/src/ripple/core/impl/Config.cpp
index 491231c90..60ed2954f 100644
--- a/src/ripple/core/impl/Config.cpp
+++ b/src/ripple/core/impl/Config.cpp
@@ -318,7 +318,7 @@ void Config::loadFromString (std::string const& fileContents)
std::string strTemp;
if (getSingleSection (secConfig, SECTION_PEER_PRIVATE, strTemp, j_))
- PEER_PRIVATE = beast::lexicalCastThrow (strTemp);
+ PEER_PRIVATE = beast::lexicalCastThrow (strTemp);
if (getSingleSection (secConfig, SECTION_PEERS_MAX, strTemp, j_))
PEERS_MAX = std::max (0, beast::lexicalCastThrow (strTemp));
@@ -378,9 +378,6 @@ void Config::loadFromString (std::string const& fileContents)
if (getSingleSection (secConfig, SECTION_NETWORK_QUORUM, strTemp, j_))
NETWORK_QUORUM = beast::lexicalCastThrow (strTemp);
- if (getSingleSection (secConfig, SECTION_VALIDATION_QUORUM, strTemp, j_))
- VALIDATION_QUORUM = std::max (0, beast::lexicalCastThrow (strTemp));
-
if (getSingleSection (secConfig, SECTION_FEE_ACCOUNT_RESERVE, strTemp, j_))
FEE_ACCOUNT_RESERVE = beast::lexicalCastThrow (strTemp);
@@ -425,107 +422,109 @@ void Config::loadFromString (std::string const& fileContents)
if (getSingleSection (secConfig, SECTION_PATH_SEARCH_MAX, strTemp, j_))
PATH_SEARCH_MAX = beast::lexicalCastThrow (strTemp);
- // 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.
- // If the specified file is not an absolute path, then look
- // for it in the same directory as the config file.
- // If no path was specified, then look for validators.txt
- // in the same directory as the config file, but don't complain
- // if we can't find it.
- boost::filesystem::path validatorsFile;
-
- if (getSingleSection (secConfig, SECTION_VALIDATORS_FILE, strTemp, j_))
- {
- validatorsFile = strTemp;
-
- if (validatorsFile.empty ())
- Throw (
- "Invalid path specified in [" SECTION_VALIDATORS_FILE "]");
-
- if (!validatorsFile.is_absolute() && !CONFIG_DIR.empty())
- validatorsFile = CONFIG_DIR / validatorsFile;
-
- if (!boost::filesystem::exists (validatorsFile))
- Throw (
- "The file specified in [" SECTION_VALIDATORS_FILE "] "
- "does not exist: " + validatorsFile.string());
-
- else if (!boost::filesystem::is_regular_file (validatorsFile) &&
- !boost::filesystem::is_symlink (validatorsFile))
- Throw (
- "Invalid file specified in [" SECTION_VALIDATORS_FILE "]: " +
- validatorsFile.string());
- }
- else if (!CONFIG_DIR.empty())
- {
- validatorsFile = CONFIG_DIR / validatorsFileName;
-
- if (!validatorsFile.empty ())
- {
- if(!boost::filesystem::exists (validatorsFile))
- validatorsFile.clear();
- else if (!boost::filesystem::is_regular_file (validatorsFile) &&
- !boost::filesystem::is_symlink (validatorsFile))
- validatorsFile.clear();
- }
- }
-
- if (!validatorsFile.empty () &&
- boost::filesystem::exists (validatorsFile) &&
- (boost::filesystem::is_regular_file (validatorsFile) ||
- boost::filesystem::is_symlink (validatorsFile)))
- {
- std::ifstream ifsDefault (validatorsFile.native().c_str());
-
- std::string data;
-
- data.assign (
- std::istreambuf_iterator(ifsDefault),
- std::istreambuf_iterator());
-
- auto iniFile = parseIniFile (data, true);
-
- auto entries = getIniFileSection (
- iniFile,
- SECTION_VALIDATORS);
-
- if (entries)
- section (SECTION_VALIDATORS).append (*entries);
-
- auto valKeyEntries = getIniFileSection(
- iniFile,
- SECTION_VALIDATOR_KEYS);
-
- if (valKeyEntries)
- section (SECTION_VALIDATOR_KEYS).append (*valKeyEntries);
-
- if (!entries && !valKeyEntries)
- Throw (
- "The file specified in [" SECTION_VALIDATORS_FILE "] "
- "does not contain a [" SECTION_VALIDATORS "] or "
- "[" SECTION_VALIDATOR_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 (
- "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 (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.
+ // If the specified file is not an absolute path, then look
+ // for it in the same directory as the config file.
+ // If no path was specified, then look for validators.txt
+ // in the same directory as the config file, but don't complain
+ // if we can't find it.
+ boost::filesystem::path validatorsFile;
+
+ if (getSingleSection (secConfig, SECTION_VALIDATORS_FILE, strTemp, j_))
+ {
+ validatorsFile = strTemp;
+
+ if (validatorsFile.empty ())
+ Throw (
+ "Invalid path specified in [" SECTION_VALIDATORS_FILE "]");
+
+ if (!validatorsFile.is_absolute() && !CONFIG_DIR.empty())
+ validatorsFile = CONFIG_DIR / validatorsFile;
+
+ if (!boost::filesystem::exists (validatorsFile))
+ Throw (
+ "The file specified in [" SECTION_VALIDATORS_FILE "] "
+ "does not exist: " + validatorsFile.string());
+
+ else if (!boost::filesystem::is_regular_file (validatorsFile) &&
+ !boost::filesystem::is_symlink (validatorsFile))
+ Throw (
+ "Invalid file specified in [" SECTION_VALIDATORS_FILE "]: " +
+ validatorsFile.string());
+ }
+ else if (!CONFIG_DIR.empty())
+ {
+ validatorsFile = CONFIG_DIR / validatorsFileName;
+
+ if (!validatorsFile.empty ())
+ {
+ if(!boost::filesystem::exists (validatorsFile))
+ validatorsFile.clear();
+ else if (!boost::filesystem::is_regular_file (validatorsFile) &&
+ !boost::filesystem::is_symlink (validatorsFile))
+ validatorsFile.clear();
+ }
+ }
+
+ if (!validatorsFile.empty () &&
+ boost::filesystem::exists (validatorsFile) &&
+ (boost::filesystem::is_regular_file (validatorsFile) ||
+ boost::filesystem::is_symlink (validatorsFile)))
+ {
+ std::ifstream ifsDefault (validatorsFile.native().c_str());
+
+ std::string data;
+
+ data.assign (
+ std::istreambuf_iterator(ifsDefault),
+ std::istreambuf_iterator());
+
+ auto iniFile = parseIniFile (data, true);
+
+ auto entries = getIniFileSection (
+ iniFile,
+ SECTION_VALIDATORS);
+
+ if (entries)
+ section (SECTION_VALIDATORS).append (*entries);
+
+ auto valKeyEntries = getIniFileSection(
+ iniFile,
+ SECTION_VALIDATOR_KEYS);
+
+ if (valKeyEntries)
+ section (SECTION_VALIDATOR_KEYS).append (*valKeyEntries);
+
+ auto valListKeys = getIniFileSection(
+ iniFile,
+ SECTION_VALIDATOR_LIST_KEYS);
+
+ if (valListKeys)
+ section (SECTION_VALIDATOR_LIST_KEYS).append (*valListKeys);
+
+ if (!entries && !valKeyEntries && !valListKeys)
+ Throw (
+ "The file specified in [" SECTION_VALIDATORS_FILE "] "
+ "does not contain a [" SECTION_VALIDATORS "], "
+ "[" SECTION_VALIDATOR_KEYS "] or "
+ "[" SECTION_VALIDATOR_LIST_KEYS "]"
+ " section: " +
+ validatorsFile.string());
+ }
+
+ // Consolidate [validator_keys] and [validators]
+ section (SECTION_VALIDATORS).append (
+ section (SECTION_VALIDATOR_KEYS).lines ());
+ }
+
{
auto const part = section("features");
for(auto const& s : part.values())
diff --git a/src/ripple/net/InfoSub.h b/src/ripple/net/InfoSub.h
index 93058341a..ee52f621e 100644
--- a/src/ripple/net/InfoSub.h
+++ b/src/ripple/net/InfoSub.h
@@ -22,7 +22,7 @@
#include
#include
-#include
+#include
#include
#include
#include
diff --git a/src/ripple/overlay/Overlay.h b/src/ripple/overlay/Overlay.h
index 0d0f4f1a2..103240bba 100644
--- a/src/ripple/overlay/Overlay.h
+++ b/src/ripple/overlay/Overlay.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:
diff --git a/src/ripple/overlay/impl/Manifest.cpp b/src/ripple/overlay/impl/Manifest.cpp
deleted file mode 100644
index ee2f8d584..000000000
--- a/src/ripple/overlay/impl/Manifest.cpp
+++ /dev/null
@@ -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
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-namespace ripple {
-
-boost::optional
-make_Manifest (std::string s)
-{
- try
- {
- STObject st (sfGeneric);
- SerialIter sit (s.data (), s.size ());
- st.set (sit);
- auto const opt_pk = get(st, sfPublicKey);
- auto const opt_spk = get(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
-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
-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::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(
- 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 ("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 ("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 lock (mutex_);
-
- auto& value = map_[pk];
-
- if (value.m)
- {
- Throw (
- "New trusted validator key already has a manifest");
- }
-
- value.comment = std::move(comment);
-}
-
-std::size_t
-ManifestCache::size () const
-{
- std::lock_guard 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 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 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 ();
-}
-}
diff --git a/src/ripple/overlay/impl/OverlayImpl.cpp b/src/ripple/overlay/impl/OverlayImpl.cpp
index df347c17d..1c7ae3e75 100644
--- a/src/ripple/overlay/impl/OverlayImpl.cpp
+++ b/src/ripple/overlay/impl/OverlayImpl.cpp
@@ -20,7 +20,7 @@
#include
#include
#include
-#include
+#include
#include
#include
#include
@@ -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 (
- "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 ("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)
{
diff --git a/src/ripple/overlay/impl/OverlayImpl.h b/src/ripple/overlay/impl/OverlayImpl.h
index 2f33873aa..bc1e327c2 100644
--- a/src/ripple/overlay/impl/OverlayImpl.h
+++ b/src/ripple/overlay/impl/OverlayImpl.h
@@ -23,7 +23,6 @@
#include
#include
#include
-#include
#include
#include
#include
@@ -118,7 +117,6 @@ private:
hash_map> ids_;
Resolver& m_resolver;
std::atomic 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
diff --git a/src/ripple/overlay/impl/PeerImp.cpp b/src/ripple/overlay/impl/PeerImp.cpp
index f5d3459a0..fad75f8c8 100644
--- a/src/ripple/overlay/impl/PeerImp.cpp
+++ b/src/ripple/overlay/impl/PeerImp.cpp
@@ -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;
diff --git a/src/ripple/protocol/JsonFields.h b/src/ripple/protocol/JsonFields.h
index 9a97b8801..86493b7db 100644
--- a/src/ripple/protocol/JsonFields.h
+++ b/src/ripple/protocol/JsonFields.h
@@ -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*
diff --git a/src/ripple/rpc/handlers/UnlList.cpp b/src/ripple/rpc/handlers/UnlList.cpp
index c2009bc4f..32a4a3f25 100644
--- a/src/ripple/rpc/handlers/UnlList.cpp
+++ b/src/ripple/rpc/handlers/UnlList.cpp
@@ -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);
});
diff --git a/src/ripple/unity/app_misc.cpp b/src/ripple/unity/app_misc.cpp
index 3fa5a8c83..33ad6ca28 100644
--- a/src/ripple/unity/app_misc.cpp
+++ b/src/ripple/unity/app_misc.cpp
@@ -29,6 +29,7 @@
#include
#include
#include
+#include
#include
#include
#include
diff --git a/src/ripple/unity/overlay.cpp b/src/ripple/unity/overlay.cpp
index 95a6b9e92..59026ed9c 100644
--- a/src/ripple/unity/overlay.cpp
+++ b/src/ripple/unity/overlay.cpp
@@ -21,7 +21,6 @@
#include
#include