Tidying & Selectively forward manifests to peers:

* Do not forward manifests to peers that already know that manifest
* Do not forward historical manifests to peers
* Save/Load ValidatorManifests from a database
* Python test for setting ephmeral keys
* Cleanup manifest interface
This commit is contained in:
seelabs
2015-04-30 13:27:35 -07:00
parent 31d352b3aa
commit 1b4e0f5f48
18 changed files with 1382 additions and 221 deletions

View File

@@ -806,6 +806,8 @@ public:
getConfig());
add (*m_overlay); // add to PropertyStream
m_overlay->setupValidatorKeyManifests (getConfig (), getWalletDB ());
{
auto setup = setup_ServerHandler(getConfig(), std::cerr);
setup.makeContexts();
@@ -900,6 +902,8 @@ public:
mValidations->flush ();
m_overlay->saveValidatorKeyManifests (getWalletDB ());
RippleAddress::clearCache ();
stopped ();
}

View File

@@ -231,6 +231,13 @@ const char* WalletDBInit[] =
PRIMARY KEY (Validator,Entry) \
);",
// Validator Manifests
R"(
CREATE TABLE IF NOT EXISTS ValidatorManifests (
RawData BLOB NOT NULL
);
)",
// List of referrals from ripple.txt files.
// Validator:
// Public key of referree.

View File

@@ -20,7 +20,7 @@
#ifndef RIPPLE_APP_PEERS_UNIQUENODELIST_H_INCLUDED
#define RIPPLE_APP_PEERS_UNIQUENODELIST_H_INCLUDED
#include <ripple/app/peers/ClusterNodeStatus.h>
#include <ripple/overlay/ClusterNodeStatus.h>
#include <ripple/protocol/AnyPublicKey.h>
#include <ripple/protocol/RippleAddress.h>
#include <beast/cxx14/memory.h> // <memory>

View File

@@ -114,6 +114,7 @@ size_t getKBUsedDB (soci::session& s);
void convert (soci::blob& from, std::vector<std::uint8_t>& to);
void convert (soci::blob& from, std::string& to);
void convert (std::vector<std::uint8_t> const& from, soci::blob& to);
void convert (std::string const& from, soci::blob& to);
class Checkpointer
{

View File

@@ -160,6 +160,16 @@ void convert (std::vector<std::uint8_t> const& from, soci::blob& to)
{
if (!from.empty ())
to.write (0, reinterpret_cast<char const*>(&from[0]), from.size ());
else
to.trim (0);
}
void convert (std::string const& from, soci::blob& to)
{
if (!from.empty ())
to.write (0, from.data (), from.size ());
else
to.trim (0);
}
namespace {

View File

@@ -38,6 +38,9 @@ namespace boost { namespace asio { namespace ssl { class context; } } }
namespace ripple {
class DatabaseCon;
class BasicConfig;
/** Manages the set of connected peers. */
class Overlay
: public beast::Stoppable
@@ -156,6 +159,15 @@ 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:
@@ -170,12 +182,11 @@ public:
@note The functor is passed by value!
*/
template<typename Function>
std::enable_if_t <
! std::is_void <typename Function::return_type>::value,
typename Function::return_type
>
foreach(Function f)
template <typename UnaryFunc>
std::enable_if_t<! std::is_void<
typename UnaryFunc::return_type>::value,
typename UnaryFunc::return_type>
foreach (UnaryFunc f)
{
PeerSequence peers (getActivePeers());
for(PeerSequence::const_iterator i = peers.begin(); i != peers.end(); ++i)

View File

@@ -18,7 +18,8 @@
//==============================================================================
#include <ripple/app/main/Application.h>
#include <ripple/app/peers/UniqueNodeList.h>
#include <ripple/app/misc/UniqueNodeList.h>
#include <ripple/core/DatabaseCon.h>
#include <ripple/overlay/impl/Manifest.h>
#include <ripple/protocol/RippleAddress.h>
#include <ripple/protocol/Sign.h>
@@ -27,12 +28,102 @@
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<AnyPublicKey>(st, sfPublicKey);
auto const opt_spk = get<AnyPublicKey>(st, sfSigningPubKey);
auto const opt_seq = get (st, sfSequence);
auto const opt_sig = get (st, sfSignature);
if (!opt_pk || !opt_spk || !opt_seq || !opt_sig)
{
return boost::none;
}
return Manifest (std::move (s), *opt_pk, *opt_spk, *opt_seq);
}
catch (...)
{
return boost::none;
}
}
template<class Stream>
Stream&
logMftAct (
Stream& s,
std::string const& action,
AnyPublicKey const& pk,
std::uint32_t seq)
{
s << "Manifest: " << action <<
";Pk: " << toString (pk) <<
";Seq: " << seq << ";";
return s;
}
template<class Stream>
Stream& logMftAct (
Stream& s,
std::string const& action,
AnyPublicKey const& pk,
std::uint32_t seq,
std::uint32_t oldSeq)
{
s << "Manifest: " << action <<
";Pk: " << toString (pk) <<
";Seq: " << seq <<
";OldSeq: " << oldSeq << ";";
return s;
}
Manifest::Manifest (std::string s,
AnyPublicKey pk,
AnyPublicKey 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);
return ripple::verify (st, HashPrefix::manifest, masterKey);
}
uint256 Manifest::hash () const
{
STObject st (sfGeneric);
SerialIter sit (serialized.data (), serialized.size ());
st.set (sit);
return st.getHash (HashPrefix::manifest);
}
bool Manifest::revoked () const
{
/*
The maximum possible sequence number means that the master key
has been revoked.
*/
return sequence == std::numeric_limits<std::uint32_t>::max ();
}
void
ManifestCache::configValidatorKey(std::string const& line, beast::Journal const& journal)
ManifestCache::configValidatorKey(
std::string const& line, beast::Journal const& journal)
{
auto const words = beast::rfc2616::split(line.begin(), line.end(), ' ');
if (words.size() != 2)
if (words.size () != 2)
{
throw std::runtime_error ("[validator_keys] format is `<key> <comment>");
}
@@ -56,44 +147,23 @@ ManifestCache::configValidatorKey(std::string const& line, beast::Journal const&
}
auto const masterKey = AnyPublicKey (key.data() + 1, key.size() - 1);
auto const& comment = words[1];
std::string comment = std::move(words[1]);
if (journal.debug) journal.debug
<< masterKey << " " << comment;
addTrustedKey (masterKey, comment);
addTrustedKey (masterKey, std::move(comment));
}
void
ManifestCache::configManifest (std::string s, beast::Journal const& journal)
ManifestCache::configManifest(Manifest m, beast::Journal const& journal)
{
STObject st(sfGeneric);
try
{
SerialIter sit(s.data(), s.size());
st.set(sit);
}
catch(...)
{
throw std::runtime_error("Malformed manifest in config");
}
auto const mseq = get(st, sfSequence);
auto const msig = get(st, sfSignature);
auto mpk = get<AnyPublicKey>(st, sfPublicKey);
auto mspk = get<AnyPublicKey>(st, sfSigningPubKey);
if (! mseq || ! msig || ! mpk || ! mspk)
{
throw std::runtime_error("Missing fields in manifest in config");
}
auto const& pk = *mpk;
if (! verify(st, HashPrefix::manifest, pk))
if (!m.verify())
{
throw std::runtime_error("Unverifiable manifest in config");
}
auto const result = applyManifest (std::move(s), journal);
auto const result = applyManifest (std::move(m), journal);
if (result != ManifestDisposition::accepted)
{
@@ -102,7 +172,7 @@ ManifestCache::configManifest (std::string s, beast::Journal const& journal)
}
void
ManifestCache::addTrustedKey (AnyPublicKey const& pk, std::string const& comment)
ManifestCache::addTrustedKey (AnyPublicKey const& pk, std::string comment)
{
std::lock_guard<std::mutex> lock (mutex_);
@@ -110,14 +180,15 @@ ManifestCache::addTrustedKey (AnyPublicKey const& pk, std::string const& comment
if (value.m)
{
throw std::runtime_error ("New trusted validator key already has a manifest");
throw std::runtime_error (
"New trusted validator key already has a manifest");
}
value.comment = comment;
value.comment = std::move(comment);
}
ManifestDisposition
ManifestCache::preflightManifest_locked (AnyPublicKey const& pk, std::uint32_t seq,
ManifestCache::canApply (AnyPublicKey const& pk, std::uint32_t seq,
beast::Journal const& journal) const
{
auto const iter = map_.find(pk);
@@ -129,14 +200,14 @@ ManifestCache::preflightManifest_locked (AnyPublicKey const& pk, std::uint32_t s
Since rippled always sends all of its current manifests,
this will happen normally any time a peer connects.
*/
if (journal.debug) journal.debug
<< "Ignoring manifest #" << seq << " from untrusted key " << pk;
if (journal.debug)
logMftAct(journal.debug, "Untrusted", pk, seq);
return ManifestDisposition::untrusted;
}
auto& old = iter->second.m;
if (old && seq <= old->seq)
if (old && seq <= old->sequence)
{
/*
A manifest was received for a validator we're tracking, but
@@ -144,67 +215,41 @@ ManifestCache::preflightManifest_locked (AnyPublicKey const& pk, std::uint32_t s
This will happen normally when a peer without the latest gossip
connects.
*/
if (journal.debug) journal.debug
<< "Ignoring manifest #" << seq
<< "which isn't newer than #" << old->seq;
if (journal.debug)
logMftAct(journal.debug, "Stale", pk, seq, old->sequence);
return ManifestDisposition::stale; // not a newer manifest, ignore
}
return ManifestDisposition::accepted;
}
ManifestDisposition
ManifestCache::applyManifest (std::string s, beast::Journal const& journal)
ManifestCache::applyManifest (Manifest m, beast::Journal const& journal)
{
STObject st(sfGeneric);
try
{
SerialIter sit(s.data(), s.size());
st.set(sit);
}
catch(...)
{
return ManifestDisposition::malformed;
}
auto const opt_pk = get<AnyPublicKey>(st, sfPublicKey);
auto const opt_spk = get<AnyPublicKey>(st, sfSigningPubKey);
auto const opt_seq = get(st, sfSequence);
auto const opt_sig = get(st, sfSignature);
if (! opt_pk || ! opt_spk || ! opt_seq || ! opt_sig)
{
return ManifestDisposition::incomplete;
}
auto const pk = *opt_pk;
auto const spk = *opt_spk;
auto const seq = *opt_seq;
{
std::lock_guard<std::mutex> lock (mutex_);
/*
"Preflight" the manifest -- before we spend time checking the
signature, make sure we trust the master key and the sequence
number is newer than any we have.
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 preflight = preflightManifest_locked(pk, seq, journal);
auto const chk = canApply(m.masterKey, m.sequence, journal);
if (preflight != ManifestDisposition::accepted)
if (chk != ManifestDisposition::accepted)
{
return preflight;
return chk;
}
}
if (! verify(st, HashPrefix::manifest, pk))
if (! m.verify())
{
/*
A manifest's signature is invalid.
This shouldn't happen normally.
A manifest's signature is invalid.
This shouldn't happen normally.
*/
if (journal.warning) journal.warning
<< "Failed to verify manifest #" << seq;
if (journal.warning)
logMftAct(journal.warning, "Invalid", m.masterKey, m.sequence);
return ManifestDisposition::invalid;
}
@@ -213,26 +258,20 @@ ManifestCache::applyManifest (std::string s, beast::Journal const& journal)
std::lock_guard<std::mutex> lock (mutex_);
/*
We released the lock above, so we have to preflight again, in case
We released the lock above, so we have to check again, in case
another thread accepted a newer manifest.
*/
auto const preflight = preflightManifest_locked(pk, seq, journal);
auto const chk = canApply(m.masterKey, m.sequence, journal);
if (preflight != ManifestDisposition::accepted)
if (chk != ManifestDisposition::accepted)
{
return preflight;
return chk;
}
auto const iter = map_.find(pk);
auto const iter = map_.find(m.masterKey);
auto& old = iter->second.m;
/*
The maximum possible sequence number means that the master key
has been revoked.
*/
auto const revoked = std::uint32_t (-1);
if (! old)
{
/*
@@ -241,20 +280,20 @@ ManifestCache::applyManifest (std::string s, beast::Journal const& journal)
run (and possibly not at all, if there's an obsolete entry in
[validator_keys] for a validator that no longer exists).
*/
if (journal.info) journal.info
<< "Adding new manifest #" << seq;
if (journal.info)
logMftAct(journal.info, "AcceptedNew", m.masterKey, m.sequence);
}
else
{
if (seq == revoked)
if (m.revoked ())
{
/*
The MASTER key for this validator was revoked. This is
expected, but should happen at most *very* rarely.
*/
if (journal.warning) journal.warning
<< "Dropping old manifest #" << old->seq
<< " because the master key was revoked";
if (journal.info)
logMftAct(journal.info, "Revoked",
m.masterKey, m.sequence, old->sequence);
}
else
{
@@ -262,19 +301,19 @@ ManifestCache::applyManifest (std::string s, beast::Journal const& journal)
An ephemeral key was revoked and superseded by a new key.
This is expected, but should happen infrequently.
*/
if (journal.warning) journal.warning
<< "Dropping old manifest #" << old->seq
<< " in favor of #" << seq;
if (journal.info)
logMftAct(journal.info, "AcceptedUpdate",
m.masterKey, m.sequence, old->sequence);
}
unl.deleteEphemeralKey (old->signingKey);
}
if (seq == revoked)
if (m.revoked ())
{
// The master key is revoked -- don't insert the signing key
if (auto const& j = journal.warning)
j << "Revoking master key: " << pk;
if (journal.warning)
logMftAct(journal.warning, "Revoked", m.masterKey, m.sequence);
/*
A validator master key has been compromised, so its manifests
@@ -286,12 +325,68 @@ ManifestCache::applyManifest (std::string s, beast::Journal const& journal)
}
else
{
unl.insertEphemeralKey (spk, iter->second.comment);
unl.insertEphemeralKey (m.signingKey, iter->second.comment);
}
old = Manifest(std::move (s), std::move (pk), std::move (spk), seq);
old = std::move(m);
return ManifestDisposition::accepted;
}
void ManifestCache::load (
DatabaseCon& dbCon, beast::Journal const& 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())
{
throw std::runtime_error("Unverifiable manifest in db");
}
// add trusted key
map_[mo->masterKey];
// OK if not accepted (may have been loaded from the config file)
applyManifest (std::move(*mo), journal);
}
else
{
if (journal.warning)
journal.warning << "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);";
// soci does not support bulk insertion of blob data
soci::blob rawData(*db);
for (auto const& v : map_)
{
if (!v.second.m)
continue;
convert (v.second.m->serialized, rawData);
*db << sql,
soci::use (rawData);
}
tr.commit ();
}
}

View File

@@ -34,17 +34,15 @@ namespace ripple {
Validator key manifests
-----------------------
First, a rationale: Suppose a system adminstrator leaves the company.
You err on the side of caution (if not paranoia) and assume that the
secret keys installed on Ripple validators are compromised. Not only
do you have to generate and install new key pairs on each validator,
EVERY rippled needs to have its config updated with the new public keys,
and is vulnerable to forged validation signatures until this is done.
The solution is a new layer of indirection: A master secret key under
Suppose the secret keys installed on a Ripple validator are compromised. Not
only do you have to generate and install new key pairs on each validator,
EVERY rippled needs to have its config updated with the new public keys, and
is vulnerable to forged validation signatures until this is done. The
solution is a new layer of indirection: A master secret key under
restrictive access control is used to sign a "manifest": essentially, a
certificate including the master public key, an ephemeral public key for
verifying validations (which will be signed by its secret counterpart),
a sequence number, and a digital signature.
verifying validations (which will be signed by its secret counterpart), a
sequence number, and a digital signature.
The manifest has two serialized forms: one which includes the digital
signature and one which doesn't. There is an obvious causal dependency
@@ -89,22 +87,16 @@ struct Manifest
std::string serialized;
AnyPublicKey masterKey;
AnyPublicKey signingKey;
std::uint32_t seq;
std::uint32_t sequence;
Manifest(std::string s, AnyPublicKey pk, AnyPublicKey spk, std::uint32_t seq)
: serialized(std::move(s))
, masterKey(std::move(pk))
, signingKey(std::move(spk))
, seq(seq)
{
}
Manifest(std::string s, AnyPublicKey pk, AnyPublicKey spk, std::uint32_t seq);
#ifdef _MSC_VER
Manifest(Manifest&& other)
: serialized(std::move(other.serialized))
, masterKey(std::move(other.masterKey))
, signingKey(std::move(other.signingKey))
, seq(other.seq)
, sequence(other.sequence)
{
}
@@ -113,26 +105,42 @@ struct Manifest
serialized = std::move(other.serialized);
masterKey = std::move(other.masterKey);
signingKey = std::move(other.signingKey);
seq = other.seq;
sequence = other.sequence;
return *this;
}
#else
Manifest(Manifest&& other) = default;
Manifest& operator=(Manifest&& other) = default;
#endif
bool verify () const;
uint256 hash () const;
bool revoked () const;
};
boost::optional<Manifest> make_Manifest(std::string s);
inline bool operator==(Manifest const& lhs, Manifest const& rhs)
{
return lhs.serialized == rhs.serialized && lhs.masterKey == rhs.masterKey &&
lhs.signingKey == rhs.signingKey && lhs.sequence == rhs.sequence;
}
inline bool operator!=(Manifest const& lhs, Manifest const& rhs)
{
return !(lhs == rhs);
}
enum class ManifestDisposition
{
accepted = 0, // everything checked out
accepted = 0, // everything checked out
malformed, // deserialization fails
incomplete, // fields are missing
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
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
};
class DatabaseCon;
/** Remembers manifests with the highest sequence number. */
class ManifestCache
{
@@ -140,6 +148,30 @@ private:
struct MappedType
{
MappedType() = default;
#ifdef _MSC_VER
MappedType(MappedType&& rhs)
:comment (std::move (rhs.comment))
, m (std::move (rhs.m))
{
}
MappedType& operator=(MappedType&& rhs)
{
comment = std::move (rhs.comment);
m = std::move (rhs.m);
return *this;
}
#else
MappedType(MappedType&&) = default;
MappedType& operator=(MappedType&&) = default;
#endif
MappedType(std::string comment,
std::string serialized,
AnyPublicKey pk, AnyPublicKey spk, std::uint32_t seq)
:comment (std::move(comment))
{
m.emplace (std::move(serialized), std::move(pk), std::move(spk),
seq);
}
std::string comment;
boost::optional<Manifest> m;
@@ -151,7 +183,7 @@ private:
MapType map_;
ManifestDisposition
preflightManifest_locked (AnyPublicKey const& pk, std::uint32_t seq,
canApply (AnyPublicKey const& pk, std::uint32_t seq,
beast::Journal const& journal) const;
public:
@@ -161,12 +193,15 @@ public:
~ManifestCache() = default;
void configValidatorKey(std::string const& line, beast::Journal const& journal);
void configManifest(std::string s, beast::Journal const& journal);
void configManifest(Manifest m, beast::Journal const& journal);
void addTrustedKey (AnyPublicKey const& pk, std::string const& comment);
void addTrustedKey (AnyPublicKey const& pk, std::string comment);
ManifestDisposition
applyManifest (std::string s, beast::Journal const& journal);
applyManifest (Manifest m, beast::Journal const& journal);
void load (DatabaseCon& dbCon, beast::Journal const& journal);
void save (DatabaseCon& dbCon) const;
// A "for_each" for populated manifests only
template <class Function>
@@ -180,6 +215,22 @@ public:
f(*m);
}
}
// 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)
template <class PreFun, class EachFun>
void
for_each_manifest(PreFun&& pf, EachFun&& f) const
{
std::lock_guard<std::mutex> lock (mutex_);
pf(map_.size ());
for (auto const& e : map_)
{
if (auto const& m = e.second.m)
f(*m);
}
}
};
} // ripple

View File

@@ -19,6 +19,7 @@
#include <BeastConfig.h>
#include <ripple/app/misc/IHashRouter.h>
#include <ripple/core/DatabaseCon.h>
#include <ripple/basics/Log.h>
#include <ripple/basics/make_SSLContext.h>
#include <ripple/protocol/JsonFields.h>
@@ -426,24 +427,24 @@ OverlayImpl::checkStopped ()
stopped();
}
static
void
prepareValidatorKeyManifests (ManifestCache& mc, beast::Journal const& journal)
OverlayImpl::setupValidatorKeyManifests (BasicConfig const& config,
DatabaseCon& db)
{
auto const validator_keys = getConfig().section("validator_keys");
auto const validation_manifest = getConfig().section("validation_manifest");
auto const validator_keys = config.section ("validator_keys");
auto const validation_manifest = config.section ("validation_manifest");
if (! validator_keys.lines().empty())
{
for (auto const& line : validator_keys.lines())
{
mc.configValidatorKey (line, journal);
manifestCache_.configValidatorKey (line, journal_);
}
}
else
{
if (journal.warning)
journal.warning << "[validator_keys] is empty";
if (journal_.warning)
journal_.warning << "[validator_keys] is empty";
}
if (! validation_manifest.lines().empty())
@@ -452,21 +453,33 @@ prepareValidatorKeyManifests (ManifestCache& mc, beast::Journal const& journal)
for (auto const& line : validation_manifest.lines())
s += beast::rfc2616::trim(line);
s = beast::base64_decode(s);
mc.configManifest(std::move(s), journal);
if (auto mo = make_Manifest (std::move (s)))
{
manifestCache_.configManifest (std::move (*mo), journal_);
}
else
{
throw std::runtime_error("Malformed manifest in config");
}
}
else
{
if (journal.warning)
journal.warning << "No [validation_manifest] section in config";
if (journal_.warning)
journal_.warning << "No [validation_manifest] section in config";
}
manifestCache_.load (db, journal_);
}
void
OverlayImpl::saveValidatorKeyManifests (DatabaseCon& db) const
{
manifestCache_.save (db);
}
void
OverlayImpl::onPrepare()
{
prepareValidatorKeyManifests (manifestCache_, journal_);
PeerFinder::Config config;
if (getConfig ().PEERS_MAX != 0)
@@ -617,53 +630,74 @@ OverlayImpl::onPeerDeactivate (Peer::id_t id,
void
OverlayImpl::onManifests (Job&,
std::shared_ptr<protocol::TMManifests> const& inbox,
std::shared_ptr<protocol::TMManifests> const& m,
std::shared_ptr<PeerImp> const& from)
{
auto& hashRouter = getApp().getHashRouter();
auto const n = inbox->list_size();
auto const n = m->list_size();
auto const& journal = from->pjournal();
if (journal.debug) journal.debug
<< "TMManifest, " << n << (n == 1 ? " item" : " items");
protocol::TMManifests outbox;
bool const history = m->history ();
for (std::size_t i = 0; i < n; ++i)
{
auto& s = inbox->list().Get(i).stobject();
auto& s = m->list ().Get (i).stobject ();
uint256 const hash = getSHA512Half (s);
auto const result = manifestCache_.applyManifest (s, journal);
if (result == ManifestDisposition::accepted)
if (auto mo = make_Manifest (s))
{
protocol::TMManifests outbox;
uint256 const hash = mo->hash ();
if (!hashRouter.addSuppressionPeer (hash, from->id ()))
continue;
outbox.add_list()->set_stobject(s);
auto const result =
manifestCache_.applyManifest (std::move (*mo), journal);
auto const msg = std::make_shared<Message>(outbox, protocol::mtMANIFESTS);
if (result == ManifestDisposition::accepted)
{
auto db = getApp ().getWalletDB ().checkoutDb ();
for_each( [&](std::shared_ptr<PeerImp> const& peer)
{
if (hashRouter.addSuppressionPeer (hash, peer->id()) && peer != from)
{
if (auto& j = peer->pjournal().warning)
j << "Forwarding manifest with hash " << hash;
peer->send(msg);
}
else if (peer != from)
{
if (auto& j = peer->pjournal().warning)
j << "Suppressed manifest with hash " << hash;
}
});
soci::transaction tr(*db);
static const char* const sql =
"INSERT INTO ValidatorManifests (RawData) VALUES (:rawData);";
soci::blob rawData(*db);
convert (mo->serialized, rawData);
*db << sql, soci::use (rawData);
tr.commit ();
}
if (history)
{
// Historical manifests are sent on initial peer connections.
// They do not need to be forwarded to other peers.
std::set<Peer::id_t> peers;
hashRouter.swapSet (hash, peers, SF_RELAYED);
continue;
}
if (result == ManifestDisposition::accepted)
{
protocol::TMManifests o;
o.add_list ()->set_stobject (s);
std::set<Peer::id_t> peers;
hashRouter.swapSet (hash, peers, SF_RELAYED);
foreach (send_if_not (
std::make_shared<Message>(o, protocol::mtMANIFESTS),
peer_in_set (peers)));
}
else
{
if (journal.info)
journal.info << "Bad manifest #" << i + 1;
}
}
else
{
if (journal.info) journal.info
<< "Bad manifest #" << i + 1;
if (journal.warning)
journal.warning << "Malformed manifest #" << i + 1;
continue;
}
}
}

View File

@@ -192,6 +192,15 @@ 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

View File

@@ -655,9 +655,11 @@ PeerImp::doProtocolStart()
onReadMessage(error_code(), 0);
protocol::TMManifests tm;
tm.set_history (true);
overlay_.manifestCache().for_each_manifest(
[&](Manifest const& manifest)
overlay_.manifestCache ().for_each_manifest (
[&tm](size_t s){tm.mutable_list()->Reserve(s);},
[&tm](Manifest const& manifest)
{
auto const& s = manifest.serialized;
auto& tm_e = *tm.add_list();

View File

@@ -20,21 +20,76 @@
#include <BeastConfig.h>
#include <ripple/basics/TestSuite.h>
#include <ripple/overlay/impl/Manifest.h>
#include <ripple/core/DatabaseCon.h>
#include <ripple/app/main/DBInit.h>
#include <ripple/protocol/Sign.h>
#include <ripple/protocol/STExchange.h>
#include <boost/filesystem.hpp>
#include <boost/algorithm/string.hpp>
namespace ripple {
namespace tests {
class manifest_test : public ripple::TestSuite
{
private:
static void cleanupDatabaseDir (boost::filesystem::path const& dbPath)
{
using namespace boost::filesystem;
if (!exists (dbPath) || !is_directory (dbPath) || !is_empty (dbPath))
return;
remove (dbPath);
}
static void setupDatabaseDir (boost::filesystem::path const& dbPath)
{
using namespace boost::filesystem;
if (!exists (dbPath))
{
create_directory (dbPath);
return;
}
if (!is_directory (dbPath))
{
// someone created a file where we want to put our directory
throw std::runtime_error ("Cannot create directory: " +
dbPath.string ());
}
}
static boost::filesystem::path getDatabasePath ()
{
return boost::filesystem::current_path () / "manifest_test_databases";
}
public:
// Return a manifest in both serialized and STObject form
std::string
make_manifest(AnySecretKey const& sk, AnyPublicKey const& spk, int seq, bool broken = false)
manifest_test ()
{
try
{
setupDatabaseDir (getDatabasePath ());
}
catch (...)
{
}
}
~manifest_test ()
{
try
{
cleanupDatabaseDir (getDatabasePath ());
}
catch (...)
{
}
}
Manifest
make_Manifest
(AnySecretKey const& sk, AnyPublicKey const& spk, int seq,
bool broken = false)
{
auto const pk = sk.publicKey();
STObject st(sfGeneric);
set(st, sfSequence, seq);
set(st, sfPublicKey, pk);
@@ -52,53 +107,125 @@ public:
st.add(s);
std::string const m (static_cast<char const*> (s.data()), s.size());
return m;
if (auto r = ripple::make_Manifest (std::move (m)))
{
return std::move (*r);
}
throw std::runtime_error("Could not create a manifest");
}
Manifest
clone (Manifest const& m)
{
return Manifest (m.serialized, m.masterKey, m.signingKey, m.sequence);
}
void testLoadStore (ManifestCache const& m)
{
testcase ("load/store");
std::string const dbName("ManifestCacheTestDB");
{
// create a database, save the manifest to the db and reload and
// check that the manifest caches are the same
DatabaseCon::Setup setup;
setup.dataDir = getDatabasePath ();
DatabaseCon dbCon(setup, dbName, WalletDBInit, WalletDBCount);
m.save (dbCon);
ManifestCache loaded;
beast::Journal journal;
loaded.load (dbCon, journal);
auto getPopulatedManifests =
[](ManifestCache const& cache) -> std::vector<Manifest const*>
{
std::vector<Manifest const*> result;
result.reserve (32);
cache.for_each_manifest (
[&result](Manifest const& m)
{result.push_back (&m);});
return result;
};
auto sort =
[](std::vector<Manifest const*> mv) -> std::vector<Manifest const*>
{
std::sort (mv.begin (),
mv.end (),
[](Manifest const* lhs, Manifest const* rhs)
{return lhs->serialized < rhs->serialized;});
return mv;
};
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;}));
}
else
{
fail ();
}
}
boost::filesystem::remove (getDatabasePath () /
boost::filesystem::path (dbName));
}
void
run() override
{
auto const accepted = ManifestDisposition::accepted;
auto const malformed = ManifestDisposition::malformed;
auto const untrusted = ManifestDisposition::untrusted;
auto const stale = ManifestDisposition::stale;
auto const invalid = ManifestDisposition::invalid;
beast::Journal journal;
auto const sk_a = AnySecretKey::make_ed25519();
auto const sk_b = AnySecretKey::make_ed25519();
auto const pk_a = sk_a.publicKey();
auto const pk_b = sk_b.publicKey();
auto const kp_a = AnySecretKey::make_secp256k1_pair();
auto const kp_b = AnySecretKey::make_secp256k1_pair();
auto const s_a0 = make_manifest(sk_a, kp_a.second, 0);
auto const s_a1 = make_manifest(sk_a, kp_a.second, 1);
auto const s_b0 = make_manifest(sk_b, kp_b.second, 0);
auto const s_b1 = make_manifest(sk_b, kp_b.second, 1);
auto const s_b2 = make_manifest(sk_b, kp_b.second, 2, true); // broken
auto const fake = s_b1 + '\0';
ManifestCache cache;
{
testcase ("apply");
auto const accepted = ManifestDisposition::accepted;
auto const untrusted = ManifestDisposition::untrusted;
auto const stale = ManifestDisposition::stale;
auto const invalid = ManifestDisposition::invalid;
expect(cache.applyManifest(s_a0, journal) == untrusted, "have to install a trusted key first");
beast::Journal journal;
cache.addTrustedKey(pk_a, "a");
cache.addTrustedKey(pk_b, "b");
auto const sk_a = AnySecretKey::make_ed25519 ();
auto const sk_b = AnySecretKey::make_ed25519 ();
auto const pk_a = sk_a.publicKey ();
auto const pk_b = sk_b.publicKey ();
auto const kp_a = AnySecretKey::make_secp256k1_pair ();
auto const kp_b = AnySecretKey::make_secp256k1_pair ();
expect(cache.applyManifest(s_a0, journal) == accepted);
expect(cache.applyManifest(s_a0, journal) == stale);
auto const s_a0 = make_Manifest (sk_a, kp_a.second, 0);
auto const s_a1 = make_Manifest (sk_a, kp_a.second, 1);
auto const s_b0 = make_Manifest (sk_b, kp_b.second, 0);
auto const s_b1 = make_Manifest (sk_b, kp_b.second, 1);
auto const s_b2 =
make_Manifest (sk_b, kp_b.second, 2, true); // broken
auto const fake = s_b1.serialized + '\0';
expect(cache.applyManifest(s_a1, journal) == accepted);
expect(cache.applyManifest(s_a1, journal) == stale);
expect(cache.applyManifest(s_a0, journal) == stale);
expect (cache.applyManifest (clone (s_a0), journal) == untrusted,
"have to install a trusted key first");
expect(cache.applyManifest(s_b0, journal) == accepted);
expect(cache.applyManifest(s_b0, journal) == stale);
cache.addTrustedKey (pk_a, "a");
cache.addTrustedKey (pk_b, "b");
expect(cache.applyManifest(fake, journal) == malformed);
expect(cache.applyManifest(s_b2, journal) == invalid);
expect (cache.applyManifest (clone (s_a0), journal) == accepted);
expect (cache.applyManifest (clone (s_a0), journal) == stale);
expect (cache.applyManifest (clone (s_a1), journal) == accepted);
expect (cache.applyManifest (clone (s_a1), journal) == stale);
expect (cache.applyManifest (clone (s_a0), journal) == stale);
expect (cache.applyManifest (clone (s_b0), journal) == accepted);
expect (cache.applyManifest (clone (s_b0), journal) == stale);
expect (!ripple::make_Manifest(fake));
expect (cache.applyManifest (clone (s_b2), journal) == invalid);
}
testLoadStore (cache);
}
};

View File

@@ -44,6 +44,9 @@ message TMManifest
message TMManifests
{
repeated TMManifest list = 1;
// The manifests sent when a peer first connects to another peer are `history`.
// The receiving peer does not forward them.
optional bool history = 2 [default = false];
}
//------------------------------------------------------------------------------

View File

@@ -123,7 +123,8 @@ public:
AnyPublicKey& operator= (AnyPublicKey&& other)
{
buffer_type::member =
std::move(other.buffer_type::member);
std::move (other.buffer_type::member);
AnyPublicKeySlice::operator= (other);
return *this;
}
#else
@@ -179,6 +180,9 @@ struct STExchange<STBlob, AnyPublicKey>
}
};
std::string
toString (AnyPublicKey const& pk);
} // ripple
#endif

View File

@@ -90,4 +90,15 @@ AnyPublicKeySlice::verify (
return false;
}
std::string
toString (AnyPublicKey const& pk)
{
Blob buffer;
buffer.reserve (1 + pk.size ());
buffer.push_back (VER_NODE_PUBLIC);
auto const data = pk.data ();
buffer.insert (buffer.end (), data, data + pk.size ());
return Base58::encodeWithCheck (buffer);
}
} // ripple

View File

@@ -34,6 +34,6 @@ HashPrefix const HashPrefix::txSign ('S', 'T', 'X');
HashPrefix const HashPrefix::txMultiSign ('S', 'M', 'T');
HashPrefix const HashPrefix::validation ('V', 'A', 'L');
HashPrefix const HashPrefix::proposal ('P', 'R', 'P');
HashPrefix const HashPrefix::manifest ('M', 'A', 'N');
HashPrefix const HashPrefix::manifest ('M', 'A', 'N');
} // ripple