diff --git a/Builds/VisualStudio2013/RippleD.vcxproj b/Builds/VisualStudio2013/RippleD.vcxproj
index 9bc98acfa..98c252fd0 100644
--- a/Builds/VisualStudio2013/RippleD.vcxproj
+++ b/Builds/VisualStudio2013/RippleD.vcxproj
@@ -2480,6 +2480,12 @@
+
+ True
+ True
+
+
+
True
True
@@ -2526,6 +2532,10 @@
+
+ True
+ True
+
True
True
diff --git a/Builds/VisualStudio2013/RippleD.vcxproj.filters b/Builds/VisualStudio2013/RippleD.vcxproj.filters
index d78733c09..bb9eadb99 100644
--- a/Builds/VisualStudio2013/RippleD.vcxproj.filters
+++ b/Builds/VisualStudio2013/RippleD.vcxproj.filters
@@ -3138,6 +3138,12 @@
ripple\overlay\impl
+
+ ripple\overlay\impl
+
+
+ ripple\overlay\impl
+
ripple\overlay\impl
@@ -3192,6 +3198,9 @@
ripple\overlay
+
+ ripple\overlay\tests
+
ripple\overlay\tests
diff --git a/src/ripple/app/misc/HashRouter.cpp b/src/ripple/app/misc/HashRouter.cpp
index b1a3d27ad..47ca8e0a3 100644
--- a/src/ripple/app/misc/HashRouter.cpp
+++ b/src/ripple/app/misc/HashRouter.cpp
@@ -110,9 +110,9 @@ private:
Entry& findCreateEntry (uint256 const& , bool& created);
- using LockType = std::mutex;
- using ScopedLockType = std::lock_guard ;
- LockType mLock;
+ using MutexType = std::mutex;
+ using ScopedLockType = std::lock_guard ;
+ MutexType mMutex;
// Stores all suppressed hashes and their expiration time
hash_map 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& peers, int flag)
{
- ScopedLockType sl (mLock);
+ ScopedLockType lock (mMutex);
bool created;
Entry& s = findCreateEntry (index, created);
diff --git a/src/ripple/app/misc/UniqueNodeList.cpp b/src/ripple/app/misc/UniqueNodeList.cpp
index dd73bba06..1a95407f8 100644
--- a/src/ripple/app/misc/UniqueNodeList.cpp
+++ b/src/ripple/app/misc/UniqueNodeList.cpp
@@ -29,6 +29,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -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
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 mUNL;
+ hash_map 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;
}
diff --git a/src/ripple/app/misc/UniqueNodeList.h b/src/ripple/app/misc/UniqueNodeList.h
index 58303cf0a..56fcd6ad8 100644
--- a/src/ripple/app/misc/UniqueNodeList.h
+++ b/src/ripple/app/misc/UniqueNodeList.h
@@ -20,7 +20,9 @@
#ifndef RIPPLE_APP_PEERS_UNIQUENODELIST_H_INCLUDED
#define RIPPLE_APP_PEERS_UNIQUENODELIST_H_INCLUDED
-#include
+#include
+#include
+#include
#include //
#include
#include
@@ -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;
diff --git a/src/ripple/overlay/impl/Manifest.cpp b/src/ripple/overlay/impl/Manifest.cpp
new file mode 100644
index 000000000..a0b35bfa6
--- /dev/null
+++ b/src/ripple/overlay/impl/Manifest.cpp
@@ -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
+#include
+#include
+#include
+#include
+#include
+#include
+
+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 ` ");
+ }
+
+ 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(st, sfPublicKey);
+ auto mspk = get(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 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(st, sfPublicKey);
+ auto const opt_spk = get(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 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 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;
+}
+
+}
diff --git a/src/ripple/overlay/impl/Manifest.h b/src/ripple/overlay/impl/Manifest.h
new file mode 100644
index 000000000..cfd7e5d61
--- /dev/null
+++ b/src/ripple/overlay/impl/Manifest.h
@@ -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
+#include
+#include
+#include
+#include
+#include
+#include
+
+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 m;
+ };
+
+ using MapType = hash_map;
+
+ 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
+ void
+ for_each_manifest(Function&& f) const
+ {
+ std::lock_guard lock (mutex_);
+ for (auto const& e : map_)
+ {
+ if (auto const& m = e.second.m)
+ f(*m);
+ }
+ }
+};
+
+} // ripple
+
+#endif
diff --git a/src/ripple/overlay/impl/OverlayImpl.cpp b/src/ripple/overlay/impl/OverlayImpl.cpp
index eb3d25d55..45abe4220 100644
--- a/src/ripple/overlay/impl/OverlayImpl.cpp
+++ b/src/ripple/overlay/impl/OverlayImpl.cpp
@@ -28,6 +28,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -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 const& inbox,
+ std::shared_ptr 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(outbox, protocol::mtMANIFESTS);
+
+ for_each( [&](std::shared_ptr 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 const&)> score)
diff --git a/src/ripple/overlay/impl/OverlayImpl.h b/src/ripple/overlay/impl/OverlayImpl.h
index 4b4f1d7e6..5ee94c18d 100644
--- a/src/ripple/overlay/impl/OverlayImpl.h
+++ b/src/ripple/overlay/impl/OverlayImpl.h
@@ -20,7 +20,9 @@
#ifndef RIPPLE_OVERLAY_OVERLAYIMPL_H_INCLUDED
#define RIPPLE_OVERLAY_OVERLAYIMPL_H_INCLUDED
+#include
#include
+#include
#include
#include
#include
@@ -113,7 +115,7 @@ private:
hash_map> m_shortIdMap;
Resolver& m_resolver;
std::atomic 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 const&)> score) override;
+ // Called when TMManifests is received from a peer
+ void
+ onManifests (Job&,
+ std::shared_ptr const& m,
+ std::shared_ptr const& from);
+
static
bool
isPeerUpgrade (beast::http::message const& request);
diff --git a/src/ripple/overlay/impl/PeerImp.cpp b/src/ripple/overlay/impl/PeerImp.cpp
index d5e4a1efc..87de80bf5 100644
--- a/src/ripple/overlay/impl/PeerImp.cpp
+++ b/src/ripple/overlay/impl/PeerImp.cpp
@@ -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(tm, protocol::mtMANIFESTS);
+ send (m);
+ }
}
// Called repeatedly with protocol message data
@@ -778,6 +794,15 @@ PeerImp::onMessage (std::shared_ptr const& m)
fail("Deprecated TMHello");
}
+void
+PeerImp::onMessage (std::shared_ptr 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 const& m)
{
diff --git a/src/ripple/overlay/impl/PeerImp.h b/src/ripple/overlay/impl/PeerImp.h
index bcd60f246..18bccf9dd 100644
--- a/src/ripple/overlay/impl/PeerImp.h
+++ b/src/ripple/overlay/impl/PeerImp.h
@@ -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 const& m);
+ void onMessage (std::shared_ptr const& m);
void onMessage (std::shared_ptr const& m);
void onMessage (std::shared_ptr const& m);
void onMessage (std::shared_ptr const& m);
diff --git a/src/ripple/overlay/impl/ProtocolMessage.h b/src/ripple/overlay/impl/ProtocolMessage.h
index 12a69af3d..0f3586022 100644
--- a/src/ripple/overlay/impl/ProtocolMessage.h
+++ b/src/ripple/overlay/impl/ProtocolMessage.h
@@ -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 (type, buffers, handler); break;
+ case protocol::mtMANIFESTS: ec = detail::invoke (type, buffers, handler); break;
case protocol::mtPING: ec = detail::invoke (type, buffers, handler); break;
case protocol::mtCLUSTER: ec = detail::invoke (type, buffers, handler); break;
case protocol::mtGET_PEERS: ec = detail::invoke (type, buffers, handler); break;
diff --git a/src/ripple/overlay/tests/manifest_test.cpp b/src/ripple/overlay/tests/manifest_test.cpp
new file mode 100644
index 000000000..c49cafb86
--- /dev/null
+++ b/src/ripple/overlay/tests/manifest_test.cpp
@@ -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
+#include
+#include
+#include
+#include
+
+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 (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
diff --git a/src/ripple/proto/ripple.proto b/src/ripple/proto/ripple.proto
index dad8e1037..b2b215223 100644
--- a/src/ripple/proto/ripple.proto
+++ b/src/ripple/proto/ripple.proto
@@ -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;
- // = 2;
// = 10;
// = 11;
// = 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.
*/
diff --git a/src/ripple/protocol/HashPrefix.h b/src/ripple/protocol/HashPrefix.h
index dc0cd9495..5b34118c4 100644
--- a/src/ripple/protocol/HashPrefix.h
+++ b/src/ripple/protocol/HashPrefix.h
@@ -92,6 +92,9 @@ public:
/** proposal for signing */
static HashPrefix const proposal;
+
+ /** Manifest */
+ static HashPrefix const manifest;
};
} // ripple
diff --git a/src/ripple/protocol/impl/HashPrefix.cpp b/src/ripple/protocol/impl/HashPrefix.cpp
index 5443918fa..f6d7aa3df 100644
--- a/src/ripple/protocol/impl/HashPrefix.cpp
+++ b/src/ripple/protocol/impl/HashPrefix.cpp
@@ -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
diff --git a/src/ripple/unity/overlay.cpp b/src/ripple/unity/overlay.cpp
index 0e94406c7..24bb9c45e 100644
--- a/src/ripple/unity/overlay.cpp
+++ b/src/ripple/unity/overlay.cpp
@@ -20,12 +20,14 @@
#include
#include
+#include
#include
#include
#include
#include
#include
+#include
#include
#include