Get quorum and trusted master validator keys from validators.txt:

* Load specified [validators_file] relative to config dir
* Add default [validators_file] to rippled-example.cfg
* Remove [validators] and [validation_quorum] from rippled-example.cfg
* Add [validation_quorum] to validators-example.txt
* Allow validators.txt to be a symlink
* Throw for invalid [validators_file] instead of logging
* Trust own master public key from configured manifest
* Do not load untrusted manifests from database

Trusted validators are loaded from [validators] and [validator_keys]
sections from both rippled.cfg and validators.txt

Quorum is loaded from [validation_quorum] section in validators.txt
only if it is not configured in rippled.cfg
This commit is contained in:
wilsonianb
2016-05-10 16:46:11 -07:00
committed by seelabs
parent 7e3dbce3d2
commit 4ed6cbdd5b
8 changed files with 585 additions and 191 deletions

View File

@@ -513,14 +513,6 @@
#
#
#
# [validation_quorum]
#
# 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.
#
#
#
# [ledger_history]
#
# The number of past ledgers to acquire on server startup and the minimum to
@@ -562,29 +554,21 @@
#
#
#
# [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.
#
# Examples:
# n9KorY8QtTdRx7TVDpwnG9NvyxsDwHUKUEeDLY3AkiGncVaSXZi5
# n9MqiExBcoG19UXwoLjBJnhsxEhAZMuWwJDRdkyDz1EkEkwzQTNt John Doe
#
#
#
# [validators_file]
#
# Path to a file that contains 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.
# Path or name of a file that contains the validation public keys of nodes
# 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] entry, followed by
# a list of validation public keys of nodes to always accept as validators,
# one per line, optionally followed by a comment separated by whitespace:
# 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.
#
# Specify the file by specifying its full path.
# Specify the file by its name or path.
# Unless an absolute path is specified, it will be considered relative to
# the folder in which the rippled.cfg file is located.
#
# Examples:
# /home/ripple/validators.txt
@@ -598,6 +582,9 @@
# n9KiYM9CgngLvtRCQHZwgC2gjpdaZcCcbt3VboxiNFcKuwFVujzS RL4
# n9LdgEtkmGB9E2h3K4Vp7iGUaKuq23Zr32ehxiU8FWY7xoxbWTSA RL5
#
# [validation_quorum]
# 3
#
#
# [path_search]
# When searching for paths, the default search aggressiveness. This can take
@@ -987,22 +974,11 @@ pool.ntp.org
[ips]
r.ripple.com 51235
# Public keys of the validators that this rippled instance trusts. The latest
# list of validators can be obtained from https://ripple.com/ripple.txt
#
# See also https://wiki.ripple.com/Ripple.txt
#
[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
# File containing validation quorum and trusted validator keys.
# Unless an absolute path is specified, it will be considered relative to the
# folder in which the rippled.cfg file is located.
[validators_file]
validators.txt
# Turn down default logging to save disk space in the long run.
# Valid values here are trace, debug, info, warning, error, and fatal

View File

@@ -8,20 +8,41 @@
# Blank lines and lines starting with a '#' are ignored.
# All other lines should be hankos or domain names.
#
# [validators]:
# List of nodes to accept as validators specified by public key or domain.
#
# For domains, rippled will probe for https web servers at the specified
# domain in the following order: ripple.DOMAIN, www.DOMAIN, DOMAIN
#
# Examples: redstem.com
# n9KorY8QtTdRx7TVDpwnG9NvyxsDwHUKUEeDLY3AkiGncVaSXZi5
# n9MqiExBcoG19UXwoLjBJnhsxEhAZMuWwJDRdkyDz1EkEkwzQTNt John Doe
# [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
#
# See also https://wiki.ripple.com/Ripple.txt
#
# Examples:
# n9KorY8QtTdRx7TVDpwnG9NvyxsDwHUKUEeDLY3AkiGncVaSXZi5
# n9MqiExBcoG19UXwoLjBJnhsxEhAZMuWwJDRdkyDz1EkEkwzQTNt John Doe
#
#
#
# [validation_quorum]
#
# 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.
#
# 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

View File

@@ -64,6 +64,8 @@ struct ConfigSection
#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_VALIDATION_MANIFEST "validation_manifest"
#define SECTION_VETO_AMENDMENTS "veto_amendments"
} // ripple

View File

@@ -426,11 +426,13 @@ void Config::loadFromString (std::string const& fileContents)
if (getSingleSection (secConfig, SECTION_PATH_SEARCH_MAX, strTemp, j_))
PATH_SEARCH_MAX = beast::lexicalCastThrow <int> (strTemp);
// If a file was explicitly specified, then warn if the
// 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 path as the config file but don't complain
// in the same directory as the config file, but don't complain
// if we can't find it.
boost::filesystem::path validatorsFile;
@@ -439,29 +441,22 @@ void Config::loadFromString (std::string const& fileContents)
validatorsFile = strTemp;
if (validatorsFile.empty ())
{
JLOG (j_.error()) <<
"[" SECTION_VALIDATORS_FILE "]" <<
": " << strTemp <<
" is not a valid path";
validatorsFile.clear ();
}
else if (!boost::filesystem::exists (validatorsFile))
{
JLOG (j_.error()) <<
"[" SECTION_VALIDATORS_FILE "]" <<
": the file " << validatorsFile <<
" does not exist";
validatorsFile.clear ();
}
else if (!boost::filesystem::is_regular_file (validatorsFile))
{
JLOG (j_.error()) <<
"[" SECTION_VALIDATORS_FILE "]" <<
": the file " << validatorsFile <<
" is not a regular file";
validatorsFile.clear ();
}
Throw<std::runtime_error> (
"Invalid path specified in [" SECTION_VALIDATORS_FILE "]");
if (!validatorsFile.is_absolute())
validatorsFile = CONFIG_DIR / validatorsFile;
if (!boost::filesystem::exists (validatorsFile))
Throw<std::runtime_error> (
"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<std::runtime_error> (
"Invalid file specified in [" SECTION_VALIDATORS_FILE "]: " +
validatorsFile.string());
}
else
{
@@ -471,14 +466,16 @@ void Config::loadFromString (std::string const& fileContents)
{
if(!boost::filesystem::exists (validatorsFile))
validatorsFile.clear();
else if (!boost::filesystem::is_regular_file (validatorsFile))
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_regular_file (validatorsFile) ||
boost::filesystem::is_symlink (validatorsFile)))
{
std::ifstream ifsDefault (validatorsFile.native().c_str());
@@ -494,17 +491,36 @@ void Config::loadFromString (std::string const& fileContents)
iniFile,
SECTION_VALIDATORS);
if (!entries)
{
JLOG (j_.error()) <<
"[" SECTION_VALIDATORS_FILE "]" <<
": the file " << validatorsFile <<
" does not contain a [" SECTION_VALIDATORS <<
"] section";
}
else
{
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<std::runtime_error> (
"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<std::runtime_error> (
"The file specified in [" SECTION_VALIDATORS_FILE "] "
"does not contain a [" SECTION_VALIDATION_QUORUM "] "
"section: " + validatorsFile.string());
else
VALIDATION_QUORUM = std::max (
0, beast::lexicalCastThrow <int> (strTemp));
}
}

View File

@@ -30,7 +30,8 @@
namespace ripple {
namespace detail {
std::string configContents (std::string const& dbPath)
std::string configContents (std::string const& dbPath,
std::string const& validatorsFile)
{
static boost::format configContentsTemplate (R"rippleConfig(
[server]
@@ -80,6 +81,7 @@ file_size_mb=8
file_size_mult=2
%1%
%2%
# This needs to be an absolute directory reference, not a relative one.
@@ -98,20 +100,6 @@ pool.ntp.org
[ips]
r.ripple.com 51235
# The latest validators can be obtained from
# https://ripple.com/ripple.txt
#
[validators]
n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7 RL1
n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj RL2
n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C RL3
n9KiYM9CgngLvtRCQHZwgC2gjpdaZcCcbt3VboxiNFcKuwFVujzS RL4
n9LdgEtkmGB9E2h3K4Vp7iGUaKuq23Zr32ehxiU8FWY7xoxbWTSA RL5
# Ditto.
[validation_quorum]
3
# Turn down default logging to save disk space in the long run.
# Valid values here are trace, debug, info, warning, error, and fatal
[rpc_startup]
@@ -126,10 +114,12 @@ n9LdgEtkmGB9E2h3K4Vp7iGUaKuq23Zr32ehxiU8FWY7xoxbWTSA RL5
backend=sqlite
)rippleConfig");
if (!dbPath.empty ())
return boost::str (configContentsTemplate % "[database_path]" % dbPath);
else
return boost::str (configContentsTemplate % "" % "");
std::string dbPathSection =
dbPath.empty () ? "" : "[database_path]\n" + dbPath;
std::string valFileSection =
validatorsFile.empty () ? "" : "[validators_file]\n" + validatorsFile;
return boost::str (
configContentsTemplate % dbPathSection % valFileSection);
}
/**
@@ -138,28 +128,28 @@ backend=sqlite
class ConfigGuard
{
private:
bool rmSubDir_{false};
protected:
using path = boost::filesystem::path;
path subDir_;
path configFile_;
path dataDir_;
beast::unit_test::suite& test_;
bool rmSubDir_{false};
bool rmDataDir_{false};
Config config_;
auto rmDir (path const& toRm)
{
if (is_directory (toRm) && is_empty (toRm))
remove (toRm);
else
test_.log << "Expected " << toRm.string ()
<< " to be an empty existing directory.";
};
public:
ConfigGuard (std::string subDir, std::string const& dbPath)
: subDir_ (std::move (subDir)), dataDir_ (dbPath)
ConfigGuard (beast::unit_test::suite& test, std::string subDir)
: subDir_ (std::move (subDir))
, test_ (test)
{
using namespace boost::filesystem;
if (dbPath.empty ())
{
dataDir_ = subDir_ / path (Config::databaseDirName);
}
configFile_ = subDir_ / path (Config::configFileName);
{
if (!exists (subDir_))
{
@@ -170,17 +160,60 @@ public:
rmSubDir_ = false;
else
{
// Cannot run the test someone created a file where we want to
// put out directory
// Cannot run the test. Someone created a file where we want to
// put our directory
Throw<std::runtime_error> (
"Cannot create directory: " + subDir_.string ());
}
}
}
~ConfigGuard ()
{
try
{
using namespace boost::filesystem;
if (rmSubDir_)
rmDir (subDir_);
else
test_.log << "Skipping rm dir: " << subDir_.string ();
}
catch (std::exception& e)
{
// if we throw here, just let it die.
test_.log << "Error in ~ConfigGuard: " << e.what ();
};
}
};
/**
Write a rippled config file and remove when done.
*/
class RippledCfgGuard : ConfigGuard
{
private:
path configFile_;
path dataDir_;
bool rmDataDir_{false};
Config config_;
public:
RippledCfgGuard (beast::unit_test::suite& test,
std::string subDir, std::string const& dbPath,
std::string const& validatorsFile)
: ConfigGuard (test, std::move (subDir)), dataDir_ (dbPath)
{
if (dbPath.empty ())
dataDir_ = subDir_ / path (Config::databaseDirName);
configFile_ = subDir_ / path (Config::configFileName);
if (!exists (configFile_))
{
std::ofstream o (configFile_.string ());
o << configContents (dbPath);
o << configContents (dbPath, validatorsFile);
}
else
{
@@ -202,42 +235,111 @@ public:
}
bool configFileExists () const
{
return boost::filesystem::is_regular_file (configFile_);
return boost::filesystem::exists (configFile_);
}
~ConfigGuard ()
~RippledCfgGuard ()
{
try
{
using namespace boost::filesystem;
if (!is_regular_file (configFile_))
std::cerr << "Expected " << configFile_.string ()
<< " to be an existing file.\n";
if (!boost::filesystem::exists (configFile_))
test_.log << "Expected " << configFile_.string ()
<< " to be an existing file.";
else
remove (configFile_.string ());
auto rmDir = [](path const& toRm)
{
if (is_directory (toRm) && is_empty (toRm))
remove (toRm);
else
std::cerr << "Expected " << toRm.string ()
<< " to be an empty existing directory.\n";
};
if (rmDataDir_)
rmDir (dataDir_);
else
std::cerr << "Skipping rm dir: " << dataDir_.string () << "\n";
if (rmSubDir_)
rmDir (subDir_);
else
std::cerr << "Skipping rm dir: " << subDir_.string () << "\n";
test_.log << "Skipping rm dir: " << dataDir_.string ();
}
catch (std::exception& e)
{
// if we throw here, just let it die.
std::cerr << "Error in CreateConfigGuard: " << e.what () << "\n";
test_.log << "Error in ~RippledCfgGuard: " << e.what ();
};
}
};
std::string valFileContents (boost::optional<int> const& quorum)
{
static boost::format configContentsTemplate (R"rippleConfig(
[validators]
n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7
n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj
n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C
n9KiYM9CgngLvtRCQHZwgC2gjpdaZcCcbt3VboxiNFcKuwFVujzS
n9LdgEtkmGB9E2h3K4Vp7iGUaKuq23Zr32ehxiU8FWY7xoxbWTSA
[validator_keys]
nHUhG1PgAG8H8myUENypM35JgfqXAKNQvRVVAFDRzJrny5eZN8d5
nHBu9PTL9dn2GuZtdW4U2WzBwffyX9qsQCd9CNU4Z5YG3PQfViM8
nHUPDdcdb2Y5DZAJne4c2iabFuAP3F34xZUgYQT2NH7qfkdapgnz
%1%
)rippleConfig");
std::string quorumSection =
quorum ? "[validation_quorum]\n" + to_string(*quorum) : "";
return boost::str (
configContentsTemplate % quorumSection);
}
/**
Write a validators.txt file and remove when done.
*/
class ValidatorsTxtGuard : ConfigGuard
{
private:
path validatorsFile_;
public:
ValidatorsTxtGuard (beast::unit_test::suite& test,
std::string subDir, std::string const& validatorsFileName,
boost::optional<int> const& quorum)
: ConfigGuard (test, std::move (subDir))
{
using namespace boost::filesystem;
validatorsFile_ = current_path () / subDir_ / path (
validatorsFileName.empty () ? Config::validatorsFileName :
validatorsFileName);
if (!exists (validatorsFile_))
{
std::ofstream o (validatorsFile_.string ());
o << valFileContents (quorum);
}
else
{
Throw<std::runtime_error> (
"Refusing to overwrite existing config file: " +
validatorsFile_.string ());
}
}
bool validatorsFileExists () const
{
return boost::filesystem::exists (validatorsFile_);
}
std::string validatorsFile () const
{
return validatorsFile_.string ();
}
~ValidatorsTxtGuard ()
{
try
{
using namespace boost::filesystem;
if (!boost::filesystem::exists (validatorsFile_))
test_.log << "Expected " << validatorsFile_.string ()
<< " to be an existing file.";
else
remove (validatorsFile_.string ());
}
catch (std::exception& e)
{
// if we throw here, just let it die.
test_.log << "Error in ~ValidatorsTxtGuard: " << e.what ();
};
}
};
@@ -301,7 +403,7 @@ port_wss_admin
expect (c.legacy ("database_path") == dataDirAbs.string ());
}
{
// No db sectcion.
// No db section.
// N.B. Config::setup will give database_path a default,
// load will not.
Config c;
@@ -314,7 +416,7 @@ port_wss_admin
auto const cwd = current_path ();
path const dataDirRel ("test_data_dir");
path const dataDirAbs (cwd / path ("test_db") / dataDirRel);
detail::ConfigGuard g ("test_db", dataDirAbs.string ());
detail::RippledCfgGuard g (*this, "test_db", dataDirAbs.string (), "");
auto& c (g.config ());
expect (g.dataDirExists ());
expect (g.configFileExists ());
@@ -324,7 +426,7 @@ port_wss_admin
{
// read from file relative path
std::string const dbPath ("my_db");
detail::ConfigGuard g ("test_db", dbPath);
detail::RippledCfgGuard g (*this, "test_db", dbPath, "");
auto& c (g.config ());
std::string const nativeDbPath = absolute (path (dbPath)).string ();
expect (g.dataDirExists ());
@@ -334,7 +436,7 @@ port_wss_admin
}
{
// read from file no path
detail::ConfigGuard g ("test_db", "");
detail::RippledCfgGuard g (*this, "test_db", "", "");
auto& c (g.config ());
std::string const nativeDbPath =
absolute (path ("test_db") /
@@ -346,10 +448,245 @@ port_wss_admin
"dbPath No Path");
}
}
void testValidatorsFile ()
{
testcase ("validators_file");
using namespace boost::filesystem;
{
// load should throw for missing specified validators file
Config c;
boost::format cc ("[validators_file]\n%1%\n");
std::string error;
std::string const missingPath = "/no/way/this/path/exists";
auto const expectedError =
"The file specified in [validators_file] does not exist: " +
missingPath;
try {
c.loadFromString (boost::str (cc % missingPath));
} catch (std::runtime_error& e) {
error = e.what();
}
expect (error == expectedError);
}
{
// load should throw for invalid [validators_file]
int const quorum = 3;
detail::ValidatorsTxtGuard vtg (
*this, "test_cfg", "validators.cfg", quorum);
Config c;
path const invalidFile = current_path () / "test_cfg";
boost::format cc ("[validators_file]\n%1%\n");
std::string error;
auto const expectedError =
"Invalid file specified in [validators_file]: " +
invalidFile.string ();
try {
c.loadFromString (boost::str (cc % invalidFile.string ()));
} catch (std::runtime_error& e) {
error = e.what();
}
expect (error == expectedError);
}
{
// load validators and quorum from config
Config c;
std::string toLoad(R"rippleConfig(
[validators]
n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7
n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj
n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C
[validator_keys]
nHUhG1PgAG8H8myUENypM35JgfqXAKNQvRVVAFDRzJrny5eZN8d5
nHBu9PTL9dn2GuZtdW4U2WzBwffyX9qsQCd9CNU4Z5YG3PQfViM8
[validation_quorum]
4
)rippleConfig");
c.loadFromString (toLoad);
expect (c.legacy ("validators_file").empty ());
expect (c.section (SECTION_VALIDATORS).values ().size () == 3);
expect (c.section (SECTION_VALIDATOR_KEYS).values ().size () == 2);
expect (c.VALIDATION_QUORUM == 4);
}
{
// load from specified [validators_file] absolute path
int const quorum = 3;
detail::ValidatorsTxtGuard vtg (
*this, "test_cfg", "validators.cfg", quorum);
expect (vtg.validatorsFileExists ());
Config c;
boost::format cc ("[validators_file]\n%1%\n");
c.loadFromString (boost::str (cc % vtg.validatorsFile ()));
expect (c.legacy ("validators_file") == vtg.validatorsFile ());
expect (c.section (SECTION_VALIDATORS).values ().size () == 5);
expect (c.section (SECTION_VALIDATOR_KEYS).values ().size () == 3);
expect (c.VALIDATION_QUORUM == quorum);
}
{
// load from specified [validators_file] file name
// in config directory
int const quorum = 3;
std::string const valFileName = "validators.txt";
detail::ValidatorsTxtGuard vtg (
*this, "test_cfg", valFileName, quorum);
detail::RippledCfgGuard rcg (
*this, "test_cfg", "", valFileName);
expect (vtg.validatorsFileExists ());
expect (rcg.configFileExists ());
auto& c (rcg.config ());
expect (c.legacy ("validators_file") == valFileName);
expect (c.section (SECTION_VALIDATORS).values ().size () == 5);
expect (c.section (SECTION_VALIDATOR_KEYS).values ().size () == 3);
expect (c.VALIDATION_QUORUM == quorum);
}
{
// load from specified [validators_file] relative path
// to config directory
int const quorum = 3;
std::string const valFilePath = "../test_cfg/validators.txt";
detail::ValidatorsTxtGuard vtg (
*this, "test_cfg", "validators.txt", quorum);
detail::RippledCfgGuard rcg (
*this, "test_cfg", "", valFilePath);
expect (vtg.validatorsFileExists ());
expect (rcg.configFileExists ());
auto& c (rcg.config ());
expect (c.legacy ("validators_file") == valFilePath);
expect (c.section (SECTION_VALIDATORS).values ().size () == 5);
expect (c.section (SECTION_VALIDATOR_KEYS).values ().size () == 3);
expect (c.VALIDATION_QUORUM == quorum);
}
{
// load from validators file in default location
int const quorum = 3;
detail::ValidatorsTxtGuard vtg (
*this, "test_cfg", "validators.txt", quorum);
detail::RippledCfgGuard rcg (*this, "test_cfg", "", "");
expect (vtg.validatorsFileExists ());
expect (rcg.configFileExists ());
auto& c (rcg.config ());
expect (c.legacy ("validators_file").empty ());
expect (c.section (SECTION_VALIDATORS).values ().size () == 5);
expect (c.section (SECTION_VALIDATOR_KEYS).values ().size () == 3);
expect (c.VALIDATION_QUORUM == quorum);
}
{
// load from specified [validators_file] instead
// of default location
int const quorum = 3;
detail::ValidatorsTxtGuard vtg (
*this, "test_cfg", "validators.cfg", quorum);
expect (vtg.validatorsFileExists ());
detail::ValidatorsTxtGuard vtgDefault (
*this, "test_cfg", "validators.txt", 4);
expect (vtgDefault.validatorsFileExists ());
detail::RippledCfgGuard rcg (
*this, "test_cfg", "", vtg.validatorsFile ());
expect (rcg.configFileExists ());
auto& c (rcg.config ());
expect (c.legacy ("validators_file") == vtg.validatorsFile ());
expect (c.section (SECTION_VALIDATORS).values ().size () == 5);
expect (c.section (SECTION_VALIDATOR_KEYS).values ().size () == 3);
expect (c.VALIDATION_QUORUM == quorum);
}
{
// do not load quorum from validators file if in config
boost::format cc (R"rippleConfig(
[validators_file]
%1%
[validation_quorum]
4
)rippleConfig");
int const quorum = 3;
detail::ValidatorsTxtGuard vtg (
*this, "test_cfg", "validators.cfg", quorum);
expect (vtg.validatorsFileExists ());
Config c;
c.loadFromString (boost::str (cc % vtg.validatorsFile ()));
expect (c.legacy ("validators_file") == vtg.validatorsFile ());
expect (c.section (SECTION_VALIDATORS).values ().size () == 5);
expect (c.section (SECTION_VALIDATOR_KEYS).values ().size () == 3);
expect (c.VALIDATION_QUORUM == 4);
}
{
// load validators from both config and validators file
boost::format cc (R"rippleConfig(
[validators_file]
%1%
[validators]
n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7
n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj
n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C
n9KiYM9CgngLvtRCQHZwgC2gjpdaZcCcbt3VboxiNFcKuwFVujzS
n9LdgEtkmGB9E2h3K4Vp7iGUaKuq23Zr32ehxiU8FWY7xoxbWTSA
[validator_keys]
nHB1X37qrniVugfQcuBTAjswphC1drx7QjFFojJPZwKHHnt8kU7v
nHUkAWDR4cB8AgPg7VXMX6et8xRTQb2KJfgv1aBEXozwrawRKgMB
)rippleConfig");
int const quorum = 3;
detail::ValidatorsTxtGuard vtg (
*this, "test_cfg", "validators.cfg", quorum);
expect (vtg.validatorsFileExists ());
Config c;
c.loadFromString (boost::str (cc % vtg.validatorsFile ()));
expect (c.legacy ("validators_file") == vtg.validatorsFile ());
expect (c.section (SECTION_VALIDATORS).values ().size () == 10);
expect (c.section (SECTION_VALIDATOR_KEYS).values ().size () == 5);
expect (c.VALIDATION_QUORUM == quorum);
}
{
// load should throw if [validators] and [validator_keys] are
// missing from rippled cfg and validators file
Config c;
boost::format cc ("[validators_file]\n%1%\n");
std::string error;
detail::ValidatorsTxtGuard vtg (
*this, "test_cfg", "validators.cfg", boost::none);
expect (vtg.validatorsFileExists ());
auto const expectedError =
"The file specified in [validators_file] does not contain a "
"[validators] or [validator_keys] section: " +
vtg.validatorsFile ();
std::ofstream o (vtg.validatorsFile ());
o << "[validation_quorum]\n3\n";
try {
c.loadFromString (boost::str (cc % vtg.validatorsFile ()));
} catch (std::runtime_error& e) {
error = e.what();
}
expect (error == expectedError);
}
{
// load should throw if [validation_quorum] is
// missing from rippled cfg and validators file
Config c;
boost::format cc ("[validators_file]\n%1%\n");
std::string error;
detail::ValidatorsTxtGuard vtg (
*this, "test_cfg", "validators.cfg", boost::none);
expect (vtg.validatorsFileExists ());
auto const expectedError =
"The file specified in [validators_file] does not contain a "
"[validation_quorum] section: " + vtg.validatorsFile ();
try {
c.loadFromString (boost::str (cc % vtg.validatorsFile ()));
} catch (std::runtime_error& e) {
error = e.what();
}
expect (error == expectedError);
}
}
void run ()
{
testLegacy ();
testDbPath ();
testValidatorsFile ();
}
};

View File

@@ -198,6 +198,12 @@ ManifestCache::configManifest (
Throw<std::runtime_error> ("Unverifiable manifest in config");
}
// Trust our own master public key
if (!trusted(m.masterKey) && !unl.trusted (m.masterKey))
{
addTrustedKey (m.masterKey, "");
}
auto const result = applyManifest (std::move(m), unl, journal);
if (result != ManifestDisposition::accepted)
@@ -412,15 +418,15 @@ void ManifestCache::load (
Throw<std::runtime_error> ("Unverifiable manifest in db");
}
// Remove master public key from permanent trusted key list
if (unl.trusted(mo->masterKey))
unl.removePermanentKey (mo->masterKey);
// add trusted key
map_[mo->masterKey];
// OK if not accepted (may have been loaded from the config file)
applyManifest (std::move(*mo), unl, journal);
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
{

View File

@@ -20,6 +20,7 @@
#include <BeastConfig.h>
#include <ripple/app/misc/HashRouter.h>
#include <ripple/app/misc/NetworkOPs.h>
#include <ripple/core/ConfigSections.h>
#include <ripple/core/DatabaseCon.h>
#include <ripple/basics/contract.h>
#include <ripple/basics/Log.h>
@@ -483,15 +484,15 @@ OverlayImpl::setupValidatorKeyManifests (BasicConfig const& config,
DatabaseCon& db)
{
auto const loaded = manifestCache_.loadValidatorKeys (
config.section ("validator_keys"),
config.section (SECTION_VALIDATOR_KEYS),
journal_);
if (!loaded)
Throw<std::runtime_error> (
"Unable to load keys from [validator_keys]");
"Unable to load keys from [" SECTION_VALIDATOR_KEYS "]");
auto const validation_manifest =
config.section ("validation_manifest");
config.section (SECTION_VALIDATION_MANIFEST);
if (! validation_manifest.lines().empty())
{
@@ -513,7 +514,8 @@ OverlayImpl::setupValidatorKeyManifests (BasicConfig const& config,
}
else
{
JLOG(journal_.debug()) << "No [validation_manifest] section in config";
JLOG(journal_.debug()) << "No [" SECTION_VALIDATION_MANIFEST <<
"] section in config";
}
manifestCache_.load (

View File

@@ -221,10 +221,19 @@ public:
expect (!cache.loadValidatorKeys (s6, journal));
expect (!cache.trusted (node1));
expect (!cache.trusted (node2));
// Trust our own master public key from configured manifest
auto unl = std::make_unique<ValidatorList> (journal);
auto const sk = randomSecretKey();
auto const kp = randomKeyPair(KeyType::secp256k1);
auto const m = make_Manifest (KeyType::ed25519, sk, kp.first, 0);
cache.configManifest (clone (m), *unl, journal);
expect (cache.trusted (m.masterKey));
}
void testLoadStore (ManifestCache const& m, ValidatorList& unl,
PublicKey& pk)
void testLoadStore (ManifestCache const& m, ValidatorList& unl)
{
testcase ("load/store");
@@ -236,19 +245,13 @@ public:
setup.dataDir = getDatabasePath ();
DatabaseCon dbCon(setup, dbName, WalletDBInit, WalletDBCount);
if (!m.size ())
fail ();
m.save (dbCon);
ManifestCache loaded;
beast::Journal journal;
// load should remove master key from permanent key list
expect (m.trusted(pk));
expect (unl.insertPermanentKey(pk, "trusted key"));
expect (unl.trusted(pk));
loaded.load (dbCon, unl, journal);
expect (!unl.trusted(pk));
expect (loaded.trusted(pk));
auto getPopulatedManifests =
[](ManifestCache const& cache) -> std::vector<Manifest const*>
{
@@ -270,19 +273,51 @@ public:
};
std::vector<Manifest const*> const inManifests (
sort (getPopulatedManifests (m)));
std::vector<Manifest const*> const loadedManifests (
sort (getPopulatedManifests (loaded)));
if (inManifests.size () == loadedManifests.size ())
{
expect (std::equal
(inManifests.begin (), inManifests.end (),
loadedManifests.begin (),
[](Manifest const* lhs, Manifest const* rhs)
{return *lhs == *rhs;}));
// load should not load untrusted master keys from db
ManifestCache loaded;
loaded.load (dbCon, unl, journal);
expect (loaded.size() == 0);
}
else
{
fail ();
// load should load all trusted master keys from db
ManifestCache loaded;
for (auto const& man : inManifests)
loaded.addTrustedKey (man->masterKey, "");
loaded.load (dbCon, unl, journal);
std::vector<Manifest const*> const loadedManifests (
sort (getPopulatedManifests (loaded)));
if (inManifests.size () == loadedManifests.size ())
{
expect (std::equal
(inManifests.begin (), inManifests.end (),
loadedManifests.begin (),
[](Manifest const* lhs, Manifest const* rhs)
{return *lhs == *rhs;}));
}
else
{
fail ();
}
}
{
// load should remove master key from permanent key list
ManifestCache loaded;
auto const iMan = inManifests.begin();
if (!*iMan)
fail ();
expect (m.trusted((*iMan)->masterKey));
expect (unl.insertPermanentKey((*iMan)->masterKey, "trusted key"));
expect (unl.trusted((*iMan)->masterKey));
loaded.load (dbCon, unl, journal);
expect (!unl.trusted((*iMan)->masterKey));
expect (loaded.trusted((*iMan)->masterKey));
}
}
boost::filesystem::remove (getDatabasePath () /
@@ -315,7 +350,6 @@ public:
ManifestCache cache;
beast::Journal journal;
auto unl = std::make_unique<ValidatorList> (journal);
PublicKey pk;
{
testcase ("apply");
auto const accepted = ManifestDisposition::accepted;
@@ -360,18 +394,18 @@ public:
// When trusted permanent key is found as manifest master key
// move to manifest cache
auto const sk_c = randomSecretKey();
pk = derivePublicKey(KeyType::ed25519, sk_c);
auto const pk_c = derivePublicKey(KeyType::ed25519, sk_c);
auto const kp_c = randomKeyPair(KeyType::secp256k1);
auto const s_c0 = make_Manifest (KeyType::ed25519, sk_c, kp_c.first, 0);
expect (unl->insertPermanentKey(pk, "trusted key"));
expect (unl->trusted(pk));
expect (!cache.trusted(pk));
expect (unl->insertPermanentKey(pk_c, "trusted key"));
expect (unl->trusted(pk_c));
expect (!cache.trusted(pk_c));
expect (cache.applyManifest(clone (s_c0), *unl, journal) == accepted);
expect (!unl->trusted(pk));
expect (cache.trusted(pk));
expect (!unl->trusted(pk_c));
expect (cache.trusted(pk_c));
}
testConfigLoad();
testLoadStore (cache, *unl, pk);
testLoadStore (cache, *unl);
testGetSignature ();
}
};