diff --git a/src/ripple/app/misc/ValidatorList.h b/src/ripple/app/misc/ValidatorList.h index 0f1a206855..b2568a24c7 100644 --- a/src/ripple/app/misc/ValidatorList.h +++ b/src/ripple/app/misc/ValidatorList.h @@ -72,8 +72,9 @@ enum class ListDisposition "expiration", and @c "validators" field. @c "expiration" contains the Ripple timestamp (seconds since January 1st, 2000 (00:00 UTC)) for when the list expires. @c "validators" contains an array of objects with a - @c "validation_public_key" field. + @c "validation_public_key" and optional @c "manifest" field. @c "validation_public_key" should be the hex-encoded master public key. + @c "manifest" should be the base64-encoded validator manifest. @li @c "manifest": Base64-encoded serialization of a manifest containing the publisher's master and signing public keys. diff --git a/src/ripple/app/misc/ValidatorSite.h b/src/ripple/app/misc/ValidatorSite.h index 5fba458e03..2be33b645b 100644 --- a/src/ripple/app/misc/ValidatorSite.h +++ b/src/ripple/app/misc/ValidatorSite.h @@ -44,8 +44,9 @@ namespace ripple { "expiration", and @c "validators" field. @c "expiration" contains the Ripple timestamp (seconds since January 1st, 2000 (00:00 UTC)) for when the list expires. @c "validators" contains an array of objects with a - @c "validation_public_key" field. + @c "validation_public_key" and optional @c "manifest" field. @c "validation_public_key" should be the hex-encoded master public key. + @c "manifest" should be the base64-encoded validator manifest. @li @c "manifest": Base64-encoded serialization of a manifest containing the publisher's master and signing public keys. diff --git a/src/ripple/app/misc/impl/ValidatorList.cpp b/src/ripple/app/misc/impl/ValidatorList.cpp index 7a71c6bfbf..03b4198110 100644 --- a/src/ripple/app/misc/impl/ValidatorList.cpp +++ b/src/ripple/app/misc/impl/ValidatorList.cpp @@ -190,6 +190,7 @@ ValidatorList::applyList ( std::vector oldList = publisherList; publisherList.clear (); publisherList.reserve (newList.size ()); + std::vector manifests; for (auto const& val : newList) { if (val.isObject () && @@ -210,6 +211,9 @@ ValidatorList::applyList ( publisherList.push_back ( PublicKey(Slice{ ret.first.data (), ret.first.size() })); } + + if (val.isMember ("manifest") && val["manifest"].isString ()) + manifests.push_back(val["manifest"].asString ()); } } @@ -254,6 +258,28 @@ ValidatorList::applyList ( "No validator keys included in valid list"; } + for (auto const& valManifest : manifests) + { + auto m = Manifest::make_Manifest ( + beast::detail::base64_decode(valManifest)); + + if (! m || ! keyListings_.count (m->masterKey)) + { + JLOG (j_.warn()) << + "List for " << strHex(pubKey) << + " contained untrusted validator manifest"; + continue; + } + + auto const result = validatorManifests_.applyManifest (std::move(*m)); + if (result == ManifestDisposition::invalid) + { + JLOG (j_.warn()) << + "List for " << strHex(pubKey) << + " contained invalid validator manifest"; + } + } + return ListDisposition::accepted; } diff --git a/src/test/app/ValidatorList_test.cpp b/src/test/app/ValidatorList_test.cpp index 01fa2d1163..091c5c6092 100644 --- a/src/test/app/ValidatorList_test.cpp +++ b/src/test/app/ValidatorList_test.cpp @@ -35,6 +35,13 @@ namespace test { class ValidatorList_test : public beast::unit_test::suite { private: + struct Validator + { + PublicKey masterPublic; + PublicKey signingPublic; + std::string manifest; + }; + static PublicKey randomNode () @@ -49,6 +56,7 @@ private: return derivePublicKey (KeyType::ed25519, randomSecretKey()); } + static std::string makeManifestString ( PublicKey const& pk, @@ -72,9 +80,22 @@ private: return std::string(static_cast (s.data()), s.size()); } + static + Validator + randomValidator () + { + auto const secret = randomSecretKey(); + auto const masterPublic = + derivePublicKey(KeyType::ed25519, secret); + auto const signingKeys = randomKeyPair(KeyType::secp256k1); + return { masterPublic, signingKeys.first, + beast::detail::base64_encode(makeManifestString ( + masterPublic, secret, signingKeys.first, signingKeys.second, 1)) }; + } + std::string makeList ( - std::vector const& validators, + std::vector const& validators, std::size_t sequence, std::size_t expiration) { @@ -85,7 +106,8 @@ private: for (auto const& val : validators) { - data += "{\"validation_public_key\":\"" + strHex(val) + "\"},"; + data += "{\"validation_public_key\":\"" + strHex(val.masterPublic) + + "\",\"manifest\":\"" + val.manifest + "\"},"; } data.pop_back(); @@ -355,15 +377,15 @@ private: emptyLocalKey, emptyCfgKeys, cfgKeys1)); auto constexpr listSize = 20; - std::vector list1; + std::vector list1; list1.reserve (listSize); while (list1.size () < listSize) - list1.push_back (randomNode()); + list1.push_back (randomValidator()); - std::vector list2; + std::vector list2; list2.reserve (listSize); while (list2.size () < listSize) - list2.push_back (randomNode()); + list2.push_back (randomValidator()); // do not apply expired list auto const version = 1; @@ -387,7 +409,10 @@ private: manifest1, blob1, sig1, version)); for (auto const& val : list1) - BEAST_EXPECT(trustedKeys->listed (val)); + { + BEAST_EXPECT(trustedKeys->listed (val.masterPublic)); + BEAST_EXPECT(trustedKeys->listed (val.signingPublic)); + } // do not use list from untrusted publisher auto const untrustedManifest = beast::detail::base64_encode( @@ -415,10 +440,16 @@ private: manifest1, blob2, sig2, version)); for (auto const& val : list1) - BEAST_EXPECT(! trustedKeys->listed (val)); + { + BEAST_EXPECT(! trustedKeys->listed (val.masterPublic)); + BEAST_EXPECT(! trustedKeys->listed (val.signingPublic)); + } for (auto const& val : list2) - BEAST_EXPECT(trustedKeys->listed (val)); + { + BEAST_EXPECT(trustedKeys->listed (val.masterPublic)); + BEAST_EXPECT(trustedKeys->listed (val.signingPublic)); + } // do not re-apply lists with past or current sequence numbers BEAST_EXPECT(ListDisposition::stale == @@ -471,7 +502,10 @@ private: BEAST_EXPECT(! trustedKeys->trustedPublisher(publisherPublic)); for (auto const& val : list1) - BEAST_EXPECT(! trustedKeys->listed (val)); + { + BEAST_EXPECT(! trustedKeys->listed (val.masterPublic)); + BEAST_EXPECT(! trustedKeys->listed (val.signingPublic)); + } } void @@ -723,8 +757,8 @@ private: BEAST_EXPECT(trustedKeys->load ( emptyLocalKey, emptyCfgKeys, cfgKeys)); - std::vector list ({randomNode()}); - hash_set activeValidators ({ list[0] }); + std::vector list ({randomValidator()}); + hash_set activeValidators ({ list[0].masterPublic }); // do not apply expired list auto const version = 1; @@ -740,11 +774,13 @@ private: manifest, blob, sig, version)); trustedKeys->onConsensusStart (activeValidators); - BEAST_EXPECT(trustedKeys->trusted (list[0])); + BEAST_EXPECT(trustedKeys->trusted (list[0].masterPublic)); + BEAST_EXPECT(trustedKeys->trusted (list[0].signingPublic)); env.timeKeeper().set(expiration); trustedKeys->onConsensusStart (activeValidators); - BEAST_EXPECT(! trustedKeys->trusted (list[0])); + BEAST_EXPECT(! trustedKeys->trusted (list[0].masterPublic)); + BEAST_EXPECT(! trustedKeys->trusted (list[0].signingPublic)); } { // Test 1-9 configured validators @@ -814,13 +850,13 @@ private: hash_set activeValidators; - std::vector valKeys; + std::vector valKeys; valKeys.reserve(n); while (valKeys.size () != n) { - valKeys.push_back (randomNode()); - activeValidators.emplace (valKeys.back()); + valKeys.push_back (randomValidator()); + activeValidators.emplace (valKeys.back().masterPublic); } auto addPublishedList = [this, &env, &trustedKeys, &valKeys]() diff --git a/src/test/app/ValidatorSite_test.cpp b/src/test/app/ValidatorSite_test.cpp index c72e50472a..3b6c96984c 100644 --- a/src/test/app/ValidatorSite_test.cpp +++ b/src/test/app/ValidatorSite_test.cpp @@ -34,6 +34,13 @@ namespace ripple { namespace test { +struct Validator +{ + PublicKey masterPublic; + PublicKey signingPublic; + std::string manifest; +}; + class http_sync_server { using endpoint_type = boost::asio::ip::tcp::endpoint; @@ -57,7 +64,7 @@ public: int sequence, std::size_t expiration, int version, - std::vector const& validators) + std::vector const& validators) : sock_(ios) , acceptor_(ios) { @@ -68,7 +75,8 @@ public: for (auto const& val : validators) { - data += "{\"validation_public_key\":\"" + strHex (val) + "\"},"; + data += "{\"validation_public_key\":\"" + strHex(val.masterPublic) + + "\",\"manifest\":\"" + val.manifest + "\"},"; } data.pop_back(); data += "]}"; @@ -202,6 +210,7 @@ private: return derivePublicKey (KeyType::secp256k1, randomSecretKey()); } + static std::string makeManifestString ( PublicKey const& pk, @@ -226,6 +235,18 @@ private: static_cast (s.data()), s.size())); } + static + Validator + randomValidator () + { + auto const secret = randomSecretKey(); + auto const masterPublic = + derivePublicKey(KeyType::ed25519, secret); + auto const signingKeys = randomKeyPair(KeyType::secp256k1); + return { masterPublic, signingKeys.first, makeManifestString ( + masterPublic, secret, signingKeys.first, signingKeys.second, 1) }; + } + void testConfigLoad () { @@ -306,15 +327,15 @@ private: emptyLocalKey, emptyCfgKeys, cfgPublishers)); auto constexpr listSize = 20; - std::vector list1; + std::vector list1; list1.reserve (listSize); while (list1.size () < listSize) - list1.push_back (randomNode()); + list1.push_back (randomValidator()); - std::vector list2; + std::vector list2; list2.reserve (listSize); while (list2.size () < listSize) - list2.push_back (randomNode()); + list2.push_back (randomValidator()); std::uint16_t constexpr port1 = 7475; std::uint16_t constexpr port2 = 7476; @@ -351,7 +372,10 @@ private: sites->join(); for (auto const& val : list1) - BEAST_EXPECT(trustedKeys.listed (val)); + { + BEAST_EXPECT(trustedKeys.listed (val.masterPublic)); + BEAST_EXPECT(trustedKeys.listed (val.signingPublic)); + } } { // fetch multiple sites @@ -367,10 +391,16 @@ private: sites->join(); for (auto const& val : list1) - BEAST_EXPECT(trustedKeys.listed (val)); + { + BEAST_EXPECT(trustedKeys.listed (val.masterPublic)); + BEAST_EXPECT(trustedKeys.listed (val.signingPublic)); + } for (auto const& val : list2) - BEAST_EXPECT(trustedKeys.listed (val)); + { + BEAST_EXPECT(trustedKeys.listed (val.masterPublic)); + BEAST_EXPECT(trustedKeys.listed (val.signingPublic)); + } } }