mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
478 lines
13 KiB
C++
478 lines
13 KiB
C++
//------------------------------------------------------------------------------
|
|
/*
|
|
This file is part of rippled: https://github.com/ripple/rippled
|
|
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
|
|
|
Permission to use, copy, modify, and/or distribute this software for any
|
|
purpose with or without fee is hereby granted, provided that the above
|
|
copyright notice and this permission notice appear in all copies.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
//==============================================================================
|
|
|
|
#include <ripple/app/main/Application.h>
|
|
#include <ripple/basics/contract.h>
|
|
#include <ripple/basics/Log.h>
|
|
#include <ripple/app/misc/ValidatorList.h>
|
|
#include <ripple/core/DatabaseCon.h>
|
|
#include <ripple/overlay/impl/Manifest.h>
|
|
#include <ripple/protocol/PublicKey.h>
|
|
#include <ripple/protocol/Sign.h>
|
|
#include <boost/regex.hpp>
|
|
#include <stdexcept>
|
|
|
|
namespace ripple {
|
|
|
|
boost::optional<Manifest>
|
|
make_Manifest (std::string s)
|
|
{
|
|
try
|
|
{
|
|
STObject st (sfGeneric);
|
|
SerialIter sit (s.data (), s.size ());
|
|
st.set (sit);
|
|
auto const opt_pk = get<PublicKey>(st, sfPublicKey);
|
|
auto const opt_spk = get<PublicKey>(st, sfSigningPubKey);
|
|
auto const opt_seq = get (st, sfSequence);
|
|
auto const opt_sig = get (st, sfSignature);
|
|
auto const opt_msig = get (st, sfMasterSignature);
|
|
if (!opt_pk || !opt_spk || !opt_seq || !opt_sig || !opt_msig)
|
|
{
|
|
return boost::none;
|
|
}
|
|
return Manifest (std::move (s), *opt_pk, *opt_spk, *opt_seq);
|
|
}
|
|
catch (std::exception const&)
|
|
{
|
|
return boost::none;
|
|
}
|
|
}
|
|
|
|
template<class Stream>
|
|
Stream&
|
|
logMftAct (
|
|
Stream& s,
|
|
std::string const& action,
|
|
PublicKey const& pk,
|
|
std::uint32_t seq)
|
|
{
|
|
s << "Manifest: " << action <<
|
|
";Pk: " << toBase58 (TokenType::TOKEN_NODE_PUBLIC, pk) <<
|
|
";Seq: " << seq << ";";
|
|
return s;
|
|
}
|
|
|
|
template<class Stream>
|
|
Stream& logMftAct (
|
|
Stream& s,
|
|
std::string const& action,
|
|
PublicKey const& pk,
|
|
std::uint32_t seq,
|
|
std::uint32_t oldSeq)
|
|
{
|
|
s << "Manifest: " << action <<
|
|
";Pk: " << toBase58 (TokenType::TOKEN_NODE_PUBLIC, pk) <<
|
|
";Seq: " << seq <<
|
|
";OldSeq: " << oldSeq << ";";
|
|
return s;
|
|
}
|
|
|
|
Manifest::Manifest (std::string s,
|
|
PublicKey pk,
|
|
PublicKey spk,
|
|
std::uint32_t seq)
|
|
: serialized (std::move (s))
|
|
, masterKey (std::move (pk))
|
|
, signingKey (std::move (spk))
|
|
, sequence (seq)
|
|
{
|
|
}
|
|
|
|
bool Manifest::verify () const
|
|
{
|
|
STObject st (sfGeneric);
|
|
SerialIter sit (serialized.data (), serialized.size ());
|
|
st.set (sit);
|
|
if (! ripple::verify (st, HashPrefix::manifest, signingKey))
|
|
return false;
|
|
|
|
return ripple::verify (
|
|
st, HashPrefix::manifest, masterKey, sfMasterSignature);
|
|
}
|
|
|
|
uint256 Manifest::hash () const
|
|
{
|
|
STObject st (sfGeneric);
|
|
SerialIter sit (serialized.data (), serialized.size ());
|
|
st.set (sit);
|
|
return st.getHash (HashPrefix::manifest);
|
|
}
|
|
|
|
bool Manifest::revoked () const
|
|
{
|
|
/*
|
|
The maximum possible sequence number means that the master key
|
|
has been revoked.
|
|
*/
|
|
return sequence == std::numeric_limits<std::uint32_t>::max ();
|
|
}
|
|
|
|
Blob Manifest::getSignature () const
|
|
{
|
|
STObject st (sfGeneric);
|
|
SerialIter sit (serialized.data (), serialized.size ());
|
|
st.set (sit);
|
|
return st.getFieldVL (sfSignature);
|
|
}
|
|
|
|
Blob Manifest::getMasterSignature () const
|
|
{
|
|
STObject st (sfGeneric);
|
|
SerialIter sit (serialized.data (), serialized.size ());
|
|
st.set (sit);
|
|
return st.getFieldVL (sfMasterSignature);
|
|
}
|
|
|
|
bool
|
|
ManifestCache::loadValidatorKeys(
|
|
Section const& keys,
|
|
beast::Journal journal)
|
|
{
|
|
static boost::regex const re (
|
|
"[[:space:]]*" // skip leading whitespace
|
|
"([[:alnum:]]+)" // node identity
|
|
"(?:" // begin optional comment block
|
|
"[[:space:]]+" // (skip all leading whitespace)
|
|
"(?:" // begin optional comment
|
|
"(.*[^[:space:]]+)" // the comment
|
|
"[[:space:]]*" // (skip all trailing whitespace)
|
|
")?" // end optional comment
|
|
")?" // end optional comment block
|
|
);
|
|
|
|
JLOG (journal.debug()) <<
|
|
"Loading configured validator keys";
|
|
|
|
std::size_t count = 0;
|
|
|
|
for (auto const& line : keys.lines())
|
|
{
|
|
boost::smatch match;
|
|
|
|
if (!boost::regex_match (line, match, re))
|
|
{
|
|
JLOG (journal.error()) <<
|
|
"Malformed entry: '" << line << "'";
|
|
return false;
|
|
}
|
|
|
|
auto const key = parseBase58<PublicKey>(
|
|
TokenType::TOKEN_NODE_PUBLIC, match[1]);
|
|
|
|
if (!key)
|
|
{
|
|
JLOG (journal.error()) <<
|
|
"Error decoding validator key: " << match[1];
|
|
return false;
|
|
}
|
|
|
|
if (publicKeyType(*key) != KeyType::ed25519)
|
|
{
|
|
JLOG (journal.error()) <<
|
|
"Validator key not using Ed25519: " << match[1];
|
|
return false;
|
|
}
|
|
|
|
JLOG (journal.debug()) << "Loaded key: " << match[1];
|
|
|
|
addTrustedKey (*key, match[2]);
|
|
++count;
|
|
}
|
|
|
|
JLOG (journal.debug()) <<
|
|
"Loaded " << count << " entries";
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
ManifestCache::configManifest (
|
|
Manifest m, ValidatorList& unl, beast::Journal journal)
|
|
{
|
|
if (! m.verify())
|
|
{
|
|
Throw<std::runtime_error> ("Unverifiable manifest in config");
|
|
}
|
|
|
|
// Trust our own master public key
|
|
if (!trusted(m.masterKey) && !unl.trusted (m.masterKey))
|
|
{
|
|
addTrustedKey (m.masterKey, "");
|
|
}
|
|
|
|
auto const result = applyManifest (std::move(m), unl, journal);
|
|
|
|
if (result != ManifestDisposition::accepted)
|
|
{
|
|
Throw<std::runtime_error> ("Our own validation manifest was not accepted");
|
|
}
|
|
}
|
|
|
|
bool
|
|
ManifestCache::trusted (PublicKey const& identity) const
|
|
{
|
|
return map_.count(identity);
|
|
}
|
|
|
|
void
|
|
ManifestCache::addTrustedKey (PublicKey const& pk, std::string comment)
|
|
{
|
|
std::lock_guard<std::mutex> lock (mutex_);
|
|
|
|
auto& value = map_[pk];
|
|
|
|
if (value.m)
|
|
{
|
|
Throw<std::runtime_error> (
|
|
"New trusted validator key already has a manifest");
|
|
}
|
|
|
|
value.comment = std::move(comment);
|
|
}
|
|
|
|
std::size_t
|
|
ManifestCache::size () const
|
|
{
|
|
std::lock_guard <std::mutex> lock (mutex_);
|
|
return map_.size ();
|
|
}
|
|
|
|
ManifestDisposition
|
|
ManifestCache::canApply (PublicKey const& pk, std::uint32_t seq,
|
|
beast::Journal journal) const
|
|
{
|
|
auto const iter = map_.find(pk);
|
|
|
|
if (iter == map_.end())
|
|
{
|
|
/*
|
|
A manifest was received whose master key we don't trust.
|
|
Since rippled always sends all of its current manifests,
|
|
this will happen normally any time a peer connects.
|
|
*/
|
|
if (auto stream = journal.debug())
|
|
logMftAct(stream, "Untrusted", pk, seq);
|
|
return ManifestDisposition::untrusted;
|
|
}
|
|
|
|
auto& old = iter->second.m;
|
|
|
|
if (old && seq <= old->sequence)
|
|
{
|
|
/*
|
|
A manifest was received for a validator we're tracking, but
|
|
its sequence number is no higher than the one already stored.
|
|
This will happen normally when a peer without the latest gossip
|
|
connects.
|
|
*/
|
|
if (auto stream = journal.debug())
|
|
logMftAct(stream, "Stale", pk, seq, old->sequence);
|
|
return ManifestDisposition::stale; // not a newer manifest, ignore
|
|
}
|
|
|
|
return ManifestDisposition::accepted;
|
|
}
|
|
|
|
|
|
ManifestDisposition
|
|
ManifestCache::applyManifest (
|
|
Manifest m, ValidatorList& unl, beast::Journal journal)
|
|
{
|
|
/*
|
|
Move master public key from permanent trusted key list
|
|
to manifest cache.
|
|
*/
|
|
if (auto unlComment = unl.member (m.masterKey))
|
|
{
|
|
addTrustedKey (m.masterKey, *unlComment);
|
|
unl.removePermanentKey (m.masterKey);
|
|
}
|
|
|
|
{
|
|
std::lock_guard<std::mutex> lock (mutex_);
|
|
|
|
/*
|
|
before we spend time checking the signature, make sure we trust the
|
|
master key and the sequence number is newer than any we have.
|
|
*/
|
|
auto const chk = canApply(m.masterKey, m.sequence, journal);
|
|
|
|
if (chk != ManifestDisposition::accepted)
|
|
{
|
|
return chk;
|
|
}
|
|
}
|
|
|
|
if (! m.verify())
|
|
{
|
|
/*
|
|
A manifest's signature is invalid.
|
|
This shouldn't happen normally.
|
|
*/
|
|
if (auto stream = journal.warn())
|
|
logMftAct(stream, "Invalid", m.masterKey, m.sequence);
|
|
return ManifestDisposition::invalid;
|
|
}
|
|
|
|
std::lock_guard<std::mutex> lock (mutex_);
|
|
|
|
/*
|
|
We released the lock above, so we have to check again, in case
|
|
another thread accepted a newer manifest.
|
|
*/
|
|
auto const chk = canApply(m.masterKey, m.sequence, journal);
|
|
|
|
if (chk != ManifestDisposition::accepted)
|
|
{
|
|
return chk;
|
|
}
|
|
|
|
auto const iter = map_.find(m.masterKey);
|
|
|
|
auto& old = iter->second.m;
|
|
|
|
if (! old)
|
|
{
|
|
/*
|
|
This is the first received manifest for a trusted master key
|
|
(possibly our own). This only happens once per validator per
|
|
run (and possibly not at all, if there's an obsolete entry in
|
|
[validator_keys] for a validator that no longer exists).
|
|
*/
|
|
if (auto stream = journal.info())
|
|
logMftAct(stream, "AcceptedNew", m.masterKey, m.sequence);
|
|
}
|
|
else
|
|
{
|
|
if (m.revoked ())
|
|
{
|
|
/*
|
|
The MASTER key for this validator was revoked. This is
|
|
expected, but should happen at most *very* rarely.
|
|
*/
|
|
if (auto stream = journal.info())
|
|
logMftAct(stream, "Revoked",
|
|
m.masterKey, m.sequence, old->sequence);
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
An ephemeral key was revoked and superseded by a new key.
|
|
This is expected, but should happen infrequently.
|
|
*/
|
|
if (auto stream = journal.info())
|
|
logMftAct(stream, "AcceptedUpdate",
|
|
m.masterKey, m.sequence, old->sequence);
|
|
}
|
|
|
|
unl.removeEphemeralKey (old->signingKey);
|
|
}
|
|
|
|
if (m.revoked ())
|
|
{
|
|
// The master key is revoked -- don't insert the signing key
|
|
if (auto stream = journal.warn())
|
|
logMftAct(stream, "Revoked", m.masterKey, m.sequence);
|
|
|
|
/*
|
|
A validator master key has been compromised, so its manifests
|
|
are now untrustworthy. In order to prevent us from accepting
|
|
a forged manifest signed by the compromised master key, store
|
|
this manifest, which has the highest possible sequence number
|
|
and therefore can't be superseded by a forged one.
|
|
*/
|
|
}
|
|
else
|
|
{
|
|
unl.insertEphemeralKey (m.signingKey, iter->second.comment);
|
|
}
|
|
|
|
old = std::move(m);
|
|
|
|
return ManifestDisposition::accepted;
|
|
}
|
|
|
|
void ManifestCache::load (
|
|
DatabaseCon& dbCon, ValidatorList& unl, beast::Journal journal)
|
|
{
|
|
static const char* const sql =
|
|
"SELECT RawData FROM ValidatorManifests;";
|
|
auto db = dbCon.checkoutDb ();
|
|
soci::blob sociRawData (*db);
|
|
soci::statement st =
|
|
(db->prepare << sql,
|
|
soci::into (sociRawData));
|
|
st.execute ();
|
|
while (st.fetch ())
|
|
{
|
|
std::string serialized;
|
|
convert (sociRawData, serialized);
|
|
if (auto mo = make_Manifest (std::move (serialized)))
|
|
{
|
|
if (!mo->verify())
|
|
{
|
|
JLOG(journal.warn())
|
|
<< "Unverifiable manifest in db";
|
|
continue;
|
|
}
|
|
|
|
if (trusted(mo->masterKey) || unl.trusted(mo->masterKey))
|
|
{
|
|
applyManifest (std::move(*mo), unl, journal);
|
|
}
|
|
else
|
|
{
|
|
JLOG(journal.info())
|
|
<< "Manifest in db is no longer trusted";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
JLOG(journal.warn())
|
|
<< "Malformed manifest in database";
|
|
}
|
|
}
|
|
}
|
|
|
|
void ManifestCache::save (DatabaseCon& dbCon) const
|
|
{
|
|
auto db = dbCon.checkoutDb ();
|
|
|
|
soci::transaction tr(*db);
|
|
*db << "DELETE FROM ValidatorManifests";
|
|
static const char* const sql =
|
|
"INSERT INTO ValidatorManifests (RawData) VALUES (:rawData);";
|
|
for (auto const& v : map_)
|
|
{
|
|
if (!v.second.m)
|
|
continue;
|
|
|
|
// soci does not support bulk insertion of blob data
|
|
// Do not reuse blob because manifest ecdsa signatures vary in length
|
|
// but blob write length is expected to be >= the last write
|
|
soci::blob rawData(*db);
|
|
convert (v.second.m->serialized, rawData);
|
|
*db << sql,
|
|
soci::use (rawData);
|
|
}
|
|
tr.commit ();
|
|
}
|
|
}
|