#include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace xrpl { namespace test { class ValidatorList_test : public beast::unit_test::suite { private: struct Validator { PublicKey masterPublic; PublicKey signingPublic; std::string manifest; }; static PublicKey randomNode() { return derivePublicKey(KeyType::secp256k1, randomSecretKey()); } static PublicKey randomMasterKey() { return derivePublicKey(KeyType::ed25519, randomSecretKey()); } static std::string makeManifestString( PublicKey const& pk, SecretKey const& sk, PublicKey const& spk, SecretKey const& ssk, int seq) { STObject st(sfGeneric); st[sfSequence] = seq; st[sfPublicKey] = pk; if (seq != std::numeric_limits::max()) { st[sfSigningPubKey] = spk; sign(st, HashPrefix::manifest, *publicKeyType(spk), ssk); } sign(st, HashPrefix::manifest, *publicKeyType(pk), sk, sfMasterSignature); Serializer s; st.add(s); return std::string(static_cast(s.data()), s.size()); } static std::string makeRevocationString(PublicKey const& pk, SecretKey const& sk) { STObject st(sfGeneric); st[sfSequence] = std::numeric_limits::max(); st[sfPublicKey] = pk; sign(st, HashPrefix::manifest, *publicKeyType(pk), sk, sfMasterSignature); Serializer s; st.add(s); 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, base64_encode(makeManifestString( masterPublic, secret, signingKeys.first, signingKeys.second, 1))}; } std::string makeList( std::vector const& validators, std::size_t sequence, std::size_t validUntil, std::optional validFrom = {}) { std::string data = "{\"sequence\":" + std::to_string(sequence) + ",\"expiration\":" + std::to_string(validUntil); if (validFrom) data += ",\"effective\":" + std::to_string(*validFrom); data += ",\"validators\":["; for (auto const& val : validators) { data += "{\"validation_public_key\":\"" + strHex(val.masterPublic) + "\",\"manifest\":\"" + val.manifest + "\"},"; } data.pop_back(); data += "]}"; return base64_encode(data); } std::string signList(std::string const& blob, std::pair const& keys) { auto const data = base64_decode(blob); return strHex(sign(keys.first, keys.second, makeSlice(data))); } static hash_set asNodeIDs(std::initializer_list const& pks) { hash_set res; res.reserve(pks.size()); for (auto const& pk : pks) res.insert(calcNodeID(pk)); return res; } void checkResult( ValidatorList::PublisherListStats const& result, PublicKey pubKey, ListDisposition expectedWorst, ListDisposition expectedBest) { BEAST_EXPECT( result.bestDisposition() > ListDisposition::same_sequence || (result.publisherKey && *result.publisherKey == pubKey)); BEAST_EXPECT(result.bestDisposition() == expectedBest); BEAST_EXPECT(result.worstDisposition() == expectedWorst); } void testGenesisQuorum() { testcase("Genesis Quorum"); ManifestCache manifests; jtx::Env env(*this); auto& app = env.app(); { auto trustedKeys = std::make_unique( manifests, manifests, env.timeKeeper(), app.config().legacy("database_path"), env.journal); BEAST_EXPECT(trustedKeys->quorum() == 1); } { std::size_t minQuorum = 0; auto trustedKeys = std::make_unique( manifests, manifests, env.timeKeeper(), app.config().legacy("database_path"), env.journal, minQuorum); BEAST_EXPECT(trustedKeys->quorum() == minQuorum); } } void testConfigLoad() { testcase("Config Load"); jtx::Env env(*this, jtx::envconfig(), nullptr, beast::severities::kDisabled); auto& app = env.app(); std::vector const emptyCfgKeys; std::vector const emptyCfgPublishers; auto const localSigningKeys = randomKeyPair(KeyType::secp256k1); auto const localSigningPublicOuter = localSigningKeys.first; auto const localSigningSecret = localSigningKeys.second; auto const localMasterSecret = randomSecretKey(); auto const localMasterPublic = derivePublicKey(KeyType::ed25519, localMasterSecret); std::string const cfgManifest(makeManifestString( localMasterPublic, localMasterSecret, localSigningPublicOuter, localSigningSecret, 1)); auto format = [](PublicKey const& publicKey, char const* comment = nullptr) { auto ret = toBase58(TokenType::NodePublic, publicKey); if (comment) ret += comment; return ret; }; std::vector configList; configList.reserve(8); while (configList.size() != 8) configList.push_back(randomNode()); // Correct configuration std::vector cfgKeys( {format(configList[0]), format(configList[1], " Comment"), format(configList[2], " Multi Word Comment"), format(configList[3], " Leading Whitespace"), format(configList[4], " Trailing Whitespace "), format(configList[5], " Leading & Trailing Whitespace "), format(configList[6], " Leading, Trailing & Internal Whitespace "), format(configList[7], " ")}); { ManifestCache manifests; auto trustedKeys = std::make_unique( manifests, manifests, env.timeKeeper(), app.config().legacy("database_path"), env.journal); // Correct (empty) configuration BEAST_EXPECT(trustedKeys->load({}, emptyCfgKeys, emptyCfgPublishers)); // load local validator key with or without manifest BEAST_EXPECT( trustedKeys->load(localSigningPublicOuter, emptyCfgKeys, emptyCfgPublishers)); BEAST_EXPECT(trustedKeys->listed(localSigningPublicOuter)); manifests.applyManifest(*deserializeManifest(cfgManifest)); BEAST_EXPECT( trustedKeys->load(localSigningPublicOuter, emptyCfgKeys, emptyCfgPublishers)); BEAST_EXPECT(trustedKeys->listed(localMasterPublic)); BEAST_EXPECT(trustedKeys->listed(localSigningPublicOuter)); } { // load should add validator keys from config ManifestCache manifests; auto trustedKeys = std::make_unique( manifests, manifests, env.timeKeeper(), app.config().legacy("database_path"), env.journal); BEAST_EXPECT(trustedKeys->load({}, cfgKeys, emptyCfgPublishers)); for (auto const& n : configList) BEAST_EXPECT(trustedKeys->listed(n)); // load should accept Ed25519 master public keys auto const masterNode1 = randomMasterKey(); auto const masterNode2 = randomMasterKey(); std::vector cfgMasterKeys( {format(masterNode1), format(masterNode2, " Comment")}); BEAST_EXPECT(trustedKeys->load({}, cfgMasterKeys, emptyCfgPublishers)); BEAST_EXPECT(trustedKeys->listed(masterNode1)); BEAST_EXPECT(trustedKeys->listed(masterNode2)); // load should reject invalid config keys BEAST_EXPECT(!trustedKeys->load({}, {"NotAPublicKey"}, emptyCfgPublishers)); BEAST_EXPECT(!trustedKeys->load({}, {format(randomNode(), "!")}, emptyCfgPublishers)); // load terminates when encountering an invalid entry auto const goodKey = randomNode(); BEAST_EXPECT(!trustedKeys->load( {}, {format(randomNode(), "!"), format(goodKey)}, emptyCfgPublishers)); BEAST_EXPECT(!trustedKeys->listed(goodKey)); } { // local validator key on config list ManifestCache manifests; auto trustedKeys = std::make_unique( manifests, manifests, env.timeKeeper(), app.config().legacy("database_path"), env.journal); auto const localSigningPublic = parseBase58(TokenType::NodePublic, cfgKeys.front()); BEAST_EXPECT(trustedKeys->load(*localSigningPublic, cfgKeys, emptyCfgPublishers)); BEAST_EXPECT(trustedKeys->localPublicKey() == localSigningPublic); BEAST_EXPECT(trustedKeys->listed(*localSigningPublic)); for (auto const& n : configList) BEAST_EXPECT(trustedKeys->listed(n)); } { // local validator key not on config list ManifestCache manifests; auto trustedKeys = std::make_unique( manifests, manifests, env.timeKeeper(), app.config().legacy("database_path"), env.journal); auto const localSigningPublic = randomNode(); BEAST_EXPECT(trustedKeys->load(localSigningPublic, cfgKeys, emptyCfgPublishers)); BEAST_EXPECT(trustedKeys->localPublicKey() == localSigningPublic); BEAST_EXPECT(trustedKeys->listed(localSigningPublic)); for (auto const& n : configList) BEAST_EXPECT(trustedKeys->listed(n)); } { // local validator key (with manifest) not on config list ManifestCache manifests; auto trustedKeys = std::make_unique( manifests, manifests, env.timeKeeper(), app.config().legacy("database_path"), env.journal); manifests.applyManifest(*deserializeManifest(cfgManifest)); BEAST_EXPECT(trustedKeys->load(localSigningPublicOuter, cfgKeys, emptyCfgPublishers)); BEAST_EXPECT(trustedKeys->localPublicKey() == localMasterPublic); BEAST_EXPECT(trustedKeys->listed(localSigningPublicOuter)); BEAST_EXPECT(trustedKeys->listed(localMasterPublic)); for (auto const& n : configList) BEAST_EXPECT(trustedKeys->listed(n)); } { ManifestCache manifests; auto trustedKeys = std::make_unique( manifests, manifests, env.timeKeeper(), app.config().legacy("database_path"), env.journal); // load should reject invalid validator list signing keys std::vector badPublishers({"NotASigningKey"}); BEAST_EXPECT(!trustedKeys->load({}, emptyCfgKeys, badPublishers)); // load should reject validator list signing keys with invalid // encoding std::vector keys({randomMasterKey(), randomMasterKey(), randomMasterKey()}); badPublishers.clear(); for (auto const& key : keys) badPublishers.push_back(toBase58(TokenType::NodePublic, key)); BEAST_EXPECT(!trustedKeys->load({}, emptyCfgKeys, badPublishers)); for (auto const& key : keys) BEAST_EXPECT(!trustedKeys->trustedPublisher(key)); // load should accept valid validator list publisher keys std::vector cfgPublishers; for (auto const& key : keys) cfgPublishers.push_back(strHex(key)); BEAST_EXPECT(trustedKeys->load({}, emptyCfgKeys, cfgPublishers)); for (auto const& key : keys) BEAST_EXPECT(trustedKeys->trustedPublisher(key)); BEAST_EXPECT(trustedKeys->getListThreshold() == keys.size() / 2 + 1); } { ManifestCache manifests; auto trustedKeys = std::make_unique( manifests, manifests, env.timeKeeper(), app.config().legacy("database_path"), env.journal); std::vector keys( {randomMasterKey(), randomMasterKey(), randomMasterKey(), randomMasterKey()}); std::vector cfgPublishers; for (auto const& key : keys) cfgPublishers.push_back(strHex(key)); // explicitly set the list threshold BEAST_EXPECT(trustedKeys->load({}, emptyCfgKeys, cfgPublishers, std::size_t(2))); for (auto const& key : keys) BEAST_EXPECT(trustedKeys->trustedPublisher(key)); BEAST_EXPECT(trustedKeys->getListThreshold() == 2); } { // Attempt to load a publisher key that has been revoked. // Should fail ManifestCache valManifests; ManifestCache pubManifests; auto trustedKeys = std::make_unique( valManifests, pubManifests, env.timeKeeper(), app.config().legacy("database_path"), env.journal); auto const pubRevokedSecret = randomSecretKey(); auto const pubRevokedPublic = derivePublicKey(KeyType::ed25519, pubRevokedSecret); auto const pubRevokedSigning = randomKeyPair(KeyType::secp256k1); // make this manifest revoked (seq num = max) // -- thus should not be loaded pubManifests.applyManifest(*deserializeManifest(makeManifestString( pubRevokedPublic, pubRevokedSecret, pubRevokedSigning.first, pubRevokedSigning.second, std::numeric_limits::max()))); // these two are not revoked (and not in the manifest cache at all.) auto legitKey1 = randomMasterKey(); auto legitKey2 = randomMasterKey(); std::vector cfgPublishers = { strHex(pubRevokedPublic), strHex(legitKey1), strHex(legitKey2)}; BEAST_EXPECT(trustedKeys->load({}, emptyCfgKeys, cfgPublishers)); BEAST_EXPECT(!trustedKeys->trustedPublisher(pubRevokedPublic)); BEAST_EXPECT(trustedKeys->trustedPublisher(legitKey1)); BEAST_EXPECT(trustedKeys->trustedPublisher(legitKey2)); // 2 is the threshold for 3 publishers (even though 1 is revoked) BEAST_EXPECT(trustedKeys->getListThreshold() == 2); } { // One (of two) publisher keys has been revoked, the user had // explicitly set validator list threshold to 2. ManifestCache valManifests; ManifestCache pubManifests; auto trustedKeys = std::make_unique( valManifests, pubManifests, env.timeKeeper(), app.config().legacy("database_path"), env.journal); auto const pubRevokedSecret = randomSecretKey(); auto const pubRevokedPublic = derivePublicKey(KeyType::ed25519, pubRevokedSecret); auto const pubRevokedSigning = randomKeyPair(KeyType::secp256k1); // make this manifest revoked (seq num = max) // -- thus should not be loaded pubManifests.applyManifest(*deserializeManifest(makeManifestString( pubRevokedPublic, pubRevokedSecret, pubRevokedSigning.first, pubRevokedSigning.second, std::numeric_limits::max()))); // this one is not revoked (and not in the manifest cache at all.) auto legitKey = randomMasterKey(); std::vector cfgPublishers = {strHex(pubRevokedPublic), strHex(legitKey)}; BEAST_EXPECT(trustedKeys->load({}, emptyCfgKeys, cfgPublishers, std::size_t(2))); BEAST_EXPECT(!trustedKeys->trustedPublisher(pubRevokedPublic)); BEAST_EXPECT(trustedKeys->trustedPublisher(legitKey)); // 2 is the threshold, as requested in configuration BEAST_EXPECT(trustedKeys->getListThreshold() == 2); } } void testApplyLists() { testcase("Apply list"); using namespace std::chrono_literals; std::string const siteUri = "testApplyList.test"; auto checkAvailable = [this]( auto const& trustedKeys, auto const& hexPublic, auto const& manifest, auto const version, std::vector> const& expected) { auto const available = trustedKeys->getAvailable(hexPublic); BEAST_EXPECT(!version || available); if (available) { auto const& a = *available; BEAST_EXPECT(a[jss::public_key] == hexPublic); BEAST_EXPECT(a[jss::manifest] == manifest); // Because multiple lists were processed, the version was // overridden BEAST_EXPECT(a[jss::version] == version); if (version == 1) { BEAST_EXPECT(expected.size() == 1); BEAST_EXPECT(a[jss::blob] == expected[0].first); BEAST_EXPECT(a[jss::signature] == expected[0].second); BEAST_EXPECT(!a.isMember(jss::blobs_v2)); } else if (BEAST_EXPECT(a.isMember(jss::blobs_v2))) { BEAST_EXPECT(!a.isMember(jss::blob)); BEAST_EXPECT(!a.isMember(jss::signature)); auto const& blobs_v2 = a[jss::blobs_v2]; BEAST_EXPECT(blobs_v2.isArray() && blobs_v2.size() == expected.size()); for (unsigned int i = 0; i < expected.size(); ++i) { BEAST_EXPECT(blobs_v2[i][jss::blob] == expected[i].first); BEAST_EXPECT(blobs_v2[i][jss::signature] == expected[i].second); } } } }; ManifestCache manifests; jtx::Env env(*this); auto& app = env.app(); auto trustedKeys = std::make_unique( manifests, manifests, env.app().timeKeeper(), app.config().legacy("database_path"), env.journal); auto expectTrusted = [this, &trustedKeys](std::vector const& list) { for (auto const& val : list) { BEAST_EXPECT(trustedKeys->listed(val.masterPublic)); BEAST_EXPECT(trustedKeys->listed(val.signingPublic)); } }; auto expectUntrusted = [this, &trustedKeys](std::vector const& list) { for (auto const& val : list) { BEAST_EXPECT(!trustedKeys->listed(val.masterPublic)); BEAST_EXPECT(!trustedKeys->listed(val.signingPublic)); } }; auto const publisherSecret = randomSecretKey(); auto const publisherPublic = derivePublicKey(KeyType::ed25519, publisherSecret); auto const hexPublic = strHex(publisherPublic.begin(), publisherPublic.end()); auto const pubSigningKeys1 = randomKeyPair(KeyType::secp256k1); auto const manifest1 = base64_encode(makeManifestString( publisherPublic, publisherSecret, pubSigningKeys1.first, pubSigningKeys1.second, 1)); std::vector cfgKeys1({strHex(publisherPublic)}); std::vector emptyCfgKeys; BEAST_EXPECT(trustedKeys->load({}, emptyCfgKeys, cfgKeys1)); std::map> const lists = []() { auto constexpr listSize = 20; auto constexpr numLists = 9; std::map> lists; // 1-based to correspond with the individually named blobs below. for (auto i = 1; i <= numLists; ++i) { auto& list = lists[i]; list.reserve(listSize); while (list.size() < listSize) list.push_back(randomValidator()); } return lists; }(); // Attempt an expired list (fail) and a single list (succeed) env.timeKeeper().set(env.timeKeeper().now() + 1s); auto const version = 1; auto const sequence1 = 1; auto const expiredblob = makeList(lists.at(1), sequence1, env.timeKeeper().now().time_since_epoch().count()); auto const expiredSig = signList(expiredblob, pubSigningKeys1); NetClock::time_point const validUntil = env.timeKeeper().now() + 3600s; auto const sequence2 = 2; auto const blob2 = makeList(lists.at(2), sequence2, validUntil.time_since_epoch().count()); auto const sig2 = signList(blob2, pubSigningKeys1); checkResult( trustedKeys->applyLists( manifest1, version, {{expiredblob, expiredSig, {}}, {blob2, sig2, {}}}, siteUri), publisherPublic, ListDisposition::expired, ListDisposition::accepted); expectTrusted(lists.at(2)); checkAvailable(trustedKeys, hexPublic, manifest1, version, {{blob2, sig2}}); // Do not apply future lists, but process them auto const version2 = 2; auto const sequence7 = 7; auto const effective7 = validUntil - 60s; auto const expiration7 = effective7 + 3600s; auto const blob7 = makeList( lists.at(7), sequence7, expiration7.time_since_epoch().count(), effective7.time_since_epoch().count()); auto const sig7 = signList(blob7, pubSigningKeys1); auto const sequence8 = 8; auto const effective8 = expiration7 - 60s; auto const expiration8 = effective8 + 3600s; auto const blob8 = makeList( lists.at(8), sequence8, expiration8.time_since_epoch().count(), effective8.time_since_epoch().count()); auto const sig8 = signList(blob8, pubSigningKeys1); checkResult( trustedKeys->applyLists( manifest1, version2, {{blob7, sig7, {}}, {blob8, sig8, {}}}, siteUri), publisherPublic, ListDisposition::pending, ListDisposition::pending); expectUntrusted(lists.at(7)); expectUntrusted(lists.at(8)); // Do not apply out-of-order future list, but process it auto const sequence6 = 6; auto const effective6 = effective7 - 60s; auto const expiration6 = effective6 + 3600s; auto const blob6 = makeList( lists.at(6), sequence6, expiration6.time_since_epoch().count(), effective6.time_since_epoch().count()); auto const sig6 = signList(blob6, pubSigningKeys1); // Process future list that is overridden by a later list auto const sequence6a = 5; auto const effective6a = effective6 + 60s; auto const expiration6a = effective6a + 3600s; auto const blob6a = makeList( lists.at(5), sequence6a, expiration6a.time_since_epoch().count(), effective6a.time_since_epoch().count()); auto const sig6a = signList(blob6a, pubSigningKeys1); checkResult( trustedKeys->applyLists( manifest1, version, {{blob6a, sig6a, {}}, {blob6, sig6, {}}}, siteUri), publisherPublic, ListDisposition::pending, ListDisposition::pending); expectUntrusted(lists.at(6)); expectTrusted(lists.at(2)); // Do not apply re-process lists known future sequence numbers checkResult( trustedKeys->applyLists( manifest1, version, {{blob7, sig7, {}}, {blob6, sig6, {}}}, siteUri), publisherPublic, ListDisposition::known_sequence, ListDisposition::known_sequence); expectUntrusted(lists.at(6)); expectUntrusted(lists.at(7)); expectTrusted(lists.at(2)); // try empty or mangled manifest checkResult( trustedKeys->applyLists("", version, {{blob7, sig7, {}}, {blob6, sig6, {}}}, siteUri), publisherPublic, ListDisposition::invalid, ListDisposition::invalid); checkResult( trustedKeys->applyLists( base64_encode("not a manifest"), version, {{blob7, sig7, {}}, {blob6, sig6, {}}}, siteUri), publisherPublic, ListDisposition::invalid, ListDisposition::invalid); // do not use list from untrusted publisher auto const untrustedManifest = base64_encode(makeManifestString( randomMasterKey(), publisherSecret, pubSigningKeys1.first, pubSigningKeys1.second, 1)); checkResult( trustedKeys->applyLists(untrustedManifest, version, {{blob2, sig2, {}}}, siteUri), publisherPublic, ListDisposition::untrusted, ListDisposition::untrusted); // do not use list with unhandled version auto const badVersion = 666; checkResult( trustedKeys->applyLists(manifest1, badVersion, {{blob2, sig2, {}}}, siteUri), publisherPublic, ListDisposition::unsupported_version, ListDisposition::unsupported_version); // apply list with highest sequence number auto const sequence3 = 3; auto const blob3 = makeList(lists.at(3), sequence3, validUntil.time_since_epoch().count()); auto const sig3 = signList(blob3, pubSigningKeys1); checkResult( trustedKeys->applyLists(manifest1, version, {{blob3, sig3, {}}}, siteUri), publisherPublic, ListDisposition::accepted, ListDisposition::accepted); expectUntrusted(lists.at(1)); expectUntrusted(lists.at(2)); expectTrusted(lists.at(3)); // Note that blob6a is not present, because it was dropped during // processing checkAvailable( trustedKeys, hexPublic, manifest1, 2, {{blob3, sig3}, {blob6, sig6}, {blob7, sig7}, {blob8, sig8}}); // do not re-apply lists with past or current sequence numbers checkResult( trustedKeys->applyLists( manifest1, version, {{blob2, sig2, {}}, {blob3, sig3, {}}}, siteUri), publisherPublic, ListDisposition::stale, ListDisposition::same_sequence); // apply list with new publisher key updated by manifest. Also send some // old lists along with the old manifest auto const pubSigningKeys2 = randomKeyPair(KeyType::secp256k1); auto manifest2 = base64_encode(makeManifestString( publisherPublic, publisherSecret, pubSigningKeys2.first, pubSigningKeys2.second, 2)); auto const sequence4 = 4; auto const blob4 = makeList(lists.at(4), sequence4, validUntil.time_since_epoch().count()); auto const sig4 = signList(blob4, pubSigningKeys2); checkResult( trustedKeys->applyLists( manifest2, version, {{blob2, sig2, manifest1}, {blob3, sig3, manifest1}, {blob4, sig4, {}}}, siteUri), publisherPublic, ListDisposition::stale, ListDisposition::accepted); expectUntrusted(lists.at(2)); expectUntrusted(lists.at(3)); expectTrusted(lists.at(4)); checkAvailable( trustedKeys, hexPublic, manifest2, 2, {{blob4, sig4}, {blob6, sig6}, {blob7, sig7}, {blob8, sig8}}); auto const sequence5 = 5; auto const blob5 = makeList(lists.at(5), sequence5, validUntil.time_since_epoch().count()); auto const badSig = signList(blob5, pubSigningKeys1); checkResult( trustedKeys->applyLists(manifest1, version, {{blob5, badSig, {}}}, siteUri), publisherPublic, ListDisposition::invalid, ListDisposition::invalid); expectUntrusted(lists.at(2)); expectUntrusted(lists.at(3)); expectTrusted(lists.at(4)); expectUntrusted(lists.at(5)); // Reprocess the pending list, but the signature is no longer valid checkResult( trustedKeys->applyLists( manifest1, version, {{blob7, sig7, {}}, {blob8, sig8, {}}}, siteUri), publisherPublic, ListDisposition::invalid, ListDisposition::invalid); expectTrusted(lists.at(4)); expectUntrusted(lists.at(7)); expectUntrusted(lists.at(8)); // Automatically rotate the first pending already processed list using // updateTrusted. Note that the timekeeper is NOT moved, so the close // time will be ahead of the test's wall clock trustedKeys->updateTrusted( {}, effective6 + 1s, env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); expectUntrusted(lists.at(3)); expectTrusted(lists.at(6)); checkAvailable( trustedKeys, hexPublic, manifest2, 2, {{blob6, sig6}, {blob7, sig7}, {blob8, sig8}}); // Automatically rotate the LAST pending list using updateTrusted, // bypassing blob7. Note that the timekeeper IS moved, so the provided // close time will be behind the test's wall clock, and thus the wall // clock is used. env.timeKeeper().set(effective8); trustedKeys->updateTrusted( {}, effective8 + 1s, env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); expectUntrusted(lists.at(6)); expectUntrusted(lists.at(7)); expectTrusted(lists.at(8)); checkAvailable(trustedKeys, hexPublic, manifest2, 2, {{blob8, sig8}}); // resign the pending list with new key and validate it, but it's // already valid Also try reprocessing the pending list with an // explicit manifest // - it is still invalid auto const sig8_2 = signList(blob8, pubSigningKeys2); checkResult( trustedKeys->applyLists( manifest2, version, {{blob8, sig8, manifest1}, {blob8, sig8_2, {}}}, siteUri), publisherPublic, ListDisposition::invalid, ListDisposition::same_sequence); expectTrusted(lists.at(8)); checkAvailable(trustedKeys, hexPublic, manifest2, 2, {{blob8, sig8}}); // do not apply list with revoked publisher key // applied list is removed due to revoked publisher key auto const signingKeysMax = randomKeyPair(KeyType::secp256k1); auto maxManifest = base64_encode(makeRevocationString(publisherPublic, publisherSecret)); auto const sequence9 = 9; auto const blob9 = makeList(lists.at(9), sequence9, validUntil.time_since_epoch().count()); auto const sig9 = signList(blob9, signingKeysMax); checkResult( trustedKeys->applyLists(maxManifest, version, {{blob9, sig9, {}}}, siteUri), publisherPublic, ListDisposition::untrusted, ListDisposition::untrusted); BEAST_EXPECT(!trustedKeys->trustedPublisher(publisherPublic)); for (auto const& [num, list] : lists) { (void)num; expectUntrusted(list); } checkAvailable(trustedKeys, hexPublic, manifest2, 0, {}); } void testGetAvailable() { testcase("GetAvailable"); using namespace std::chrono_literals; std::string const siteUri = "testApplyList.test"; ManifestCache manifests; jtx::Env env(*this); auto& app = env.app(); auto trustedKeys = std::make_unique( manifests, manifests, env.app().timeKeeper(), app.config().legacy("database_path"), env.journal); auto const publisherSecret = randomSecretKey(); auto const publisherPublic = derivePublicKey(KeyType::ed25519, publisherSecret); auto const hexPublic = strHex(publisherPublic.begin(), publisherPublic.end()); auto const pubSigningKeys1 = randomKeyPair(KeyType::secp256k1); auto const manifest = base64_encode(makeManifestString( publisherPublic, publisherSecret, pubSigningKeys1.first, pubSigningKeys1.second, 1)); std::vector cfgKeys1({strHex(publisherPublic)}); std::vector emptyCfgKeys; BEAST_EXPECT(trustedKeys->load({}, emptyCfgKeys, cfgKeys1)); std::vector const list = []() { auto constexpr listSize = 20; std::vector list; list.reserve(listSize); while (list.size() < listSize) list.push_back(randomValidator()); return list; }(); // Process a list env.timeKeeper().set(env.timeKeeper().now() + 1s); NetClock::time_point const validUntil = env.timeKeeper().now() + 3600s; auto const blob = makeList(list, 1, validUntil.time_since_epoch().count()); auto const sig = signList(blob, pubSigningKeys1); { // list unavailable auto const available = trustedKeys->getAvailable(hexPublic); BEAST_EXPECT(!available); } BEAST_EXPECT( trustedKeys->applyLists(manifest, 1, {{blob, sig, {}}}, siteUri).bestDisposition() == ListDisposition::accepted); { // invalid public key auto const available = trustedKeys->getAvailable(hexPublic + "invalid", 1); BEAST_EXPECT(!available); } { // unknown public key auto const badSecret = randomSecretKey(); auto const badPublic = derivePublicKey(KeyType::ed25519, badSecret); auto const hexBad = strHex(badPublic.begin(), badPublic.end()); auto const available = trustedKeys->getAvailable(hexBad, 1); BEAST_EXPECT(!available); } { // bad version 0 auto const available = trustedKeys->getAvailable(hexPublic, 0); if (BEAST_EXPECT(available)) { auto const& a = *available; BEAST_EXPECT(!a); } } { // bad version 3 auto const available = trustedKeys->getAvailable(hexPublic, 3); if (BEAST_EXPECT(available)) { auto const& a = *available; BEAST_EXPECT(!a); } } { // version 1 auto const available = trustedKeys->getAvailable(hexPublic, 1); if (BEAST_EXPECT(available)) { auto const& a = *available; BEAST_EXPECT(a[jss::public_key] == hexPublic); BEAST_EXPECT(a[jss::manifest] == manifest); BEAST_EXPECT(a[jss::version] == 1); BEAST_EXPECT(a[jss::blob] == blob); BEAST_EXPECT(a[jss::signature] == sig); BEAST_EXPECT(!a.isMember(jss::blobs_v2)); } } { // version 2 auto const available = trustedKeys->getAvailable(hexPublic, 2); if (BEAST_EXPECT(available)) { auto const& a = *available; BEAST_EXPECT(a[jss::public_key] == hexPublic); BEAST_EXPECT(a[jss::manifest] == manifest); BEAST_EXPECT(a[jss::version] == 2); if (BEAST_EXPECT(a.isMember(jss::blobs_v2))) { BEAST_EXPECT(!a.isMember(jss::blob)); BEAST_EXPECT(!a.isMember(jss::signature)); auto const& blobs_v2 = a[jss::blobs_v2]; BEAST_EXPECT(blobs_v2.isArray() && blobs_v2.size() == 1); BEAST_EXPECT(blobs_v2[0u][jss::blob] == blob); BEAST_EXPECT(blobs_v2[0u][jss::signature] == sig); } } } } void testUpdateTrusted() { testcase("Update trusted"); std::string const siteUri = "testUpdateTrusted.test"; ManifestCache manifestsOuter; jtx::Env env(*this); auto& app = env.app(); auto trustedKeysOuter = std::make_unique( manifestsOuter, manifestsOuter, env.timeKeeper(), app.config().legacy("database_path"), env.journal); std::vector cfgPublishersOuter; hash_set activeValidatorsOuter; std::size_t const maxKeys = 40; { std::vector cfgKeys; cfgKeys.reserve(maxKeys); hash_set unseenValidators; while (cfgKeys.size() != maxKeys) { auto const valKey = randomNode(); cfgKeys.push_back(toBase58(TokenType::NodePublic, valKey)); if (cfgKeys.size() <= maxKeys - 5) activeValidatorsOuter.emplace(calcNodeID(valKey)); else unseenValidators.emplace(calcNodeID(valKey)); } BEAST_EXPECT(trustedKeysOuter->load({}, cfgKeys, cfgPublishersOuter)); // updateTrusted should make all configured validators trusted // even if they are not active/seen TrustChanges changes = trustedKeysOuter->updateTrusted( activeValidatorsOuter, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); for (auto const& val : unseenValidators) activeValidatorsOuter.emplace(val); BEAST_EXPECT(changes.added == activeValidatorsOuter); BEAST_EXPECT(changes.removed.empty()); BEAST_EXPECT(trustedKeysOuter->quorum() == std::ceil(cfgKeys.size() * 0.8f)); for (auto const& val : cfgKeys) { if (auto const valKey = parseBase58(TokenType::NodePublic, val)) { BEAST_EXPECT(trustedKeysOuter->listed(*valKey)); BEAST_EXPECT(trustedKeysOuter->trusted(*valKey)); } else fail(); } changes = trustedKeysOuter->updateTrusted( activeValidatorsOuter, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(changes.added.empty()); BEAST_EXPECT(changes.removed.empty()); BEAST_EXPECT(trustedKeysOuter->quorum() == std::ceil(cfgKeys.size() * 0.8f)); } { // update with manifests auto const masterPrivate = randomSecretKey(); auto const masterPublic = derivePublicKey(KeyType::ed25519, masterPrivate); std::vector cfgKeys({toBase58(TokenType::NodePublic, masterPublic)}); BEAST_EXPECT(trustedKeysOuter->load({}, cfgKeys, cfgPublishersOuter)); auto const signingKeys1 = randomKeyPair(KeyType::secp256k1); auto const signingPublic1 = signingKeys1.first; activeValidatorsOuter.emplace(calcNodeID(masterPublic)); // Should not trust ephemeral signing key if there is no manifest TrustChanges changes = trustedKeysOuter->updateTrusted( activeValidatorsOuter, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(changes.added == asNodeIDs({masterPublic})); BEAST_EXPECT(changes.removed.empty()); BEAST_EXPECT(trustedKeysOuter->quorum() == std::ceil((maxKeys + 1) * 0.8f)); BEAST_EXPECT(trustedKeysOuter->listed(masterPublic)); BEAST_EXPECT(trustedKeysOuter->trusted(masterPublic)); BEAST_EXPECT(!trustedKeysOuter->listed(signingPublic1)); BEAST_EXPECT(!trustedKeysOuter->trusted(signingPublic1)); // Should trust the ephemeral signing key from the applied manifest auto m1 = deserializeManifest(makeManifestString( masterPublic, masterPrivate, signingPublic1, signingKeys1.second, 1)); BEAST_EXPECT( manifestsOuter.applyManifest(std::move(*m1)) == ManifestDisposition::accepted); BEAST_EXPECT(trustedKeysOuter->listed(masterPublic)); BEAST_EXPECT(trustedKeysOuter->trusted(masterPublic)); BEAST_EXPECT(trustedKeysOuter->listed(signingPublic1)); BEAST_EXPECT(trustedKeysOuter->trusted(signingPublic1)); // Should only trust the ephemeral signing key // from the newest applied manifest auto const signingKeys2 = randomKeyPair(KeyType::secp256k1); auto const signingPublic2 = signingKeys2.first; auto m2 = deserializeManifest(makeManifestString( masterPublic, masterPrivate, signingPublic2, signingKeys2.second, 2)); BEAST_EXPECT( manifestsOuter.applyManifest(std::move(*m2)) == ManifestDisposition::accepted); BEAST_EXPECT(trustedKeysOuter->listed(masterPublic)); BEAST_EXPECT(trustedKeysOuter->trusted(masterPublic)); BEAST_EXPECT(trustedKeysOuter->listed(signingPublic2)); BEAST_EXPECT(trustedKeysOuter->trusted(signingPublic2)); BEAST_EXPECT(!trustedKeysOuter->listed(signingPublic1)); BEAST_EXPECT(!trustedKeysOuter->trusted(signingPublic1)); // Should not trust keys from revoked master public key auto const signingKeysMax = randomKeyPair(KeyType::secp256k1); auto const signingPublicMax = signingKeysMax.first; activeValidatorsOuter.emplace(calcNodeID(signingPublicMax)); auto mMax = deserializeManifest(makeRevocationString(masterPublic, masterPrivate)); BEAST_EXPECT(mMax->revoked()); BEAST_EXPECT( manifestsOuter.applyManifest(std::move(*mMax)) == ManifestDisposition::accepted); BEAST_EXPECT(manifestsOuter.getSigningKey(masterPublic) == masterPublic); BEAST_EXPECT(manifestsOuter.revoked(masterPublic)); // Revoked key remains trusted until list is updated BEAST_EXPECT(trustedKeysOuter->listed(masterPublic)); BEAST_EXPECT(trustedKeysOuter->trusted(masterPublic)); changes = trustedKeysOuter->updateTrusted( activeValidatorsOuter, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(changes.removed == asNodeIDs({masterPublic})); BEAST_EXPECT(changes.added.empty()); BEAST_EXPECT(trustedKeysOuter->quorum() == std::ceil(maxKeys * 0.8f)); BEAST_EXPECT(trustedKeysOuter->listed(masterPublic)); BEAST_EXPECT(!trustedKeysOuter->trusted(masterPublic)); BEAST_EXPECT(!trustedKeysOuter->listed(signingPublicMax)); BEAST_EXPECT(!trustedKeysOuter->trusted(signingPublicMax)); BEAST_EXPECT(!trustedKeysOuter->listed(signingPublic2)); BEAST_EXPECT(!trustedKeysOuter->trusted(signingPublic2)); BEAST_EXPECT(!trustedKeysOuter->listed(signingPublic1)); BEAST_EXPECT(!trustedKeysOuter->trusted(signingPublic1)); } { // Make quorum unattainable if lists from any publishers are // unavailable auto trustedKeys = std::make_unique( manifestsOuter, manifestsOuter, env.timeKeeper(), app.config().legacy("database_path"), env.journal); auto const publisherSecret = randomSecretKey(); auto const publisherPublic = derivePublicKey(KeyType::ed25519, publisherSecret); std::vector cfgPublishers({strHex(publisherPublic)}); std::vector emptyCfgKeys; BEAST_EXPECT(trustedKeys->load({}, emptyCfgKeys, cfgPublishers)); TrustChanges changes = trustedKeys->updateTrusted( activeValidatorsOuter, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(changes.removed.empty()); BEAST_EXPECT(changes.added.empty()); BEAST_EXPECT(trustedKeys->quorum() == std::numeric_limits::max()); } { // Trust explicitly listed validators also when list threshold is // higher than 1 auto trustedKeys = std::make_unique( manifestsOuter, manifestsOuter, env.timeKeeper(), app.config().legacy("database_path"), env.journal); auto const masterPrivate = randomSecretKey(); auto const masterPublic = derivePublicKey(KeyType::ed25519, masterPrivate); std::vector cfgKeys({toBase58(TokenType::NodePublic, masterPublic)}); auto const publisher1Secret = randomSecretKey(); auto const publisher1Public = derivePublicKey(KeyType::ed25519, publisher1Secret); auto const publisher2Secret = randomSecretKey(); auto const publisher2Public = derivePublicKey(KeyType::ed25519, publisher2Secret); std::vector cfgPublishers( {strHex(publisher1Public), strHex(publisher2Public)}); BEAST_EXPECT(trustedKeys->load({}, cfgKeys, cfgPublishers, std::size_t(2))); TrustChanges changes = trustedKeys->updateTrusted( activeValidatorsOuter, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(changes.removed.empty()); BEAST_EXPECT(changes.added.size() == 1); BEAST_EXPECT(trustedKeys->listed(masterPublic)); BEAST_EXPECT(trustedKeys->trusted(masterPublic)); } { // Should use custom minimum quorum std::size_t const minQuorum = 1; ManifestCache manifests; auto trustedKeys = std::make_unique( manifests, manifests, env.timeKeeper(), app.config().legacy("database_path"), env.journal, minQuorum); std::size_t n = 10; std::vector cfgKeys; cfgKeys.reserve(n); hash_set expectedTrusted; hash_set activeValidators; NodeID toBeSeen; while (cfgKeys.size() < n) { auto const valKey = randomNode(); cfgKeys.push_back(toBase58(TokenType::NodePublic, valKey)); expectedTrusted.emplace(calcNodeID(valKey)); if (cfgKeys.size() < std::ceil(n * 0.8f)) activeValidators.emplace(calcNodeID(valKey)); else if (cfgKeys.size() < std::ceil(n * 0.8f)) toBeSeen = calcNodeID(valKey); } BEAST_EXPECT(trustedKeys->load({}, cfgKeys, cfgPublishersOuter)); TrustChanges changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(changes.removed.empty()); BEAST_EXPECT(changes.added == expectedTrusted); BEAST_EXPECT(trustedKeys->quorum() == minQuorum); // Use configured quorum even when seen validators >= quorum activeValidators.emplace(toBeSeen); changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(changes.removed.empty()); BEAST_EXPECT(changes.added.empty()); BEAST_EXPECT(trustedKeys->quorum() == minQuorum); } { // Remove expired published list auto trustedKeys = std::make_unique( manifestsOuter, manifestsOuter, env.app().timeKeeper(), app.config().legacy("database_path"), env.journal); std::vector emptyCfgKeys; auto const publisherKeys = randomKeyPair(KeyType::secp256k1); auto const pubSigningKeys = randomKeyPair(KeyType::secp256k1); auto const manifest = base64_encode(makeManifestString( publisherKeys.first, publisherKeys.second, pubSigningKeys.first, pubSigningKeys.second, 1)); std::vector cfgKeys({strHex(publisherKeys.first)}); BEAST_EXPECT(trustedKeys->load({}, emptyCfgKeys, cfgKeys)); std::vector list({randomValidator(), randomValidator()}); hash_set activeValidators( asNodeIDs({list[0].masterPublic, list[1].masterPublic})); // do not apply expired list auto const version = 1; auto const sequence = 1; using namespace std::chrono_literals; NetClock::time_point const validUntil = env.timeKeeper().now() + 60s; auto const blob = makeList(list, sequence, validUntil.time_since_epoch().count()); auto const sig = signList(blob, pubSigningKeys); BEAST_EXPECT( ListDisposition::accepted == trustedKeys->applyLists(manifest, version, {{blob, sig, {}}}, siteUri) .bestDisposition()); TrustChanges changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(changes.removed.empty()); BEAST_EXPECT(changes.added == activeValidators); for (Validator const& val : list) { BEAST_EXPECT(trustedKeys->trusted(val.masterPublic)); BEAST_EXPECT(trustedKeys->trusted(val.signingPublic)); } BEAST_EXPECT(trustedKeys->quorum() == 2); env.timeKeeper().set(validUntil); changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(changes.removed == activeValidators); BEAST_EXPECT(changes.added.empty()); BEAST_EXPECT(!trustedKeys->trusted(list[0].masterPublic)); BEAST_EXPECT(!trustedKeys->trusted(list[1].masterPublic)); BEAST_EXPECT(trustedKeys->quorum() == std::numeric_limits::max()); // (Re)trust validators from new valid list std::vector list2({list[0], randomValidator()}); activeValidators.insert(calcNodeID(list2[1].masterPublic)); auto const sequence2 = 2; NetClock::time_point const expiration2 = env.timeKeeper().now() + 60s; auto const blob2 = makeList(list2, sequence2, expiration2.time_since_epoch().count()); auto const sig2 = signList(blob2, pubSigningKeys); BEAST_EXPECT( ListDisposition::accepted == trustedKeys->applyLists(manifest, version, {{blob2, sig2, {}}}, siteUri) .bestDisposition()); changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(changes.removed.empty()); BEAST_EXPECT( changes.added == asNodeIDs({list2[0].masterPublic, list2[1].masterPublic})); for (Validator const& val : list2) { BEAST_EXPECT(trustedKeys->trusted(val.masterPublic)); BEAST_EXPECT(trustedKeys->trusted(val.signingPublic)); } BEAST_EXPECT(!trustedKeys->trusted(list[1].masterPublic)); BEAST_EXPECT(!trustedKeys->trusted(list[1].signingPublic)); BEAST_EXPECT(trustedKeys->quorum() == 2); } { // Test 1-9 configured validators auto trustedKeys = std::make_unique( manifestsOuter, manifestsOuter, env.timeKeeper(), app.config().legacy("database_path"), env.journal); std::vector cfgPublishers; hash_set activeValidators; hash_set activeKeys; std::vector cfgKeys; cfgKeys.reserve(9); while (cfgKeys.size() < cfgKeys.capacity()) { auto const valKey = randomNode(); cfgKeys.push_back(toBase58(TokenType::NodePublic, valKey)); activeValidators.emplace(calcNodeID(valKey)); activeKeys.emplace(valKey); BEAST_EXPECT(trustedKeys->load({}, cfgKeys, cfgPublishers)); TrustChanges changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(changes.removed.empty()); BEAST_EXPECT(changes.added == asNodeIDs({valKey})); BEAST_EXPECT(trustedKeys->quorum() == std::ceil(cfgKeys.size() * 0.8f)); for (auto const& key : activeKeys) BEAST_EXPECT(trustedKeys->trusted(key)); } } { // Test 2-9 configured validators as validator auto trustedKeys = std::make_unique( manifestsOuter, manifestsOuter, env.timeKeeper(), app.config().legacy("database_path"), env.journal); auto const localKey = randomNode(); std::vector cfgPublishers; hash_set activeValidators; hash_set activeKeys; std::vector cfgKeys{toBase58(TokenType::NodePublic, localKey)}; cfgKeys.reserve(9); while (cfgKeys.size() < cfgKeys.capacity()) { auto const valKey = randomNode(); cfgKeys.push_back(toBase58(TokenType::NodePublic, valKey)); activeValidators.emplace(calcNodeID(valKey)); activeKeys.emplace(valKey); BEAST_EXPECT(trustedKeys->load(localKey, cfgKeys, cfgPublishers)); TrustChanges changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(changes.removed.empty()); if (cfgKeys.size() > 2) BEAST_EXPECT(changes.added == asNodeIDs({valKey})); else BEAST_EXPECT(changes.added == asNodeIDs({localKey, valKey})); BEAST_EXPECT(trustedKeys->quorum() == std::ceil(cfgKeys.size() * 0.8f)); for (auto const& key : activeKeys) BEAST_EXPECT(trustedKeys->trusted(key)); } } { // Trusted set should include all validators from multiple lists ManifestCache manifests; auto trustedKeys = std::make_unique( manifests, manifests, env.timeKeeper(), app.config().legacy("database_path"), env.journal); hash_set activeValidators; std::vector valKeys; valKeys.reserve(maxKeys); while (valKeys.size() != maxKeys) { valKeys.push_back(randomValidator()); activeValidators.emplace(calcNodeID(valKeys.back().masterPublic)); } // locals[0]: from 0 to maxKeys - 4 // locals[1]: from 1 to maxKeys - 2 // locals[2]: from 2 to maxKeys constexpr static int publishers = 3; std::array< std::pair, publishers> locals = { std::make_pair(valKeys.cbegin(), valKeys.cend() - 4), std::make_pair(valKeys.cbegin() + 1, valKeys.cend() - 2), std::make_pair(valKeys.cbegin() + 2, valKeys.cend()), }; auto addPublishedList = [&, this](int i) { auto const publisherSecret = randomSecretKey(); auto const publisherPublic = derivePublicKey(KeyType::ed25519, publisherSecret); auto const pubSigningKeys = randomKeyPair(KeyType::secp256k1); auto const manifest = base64_encode(makeManifestString( publisherPublic, publisherSecret, pubSigningKeys.first, pubSigningKeys.second, 1)); std::vector cfgPublishers({strHex(publisherPublic)}); std::vector emptyCfgKeys; // Threshold of 1 will result in a union of all the lists BEAST_EXPECT(trustedKeys->load({}, emptyCfgKeys, cfgPublishers, std::size_t(1))); auto const version = 1; auto const sequence = 1; using namespace std::chrono_literals; NetClock::time_point const validUntil = env.timeKeeper().now() + 3600s; std::vector localKeys{locals[i].first, locals[i].second}; auto const blob = makeList(localKeys, sequence, validUntil.time_since_epoch().count()); auto const sig = signList(blob, pubSigningKeys); BEAST_EXPECT( ListDisposition::accepted == trustedKeys->applyLists(manifest, version, {{blob, sig, {}}}, siteUri) .bestDisposition()); }; // Apply multiple published lists for (auto i = 0; i < publishers; ++i) addPublishedList(i); BEAST_EXPECT(trustedKeys->getListThreshold() == 1); TrustChanges changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == std::ceil(valKeys.size() * 0.8f)); hash_set added; for (auto const& val : valKeys) { BEAST_EXPECT(trustedKeys->trusted(val.masterPublic)); added.insert(calcNodeID(val.masterPublic)); } BEAST_EXPECT(changes.added == added); BEAST_EXPECT(changes.removed.empty()); } { // Trusted set should include validators from intersection of lists ManifestCache manifests; auto trustedKeys = std::make_unique( manifests, manifests, env.timeKeeper(), app.config().legacy("database_path"), env.journal); hash_set activeValidators; std::vector valKeys; valKeys.reserve(maxKeys); while (valKeys.size() != maxKeys) { valKeys.push_back(randomValidator()); activeValidators.emplace(calcNodeID(valKeys.back().masterPublic)); } // locals[0]: from 0 to maxKeys - 4 // locals[1]: from 1 to maxKeys - 2 // locals[2]: from 2 to maxKeys // intersection of at least 2: same as locals[1] // intersection when 1 is dropped: from 2 to maxKeys - 4 constexpr static int publishers = 3; std::array< std::pair, publishers> locals = { std::make_pair(valKeys.cbegin(), valKeys.cend() - 4), std::make_pair(valKeys.cbegin() + 1, valKeys.cend() - 2), std::make_pair(valKeys.cbegin() + 2, valKeys.cend()), }; auto addPublishedList = [&, this]( int i, NetClock::time_point& validUntil1, NetClock::time_point& validUntil2) { auto const publisherSecret = randomSecretKey(); auto const publisherPublic = derivePublicKey(KeyType::ed25519, publisherSecret); auto const pubSigningKeys = randomKeyPair(KeyType::secp256k1); auto const manifest = base64_encode(makeManifestString( publisherPublic, publisherSecret, pubSigningKeys.first, pubSigningKeys.second, 1)); std::vector cfgPublishers({strHex(publisherPublic)}); std::vector emptyCfgKeys; BEAST_EXPECT(trustedKeys->load({}, emptyCfgKeys, cfgPublishers)); auto const version = 1; auto const sequence = 1; using namespace std::chrono_literals; // Want to drop 1 sooner NetClock::time_point const validUntil = env.timeKeeper().now() + (i == 2 ? 120s : i == 1 ? 60s : 3600s); if (i == 1) validUntil1 = validUntil; else if (i == 2) validUntil2 = validUntil; std::vector localKeys{locals[i].first, locals[i].second}; auto const blob = makeList(localKeys, sequence, validUntil.time_since_epoch().count()); auto const sig = signList(blob, pubSigningKeys); BEAST_EXPECT( ListDisposition::accepted == trustedKeys->applyLists(manifest, version, {{blob, sig, {}}}, siteUri) .bestDisposition()); }; // Apply multiple published lists // validUntil1 is expiration time for locals[1] NetClock::time_point validUntil1, validUntil2; for (auto i = 0; i < publishers; ++i) addPublishedList(i, validUntil1, validUntil2); BEAST_EXPECT(trustedKeys->getListThreshold() == 2); TrustChanges changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == std::ceil((valKeys.size() - 3) * 0.8f)); for (auto const& val : valKeys) BEAST_EXPECT(trustedKeys->listed(val.masterPublic)); hash_set added; for (std::size_t i = 0; i < maxKeys; ++i) { auto const& val = valKeys[i]; if (i >= 1 && i < maxKeys - 2) { BEAST_EXPECT(trustedKeys->trusted(val.masterPublic)); added.insert(calcNodeID(val.masterPublic)); } else BEAST_EXPECT(!trustedKeys->trusted(val.masterPublic)); } BEAST_EXPECT(changes.added == added); BEAST_EXPECT(changes.removed.empty()); // Expire locals[1] env.timeKeeper().set(validUntil1); changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == std::ceil((valKeys.size() - 6) * 0.8f)); for (auto const& val : valKeys) BEAST_EXPECT(trustedKeys->listed(val.masterPublic)); hash_set removed; for (std::size_t i = 0; i < maxKeys; ++i) { auto const& val = valKeys[i]; if (i >= 2 && i < maxKeys - 4) BEAST_EXPECT(trustedKeys->trusted(val.masterPublic)); else { BEAST_EXPECT(!trustedKeys->trusted(val.masterPublic)); if (i >= 1 && i < maxKeys - 2) removed.insert(calcNodeID(val.masterPublic)); } } BEAST_EXPECT(changes.added.empty()); BEAST_EXPECT(changes.removed == removed); // Expire locals[2], which removes all validators env.timeKeeper().set(validUntil2); changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == std::numeric_limits::max()); removed.clear(); for (std::size_t i = 0; i < maxKeys; ++i) { auto const& val = valKeys[i]; if (i < maxKeys - 4) BEAST_EXPECT(trustedKeys->listed(val.masterPublic)); else BEAST_EXPECT(!trustedKeys->listed(val.masterPublic)); BEAST_EXPECT(!trustedKeys->trusted(val.masterPublic)); if (i >= 2 && i < maxKeys - 4) removed.insert(calcNodeID(val.masterPublic)); } BEAST_EXPECT(changes.added.empty()); BEAST_EXPECT(changes.removed == removed); } } void testExpires() { testcase("Expires"); std::string const siteUri = "testExpires.test"; jtx::Env env(*this); auto& app = env.app(); auto toStr = [](PublicKey const& publicKey) { return toBase58(TokenType::NodePublic, publicKey); }; // Config listed keys { ManifestCache manifests; auto trustedKeys = std::make_unique( manifests, manifests, env.timeKeeper(), app.config().legacy("database_path"), env.journal); // Empty list has no expiration BEAST_EXPECT(trustedKeys->expires() == std::nullopt); // Config listed keys have maximum expiry PublicKey localCfgListed = randomNode(); trustedKeys->load({}, {toStr(localCfgListed)}, {}); BEAST_EXPECT( trustedKeys->expires() && trustedKeys->expires().value() == NetClock::time_point::max()); BEAST_EXPECT(trustedKeys->listed(localCfgListed)); } // Published keys with expirations { ManifestCache manifests; auto trustedKeys = std::make_unique( manifests, manifests, env.app().timeKeeper(), app.config().legacy("database_path"), env.journal); std::vector validators = {randomValidator()}; hash_set activeValidators; for (Validator const& val : validators) activeValidators.insert(calcNodeID(val.masterPublic)); // Store prepared list data to control when it is applied struct PreparedList { PublicKey publisherPublic; std::string manifest; std::vector blobs; int version; std::vector expirations; }; using namespace std::chrono_literals; auto addPublishedList = [this, &env, &trustedKeys, &validators]() { auto const publisherSecret = randomSecretKey(); auto const publisherPublic = derivePublicKey(KeyType::ed25519, publisherSecret); auto const pubSigningKeys = randomKeyPair(KeyType::secp256k1); auto const manifest = base64_encode(makeManifestString( publisherPublic, publisherSecret, pubSigningKeys.first, pubSigningKeys.second, 1)); std::vector cfgPublishers({strHex(publisherPublic)}); std::vector emptyCfgKeys; BEAST_EXPECT(trustedKeys->load({}, emptyCfgKeys, cfgPublishers)); auto const version = 2; auto const sequence1 = 1; NetClock::time_point const expiration1 = env.timeKeeper().now() + 1800s; auto const blob1 = makeList(validators, sequence1, expiration1.time_since_epoch().count()); auto const sig1 = signList(blob1, pubSigningKeys); NetClock::time_point const effective2 = expiration1 - 300s; NetClock::time_point const expiration2 = effective2 + 1800s; auto const sequence2 = 2; auto const blob2 = makeList( validators, sequence2, expiration2.time_since_epoch().count(), effective2.time_since_epoch().count()); auto const sig2 = signList(blob2, pubSigningKeys); return PreparedList{ publisherPublic, manifest, {{blob1, sig1, {}}, {blob2, sig2, {}}}, version, {expiration1, expiration2}}; }; // Configure two publishers and prepare 2 lists PreparedList prep1 = addPublishedList(); env.timeKeeper().set(env.timeKeeper().now() + 200s); PreparedList prep2 = addPublishedList(); // Initially, no list has been published, so no known expiration BEAST_EXPECT(trustedKeys->expires() == std::nullopt); // Apply first list checkResult( trustedKeys->applyLists(prep1.manifest, prep1.version, prep1.blobs, siteUri), prep1.publisherPublic, ListDisposition::pending, ListDisposition::accepted); // One list still hasn't published, so expiration is still // unknown BEAST_EXPECT(trustedKeys->expires() == std::nullopt); // Apply second list checkResult( trustedKeys->applyLists(prep2.manifest, prep2.version, prep2.blobs, siteUri), prep2.publisherPublic, ListDisposition::pending, ListDisposition::accepted); // We now have loaded both lists, so expiration is known BEAST_EXPECT( trustedKeys->expires() && trustedKeys->expires().value() == prep1.expirations.back()); // Advance past the first list's LAST validFrom date. It remains // the earliest validUntil, while rotating in the second list { env.timeKeeper().set(prep1.expirations.front() - 1s); auto changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT( trustedKeys->expires() && trustedKeys->expires().value() == prep1.expirations.back()); BEAST_EXPECT(!changes.added.empty()); BEAST_EXPECT(changes.removed.empty()); } // Advance past the first list's LAST validUntil, but it remains // the earliest validUntil, while being invalidated { env.timeKeeper().set(prep1.expirations.back() + 1s); auto changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT( trustedKeys->expires() && trustedKeys->expires().value() == prep1.expirations.back()); BEAST_EXPECT(changes.added.empty()); BEAST_EXPECT(changes.removed.empty()); } } } void testNegativeUNL() { testcase("NegativeUNL"); jtx::Env env(*this); ManifestCache manifests; auto createValidatorList = [&](std::uint32_t vlSize, std::optional minimumQuorum = {}) -> std::shared_ptr { auto trustedKeys = std::make_shared( manifests, manifests, env.timeKeeper(), env.app().config().legacy("database_path"), env.journal, minimumQuorum); std::vector cfgPublishers; std::vector cfgKeys; hash_set activeValidators; cfgKeys.reserve(vlSize); while (cfgKeys.size() < cfgKeys.capacity()) { auto const valKey = randomNode(); cfgKeys.push_back(toBase58(TokenType::NodePublic, valKey)); activeValidators.emplace(calcNodeID(valKey)); } if (trustedKeys->load({}, cfgKeys, cfgPublishers)) { trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); if (minimumQuorum == trustedKeys->quorum() || trustedKeys->quorum() == std::ceil(cfgKeys.size() * 0.8f)) return trustedKeys; } return nullptr; }; /* * Test NegativeUNL * == Combinations == * -- UNL size: 34, 35, 57 * -- nUNL size: 0%, 20%, 30%, 50% * * == with UNL size 60 * -- set == get, * -- check quorum, with nUNL size: 0, 12, 30, 18 * -- nUNL overlap: |nUNL - UNL| = 5, with nUNL size: 18 * -- with command line minimumQuorum = 50%, * seen_reliable affected by nUNL */ { hash_set activeValidators; //== Combinations == std::array unlSizes = {34, 35, 39, 60}; std::array nUnlPercent = {0, 20, 30, 50}; for (auto us : unlSizes) { for (auto np : nUnlPercent) { auto validators = createValidatorList(us); BEAST_EXPECT(validators); if (validators) { std::uint32_t nUnlSize = us * np / 100; auto unl = validators->getTrustedMasterKeys(); hash_set nUnl; auto it = unl.begin(); for (std::uint32_t i = 0; i < nUnlSize; ++i) { nUnl.insert(*it); ++it; } validators->setNegativeUNL(nUnl); validators->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT( validators->quorum() == static_cast( std::ceil(std::max((us - nUnlSize) * 0.8f, us * 0.6f)))); } } } } { //== with UNL size 60 auto validators = createValidatorList(60); BEAST_EXPECT(validators); if (validators) { hash_set activeValidators; auto unl = validators->getTrustedMasterKeys(); BEAST_EXPECT(unl.size() == 60); { //-- set == get, //-- check quorum, with nUNL size: 0, 30, 18, 12 auto nUnlChange = [&](std::uint32_t nUnlSize, std::uint32_t quorum) -> bool { hash_set nUnl; auto it = unl.begin(); for (std::uint32_t i = 0; i < nUnlSize; ++i) { nUnl.insert(*it); ++it; } validators->setNegativeUNL(nUnl); auto nUnl_temp = validators->getNegativeUNL(); if (nUnl_temp.size() == nUnl.size()) { for (auto& n : nUnl_temp) { if (nUnl.find(n) == nUnl.end()) return false; } validators->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); return validators->quorum() == quorum; } return false; }; BEAST_EXPECT(nUnlChange(0, 48)); BEAST_EXPECT(nUnlChange(30, 36)); BEAST_EXPECT(nUnlChange(18, 36)); BEAST_EXPECT(nUnlChange(12, 39)); } { // nUNL overlap: |nUNL - UNL| = 5, with nUNL size: // 18 auto nUnl = validators->getNegativeUNL(); BEAST_EXPECT(nUnl.size() == 12); std::size_t ss = 33; std::vector data(ss, 0); data[0] = 0xED; for (int i = 0; i < 6; ++i) { Slice s(data.data(), ss); data[1]++; nUnl.emplace(s); } validators->setNegativeUNL(nUnl); validators->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(validators->quorum() == 39); } } } { //== with UNL size 60 //-- with command line minimumQuorum = 50%, // seen_reliable affected by nUNL auto validators = createValidatorList(60, 30); BEAST_EXPECT(validators); if (validators) { hash_set activeValidators; hash_set unl = validators->getTrustedMasterKeys(); auto it = unl.begin(); for (std::uint32_t i = 0; i < 50; ++i) { activeValidators.insert(calcNodeID(*it)); ++it; } validators->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(validators->quorum() == 30); hash_set nUnl; it = unl.begin(); for (std::uint32_t i = 0; i < 20; ++i) { nUnl.insert(*it); ++it; } validators->setNegativeUNL(nUnl); validators->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(validators->quorum() == 30); } } } void testSha512Hash() { testcase("Sha512 hashing"); // Tests that ValidatorList hash_append helpers with a single blob // returns the same result as xrpl::Sha512Half used by the // TMValidatorList protocol message handler std::string const manifest = "This is not really a manifest"; std::string const blob = "This is not really a blob"; std::string const signature = "This is not really a signature"; std::uint32_t const version = 1; auto const global = sha512Half(manifest, blob, signature, version); BEAST_EXPECT(!!global); std::vector blobVector(1); blobVector[0].blob = blob; blobVector[0].signature = signature; BEAST_EXPECT(global == sha512Half(manifest, blobVector, version)); BEAST_EXPECT(global != sha512Half(signature, blobVector, version)); { std::map blobMap{{99, blobVector[0]}}; BEAST_EXPECT(global == sha512Half(manifest, blobMap, version)); BEAST_EXPECT(global != sha512Half(blob, blobMap, version)); } { protocol::TMValidatorList msg1; msg1.set_manifest(manifest); msg1.set_blob(blob); msg1.set_signature(signature); msg1.set_version(version); BEAST_EXPECT(global == sha512Half(msg1)); msg1.set_signature(blob); BEAST_EXPECT(global != sha512Half(msg1)); } { protocol::TMValidatorListCollection msg2; msg2.set_manifest(manifest); msg2.set_version(version); auto& bi = *msg2.add_blobs(); bi.set_blob(blob); bi.set_signature(signature); BEAST_EXPECT(global == sha512Half(msg2)); bi.set_manifest(manifest); BEAST_EXPECT(global != sha512Half(msg2)); } } void testBuildMessages() { testcase("Build and split messages"); std::uint32_t const manifestCutoff = 7; auto extractHeader = [this](Message& message) { auto const& buffer = message.getBuffer(compression::Compressed::Off); boost::beast::multi_buffer buffers; // simulate multi-buffer auto start = buffer.begin(); auto end = buffer.end(); std::vector slice(start, end); buffers.commit( boost::asio::buffer_copy( buffers.prepare(slice.size()), boost::asio::buffer(slice))); boost::system::error_code ec; auto header = detail::parseMessageHeader(ec, buffers.data(), buffers.size()); BEAST_EXPECT(!ec); return std::make_pair(header, buffers); }; auto extractProtocolMessage1 = [this, &extractHeader](Message& message) { auto [header, buffers] = extractHeader(message); if (BEAST_EXPECT(header) && BEAST_EXPECT(header->message_type == protocol::mtVALIDATOR_LIST)) { auto const msg = detail::parseMessageContent(*header, buffers.data()); BEAST_EXPECT(msg); return msg; } return std::shared_ptr(); }; auto extractProtocolMessage2 = [this, &extractHeader](Message& message) { auto [header, buffers] = extractHeader(message); if (BEAST_EXPECT(header) && BEAST_EXPECT(header->message_type == protocol::mtVALIDATOR_LIST_COLLECTION)) { auto const msg = detail::parseMessageContent( *header, buffers.data()); BEAST_EXPECT(msg); return msg; } return std::shared_ptr(); }; auto verifyMessage = [this, manifestCutoff, &extractProtocolMessage1, &extractProtocolMessage2]( auto const version, auto const& manifest, auto const& blobInfos, auto const& messages, std::vector>> expectedInfo) { BEAST_EXPECT(messages.size() == expectedInfo.size()); auto msgIter = expectedInfo.begin(); for (auto const& messageWithHash : messages) { if (!BEAST_EXPECT(msgIter != expectedInfo.end())) break; if (!BEAST_EXPECT(messageWithHash.message)) continue; auto const& expectedSeqs = msgIter->second; auto seqIter = expectedSeqs.begin(); auto const size = messageWithHash.message->getBuffer(compression::Compressed::Off).size(); // This size is arbitrary, but shouldn't change BEAST_EXPECT(size == msgIter->first); if (expectedSeqs.size() == 1) { auto const msg = extractProtocolMessage1(*messageWithHash.message); auto const expectedVersion = 1; if (BEAST_EXPECT(msg)) { BEAST_EXPECT(msg->version() == expectedVersion); if (!BEAST_EXPECT(seqIter != expectedSeqs.end())) continue; auto const& expectedBlob = blobInfos.at(*seqIter); BEAST_EXPECT((*seqIter < manifestCutoff) == !!expectedBlob.manifest); auto const expectedManifest = *seqIter < manifestCutoff && expectedBlob.manifest ? *expectedBlob.manifest : manifest; BEAST_EXPECT(msg->manifest() == expectedManifest); BEAST_EXPECT(msg->blob() == expectedBlob.blob); BEAST_EXPECT(msg->signature() == expectedBlob.signature); ++seqIter; BEAST_EXPECT(seqIter == expectedSeqs.end()); BEAST_EXPECT( messageWithHash.hash == sha512Half( expectedManifest, expectedBlob.blob, expectedBlob.signature, expectedVersion)); } } else { std::vector hashingBlobs; hashingBlobs.reserve(msgIter->second.size()); auto const msg = extractProtocolMessage2(*messageWithHash.message); if (BEAST_EXPECT(msg)) { BEAST_EXPECT(msg->version() == version); BEAST_EXPECT(msg->manifest() == manifest); for (auto const& blobInfo : msg->blobs()) { if (!BEAST_EXPECT(seqIter != expectedSeqs.end())) break; auto const& expectedBlob = blobInfos.at(*seqIter); hashingBlobs.push_back(expectedBlob); BEAST_EXPECT(blobInfo.has_manifest() == !!expectedBlob.manifest); BEAST_EXPECT( blobInfo.has_manifest() == (*seqIter < manifestCutoff)); if (*seqIter < manifestCutoff) BEAST_EXPECT(blobInfo.manifest() == *expectedBlob.manifest); BEAST_EXPECT(blobInfo.blob() == expectedBlob.blob); BEAST_EXPECT(blobInfo.signature() == expectedBlob.signature); ++seqIter; } BEAST_EXPECT(seqIter == expectedSeqs.end()); } BEAST_EXPECT( messageWithHash.hash == sha512Half(manifest, hashingBlobs, version)); } ++msgIter; } BEAST_EXPECT(msgIter == expectedInfo.end()); }; auto verifyBuildMessages = [this]( std::pair const& result, std::size_t expectedSequence, std::size_t expectedSize) { BEAST_EXPECT(result.first == expectedSequence); BEAST_EXPECT(result.second == expectedSize); }; std::string const manifest = "This is not a manifest"; std::uint32_t const version = 2; // Mutable so items can be removed in later tests. auto const blobInfos = [manifestCutoff = manifestCutoff]() { std::map bis; for (auto seq : {5, 6, 7, 10, 12}) { auto& b = bis[seq]; std::stringstream s; s << "This is not a blob with sequence " << seq; b.blob = s.str(); s.str(std::string()); s << "This is not a signature for sequence " << seq; b.signature = s.str(); if (seq < manifestCutoff) { // add a manifest for the "early" blobs s.str(std::string()); s << "This is not manifest " << seq; b.manifest = s.str(); } } return bis; }(); auto const maxSequence = blobInfos.rbegin()->first; BEAST_EXPECT(maxSequence == 12); std::vector messages; // Version 1 // This peer has a VL ahead of our "current" verifyBuildMessages( ValidatorList::buildValidatorListMessages( 1, 8, maxSequence, version, manifest, blobInfos, messages), 0, 0); BEAST_EXPECT(messages.size() == 0); // Don't repeat the work if messages is populated, even though the // peerSequence provided indicates it should. Note that this // situation is contrived for this test and should never happen in // real code. messages.emplace_back(); verifyBuildMessages( ValidatorList::buildValidatorListMessages( 1, 3, maxSequence, version, manifest, blobInfos, messages), 5, 0); BEAST_EXPECT(messages.size() == 1 && !messages.front().message); // Generate a version 1 message messages.clear(); verifyBuildMessages( ValidatorList::buildValidatorListMessages( 1, 3, maxSequence, version, manifest, blobInfos, messages), 5, 1); if (BEAST_EXPECT(messages.size() == 1) && BEAST_EXPECT(messages.front().message)) { auto const& messageWithHash = messages.front(); auto const msg = extractProtocolMessage1(*messageWithHash.message); auto const size = messageWithHash.message->getBuffer(compression::Compressed::Off).size(); // This size is arbitrary, but shouldn't change BEAST_EXPECT(size == 108); auto const& expected = blobInfos.at(5); if (BEAST_EXPECT(msg)) { BEAST_EXPECT(msg->version() == 1); BEAST_EXPECT(msg->manifest() == *expected.manifest); BEAST_EXPECT(msg->blob() == expected.blob); BEAST_EXPECT(msg->signature() == expected.signature); } BEAST_EXPECT( messageWithHash.hash == sha512Half(*expected.manifest, expected.blob, expected.signature, 1)); } // Version 2 messages.clear(); // This peer has a VL ahead of us. verifyBuildMessages( ValidatorList::buildValidatorListMessages( 2, maxSequence * 2, maxSequence, version, manifest, blobInfos, messages), 0, 0); BEAST_EXPECT(messages.size() == 0); // Don't repeat the work if messages is populated, even though the // peerSequence provided indicates it should. Note that this // situation is contrived for this test and should never happen in // real code. messages.emplace_back(); verifyBuildMessages( ValidatorList::buildValidatorListMessages( 2, 3, maxSequence, version, manifest, blobInfos, messages), maxSequence, 0); BEAST_EXPECT(messages.size() == 1 && !messages.front().message); // Generate a version 2 message. Don't send the current messages.clear(); verifyBuildMessages( ValidatorList::buildValidatorListMessages( 2, 5, maxSequence, version, manifest, blobInfos, messages), maxSequence, 4); verifyMessage(version, manifest, blobInfos, messages, {{372, {6, 7, 10, 12}}}); // Test message splitting on size limits. // Set a limit that should give two messages messages.clear(); verifyBuildMessages( ValidatorList::buildValidatorListMessages( 2, 5, maxSequence, version, manifest, blobInfos, messages, 300), maxSequence, 4); verifyMessage(version, manifest, blobInfos, messages, {{212, {6, 7}}, {192, {10, 12}}}); // Set a limit between the size of the two earlier messages so one // will split and the other won't messages.clear(); verifyBuildMessages( ValidatorList::buildValidatorListMessages( 2, 5, maxSequence, version, manifest, blobInfos, messages, 200), maxSequence, 4); verifyMessage( version, manifest, blobInfos, messages, {{108, {6}}, {108, {7}}, {192, {10, 12}}}); // Set a limit so that all the VLs are sent individually messages.clear(); verifyBuildMessages( ValidatorList::buildValidatorListMessages( 2, 5, maxSequence, version, manifest, blobInfos, messages, 150), maxSequence, 4); verifyMessage( version, manifest, blobInfos, messages, {{108, {6}}, {108, {7}}, {110, {10}}, {110, {12}}}); // Set a limit smaller than some of the messages. Because single // messages send regardless, they will all still be sent messages.clear(); verifyBuildMessages( ValidatorList::buildValidatorListMessages( 2, 5, maxSequence, version, manifest, blobInfos, messages, 108), maxSequence, 4); verifyMessage( version, manifest, blobInfos, messages, {{108, {6}}, {108, {7}}, {110, {10}}, {110, {12}}}); } void testQuorumDisabled() { testcase("Test quorum disabled"); std::string const siteUri = "testQuorumDisabled.test"; jtx::Env env(*this); auto& app = env.app(); constexpr std::size_t maxKeys = 20; hash_set activeValidators; std::vector valKeys; while (valKeys.size() != maxKeys) { valKeys.push_back(randomValidator()); activeValidators.emplace(calcNodeID(valKeys.back().masterPublic)); } struct Publisher { bool revoked; PublicKey pubKey; std::pair signingKeys; std::string manifest; NetClock::time_point expiry = {}; }; // Create ValidatorList with a set of countTotal publishers, of which // first countRevoked are revoked and the last one expires early auto makeValidatorList = [&, this]( std::size_t countTotal, std::size_t countRevoked, std::size_t listThreshold, ManifestCache& pubManifests, ManifestCache& valManifests, std::optional self, std::vector& publishers // out ) -> std::unique_ptr { auto result = std::make_unique( valManifests, pubManifests, env.timeKeeper(), app.config().legacy("database_path"), env.journal); std::vector cfgPublishers; for (std::size_t i = 0; i < countTotal; ++i) { auto const publisherSecret = randomSecretKey(); auto const publisherPublic = derivePublicKey(KeyType::ed25519, publisherSecret); auto const pubSigningKeys = randomKeyPair(KeyType::secp256k1); cfgPublishers.push_back(strHex(publisherPublic)); constexpr auto revoked = std::numeric_limits::max(); auto const manifest = base64_encode(makeManifestString( publisherPublic, publisherSecret, pubSigningKeys.first, pubSigningKeys.second, i < countRevoked ? revoked : 1)); publishers.push_back( Publisher{i < countRevoked, publisherPublic, pubSigningKeys, manifest}); } std::vector const emptyCfgKeys; auto threshold = listThreshold > 0 ? std::optional(listThreshold) : std::nullopt; if (self) { valManifests.applyManifest(*deserializeManifest(base64_decode(self->manifest))); BEAST_EXPECT( result->load(self->signingPublic, emptyCfgKeys, cfgPublishers, threshold)); } else { BEAST_EXPECT(result->load({}, emptyCfgKeys, cfgPublishers, threshold)); } for (std::size_t i = 0; i < countTotal; ++i) { using namespace std::chrono_literals; publishers[i].expiry = env.timeKeeper().now() + (i == countTotal - 1 ? 60s : 3600s); auto const blob = makeList(valKeys, 1, publishers[i].expiry.time_since_epoch().count()); auto const sig = signList(blob, publishers[i].signingKeys); BEAST_EXPECT( result->applyLists(publishers[i].manifest, 1, {{blob, sig, {}}}, siteUri) .bestDisposition() == (publishers[i].revoked ? ListDisposition::untrusted : ListDisposition::accepted)); } return result; }; // Test cases use 5 publishers. constexpr auto quorumDisabled = std::numeric_limits::max(); { // List threshold = 5 (same as number of trusted publishers) ManifestCache pubManifests; ManifestCache valManifests; std::vector publishers; // Self is a random validator auto const self = randomValidator(); auto const keysTotal = valKeys.size() + 1; auto trustedKeys = makeValidatorList( 5, // 0, 5, pubManifests, valManifests, self, publishers); BEAST_EXPECT(trustedKeys->getListThreshold() == 5); for (auto const& p : publishers) BEAST_EXPECT(trustedKeys->trustedPublisher(p.pubKey)); TrustChanges changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == std::ceil(keysTotal * 0.8f)); BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal); hash_set added; added.insert(calcNodeID(self.masterPublic)); for (auto const& val : valKeys) { BEAST_EXPECT(trustedKeys->trusted(val.masterPublic)); added.insert(calcNodeID(val.masterPublic)); } BEAST_EXPECT(changes.added == added); BEAST_EXPECT(changes.removed.empty()); // Expire one publisher - only trusted validator is self env.timeKeeper().set(publishers.back().expiry); changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled); BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == 1); hash_set removed; BEAST_EXPECT(trustedKeys->trusted(self.masterPublic)); for (auto const& val : valKeys) { BEAST_EXPECT(trustedKeys->listed(val.masterPublic)); BEAST_EXPECT(!trustedKeys->trusted(val.masterPublic)); removed.insert(calcNodeID(val.masterPublic)); } BEAST_EXPECT(changes.added.empty()); BEAST_EXPECT(changes.removed == removed); } { // List threshold = 5 (same as number of trusted publishers) ManifestCache pubManifests; ManifestCache valManifests; std::vector publishers; auto const keysTotal = valKeys.size(); auto trustedKeys = makeValidatorList( 5, // 0, 5, pubManifests, valManifests, {}, publishers); BEAST_EXPECT(trustedKeys->getListThreshold() == 5); for (auto const& p : publishers) BEAST_EXPECT(trustedKeys->trustedPublisher(p.pubKey)); TrustChanges changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == std::ceil(keysTotal * 0.8f)); BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal); hash_set added; for (auto const& val : valKeys) { BEAST_EXPECT(trustedKeys->trusted(val.masterPublic)); added.insert(calcNodeID(val.masterPublic)); } BEAST_EXPECT(changes.added == added); BEAST_EXPECT(changes.removed.empty()); // Expire one publisher - no trusted validators env.timeKeeper().set(publishers.back().expiry); changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled); BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == 0); hash_set removed; for (auto const& val : valKeys) { BEAST_EXPECT(trustedKeys->listed(val.masterPublic)); BEAST_EXPECT(!trustedKeys->trusted(val.masterPublic)); removed.insert(calcNodeID(val.masterPublic)); } BEAST_EXPECT(changes.added.empty()); BEAST_EXPECT(changes.removed == removed); } { // List threshold = 4, 1 publisher is revoked ManifestCache pubManifests; ManifestCache valManifests; std::vector publishers; // Self is in UNL auto const self = valKeys[1]; auto const keysTotal = valKeys.size(); auto trustedKeys = makeValidatorList( 5, // 1, 4, pubManifests, valManifests, self, publishers); BEAST_EXPECT(trustedKeys->getListThreshold() == 4); int untrustedCount = 0; for (auto const& p : publishers) { bool const trusted = trustedKeys->trustedPublisher(p.pubKey); BEAST_EXPECT(p.revoked ^ trusted); untrustedCount += trusted ? 0 : 1; } BEAST_EXPECT(untrustedCount == 1); TrustChanges changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == std::ceil(keysTotal * 0.8f)); BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal); hash_set added; for (auto const& val : valKeys) { BEAST_EXPECT(trustedKeys->trusted(val.masterPublic)); added.insert(calcNodeID(val.masterPublic)); } BEAST_EXPECT(changes.added == added); BEAST_EXPECT(changes.removed.empty()); // Expire one publisher - only trusted validator is self env.timeKeeper().set(publishers.back().expiry); changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled); BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == 1); hash_set removed; BEAST_EXPECT(trustedKeys->trusted(self.masterPublic)); for (auto const& val : valKeys) { BEAST_EXPECT(trustedKeys->listed(val.masterPublic)); if (val.masterPublic != self.masterPublic) { BEAST_EXPECT(!trustedKeys->trusted(val.masterPublic)); removed.insert(calcNodeID(val.masterPublic)); } } BEAST_EXPECT(changes.added.empty()); BEAST_EXPECT(changes.removed == removed); } { // List threshold = 3 (default), 2 publishers are revoked ManifestCache pubManifests; ManifestCache valManifests; std::vector publishers; // Self is a random validator auto const self = randomValidator(); auto const keysTotal = valKeys.size() + 1; auto trustedKeys = makeValidatorList( 5, // 2, 0, pubManifests, valManifests, self, publishers); BEAST_EXPECT(trustedKeys->getListThreshold() == 3); int untrustedCount = 0; for (auto const& p : publishers) { bool const trusted = trustedKeys->trustedPublisher(p.pubKey); BEAST_EXPECT(p.revoked ^ trusted); untrustedCount += trusted ? 0 : 1; } BEAST_EXPECT(untrustedCount == 2); TrustChanges changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == std::ceil(keysTotal * 0.8f)); BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal); hash_set added; added.insert(calcNodeID(self.masterPublic)); for (auto const& val : valKeys) { BEAST_EXPECT(trustedKeys->trusted(val.masterPublic)); added.insert(calcNodeID(val.masterPublic)); } BEAST_EXPECT(changes.added == added); BEAST_EXPECT(changes.removed.empty()); // Expire one publisher - no quorum, only trusted validator is self env.timeKeeper().set(publishers.back().expiry); changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled); BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == 1); hash_set removed; BEAST_EXPECT(trustedKeys->trusted(self.masterPublic)); for (auto const& val : valKeys) { BEAST_EXPECT(trustedKeys->listed(val.masterPublic)); BEAST_EXPECT(!trustedKeys->trusted(val.masterPublic)); removed.insert(calcNodeID(val.masterPublic)); } BEAST_EXPECT(changes.added.empty()); BEAST_EXPECT(changes.removed == removed); } { // List threshold = 3 (default), 2 publishers are revoked ManifestCache pubManifests; ManifestCache valManifests; std::vector publishers; // Self is in UNL auto const self = valKeys[5]; auto const keysTotal = valKeys.size(); auto trustedKeys = makeValidatorList( 5, // 2, 0, pubManifests, valManifests, self, publishers); BEAST_EXPECT(trustedKeys->getListThreshold() == 3); int untrustedCount = 0; for (auto const& p : publishers) { bool const trusted = trustedKeys->trustedPublisher(p.pubKey); BEAST_EXPECT(p.revoked ^ trusted); untrustedCount += trusted ? 0 : 1; } BEAST_EXPECT(untrustedCount == 2); TrustChanges changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == std::ceil(keysTotal * 0.8f)); BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal); hash_set added; for (auto const& val : valKeys) { BEAST_EXPECT(trustedKeys->trusted(val.masterPublic)); added.insert(calcNodeID(val.masterPublic)); } BEAST_EXPECT(changes.added == added); BEAST_EXPECT(changes.removed.empty()); // Expire one publisher - no quorum, only trusted validator is self env.timeKeeper().set(publishers.back().expiry); changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled); BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == 1); hash_set removed; BEAST_EXPECT(trustedKeys->trusted(self.masterPublic)); for (auto const& val : valKeys) { BEAST_EXPECT(trustedKeys->listed(val.masterPublic)); if (val.masterPublic != self.masterPublic) { BEAST_EXPECT(!trustedKeys->trusted(val.masterPublic)); removed.insert(calcNodeID(val.masterPublic)); } } BEAST_EXPECT(changes.added.empty()); BEAST_EXPECT(changes.removed == removed); } { // List threshold = 3 (default), 2 publishers are revoked ManifestCache pubManifests; ManifestCache valManifests; std::vector publishers; auto const keysTotal = valKeys.size(); auto trustedKeys = makeValidatorList( 5, // 2, 0, pubManifests, valManifests, {}, publishers); BEAST_EXPECT(trustedKeys->getListThreshold() == 3); int untrustedCount = 0; for (auto const& p : publishers) { bool const trusted = trustedKeys->trustedPublisher(p.pubKey); BEAST_EXPECT(p.revoked ^ trusted); untrustedCount += trusted ? 0 : 1; } BEAST_EXPECT(untrustedCount == 2); TrustChanges changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == std::ceil(keysTotal * 0.8f)); BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal); hash_set added; for (auto const& val : valKeys) { BEAST_EXPECT(trustedKeys->trusted(val.masterPublic)); added.insert(calcNodeID(val.masterPublic)); } BEAST_EXPECT(changes.added == added); BEAST_EXPECT(changes.removed.empty()); // Expire one publisher - no quorum, no trusted validators env.timeKeeper().set(publishers.back().expiry); changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled); BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == 0); hash_set removed; for (auto const& val : valKeys) { BEAST_EXPECT(trustedKeys->listed(val.masterPublic)); BEAST_EXPECT(!trustedKeys->trusted(val.masterPublic)); removed.insert(calcNodeID(val.masterPublic)); } BEAST_EXPECT(changes.added.empty()); BEAST_EXPECT(changes.removed == removed); } { // List threshold = 2, 1 publisher is revoked ManifestCache pubManifests; ManifestCache valManifests; std::vector publishers; // Self is a random validator auto const self = randomValidator(); auto const keysTotal = valKeys.size() + 1; auto trustedKeys = makeValidatorList( 5, // 1, 2, pubManifests, valManifests, self, publishers); BEAST_EXPECT(trustedKeys->getListThreshold() == 2); int untrustedCount = 0; for (auto const& p : publishers) { bool const trusted = trustedKeys->trustedPublisher(p.pubKey); BEAST_EXPECT(p.revoked ^ trusted); untrustedCount += trusted ? 0 : 1; } BEAST_EXPECT(untrustedCount == 1); TrustChanges changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == std::ceil(keysTotal * 0.8f)); BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal); hash_set added; added.insert(calcNodeID(self.masterPublic)); for (auto const& val : valKeys) { BEAST_EXPECT(trustedKeys->trusted(val.masterPublic)); added.insert(calcNodeID(val.masterPublic)); } BEAST_EXPECT(changes.added == added); BEAST_EXPECT(changes.removed.empty()); // Expire one publisher - no quorum env.timeKeeper().set(publishers.back().expiry); changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled); BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal); BEAST_EXPECT(trustedKeys->trusted(self.masterPublic)); for (auto const& val : valKeys) { BEAST_EXPECT(trustedKeys->listed(val.masterPublic)); BEAST_EXPECT(trustedKeys->trusted(val.masterPublic)); } BEAST_EXPECT(changes.added.empty()); BEAST_EXPECT(changes.removed.empty()); } { // List threshold = 1 ManifestCache pubManifests; ManifestCache valManifests; std::vector publishers; // Self is a random validator auto const self = randomValidator(); auto const keysTotal = valKeys.size() + 1; auto trustedKeys = makeValidatorList( 5, // 0, 1, pubManifests, valManifests, self, publishers); BEAST_EXPECT(trustedKeys->getListThreshold() == 1); for (auto const& p : publishers) BEAST_EXPECT(trustedKeys->trustedPublisher(p.pubKey)); TrustChanges changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == std::ceil(keysTotal * 0.8f)); BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal); hash_set added; added.insert(calcNodeID(self.masterPublic)); for (auto const& val : valKeys) { BEAST_EXPECT(trustedKeys->trusted(val.masterPublic)); added.insert(calcNodeID(val.masterPublic)); } BEAST_EXPECT(changes.added == added); BEAST_EXPECT(changes.removed.empty()); // Expire one publisher - no quorum env.timeKeeper().set(publishers.back().expiry); changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled); BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal); BEAST_EXPECT(trustedKeys->trusted(self.masterPublic)); for (auto const& val : valKeys) { BEAST_EXPECT(trustedKeys->listed(val.masterPublic)); BEAST_EXPECT(trustedKeys->trusted(val.masterPublic)); } BEAST_EXPECT(changes.added.empty()); BEAST_EXPECT(changes.removed.empty()); } { // List threshold = 1 ManifestCache pubManifests; ManifestCache valManifests; std::vector publishers; // Self is in UNL auto const self = valKeys[7]; auto const keysTotal = valKeys.size(); auto trustedKeys = makeValidatorList( 5, // 0, 1, pubManifests, valManifests, self, publishers); BEAST_EXPECT(trustedKeys->getListThreshold() == 1); for (auto const& p : publishers) BEAST_EXPECT(trustedKeys->trustedPublisher(p.pubKey)); TrustChanges changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == std::ceil(keysTotal * 0.8f)); BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal); hash_set added; for (auto const& val : valKeys) { BEAST_EXPECT(trustedKeys->trusted(val.masterPublic)); added.insert(calcNodeID(val.masterPublic)); } BEAST_EXPECT(changes.added == added); BEAST_EXPECT(changes.removed.empty()); // Expire one publisher - no quorum env.timeKeeper().set(publishers.back().expiry); changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled); BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal); BEAST_EXPECT(trustedKeys->trusted(self.masterPublic)); for (auto const& val : valKeys) { BEAST_EXPECT(trustedKeys->listed(val.masterPublic)); BEAST_EXPECT(trustedKeys->trusted(val.masterPublic)); } BEAST_EXPECT(changes.added.empty()); BEAST_EXPECT(changes.removed.empty()); } { // List threshold = 1 ManifestCache pubManifests; ManifestCache valManifests; std::vector publishers; auto const keysTotal = valKeys.size(); auto trustedKeys = makeValidatorList( 5, // 0, 1, pubManifests, valManifests, {}, publishers); BEAST_EXPECT(trustedKeys->getListThreshold() == 1); for (auto const& p : publishers) BEAST_EXPECT(trustedKeys->trustedPublisher(p.pubKey)); TrustChanges changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == std::ceil(keysTotal * 0.8f)); BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal); hash_set added; for (auto const& val : valKeys) { BEAST_EXPECT(trustedKeys->trusted(val.masterPublic)); added.insert(calcNodeID(val.masterPublic)); } BEAST_EXPECT(changes.added == added); BEAST_EXPECT(changes.removed.empty()); // Expire one publisher - no quorum env.timeKeeper().set(publishers.back().expiry); changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled); BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal); for (auto const& val : valKeys) { BEAST_EXPECT(trustedKeys->listed(val.masterPublic)); BEAST_EXPECT(trustedKeys->trusted(val.masterPublic)); } BEAST_EXPECT(changes.added.empty()); BEAST_EXPECT(changes.removed.empty()); } // Test cases use 2 publishers { // List threshold = 1, 1 publisher revoked ManifestCache pubManifests; ManifestCache valManifests; std::vector publishers; // Self is a random validator auto const self = randomValidator(); auto const keysTotal = valKeys.size() + 1; auto trustedKeys = makeValidatorList( 2, // 1, 1, pubManifests, valManifests, self, publishers); BEAST_EXPECT(trustedKeys->getListThreshold() == 1); int untrustedCount = 0; for (auto const& p : publishers) { bool const trusted = trustedKeys->trustedPublisher(p.pubKey); BEAST_EXPECT(p.revoked ^ trusted); untrustedCount += trusted ? 0 : 1; } BEAST_EXPECT(untrustedCount == 1); TrustChanges changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled); BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal); hash_set added; added.insert(calcNodeID(self.masterPublic)); for (auto const& val : valKeys) { BEAST_EXPECT(trustedKeys->trusted(val.masterPublic)); added.insert(calcNodeID(val.masterPublic)); } BEAST_EXPECT(changes.added == added); BEAST_EXPECT(changes.removed.empty()); // Expire one publisher - no quorum, only trusted validator is self env.timeKeeper().set(publishers.back().expiry); changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled); BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == 1); hash_set removed; BEAST_EXPECT(trustedKeys->trusted(self.masterPublic)); for (auto const& val : valKeys) { BEAST_EXPECT(!trustedKeys->listed(val.masterPublic)); BEAST_EXPECT(!trustedKeys->trusted(val.masterPublic)); removed.insert(calcNodeID(val.masterPublic)); } BEAST_EXPECT(changes.added.empty()); BEAST_EXPECT(changes.removed == removed); } { // List threshold = 1, 1 publisher revoked ManifestCache pubManifests; ManifestCache valManifests; std::vector publishers; // Self is in UNL auto const self = valKeys[5]; auto const keysTotal = valKeys.size(); auto trustedKeys = makeValidatorList( 2, // 1, 1, pubManifests, valManifests, self, publishers); BEAST_EXPECT(trustedKeys->getListThreshold() == 1); int untrustedCount = 0; for (auto const& p : publishers) { bool const trusted = trustedKeys->trustedPublisher(p.pubKey); BEAST_EXPECT(p.revoked ^ trusted); untrustedCount += trusted ? 0 : 1; } BEAST_EXPECT(untrustedCount == 1); TrustChanges changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled); BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal); hash_set added; for (auto const& val : valKeys) { BEAST_EXPECT(trustedKeys->trusted(val.masterPublic)); added.insert(calcNodeID(val.masterPublic)); } BEAST_EXPECT(changes.added == added); BEAST_EXPECT(changes.removed.empty()); // Expire one publisher - no quorum, only trusted validator is self env.timeKeeper().set(publishers.back().expiry); changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled); BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == 1); hash_set removed; BEAST_EXPECT(trustedKeys->trusted(self.masterPublic)); for (auto const& val : valKeys) { if (val.masterPublic != self.masterPublic) { BEAST_EXPECT(!trustedKeys->listed(val.masterPublic)); BEAST_EXPECT(!trustedKeys->trusted(val.masterPublic)); removed.insert(calcNodeID(val.masterPublic)); } } BEAST_EXPECT(changes.added.empty()); BEAST_EXPECT(changes.removed == removed); } { // List threshold = 1, 1 publisher revoked ManifestCache pubManifests; ManifestCache valManifests; std::vector publishers; auto const keysTotal = valKeys.size(); auto trustedKeys = makeValidatorList( 2, // 1, 1, pubManifests, valManifests, {}, publishers); BEAST_EXPECT(trustedKeys->getListThreshold() == 1); int untrustedCount = 0; for (auto const& p : publishers) { bool const trusted = trustedKeys->trustedPublisher(p.pubKey); BEAST_EXPECT(p.revoked ^ trusted); untrustedCount += trusted ? 0 : 1; } BEAST_EXPECT(untrustedCount == 1); TrustChanges changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled); BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal); hash_set added; for (auto const& val : valKeys) { BEAST_EXPECT(trustedKeys->trusted(val.masterPublic)); added.insert(calcNodeID(val.masterPublic)); } BEAST_EXPECT(changes.added == added); BEAST_EXPECT(changes.removed.empty()); // Expire one publisher - no quorum, no trusted validators env.timeKeeper().set(publishers.back().expiry); changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled); BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == 0); hash_set removed; for (auto const& val : valKeys) { BEAST_EXPECT(!trustedKeys->listed(val.masterPublic)); BEAST_EXPECT(!trustedKeys->trusted(val.masterPublic)); removed.insert(calcNodeID(val.masterPublic)); } BEAST_EXPECT(changes.added.empty()); BEAST_EXPECT(changes.removed == removed); } { // List threshold = 2 (same as number of trusted publishers) ManifestCache pubManifests; ManifestCache valManifests; std::vector publishers; // Self is a random validator auto const self = randomValidator(); auto const keysTotal = valKeys.size() + 1; auto trustedKeys = makeValidatorList( 2, // 0, 2, pubManifests, valManifests, self, publishers); BEAST_EXPECT(trustedKeys->getListThreshold() == 2); for (auto const& p : publishers) BEAST_EXPECT(trustedKeys->trustedPublisher(p.pubKey)); TrustChanges changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == std::ceil(keysTotal * 0.8f)); BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal); hash_set added; added.insert(calcNodeID(self.masterPublic)); for (auto const& val : valKeys) { BEAST_EXPECT(trustedKeys->trusted(val.masterPublic)); added.insert(calcNodeID(val.masterPublic)); } BEAST_EXPECT(changes.added == added); BEAST_EXPECT(changes.removed.empty()); // Expire one publisher - only trusted validator is self env.timeKeeper().set(publishers.back().expiry); changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled); BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == 1); hash_set removed; BEAST_EXPECT(trustedKeys->trusted(self.masterPublic)); for (auto const& val : valKeys) { BEAST_EXPECT(trustedKeys->listed(val.masterPublic)); BEAST_EXPECT(!trustedKeys->trusted(val.masterPublic)); removed.insert(calcNodeID(val.masterPublic)); } BEAST_EXPECT(changes.added.empty()); BEAST_EXPECT(changes.removed == removed); } { // List threshold = 2 (same as number of trusted publishers) ManifestCache pubManifests; ManifestCache valManifests; std::vector publishers; // Self is in UNL auto const self = valKeys[5]; auto const keysTotal = valKeys.size(); auto trustedKeys = makeValidatorList( 2, // 0, 2, pubManifests, valManifests, self, publishers); BEAST_EXPECT(trustedKeys->getListThreshold() == 2); for (auto const& p : publishers) BEAST_EXPECT(trustedKeys->trustedPublisher(p.pubKey)); TrustChanges changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == std::ceil(keysTotal * 0.8f)); BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal); hash_set added; added.insert(calcNodeID(self.masterPublic)); for (auto const& val : valKeys) { BEAST_EXPECT(trustedKeys->trusted(val.masterPublic)); added.insert(calcNodeID(val.masterPublic)); } BEAST_EXPECT(changes.added == added); BEAST_EXPECT(changes.removed.empty()); // Expire one publisher - only trusted validator is self env.timeKeeper().set(publishers.back().expiry); changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled); BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == 1); hash_set removed; BEAST_EXPECT(trustedKeys->trusted(self.masterPublic)); for (auto const& val : valKeys) { if (val.masterPublic != self.masterPublic) { BEAST_EXPECT(trustedKeys->listed(val.masterPublic)); BEAST_EXPECT(!trustedKeys->trusted(val.masterPublic)); removed.insert(calcNodeID(val.masterPublic)); } } BEAST_EXPECT(changes.added.empty()); BEAST_EXPECT(changes.removed == removed); } { // List threshold = 2 (same as number of trusted publishers) ManifestCache pubManifests; ManifestCache valManifests; std::vector publishers; auto const keysTotal = valKeys.size(); auto trustedKeys = makeValidatorList( 2, // 0, 2, pubManifests, valManifests, {}, publishers); BEAST_EXPECT(trustedKeys->getListThreshold() == 2); for (auto const& p : publishers) BEAST_EXPECT(trustedKeys->trustedPublisher(p.pubKey)); TrustChanges changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == std::ceil(keysTotal * 0.8f)); BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal); hash_set added; for (auto const& val : valKeys) { BEAST_EXPECT(trustedKeys->trusted(val.masterPublic)); added.insert(calcNodeID(val.masterPublic)); } BEAST_EXPECT(changes.added == added); BEAST_EXPECT(changes.removed.empty()); // Expire one publisher - no trusted validators env.timeKeeper().set(publishers.back().expiry); changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled); BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == 0); hash_set removed; for (auto const& val : valKeys) { BEAST_EXPECT(trustedKeys->listed(val.masterPublic)); BEAST_EXPECT(!trustedKeys->trusted(val.masterPublic)); removed.insert(calcNodeID(val.masterPublic)); } BEAST_EXPECT(changes.added.empty()); BEAST_EXPECT(changes.removed == removed); } // Test case for 1 publisher { // List threshold = 1 (default), no publisher revoked ManifestCache pubManifests; ManifestCache valManifests; std::vector publishers; // Self is a random validator auto const self = randomValidator(); auto const keysTotal = valKeys.size() + 1; auto trustedKeys = makeValidatorList( 1, // 0, 0, pubManifests, valManifests, self, publishers); BEAST_EXPECT(trustedKeys->getListThreshold() == 1); for (auto const& p : publishers) BEAST_EXPECT(trustedKeys->trustedPublisher(p.pubKey)); TrustChanges changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == std::ceil(keysTotal * 0.8f)); BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal); hash_set added; added.insert(calcNodeID(self.masterPublic)); for (auto const& val : valKeys) { BEAST_EXPECT(trustedKeys->trusted(val.masterPublic)); added.insert(calcNodeID(val.masterPublic)); } BEAST_EXPECT(changes.added == added); BEAST_EXPECT(changes.removed.empty()); // Expire one publisher - no quorum, only trusted validator is self env.timeKeeper().set(publishers.back().expiry); changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled); BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == 1); hash_set removed; BEAST_EXPECT(trustedKeys->trusted(self.masterPublic)); for (auto const& val : valKeys) { BEAST_EXPECT(!trustedKeys->listed(val.masterPublic)); BEAST_EXPECT(!trustedKeys->trusted(val.masterPublic)); removed.insert(calcNodeID(val.masterPublic)); } BEAST_EXPECT(changes.added.empty()); BEAST_EXPECT(changes.removed == removed); } // Test case for 3 publishers (for 2 is a block above) { // List threshold = 2 (default), no publisher revoked ManifestCache pubManifests; ManifestCache valManifests; std::vector publishers; // Self is in UNL auto const self = valKeys[2]; auto const keysTotal = valKeys.size(); auto trustedKeys = makeValidatorList( 3, // 0, 0, pubManifests, valManifests, self, publishers); BEAST_EXPECT(trustedKeys->getListThreshold() == 2); for (auto const& p : publishers) BEAST_EXPECT(trustedKeys->trustedPublisher(p.pubKey)); TrustChanges changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == std::ceil(keysTotal * 0.8f)); BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal); hash_set added; for (auto const& val : valKeys) { BEAST_EXPECT(trustedKeys->trusted(val.masterPublic)); added.insert(calcNodeID(val.masterPublic)); } BEAST_EXPECT(changes.added == added); BEAST_EXPECT(changes.removed.empty()); } // Test case for 4 publishers { // List threshold = 3 (default), no publisher revoked ManifestCache pubManifests; ManifestCache valManifests; std::vector publishers; auto const keysTotal = valKeys.size(); auto trustedKeys = makeValidatorList( 4, // 0, 0, pubManifests, valManifests, {}, publishers); BEAST_EXPECT(trustedKeys->getListThreshold() == 3); for (auto const& p : publishers) BEAST_EXPECT(trustedKeys->trustedPublisher(p.pubKey)); TrustChanges changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == std::ceil(keysTotal * 0.8f)); BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal); hash_set added; for (auto const& val : valKeys) { BEAST_EXPECT(trustedKeys->trusted(val.masterPublic)); added.insert(calcNodeID(val.masterPublic)); } BEAST_EXPECT(changes.added == added); BEAST_EXPECT(changes.removed.empty()); } // Test case for 6 publishers (for 5 is a large block above) { // List threshold = 4 (default), 2 publishers revoked ManifestCache pubManifests; ManifestCache valManifests; std::vector publishers; // Self is a random validator auto const self = randomValidator(); auto const keysTotal = valKeys.size() + 1; auto trustedKeys = makeValidatorList( 6, // 2, 0, pubManifests, valManifests, self, publishers); BEAST_EXPECT(trustedKeys->getListThreshold() == 4); int untrustedCount = 0; for (auto const& p : publishers) { bool const trusted = trustedKeys->trustedPublisher(p.pubKey); BEAST_EXPECT(p.revoked ^ trusted); untrustedCount += trusted ? 0 : 1; } BEAST_EXPECT(untrustedCount == 2); TrustChanges changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == std::ceil(keysTotal * 0.8f)); BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal); hash_set added; added.insert(calcNodeID(self.masterPublic)); for (auto const& val : valKeys) { BEAST_EXPECT(trustedKeys->trusted(val.masterPublic)); added.insert(calcNodeID(val.masterPublic)); } BEAST_EXPECT(changes.added == added); BEAST_EXPECT(changes.removed.empty()); // Expire one publisher - no quorum, only trusted validator is self env.timeKeeper().set(publishers.back().expiry); changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled); BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == 1); hash_set removed; BEAST_EXPECT(trustedKeys->trusted(self.masterPublic)); for (auto const& val : valKeys) { BEAST_EXPECT(trustedKeys->listed(val.masterPublic)); BEAST_EXPECT(!trustedKeys->trusted(val.masterPublic)); removed.insert(calcNodeID(val.masterPublic)); } BEAST_EXPECT(changes.added.empty()); BEAST_EXPECT(changes.removed == removed); } // Test case for 7 publishers { // List threshold = 4 (default), 3 publishers revoked ManifestCache pubManifests; ManifestCache valManifests; std::vector publishers; // Self is in UNL auto const self = valKeys[2]; auto const keysTotal = valKeys.size(); auto trustedKeys = makeValidatorList( 7, // 3, 0, pubManifests, valManifests, self, publishers); BEAST_EXPECT(trustedKeys->getListThreshold() == 4); int untrustedCount = 0; for (auto const& p : publishers) { bool const trusted = trustedKeys->trustedPublisher(p.pubKey); BEAST_EXPECT(p.revoked ^ trusted); untrustedCount += trusted ? 0 : 1; } BEAST_EXPECT(untrustedCount == 3); TrustChanges changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == std::ceil(keysTotal * 0.8f)); BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == keysTotal); hash_set added; for (auto const& val : valKeys) { BEAST_EXPECT(trustedKeys->trusted(val.masterPublic)); added.insert(calcNodeID(val.masterPublic)); } BEAST_EXPECT(changes.added == added); BEAST_EXPECT(changes.removed.empty()); // Expire one publisher - only trusted validator is self env.timeKeeper().set(publishers.back().expiry); changes = trustedKeys->updateTrusted( activeValidators, env.timeKeeper().now(), env.app().getOPs(), env.app().overlay(), env.app().getHashRouter()); BEAST_EXPECT(trustedKeys->quorum() == quorumDisabled); BEAST_EXPECT(trustedKeys->getTrustedMasterKeys().size() == 1); hash_set removed; BEAST_EXPECT(trustedKeys->trusted(self.masterPublic)); for (auto const& val : valKeys) { if (val.masterPublic != self.masterPublic) { BEAST_EXPECT(trustedKeys->listed(val.masterPublic)); BEAST_EXPECT(!trustedKeys->trusted(val.masterPublic)); removed.insert(calcNodeID(val.masterPublic)); } } BEAST_EXPECT(changes.added.empty()); BEAST_EXPECT(changes.removed == removed); } } public: void run() override { testGenesisQuorum(); testConfigLoad(); testApplyLists(); testGetAvailable(); testUpdateTrusted(); testExpires(); testNegativeUNL(); testSha512Hash(); testBuildMessages(); testQuorumDisabled(); } }; // namespace test BEAST_DEFINE_TESTSUITE(ValidatorList, app, xrpl); } // namespace test } // namespace xrpl