mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-19 02:25:52 +00:00
Add Validator Manifests (RIPD-772):
A Validator Manifest allows validators to use a generated ed25519 secret key as a master key for generating new validator public/secret key pairs. Using this mechanism, rippled instances trust the master ed25519 public key instead of the now-ephemeral validator public key. Through a new message and propagation scheme, this lets a validator change its ephemeral public key without requiring that all rippled instances on the network restart after maintaining the configuration file.
This commit is contained in:
@@ -2480,6 +2480,12 @@
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ripple\overlay\impl\ConnectAttempt.h">
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ripple\overlay\impl\Manifest.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ripple\overlay\impl\Manifest.h">
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ripple\overlay\impl\Message.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
@@ -2526,6 +2532,10 @@
|
||||
</ClInclude>
|
||||
<None Include="..\..\src\ripple\overlay\README.md">
|
||||
</None>
|
||||
<ClCompile Include="..\..\src\ripple\overlay\tests\manifest_test.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\overlay\tests\short_read.test.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
|
||||
@@ -3138,6 +3138,12 @@
|
||||
<ClInclude Include="..\..\src\ripple\overlay\impl\ConnectAttempt.h">
|
||||
<Filter>ripple\overlay\impl</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ripple\overlay\impl\Manifest.cpp">
|
||||
<Filter>ripple\overlay\impl</Filter>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ripple\overlay\impl\Manifest.h">
|
||||
<Filter>ripple\overlay\impl</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ripple\overlay\impl\Message.cpp">
|
||||
<Filter>ripple\overlay\impl</Filter>
|
||||
</ClCompile>
|
||||
@@ -3192,6 +3198,9 @@
|
||||
<None Include="..\..\src\ripple\overlay\README.md">
|
||||
<Filter>ripple\overlay</Filter>
|
||||
</None>
|
||||
<ClCompile Include="..\..\src\ripple\overlay\tests\manifest_test.cpp">
|
||||
<Filter>ripple\overlay\tests</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\overlay\tests\short_read.test.cpp">
|
||||
<Filter>ripple\overlay\tests</Filter>
|
||||
</ClCompile>
|
||||
|
||||
@@ -110,9 +110,9 @@ private:
|
||||
|
||||
Entry& findCreateEntry (uint256 const& , bool& created);
|
||||
|
||||
using LockType = std::mutex;
|
||||
using ScopedLockType = std::lock_guard <LockType>;
|
||||
LockType mLock;
|
||||
using MutexType = std::mutex;
|
||||
using ScopedLockType = std::lock_guard <MutexType>;
|
||||
MutexType mMutex;
|
||||
|
||||
// Stores all suppressed hashes and their expiration time
|
||||
hash_map <uint256, Entry> mSuppressionMap;
|
||||
@@ -156,7 +156,7 @@ HashRouter::Entry& HashRouter::findCreateEntry (uint256 const& index, bool& crea
|
||||
|
||||
bool HashRouter::addSuppression (uint256 const& index)
|
||||
{
|
||||
ScopedLockType sl (mLock);
|
||||
ScopedLockType lock (mMutex);
|
||||
|
||||
bool created;
|
||||
findCreateEntry (index, created);
|
||||
@@ -165,7 +165,7 @@ bool HashRouter::addSuppression (uint256 const& index)
|
||||
|
||||
HashRouter::Entry HashRouter::getEntry (uint256 const& index)
|
||||
{
|
||||
ScopedLockType sl (mLock);
|
||||
ScopedLockType lock (mMutex);
|
||||
|
||||
bool created;
|
||||
return findCreateEntry (index, created);
|
||||
@@ -173,7 +173,7 @@ HashRouter::Entry HashRouter::getEntry (uint256 const& index)
|
||||
|
||||
bool HashRouter::addSuppressionPeer (uint256 const& index, PeerShortID peer)
|
||||
{
|
||||
ScopedLockType sl (mLock);
|
||||
ScopedLockType lock (mMutex);
|
||||
|
||||
bool created;
|
||||
findCreateEntry (index, created).addPeer (peer);
|
||||
@@ -182,7 +182,7 @@ bool HashRouter::addSuppressionPeer (uint256 const& index, PeerShortID peer)
|
||||
|
||||
bool HashRouter::addSuppressionPeer (uint256 const& index, PeerShortID peer, int& flags)
|
||||
{
|
||||
ScopedLockType sl (mLock);
|
||||
ScopedLockType lock (mMutex);
|
||||
|
||||
bool created;
|
||||
Entry& s = findCreateEntry (index, created);
|
||||
@@ -193,7 +193,7 @@ bool HashRouter::addSuppressionPeer (uint256 const& index, PeerShortID peer, int
|
||||
|
||||
int HashRouter::getFlags (uint256 const& index)
|
||||
{
|
||||
ScopedLockType sl (mLock);
|
||||
ScopedLockType lock (mMutex);
|
||||
|
||||
bool created;
|
||||
return findCreateEntry (index, created).getFlags ();
|
||||
@@ -201,7 +201,7 @@ int HashRouter::getFlags (uint256 const& index)
|
||||
|
||||
bool HashRouter::addSuppressionFlags (uint256 const& index, int flag)
|
||||
{
|
||||
ScopedLockType sl (mLock);
|
||||
ScopedLockType lock (mMutex);
|
||||
|
||||
bool created;
|
||||
findCreateEntry (index, created).setFlag (flag);
|
||||
@@ -217,7 +217,7 @@ bool HashRouter::setFlag (uint256 const& index, int flag)
|
||||
// return: true = changed, false = unchanged
|
||||
assert (flag != 0);
|
||||
|
||||
ScopedLockType sl (mLock);
|
||||
ScopedLockType lock (mMutex);
|
||||
|
||||
bool created;
|
||||
Entry& s = findCreateEntry (index, created);
|
||||
@@ -231,7 +231,7 @@ bool HashRouter::setFlag (uint256 const& index, int flag)
|
||||
|
||||
bool HashRouter::swapSet (uint256 const& index, std::set<PeerShortID>& peers, int flag)
|
||||
{
|
||||
ScopedLockType sl (mLock);
|
||||
ScopedLockType lock (mMutex);
|
||||
|
||||
bool created;
|
||||
Entry& s = findCreateEntry (index, created);
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <ripple/basics/Time.h>
|
||||
#include <ripple/core/Config.h>
|
||||
#include <ripple/core/LoadFeeTrack.h>
|
||||
#include <ripple/crypto/Base58.h>
|
||||
#include <ripple/net/HTTPClient.h>
|
||||
#include <ripple/protocol/JsonFields.h>
|
||||
#include <beast/module/core/thread/DeadlineTimer.h>
|
||||
@@ -90,6 +91,18 @@ strJoin (Iterator first, Iterator last, std::string strSeparator)
|
||||
return ossValues.str ();
|
||||
}
|
||||
|
||||
static
|
||||
std::string
|
||||
encodeCredential (AnyPublicKey const& pk, unsigned char type)
|
||||
{
|
||||
Blob buffer;
|
||||
buffer.reserve(1 + pk.size());
|
||||
buffer.push_back (type);
|
||||
auto const data = pk.data();
|
||||
buffer.insert (buffer.end(), data, data + pk.size());
|
||||
return Base58::encodeWithCheck (buffer);
|
||||
}
|
||||
|
||||
template <size_t I, class String>
|
||||
void selectBlobsIntoStrings (
|
||||
soci::session& s,
|
||||
@@ -214,6 +227,7 @@ private:
|
||||
// XXX Make this faster, make this the contents vector unsigned char or raw public key.
|
||||
// XXX Contents needs to based on score.
|
||||
hash_set<std::string> mUNL;
|
||||
hash_map<AnyPublicKey, std::string> ephemeralValidatorKeys_;
|
||||
|
||||
boost::posix_time::ptime mtpScoreNext; // When to start scoring.
|
||||
boost::posix_time::ptime mtpScoreStart; // Time currently started scoring.
|
||||
@@ -244,6 +258,9 @@ public:
|
||||
// Get update times and start fetching and scoring as needed.
|
||||
void start();
|
||||
|
||||
void insertEphemeralKey (AnyPublicKey pk, std::string comment);
|
||||
void deleteEphemeralKey (AnyPublicKey const& pk);
|
||||
|
||||
// Add a trusted node. Called by RPC or other source.
|
||||
void nodeAddPublic (RippleAddress const& naNodePublic, ValidatorSource vsWhy, std::string const& strComment);
|
||||
|
||||
@@ -468,6 +485,22 @@ void UniqueNodeListImp::start()
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
void UniqueNodeListImp::insertEphemeralKey (AnyPublicKey pk, std::string comment)
|
||||
{
|
||||
ScopedUNLLockType sl (mUNLLock);
|
||||
|
||||
ephemeralValidatorKeys_.insert (std::make_pair(std::move(pk), std::move(comment)));
|
||||
}
|
||||
|
||||
void UniqueNodeListImp::deleteEphemeralKey (AnyPublicKey const& pk)
|
||||
{
|
||||
ScopedUNLLockType sl (mUNLLock);
|
||||
|
||||
ephemeralValidatorKeys_.erase (pk);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
// Add a trusted node. Called by RPC or other source.
|
||||
void UniqueNodeListImp::nodeAddPublic (RippleAddress const& naNodePublic, ValidatorSource vsWhy, std::string const& strComment)
|
||||
{
|
||||
@@ -612,9 +645,17 @@ void UniqueNodeListImp::nodeScore()
|
||||
|
||||
bool UniqueNodeListImp::nodeInUNL (RippleAddress const& naNodePublic)
|
||||
{
|
||||
auto const& blob = naNodePublic.getNodePublic();
|
||||
AnyPublicKey const pk (blob.data(), blob.size());
|
||||
|
||||
ScopedUNLLockType sl (mUNLLock);
|
||||
|
||||
return mUNL.end () != mUNL.find (naNodePublic.humanNodePublic ());
|
||||
if (ephemeralValidatorKeys_.find (pk) != ephemeralValidatorKeys_.end())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return mUNL.find (naNodePublic.humanNodePublic()) != mUNL.end();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -883,6 +924,18 @@ Json::Value UniqueNodeListImp::getUnlJson()
|
||||
ret.append (node);
|
||||
}
|
||||
|
||||
ScopedUNLLockType sl (mUNLLock);
|
||||
|
||||
for (auto const& key : ephemeralValidatorKeys_)
|
||||
{
|
||||
Json::Value node (Json::objectValue);
|
||||
|
||||
node["publicKey"] = encodeCredential (key.first, VER_NODE_PUBLIC);
|
||||
node["comment"] = key.second;
|
||||
|
||||
ret.append (node);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,9 @@
|
||||
#ifndef RIPPLE_APP_PEERS_UNIQUENODELIST_H_INCLUDED
|
||||
#define RIPPLE_APP_PEERS_UNIQUENODELIST_H_INCLUDED
|
||||
|
||||
#include <ripple/overlay/ClusterNodeStatus.h>
|
||||
#include <ripple/app/peers/ClusterNodeStatus.h>
|
||||
#include <ripple/protocol/AnyPublicKey.h>
|
||||
#include <ripple/protocol/RippleAddress.h>
|
||||
#include <beast/cxx14/memory.h> // <memory>
|
||||
#include <beast/threads/Stoppable.h>
|
||||
#include <boost/filesystem.hpp>
|
||||
@@ -53,6 +55,9 @@ public:
|
||||
// VFALCO TODO Roll this into the constructor so there is one less state.
|
||||
virtual void start () = 0;
|
||||
|
||||
virtual void insertEphemeralKey (AnyPublicKey pk, std::string comment) = 0;
|
||||
virtual void deleteEphemeralKey (AnyPublicKey const& pk) = 0;
|
||||
|
||||
// VFALCO TODO rename all these, the "node" prefix is redundant (lol)
|
||||
virtual void nodeAddPublic (RippleAddress const& naNodePublic, ValidatorSource vsWhy, std::string const& strComment) = 0;
|
||||
virtual void nodeAddDomain (std::string strDomain, ValidatorSource vsWhy, std::string const& strComment = "") = 0;
|
||||
|
||||
297
src/ripple/overlay/impl/Manifest.cpp
Normal file
297
src/ripple/overlay/impl/Manifest.cpp
Normal file
@@ -0,0 +1,297 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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/app/peers/UniqueNodeList.h>
|
||||
#include <ripple/overlay/impl/Manifest.h>
|
||||
#include <ripple/protocol/RippleAddress.h>
|
||||
#include <ripple/protocol/Sign.h>
|
||||
#include <beast/http/rfc2616.h>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
void
|
||||
ManifestCache::configValidatorKey(std::string const& line, beast::Journal const& journal)
|
||||
{
|
||||
auto const words = beast::rfc2616::split(line.begin(), line.end(), ' ');
|
||||
|
||||
if (words.size() != 2)
|
||||
{
|
||||
throw std::runtime_error ("[validator_keys] format is `<key> <comment>");
|
||||
}
|
||||
|
||||
Blob key;
|
||||
if (! Base58::decodeWithCheck (words[0], key))
|
||||
{
|
||||
throw std::runtime_error ("Error decoding validator key");
|
||||
}
|
||||
if (key.size() != 34)
|
||||
{
|
||||
throw std::runtime_error ("Expected 34-byte validator key");
|
||||
}
|
||||
if (key[0] != VER_NODE_PUBLIC)
|
||||
{
|
||||
throw std::runtime_error ("Expected VER_NODE_PUBLIC (28)");
|
||||
}
|
||||
if (key[1] != 0xED)
|
||||
{
|
||||
throw std::runtime_error ("Expected Ed25519 key (0xED)");
|
||||
}
|
||||
|
||||
auto const masterKey = AnyPublicKey (key.data() + 1, key.size() - 1);
|
||||
auto const& comment = words[1];
|
||||
|
||||
if (journal.debug) journal.debug
|
||||
<< masterKey << " " << comment;
|
||||
|
||||
addTrustedKey (masterKey, comment);
|
||||
}
|
||||
|
||||
void
|
||||
ManifestCache::configManifest (std::string s, 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))
|
||||
{
|
||||
throw std::runtime_error("Unverifiable manifest in config");
|
||||
}
|
||||
|
||||
auto const result = applyManifest (std::move(s), journal);
|
||||
|
||||
if (result != ManifestDisposition::accepted)
|
||||
{
|
||||
throw std::runtime_error("Our own validation manifest was not accepted");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ManifestCache::addTrustedKey (AnyPublicKey const& pk, std::string const& 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 = comment;
|
||||
}
|
||||
|
||||
ManifestDisposition
|
||||
ManifestCache::preflightManifest_locked (AnyPublicKey const& pk, std::uint32_t seq,
|
||||
beast::Journal const& 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 (journal.debug) journal.debug
|
||||
<< "Ignoring manifest #" << seq << " from untrusted key " << pk;
|
||||
return ManifestDisposition::untrusted;
|
||||
}
|
||||
|
||||
auto& old = iter->second.m;
|
||||
|
||||
if (old && seq <= old->seq)
|
||||
{
|
||||
/*
|
||||
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 (journal.debug) journal.debug
|
||||
<< "Ignoring manifest #" << seq
|
||||
<< "which isn't newer than #" << old->seq;
|
||||
return ManifestDisposition::stale; // not a newer manifest, ignore
|
||||
}
|
||||
|
||||
return ManifestDisposition::accepted;
|
||||
}
|
||||
|
||||
ManifestDisposition
|
||||
ManifestCache::applyManifest (std::string s, 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.
|
||||
*/
|
||||
auto const preflight = preflightManifest_locked(pk, seq, journal);
|
||||
|
||||
if (preflight != ManifestDisposition::accepted)
|
||||
{
|
||||
return preflight;
|
||||
}
|
||||
}
|
||||
|
||||
if (! verify(st, HashPrefix::manifest, pk))
|
||||
{
|
||||
/*
|
||||
A manifest's signature is invalid.
|
||||
This shouldn't happen normally.
|
||||
*/
|
||||
if (journal.warning) journal.warning
|
||||
<< "Failed to verify manifest #" << seq;
|
||||
return ManifestDisposition::invalid;
|
||||
}
|
||||
|
||||
auto& unl = getApp().getUNL();
|
||||
|
||||
std::lock_guard<std::mutex> lock (mutex_);
|
||||
|
||||
/*
|
||||
We released the lock above, so we have to preflight again, in case
|
||||
another thread accepted a newer manifest.
|
||||
*/
|
||||
auto const preflight = preflightManifest_locked(pk, seq, journal);
|
||||
|
||||
if (preflight != ManifestDisposition::accepted)
|
||||
{
|
||||
return preflight;
|
||||
}
|
||||
|
||||
auto const iter = map_.find(pk);
|
||||
|
||||
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)
|
||||
{
|
||||
/*
|
||||
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 (journal.info) journal.info
|
||||
<< "Adding new manifest #" << seq;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (seq == 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";
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
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;
|
||||
}
|
||||
|
||||
unl.deleteEphemeralKey (old->signingKey);
|
||||
}
|
||||
|
||||
if (seq == revoked)
|
||||
{
|
||||
// The master key is revoked -- don't insert the signing key
|
||||
if (auto const& j = journal.warning)
|
||||
j << "Revoking master key: " << pk;
|
||||
|
||||
/*
|
||||
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 (spk, iter->second.comment);
|
||||
}
|
||||
|
||||
old = Manifest(std::move (s), std::move (pk), std::move (spk), seq);
|
||||
|
||||
return ManifestDisposition::accepted;
|
||||
}
|
||||
|
||||
}
|
||||
187
src/ripple/overlay/impl/Manifest.h
Normal file
187
src/ripple/overlay/impl/Manifest.h
Normal file
@@ -0,0 +1,187 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_OVERLAY_MANIFEST_H_INCLUDED
|
||||
#define RIPPLE_OVERLAY_MANIFEST_H_INCLUDED
|
||||
|
||||
#include <ripple/basics/BasicConfig.h>
|
||||
#include <ripple/basics/UnorderedContainers.h>
|
||||
#include <ripple/protocol/AnyPublicKey.h>
|
||||
#include <ripple/protocol/STExchange.h>
|
||||
#include <beast/utility/Journal.h>
|
||||
#include <boost/optional.hpp>
|
||||
#include <string>
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
The manifest has two serialized forms: one which includes the digital
|
||||
signature and one which doesn't. There is an obvious causal dependency
|
||||
relationship between the (latter) form with no signature, the signature
|
||||
of that form, and the (former) form which includes that signature. In
|
||||
other words, a message can't contain a signature of itself. The code
|
||||
below stores a serialized manifest which includes the signature, and
|
||||
dynamically generates the signatureless form when it needs to verify
|
||||
the signature.
|
||||
|
||||
There are two stores of information within rippled related to manifests.
|
||||
An instance of ManifestCache stores, for each trusted validator, (a) its
|
||||
master public key, and (b) the most senior of all valid manifests it has
|
||||
seen for that validator, if any. On startup, the [validator_keys] config
|
||||
entries are used to prime the manifest cache with the trusted master keys.
|
||||
At this point, the manifest cache has all the entries it will ever have,
|
||||
but none of them have manifests. The [validation_manifest] config entry
|
||||
(which is the manifest for this validator) is then decoded and added to
|
||||
the manifest cache. Other manifests are added as "gossip" is received
|
||||
from rippled peers.
|
||||
|
||||
The other data store (which does not involve manifests per se) contains
|
||||
the set of active ephemeral validator keys. Keys are added to the set
|
||||
when a manifest is accepted, and removed when that manifest is obsoleted.
|
||||
|
||||
When an ephemeral key is compromised, a new signing key pair is created,
|
||||
along with a new manifest vouching for it (with a higher sequence number),
|
||||
signed by the master key. When a rippled peer receives the new manifest,
|
||||
it verifies it with the master key and (assuming it's valid) discards the
|
||||
old ephemeral key and stores the new one. If the master key itself gets
|
||||
compromised, a manifest with sequence number 0xFFFFFFFF will supersede a
|
||||
prior manifest and discard any existing ephemeral key without storing a
|
||||
new one. Since no further manifests for this master key will be accepted
|
||||
(since no higher sequence number is possible), and no signing key is on
|
||||
record, no validations will be accepted from the compromised validator.
|
||||
*/
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
struct Manifest
|
||||
{
|
||||
std::string serialized;
|
||||
AnyPublicKey masterKey;
|
||||
AnyPublicKey signingKey;
|
||||
std::uint32_t seq;
|
||||
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
Manifest(Manifest&& other)
|
||||
: serialized(std::move(other.serialized))
|
||||
, masterKey(std::move(other.masterKey))
|
||||
, signingKey(std::move(other.signingKey))
|
||||
, seq(other.seq)
|
||||
{
|
||||
}
|
||||
|
||||
Manifest& operator=(Manifest&& other)
|
||||
{
|
||||
serialized = std::move(other.serialized);
|
||||
masterKey = std::move(other.masterKey);
|
||||
signingKey = std::move(other.signingKey);
|
||||
seq = other.seq;
|
||||
return *this;
|
||||
}
|
||||
#else
|
||||
Manifest(Manifest&& other) = default;
|
||||
Manifest& operator=(Manifest&& other) = default;
|
||||
#endif
|
||||
};
|
||||
|
||||
enum class ManifestDisposition
|
||||
{
|
||||
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
|
||||
};
|
||||
|
||||
/** Remembers manifests with the highest sequence number. */
|
||||
class ManifestCache
|
||||
{
|
||||
private:
|
||||
struct MappedType
|
||||
{
|
||||
MappedType() = default;
|
||||
|
||||
std::string comment;
|
||||
boost::optional<Manifest> m;
|
||||
};
|
||||
|
||||
using MapType = hash_map<AnyPublicKey, MappedType>;
|
||||
|
||||
mutable std::mutex mutex_;
|
||||
MapType map_;
|
||||
|
||||
ManifestDisposition
|
||||
preflightManifest_locked (AnyPublicKey const& pk, std::uint32_t seq,
|
||||
beast::Journal const& journal) const;
|
||||
|
||||
public:
|
||||
ManifestCache() = default;
|
||||
ManifestCache (ManifestCache const&) = delete;
|
||||
ManifestCache& operator= (ManifestCache const&) = delete;
|
||||
~ManifestCache() = default;
|
||||
|
||||
void configValidatorKey(std::string const& line, beast::Journal const& journal);
|
||||
void configManifest(std::string s, beast::Journal const& journal);
|
||||
|
||||
void addTrustedKey (AnyPublicKey const& pk, std::string const& comment);
|
||||
|
||||
ManifestDisposition
|
||||
applyManifest (std::string s, beast::Journal const& journal);
|
||||
|
||||
// A "for_each" for populated manifests only
|
||||
template <class Function>
|
||||
void
|
||||
for_each_manifest(Function&& f) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock (mutex_);
|
||||
for (auto const& e : map_)
|
||||
{
|
||||
if (auto const& m = e.second.m)
|
||||
f(*m);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // ripple
|
||||
|
||||
#endif
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <ripple/overlay/impl/PeerImp.h>
|
||||
#include <ripple/overlay/impl/TMHello.h>
|
||||
#include <ripple/peerfinder/make_Manager.h>
|
||||
#include <ripple/protocol/STExchange.h>
|
||||
#include <beast/ByteOrder.h>
|
||||
#include <beast/crypto/base64.h>
|
||||
#include <beast/http/rfc2616.h>
|
||||
@@ -425,9 +426,47 @@ OverlayImpl::checkStopped ()
|
||||
stopped();
|
||||
}
|
||||
|
||||
static
|
||||
void
|
||||
prepareValidatorKeyManifests (ManifestCache& mc, beast::Journal const& journal)
|
||||
{
|
||||
auto const validator_keys = getConfig().section("validator_keys");
|
||||
auto const validation_manifest = getConfig().section("validation_manifest");
|
||||
|
||||
if (! validator_keys.lines().empty())
|
||||
{
|
||||
for (auto const& line : validator_keys.lines())
|
||||
{
|
||||
mc.configValidatorKey (line, journal);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (journal.warning)
|
||||
journal.warning << "[validator_keys] is empty";
|
||||
}
|
||||
|
||||
if (! validation_manifest.lines().empty())
|
||||
{
|
||||
std::string s;
|
||||
for (auto const& line : validation_manifest.lines())
|
||||
s += beast::rfc2616::trim(line);
|
||||
s = beast::base64_decode(s);
|
||||
mc.configManifest(std::move(s), journal);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (journal.warning)
|
||||
journal.warning << "No [validation_manifest] section in config";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
OverlayImpl::onPrepare()
|
||||
{
|
||||
prepareValidatorKeyManifests (manifestCache_, journal_);
|
||||
|
||||
PeerFinder::Config config;
|
||||
|
||||
if (getConfig ().PEERS_MAX != 0)
|
||||
@@ -576,6 +615,59 @@ OverlayImpl::onPeerDeactivate (Peer::id_t id,
|
||||
m_publicKeyMap.erase(publicKey);
|
||||
}
|
||||
|
||||
void
|
||||
OverlayImpl::onManifests (Job&,
|
||||
std::shared_ptr<protocol::TMManifests> const& inbox,
|
||||
std::shared_ptr<PeerImp> const& from)
|
||||
{
|
||||
auto& hashRouter = getApp().getHashRouter();
|
||||
auto const n = inbox->list_size();
|
||||
auto const& journal = from->pjournal();
|
||||
|
||||
if (journal.debug) journal.debug
|
||||
<< "TMManifest, " << n << (n == 1 ? " item" : " items");
|
||||
|
||||
protocol::TMManifests outbox;
|
||||
|
||||
for (std::size_t i = 0; i < n; ++i)
|
||||
{
|
||||
auto& s = inbox->list().Get(i).stobject();
|
||||
|
||||
uint256 const hash = getSHA512Half (s);
|
||||
|
||||
auto const result = manifestCache_.applyManifest (s, journal);
|
||||
|
||||
if (result == ManifestDisposition::accepted)
|
||||
{
|
||||
protocol::TMManifests outbox;
|
||||
|
||||
outbox.add_list()->set_stobject(s);
|
||||
|
||||
auto const msg = std::make_shared<Message>(outbox, protocol::mtMANIFESTS);
|
||||
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
if (journal.info) journal.info
|
||||
<< "Bad manifest #" << i + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t
|
||||
OverlayImpl::selectPeers (PeerSet& set, std::size_t limit,
|
||||
std::function<bool(std::shared_ptr<Peer> const&)> score)
|
||||
|
||||
@@ -20,7 +20,9 @@
|
||||
#ifndef RIPPLE_OVERLAY_OVERLAYIMPL_H_INCLUDED
|
||||
#define RIPPLE_OVERLAY_OVERLAYIMPL_H_INCLUDED
|
||||
|
||||
#include <ripple/core/Job.h>
|
||||
#include <ripple/overlay/Overlay.h>
|
||||
#include <ripple/overlay/impl/Manifest.h>
|
||||
#include <ripple/server/Handoff.h>
|
||||
#include <ripple/server/ServerHandler.h>
|
||||
#include <ripple/basics/Resolver.h>
|
||||
@@ -113,7 +115,7 @@ private:
|
||||
hash_map<Peer::id_t, std::weak_ptr<PeerImp>> m_shortIdMap;
|
||||
Resolver& m_resolver;
|
||||
std::atomic <Peer::id_t> next_id_;
|
||||
|
||||
ManifestCache manifestCache_;
|
||||
int timer_count_;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -147,6 +149,12 @@ public:
|
||||
return serverHandler_;
|
||||
}
|
||||
|
||||
ManifestCache const&
|
||||
manifestCache() const
|
||||
{
|
||||
return manifestCache_;
|
||||
}
|
||||
|
||||
Setup const&
|
||||
setup() const
|
||||
{
|
||||
@@ -234,6 +242,12 @@ public:
|
||||
selectPeers (PeerSet& set, std::size_t limit, std::function<
|
||||
bool(std::shared_ptr<Peer> const&)> score) override;
|
||||
|
||||
// Called when TMManifests is received from a peer
|
||||
void
|
||||
onManifests (Job&,
|
||||
std::shared_ptr<protocol::TMManifests> const& m,
|
||||
std::shared_ptr<PeerImp> const& from);
|
||||
|
||||
static
|
||||
bool
|
||||
isPeerUpgrade (beast::http::message const& request);
|
||||
|
||||
@@ -653,6 +653,22 @@ void
|
||||
PeerImp::doProtocolStart()
|
||||
{
|
||||
onReadMessage(error_code(), 0);
|
||||
|
||||
protocol::TMManifests tm;
|
||||
|
||||
overlay_.manifestCache().for_each_manifest(
|
||||
[&](Manifest const& manifest)
|
||||
{
|
||||
auto const& s = manifest.serialized;
|
||||
auto& tm_e = *tm.add_list();
|
||||
tm_e.set_stobject(s.data(), s.size());
|
||||
});
|
||||
|
||||
if (tm.list_size() > 0)
|
||||
{
|
||||
auto m = std::make_shared<Message>(tm, protocol::mtMANIFESTS);
|
||||
send (m);
|
||||
}
|
||||
}
|
||||
|
||||
// Called repeatedly with protocol message data
|
||||
@@ -778,6 +794,15 @@ PeerImp::onMessage (std::shared_ptr <protocol::TMHello> const& m)
|
||||
fail("Deprecated TMHello");
|
||||
}
|
||||
|
||||
void
|
||||
PeerImp::onMessage (std::shared_ptr<protocol::TMManifests> const& m)
|
||||
{
|
||||
// VFALCO What's the right job type?
|
||||
getApp().getJobQueue().addJob (jtVALIDATION_ut,
|
||||
"receiveManifests", std::bind(&OverlayImpl::onManifests,
|
||||
&overlay_, std::placeholders::_1, m, shared_from_this()));
|
||||
}
|
||||
|
||||
void
|
||||
PeerImp::onMessage (std::shared_ptr <protocol::TMPing> const& m)
|
||||
{
|
||||
|
||||
@@ -188,6 +188,12 @@ public:
|
||||
virtual
|
||||
~PeerImp();
|
||||
|
||||
beast::Journal const&
|
||||
pjournal() const
|
||||
{
|
||||
return p_journal_;
|
||||
}
|
||||
|
||||
PeerFinder::Slot::ptr const&
|
||||
slot()
|
||||
{
|
||||
@@ -395,6 +401,7 @@ public:
|
||||
std::shared_ptr <::google::protobuf::Message> const& m);
|
||||
|
||||
void onMessage (std::shared_ptr <protocol::TMHello> const& m);
|
||||
void onMessage (std::shared_ptr <protocol::TMManifests> const& m);
|
||||
void onMessage (std::shared_ptr <protocol::TMPing> const& m);
|
||||
void onMessage (std::shared_ptr <protocol::TMCluster> const& m);
|
||||
void onMessage (std::shared_ptr <protocol::TMGetPeers> const& m);
|
||||
|
||||
@@ -42,6 +42,7 @@ protocolMessageName (int type)
|
||||
switch (type)
|
||||
{
|
||||
case protocol::mtHELLO: return "hello";
|
||||
case protocol::mtMANIFESTS: return "manifests";
|
||||
case protocol::mtPING: return "ping";
|
||||
case protocol::mtPROOFOFWORK: return "proof_of_work";
|
||||
case protocol::mtCLUSTER: return "cluster";
|
||||
@@ -112,6 +113,7 @@ invokeProtocolMessage (Buffers const& buffers, Handler& handler)
|
||||
switch (type)
|
||||
{
|
||||
case protocol::mtHELLO: ec = detail::invoke<protocol::TMHello> (type, buffers, handler); break;
|
||||
case protocol::mtMANIFESTS: ec = detail::invoke<protocol::TMManifests> (type, buffers, handler); break;
|
||||
case protocol::mtPING: ec = detail::invoke<protocol::TMPing> (type, buffers, handler); break;
|
||||
case protocol::mtCLUSTER: ec = detail::invoke<protocol::TMCluster> (type, buffers, handler); break;
|
||||
case protocol::mtGET_PEERS: ec = detail::invoke<protocol::TMGetPeers> (type, buffers, handler); break;
|
||||
|
||||
108
src/ripple/overlay/tests/manifest_test.cpp
Normal file
108
src/ripple/overlay/tests/manifest_test.cpp
Normal file
@@ -0,0 +1,108 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright 2014 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 <BeastConfig.h>
|
||||
#include <ripple/basics/TestSuite.h>
|
||||
#include <ripple/overlay/impl/Manifest.h>
|
||||
#include <ripple/protocol/Sign.h>
|
||||
#include <ripple/protocol/STExchange.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace tests {
|
||||
|
||||
class manifest_test : public ripple::TestSuite
|
||||
{
|
||||
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)
|
||||
{
|
||||
auto const pk = sk.publicKey();
|
||||
|
||||
STObject st(sfGeneric);
|
||||
set(st, sfSequence, seq);
|
||||
set(st, sfPublicKey, pk);
|
||||
set(st, sfSigningPubKey, spk);
|
||||
|
||||
sign(st, HashPrefix::manifest, sk);
|
||||
expect(verify(st, HashPrefix::manifest, pk));
|
||||
|
||||
if (broken)
|
||||
{
|
||||
set(st, sfSequence, seq + 1);
|
||||
}
|
||||
|
||||
Serializer s;
|
||||
st.add(s);
|
||||
|
||||
std::string const m (static_cast<char const*> (s.data()), s.size());
|
||||
return m;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
expect(cache.applyManifest(s_a0, journal) == untrusted, "have to install a trusted key first");
|
||||
|
||||
cache.addTrustedKey(pk_a, "a");
|
||||
cache.addTrustedKey(pk_b, "b");
|
||||
|
||||
expect(cache.applyManifest(s_a0, journal) == accepted);
|
||||
expect(cache.applyManifest(s_a0, journal) == stale);
|
||||
|
||||
expect(cache.applyManifest(s_a1, journal) == accepted);
|
||||
expect(cache.applyManifest(s_a1, journal) == stale);
|
||||
expect(cache.applyManifest(s_a0, journal) == stale);
|
||||
|
||||
expect(cache.applyManifest(s_b0, journal) == accepted);
|
||||
expect(cache.applyManifest(s_b0, journal) == stale);
|
||||
|
||||
expect(cache.applyManifest(fake, journal) == malformed);
|
||||
expect(cache.applyManifest(s_b2, journal) == invalid);
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(manifest,overlay,ripple);
|
||||
|
||||
} // tests
|
||||
} // ripple
|
||||
@@ -3,6 +3,7 @@ package protocol;
|
||||
enum MessageType
|
||||
{
|
||||
mtHELLO = 1;
|
||||
mtMANIFESTS = 2;
|
||||
mtPING = 3;
|
||||
mtPROOFOFWORK = 4;
|
||||
mtCLUSTER = 5;
|
||||
@@ -18,7 +19,6 @@ enum MessageType
|
||||
mtVALIDATION = 41;
|
||||
mtGET_OBJECTS = 42;
|
||||
|
||||
// <available> = 2;
|
||||
// <available> = 10;
|
||||
// <available> = 11;
|
||||
// <available> = 14;
|
||||
@@ -34,6 +34,20 @@ enum MessageType
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/* Provides the current ephemeral key for a validator. */
|
||||
message TMManifest
|
||||
{
|
||||
// A Manifest object in the Ripple serialization format.
|
||||
required bytes stobject = 1;
|
||||
}
|
||||
|
||||
message TMManifests
|
||||
{
|
||||
repeated TMManifest list = 1;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/* Requests or responds to a proof of work.
|
||||
Unimplemented and unused currently.
|
||||
*/
|
||||
|
||||
@@ -92,6 +92,9 @@ public:
|
||||
|
||||
/** proposal for signing */
|
||||
static HashPrefix const proposal;
|
||||
|
||||
/** Manifest */
|
||||
static HashPrefix const manifest;
|
||||
};
|
||||
|
||||
} // ripple
|
||||
|
||||
@@ -34,5 +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');
|
||||
|
||||
} // ripple
|
||||
|
||||
@@ -20,12 +20,14 @@
|
||||
#include <BeastConfig.h>
|
||||
|
||||
#include <ripple/overlay/impl/ConnectAttempt.cpp>
|
||||
#include <ripple/overlay/impl/Manifest.cpp>
|
||||
#include <ripple/overlay/impl/Message.cpp>
|
||||
#include <ripple/overlay/impl/OverlayImpl.cpp>
|
||||
#include <ripple/overlay/impl/PeerImp.cpp>
|
||||
#include <ripple/overlay/impl/PeerSet.cpp>
|
||||
#include <ripple/overlay/impl/TMHello.cpp>
|
||||
|
||||
#include <ripple/overlay/tests/manifest_test.cpp>
|
||||
#include <ripple/overlay/tests/short_read.test.cpp>
|
||||
#include <ripple/overlay/tests/TMHello.test.cpp>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user