mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
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:
@@ -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 ();
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 ();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user