mirror of
https://github.com/XRPLF/rippled.git
synced 2026-10-12 22:58:11 +00:00
Compare commits
12 Commits
develop
...
dangell7/t
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6714c1aac9 | ||
|
|
553dd9d48d | ||
|
|
0501b155d2 | ||
|
|
f589ea0cd9 | ||
|
|
f11b7da32c | ||
|
|
caf8055d68 | ||
|
|
b9dfb963cd | ||
|
|
203517ac30 | ||
|
|
26eabfeeaf | ||
|
|
67ee1ae8c9 | ||
|
|
75ebb960d4 | ||
|
|
3ff6a62f0c |
@@ -1230,10 +1230,10 @@ Consensus<Adaptor>::shouldPause(std::unique_ptr<std::stringstream> const& clog)
|
||||
auto const& parms = adaptor_.parms();
|
||||
std::uint32_t const ahead(
|
||||
previousLedger_.seq() - std::min(adaptor_.getValidLedgerIndex(), previousLedger_.seq()));
|
||||
auto [quorum, trustedKeys] = adaptor_.getQuorumKeys();
|
||||
std::size_t const totalValidators = trustedKeys.size();
|
||||
std::size_t const laggards = adaptor_.laggards(previousLedger_.seq(), trustedKeys);
|
||||
std::size_t const offline = trustedKeys.size();
|
||||
auto [quorum, trustedNodes] = adaptor_.getQuorumKeys();
|
||||
std::size_t const totalValidators = trustedNodes.size();
|
||||
std::size_t const laggards = adaptor_.laggards(previousLedger_.seq(), trustedNodes);
|
||||
std::size_t const offline = trustedNodes.size();
|
||||
|
||||
std::stringstream vars;
|
||||
vars << " consensuslog (working seq: " << previousLedger_.seq() << ", "
|
||||
|
||||
@@ -236,7 +236,6 @@ to_string(ValStatus m)
|
||||
* struct Validation
|
||||
* {
|
||||
* using NodeID = ...;
|
||||
* using NodeKey = ...;
|
||||
*
|
||||
* // Ledger ID associated with this validation
|
||||
* Ledger::ID ledgerID() const;
|
||||
@@ -250,9 +249,6 @@ to_string(ValStatus m)
|
||||
* // When the validation was first observed by this node
|
||||
* NetClock::time_point seenTime() const;
|
||||
*
|
||||
* // Signing key of node that published the validation
|
||||
* NodeKey key() const;
|
||||
*
|
||||
* // Whether the publishing node was trusted at the time the validation
|
||||
* // arrived
|
||||
* bool trusted() const;
|
||||
@@ -303,7 +299,6 @@ class Validations
|
||||
using ID = Ledger::ID;
|
||||
using Seq = Ledger::Seq;
|
||||
using NodeID = Validation::NodeID;
|
||||
using NodeKey = Validation::NodeKey;
|
||||
|
||||
using WrappedValidationType =
|
||||
std::decay_t<std::invoke_result_t<decltype(&Validation::unwrap), Validation>>;
|
||||
@@ -1120,22 +1115,22 @@ public:
|
||||
* redundant by checking the list of proposers.
|
||||
*
|
||||
* @param seq Our current sequence number.
|
||||
* @param trustedKeys Public keys of trusted proposers.
|
||||
* @param trustedNodes NodeIDs of trusted proposers.
|
||||
* @return Quantity of laggards.
|
||||
*/
|
||||
std::size_t
|
||||
laggards(Seq const seq, HashSet<NodeKey>& trustedKeys)
|
||||
laggards(Seq const seq, HashSet<NodeID>& trustedNodes)
|
||||
{
|
||||
std::size_t laggards = 0;
|
||||
|
||||
current(
|
||||
std::scoped_lock{mutex_},
|
||||
[](std::size_t) {},
|
||||
[&](NodeID const&, Validation const& v) {
|
||||
[&](NodeID const& node, Validation const& v) {
|
||||
if (adaptor_.now() < v.seenTime() + parms_.validationFRESHNESS &&
|
||||
trustedKeys.find(v.key()) != trustedKeys.end())
|
||||
trustedNodes.contains(node))
|
||||
{
|
||||
trustedKeys.erase(v.key());
|
||||
trustedNodes.erase(node);
|
||||
if (seq > v.seq())
|
||||
++laggards;
|
||||
}
|
||||
|
||||
@@ -12,13 +12,13 @@
|
||||
#include <xrpl/nodestore/Database.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/STValidation.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
#include <xrpl/shamap/SHAMap.h>
|
||||
#include <xrpl/shamap/SHAMapItem.h>
|
||||
#include <xrpl/shamap/SHAMapTreeNode.h>
|
||||
@@ -127,7 +127,7 @@ public:
|
||||
|
||||
// Called when the set of trusted validators changes.
|
||||
virtual void
|
||||
trustChanged(HashSet<PublicKey> const& allTrusted) = 0;
|
||||
trustChanged(HashSet<NodeID> const& allTrusted) = 0;
|
||||
|
||||
// Called by the consensus code when we need to
|
||||
// inject pseudo-transactions
|
||||
|
||||
@@ -47,9 +47,10 @@ class STValidation final : public STObject, public CountedObject<STValidation>
|
||||
// The public key associated with the key used to sign this validation
|
||||
PublicKey const signingPubKey_;
|
||||
|
||||
// The ID of the validator that issued this validation. For validators
|
||||
// that use manifests this will be derived from the master public key.
|
||||
NodeID const nodeID_;
|
||||
// The ID of the validator that issued this validation. handleNewValidation
|
||||
// derives it from the master public key when the signer is trusted or
|
||||
// listed, and from the signing key otherwise.
|
||||
NodeID nodeID_;
|
||||
|
||||
NetClock::time_point seenTime_;
|
||||
|
||||
@@ -152,6 +153,9 @@ public:
|
||||
void
|
||||
setSeen(NetClock::time_point s);
|
||||
|
||||
void
|
||||
setNodeID(NodeID const& nodeID);
|
||||
|
||||
[[nodiscard]] Blob
|
||||
getSerialized() const;
|
||||
|
||||
@@ -296,4 +300,11 @@ STValidation::setSeen(NetClock::time_point s)
|
||||
seenTime_ = s;
|
||||
}
|
||||
|
||||
inline void
|
||||
STValidation::setNodeID(NodeID const& nodeID)
|
||||
{
|
||||
XRPL_ASSERT(nodeID.isNonZero(), "xrpl::STValidation::setNodeID : nonzero node");
|
||||
nodeID_ = nodeID;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/TrustedPublisherServer.h>
|
||||
#include <test/jtx/envconfig.h>
|
||||
#include <test/unit_test/SuiteJournal.h>
|
||||
|
||||
#include <xrpld/app/consensus/RCLValidations.h>
|
||||
#include <xrpld/app/main/Application.h>
|
||||
#include <xrpld/core/Config.h>
|
||||
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/UnorderedContainers.h>
|
||||
#include <xrpl/basics/base64.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
@@ -13,6 +17,7 @@
|
||||
#include <xrpl/config/BasicConfig.h>
|
||||
#include <xrpl/config/Constants.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/json/json_writer.h>
|
||||
#include <xrpl/ledger/AmendmentTable.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
@@ -21,10 +26,15 @@
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STValidation.h>
|
||||
#include <xrpl/protocol/STVector256.h>
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/protocol/tokens.h>
|
||||
#include <xrpl/server/Manifest.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
@@ -467,12 +477,12 @@ public:
|
||||
{
|
||||
std::vector<std::pair<PublicKey, SecretKey>> ret;
|
||||
ret.reserve(num);
|
||||
HashSet<PublicKey> trustedValidators;
|
||||
HashSet<NodeID> trustedValidators;
|
||||
trustedValidators.reserve(num);
|
||||
for (int i = 0; i < num; ++i)
|
||||
{
|
||||
auto const& back = ret.emplace_back(randomKeyPair(KeyType::Secp256k1));
|
||||
trustedValidators.insert(back.first);
|
||||
trustedValidators.insert(calcNodeID(back.first));
|
||||
}
|
||||
table->trustChanged(trustedValidators);
|
||||
return ret;
|
||||
@@ -976,10 +986,10 @@ public:
|
||||
[](std::vector<std::pair<PublicKey, SecretKey>> const& validators,
|
||||
std::unique_ptr<AmendmentTable> const& table) {
|
||||
// We need a HashSet to pass to trustChanged.
|
||||
HashSet<PublicKey> trustedValidators;
|
||||
HashSet<NodeID> trustedValidators;
|
||||
trustedValidators.reserve(validators.size());
|
||||
std::ranges::for_each(validators, [&trustedValidators](auto const& val) {
|
||||
trustedValidators.insert(val.first);
|
||||
trustedValidators.insert(calcNodeID(val.first));
|
||||
});
|
||||
|
||||
// Tell the AmendmentTable that the UNL changed.
|
||||
@@ -1160,6 +1170,114 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
// A trusted validator that rotates its signing key keeps its amendment
|
||||
// vote without a restart or a change to the trusted set.
|
||||
void
|
||||
testSigningKeyRotation(FeatureBitset const& feat)
|
||||
{
|
||||
testcase("signingKeyRotation");
|
||||
|
||||
auto const amendment = fixCleanup3_5_0;
|
||||
auto const masterKeys = randomKeyPair(KeyType::Ed25519);
|
||||
auto cfg = test::jtx::envconfig();
|
||||
cfg->section(Sections::kValidators)
|
||||
.append(toBase58(TokenType::NodePublic, masterKeys.first));
|
||||
test::jtx::Env env{*this, std::move(cfg), feat - amendment};
|
||||
auto& table = env.app().getAmendmentTable();
|
||||
|
||||
auto const rotate = [&](std::pair<PublicKey, SecretKey> const& signingKeys, int seq) {
|
||||
auto m = deserializeManifest(base64Decode(
|
||||
test::TrustedPublisherServer::makeManifestString(
|
||||
masterKeys.first,
|
||||
masterKeys.second,
|
||||
signingKeys.first,
|
||||
signingKeys.second,
|
||||
seq)));
|
||||
if (!BEAST_EXPECT(m))
|
||||
return;
|
||||
BEAST_EXPECT(
|
||||
env.app().getValidatorManifests().applyManifest(
|
||||
std::move(*m), ManifestRateLimitCapPolicy::Uncapped) ==
|
||||
ManifestDisposition::Accepted);
|
||||
env.close();
|
||||
};
|
||||
|
||||
// Tally the given validations at the given hour and report the amendment's votes
|
||||
auto const tally = [&](std::chrono::hours hour,
|
||||
std::vector<std::shared_ptr<STValidation>> const& validations) {
|
||||
table.doVoting(env.current()->rules(), hourTime(hour), {}, {}, validations);
|
||||
return table.getJson(amendment, true)[to_string(amendment)];
|
||||
};
|
||||
|
||||
// A validation signed by signingKeys, carrying the master key's NodeID
|
||||
auto const validation = [&](std::pair<PublicKey, SecretKey> const& signingKeys, bool yes) {
|
||||
return std::make_shared<STValidation>(
|
||||
NetClock::time_point{},
|
||||
signingKeys.first,
|
||||
signingKeys.second,
|
||||
calcNodeID(masterKeys.first),
|
||||
[&amendment, yes](STValidation& v) {
|
||||
if (yes)
|
||||
{
|
||||
v.setFieldV256(
|
||||
sfAmendments,
|
||||
STVector256(sfAmendments, std::vector<UInt256>{amendment}));
|
||||
}
|
||||
v.setFieldU32(sfLedgerSequence, 6180339);
|
||||
});
|
||||
};
|
||||
|
||||
auto const signingKeys1 = randomKeyPair(KeyType::Secp256k1);
|
||||
rotate(signingKeys1, 1);
|
||||
auto json = tally(std::chrono::hours(1), {validation(signingKeys1, true)});
|
||||
BEAST_EXPECT(json[jss::validations].asInt() == 1);
|
||||
BEAST_EXPECT(json[jss::count].asInt() == 1);
|
||||
|
||||
// The recorded vote carries across a rotation
|
||||
auto const signingKeys2 = randomKeyPair(KeyType::Secp256k1);
|
||||
rotate(signingKeys2, 2);
|
||||
json = tally(std::chrono::hours(2), {});
|
||||
BEAST_EXPECT(json[jss::validations].asInt() == 1);
|
||||
BEAST_EXPECT(json[jss::count].asInt() == 1);
|
||||
|
||||
// The new key's vote is recorded after the old vote would have expired
|
||||
json = tally(std::chrono::hours(30), {validation(signingKeys2, false)});
|
||||
BEAST_EXPECT(json[jss::validations].asInt() == 1);
|
||||
BEAST_EXPECT(json[jss::count].asInt() == 0);
|
||||
|
||||
// A validation received before its manifest is applied and handled after it
|
||||
// carries the master key's NodeID and counts
|
||||
auto const signingKeys3 = randomKeyPair(KeyType::Secp256k1);
|
||||
auto const closed = env.closed();
|
||||
auto const blob =
|
||||
std::make_shared<STValidation>(
|
||||
env.app().getTimeKeeper().closeTime(),
|
||||
signingKeys3.first,
|
||||
signingKeys3.second,
|
||||
calcNodeID(masterKeys.first),
|
||||
[&amendment, &closed](STValidation& v) {
|
||||
v.setFieldV256(
|
||||
sfAmendments, STVector256(sfAmendments, std::vector<UInt256>{amendment}));
|
||||
v.setFieldH256(sfLedgerHash, closed->header().hash);
|
||||
v.setFieldU32(sfLedgerSequence, closed->seq());
|
||||
})
|
||||
->getSerialized();
|
||||
SerialIter sit(makeSlice(blob));
|
||||
auto const received = std::make_shared<STValidation>(
|
||||
sit,
|
||||
[](PublicKey const& pk) { return calcNodeID(pk); },
|
||||
STValidation::DeserializeOptions{
|
||||
.checkSignature = true, .requireCanonicalOrder = true});
|
||||
BEAST_EXPECT(received->getNodeID() == calcNodeID(signingKeys3.first));
|
||||
rotate(signingKeys3, 3);
|
||||
handleNewValidation(env.app(), received, "test", BypassAccept::Yes, env.journal);
|
||||
BEAST_EXPECT(received->isTrusted());
|
||||
BEAST_EXPECT(received->getNodeID() == calcNodeID(masterKeys.first));
|
||||
json = tally(std::chrono::hours(31), {received});
|
||||
BEAST_EXPECT(json[jss::validations].asInt() == 1);
|
||||
BEAST_EXPECT(json[jss::count].asInt() == 1);
|
||||
}
|
||||
|
||||
void
|
||||
testHasUnsupported()
|
||||
{
|
||||
@@ -1208,6 +1326,7 @@ public:
|
||||
testLostMajority(feat);
|
||||
testChangedUNL(feat);
|
||||
testValidatorFlapping(feat);
|
||||
testSigningKeyRotation(feat);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -1218,6 +1218,8 @@ private:
|
||||
BEAST_EXPECT(changes.added == asNodeIDs({masterPublic}));
|
||||
BEAST_EXPECT(changes.removed.empty());
|
||||
BEAST_EXPECT(trustedKeysOuter->quorum() == std::ceil((maxKeys + 1) * 0.8f));
|
||||
BEAST_EXPECT(
|
||||
trustedKeysOuter->getQuorumKeys().second.contains(calcNodeID(masterPublic)));
|
||||
BEAST_EXPECT(trustedKeysOuter->listed(masterPublic));
|
||||
BEAST_EXPECT(trustedKeysOuter->trusted(masterPublic));
|
||||
BEAST_EXPECT(!trustedKeysOuter->listed(signingPublic1));
|
||||
@@ -1253,6 +1255,18 @@ private:
|
||||
BEAST_EXPECT(!trustedKeysOuter->listed(signingPublic1));
|
||||
BEAST_EXPECT(!trustedKeysOuter->trusted(signingPublic1));
|
||||
|
||||
// A signing key rotation leaves the trusted set and its NodeIDs unchanged
|
||||
changes = trustedKeysOuter->updateTrusted(
|
||||
activeValidatorsOuter,
|
||||
env.timeKeeper().now(),
|
||||
env.app().getOPs(),
|
||||
env.app().getOverlay(),
|
||||
env.app().getHashRouter());
|
||||
BEAST_EXPECT(changes.added.empty());
|
||||
BEAST_EXPECT(changes.removed.empty());
|
||||
BEAST_EXPECT(
|
||||
trustedKeysOuter->getQuorumKeys().second.contains(calcNodeID(masterPublic)));
|
||||
|
||||
// Should not trust keys from revoked master public key
|
||||
auto const signingKeysMax = randomKeyPair(KeyType::Secp256k1);
|
||||
auto const signingPublicMax = signingKeysMax.first;
|
||||
@@ -1282,6 +1296,8 @@ private:
|
||||
BEAST_EXPECT(changes.removed == asNodeIDs({masterPublic}));
|
||||
BEAST_EXPECT(changes.added.empty());
|
||||
BEAST_EXPECT(trustedKeysOuter->quorum() == std::ceil(maxKeys * 0.8f));
|
||||
BEAST_EXPECT(
|
||||
!trustedKeysOuter->getQuorumKeys().second.contains(calcNodeID(masterPublic)));
|
||||
BEAST_EXPECT(trustedKeysOuter->listed(masterPublic));
|
||||
BEAST_EXPECT(!trustedKeysOuter->trusted(masterPublic));
|
||||
BEAST_EXPECT(!trustedKeysOuter->listed(signingPublicMax));
|
||||
|
||||
@@ -547,6 +547,31 @@ TEST(ValidationsTest, get_current_public_keys)
|
||||
EXPECT_TRUE(harness.vals().getCurrentNodeIDs().empty());
|
||||
}
|
||||
|
||||
TEST(ValidationsTest, laggards_after_key_rotation)
|
||||
{
|
||||
SCOPED_TRACE("Laggards after key rotation");
|
||||
|
||||
LedgerHistoryHelper h;
|
||||
Ledger const ledgerA = h["a"];
|
||||
Ledger const ledgerAB = h["ab"];
|
||||
|
||||
TestHarness harness(h.oracle);
|
||||
Node a = harness.makeNode();
|
||||
Node const b = harness.makeNode();
|
||||
Node const c = harness.makeNode();
|
||||
|
||||
// a rotates its signing key and validates behind; b is current; c is silent
|
||||
a.advanceKey();
|
||||
EXPECT_TRUE(ValStatus::Current == harness.add(a.validate(ledgerA)));
|
||||
EXPECT_TRUE(ValStatus::Current == harness.add(b.validate(ledgerAB)));
|
||||
|
||||
HashSet<PeerID> trusted = {a.nodeID(), b.nodeID(), c.nodeID()};
|
||||
EXPECT_TRUE(harness.vals().laggards(ledgerAB.seq(), trusted) == 1);
|
||||
|
||||
// Nodes heard from leave the set; the silent node stays as offline
|
||||
EXPECT_TRUE(trusted == HashSet<PeerID>{c.nodeID()});
|
||||
}
|
||||
|
||||
TEST(ValidationsTest, trusted_by_ledger_functions)
|
||||
{
|
||||
// Test the Validations functions that calculate a value by ledger ID
|
||||
|
||||
@@ -880,17 +880,17 @@ struct Peer
|
||||
return earliestAllowedSeq();
|
||||
}
|
||||
|
||||
std::pair<std::size_t, HashSet<NodeKeyT>>
|
||||
std::pair<std::size_t, HashSet<NodeIDT>>
|
||||
getQuorumKeys()
|
||||
{
|
||||
HashSet<NodeKeyT> keys;
|
||||
HashSet<NodeIDT> nodes;
|
||||
for (auto const p : trustGraph.trustedPeers(this))
|
||||
keys.insert(p->key);
|
||||
return {quorum, keys};
|
||||
nodes.insert(p->id);
|
||||
return {quorum, nodes};
|
||||
}
|
||||
|
||||
std::size_t
|
||||
laggards(Ledger::Seq const seq, HashSet<NodeKeyT>& trusted)
|
||||
laggards(Ledger::Seq const seq, HashSet<NodeIDT>& trusted)
|
||||
{
|
||||
return validations.laggards(seq, trusted);
|
||||
}
|
||||
|
||||
@@ -1023,7 +1023,7 @@ RCLConsensus::Adaptor::getValidLedgerIndex() const
|
||||
return ledgerMaster_.getValidLedgerIndex();
|
||||
}
|
||||
|
||||
std::pair<std::size_t, HashSet<RCLConsensus::Adaptor::NodeKeyT>>
|
||||
std::pair<std::size_t, HashSet<RCLConsensus::Adaptor::NodeIDT>>
|
||||
RCLConsensus::Adaptor::getQuorumKeys() const
|
||||
{
|
||||
return app_.getValidators().getQuorumKeys();
|
||||
@@ -1032,9 +1032,9 @@ RCLConsensus::Adaptor::getQuorumKeys() const
|
||||
std::size_t
|
||||
RCLConsensus::Adaptor::laggards(
|
||||
LedgerT::Seq const seq,
|
||||
HashSet<RCLConsensus::Adaptor::NodeKeyT>& trustedKeys) const
|
||||
HashSet<RCLConsensus::Adaptor::NodeIDT>& trustedNodes) const
|
||||
{
|
||||
return app_.getValidations().laggards(seq, trustedKeys);
|
||||
return app_.getValidations().laggards(seq, trustedNodes);
|
||||
}
|
||||
|
||||
bool
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/ledger/CanonicalTXSet.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/RippleLedgerHash.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
|
||||
@@ -89,7 +88,6 @@ class RCLConsensus
|
||||
public:
|
||||
using LedgerT = RCLCxLedger;
|
||||
using NodeIDT = NodeID;
|
||||
using NodeKeyT = PublicKey;
|
||||
using TxSetT = RCLTxSet;
|
||||
using PeerPositionT = RCLCxPeerPos;
|
||||
|
||||
@@ -144,11 +142,11 @@ class RCLConsensus
|
||||
LedgerIndex
|
||||
getValidLedgerIndex() const;
|
||||
|
||||
std::pair<std::size_t, HashSet<NodeKeyT>>
|
||||
std::pair<std::size_t, HashSet<NodeIDT>>
|
||||
getQuorumKeys() const;
|
||||
|
||||
std::size_t
|
||||
laggards(LedgerT::Seq const seq, HashSet<NodeKeyT>& trustedKeys) const;
|
||||
laggards(LedgerT::Seq const seq, HashSet<NodeIDT>& trustedNodes) const;
|
||||
|
||||
/**
|
||||
* Whether I am a validator.
|
||||
|
||||
@@ -169,8 +169,11 @@ handleNewValidation(
|
||||
|
||||
auto& validations = app.getValidations();
|
||||
|
||||
// masterKey is seated only if validator is trusted or listed
|
||||
auto const outcome = validations.add(calcNodeID(masterKey.value_or(signingKey)), val);
|
||||
// masterKey is seated only if validator is trusted or listed; a manifest
|
||||
// applied since deserialization can change the NodeID set then
|
||||
auto const nodeID = calcNodeID(masterKey.value_or(signingKey));
|
||||
val->setNodeID(nodeID);
|
||||
auto const outcome = validations.add(nodeID, val);
|
||||
|
||||
if (outcome == ValStatus::Current)
|
||||
{
|
||||
|
||||
@@ -270,7 +270,8 @@ using RCLValidations = Validations<RCLValidationsAdaptor>;
|
||||
* Handle a new validation
|
||||
*
|
||||
* Also sets the trust status of a validation based on the validating node's
|
||||
* public key and this node's current UNL.
|
||||
* public key and this node's current UNL, and sets its NodeID to the one it
|
||||
* is stored under, from the master key a manifest now maps its signer to.
|
||||
*
|
||||
* @param app Application object containing validations and ledgerMaster
|
||||
* @param val The validation to add
|
||||
|
||||
@@ -259,11 +259,6 @@ class ValidatorList
|
||||
// Minimum number of lists on which a trusted validator must appear on
|
||||
std::size_t listThreshold_{1};
|
||||
|
||||
// The current list of trusted signing keys. For those validators using
|
||||
// a manifest, the signing key is the ephemeral key. For the ones using
|
||||
// a seed, the signing key is the same as the master key.
|
||||
HashSet<PublicKey> trustedSigningKeys_;
|
||||
|
||||
std::optional<PublicKey> localPubKey_;
|
||||
|
||||
// The below variable contains the Publisher list specified in the local
|
||||
@@ -689,18 +684,17 @@ public:
|
||||
json::Value
|
||||
getJson() const;
|
||||
|
||||
using QuorumKeys = std::pair<std::size_t const, HashSet<PublicKey>>;
|
||||
using QuorumKeys = std::pair<std::size_t const, HashSet<NodeID>>;
|
||||
/**
|
||||
* Get the quorum and all of the trusted keys.
|
||||
* Get the quorum and the NodeIDs of all trusted validators.
|
||||
*
|
||||
* @return quorum and keys.
|
||||
* A NodeID derives from the master key, so a validator keeps it across
|
||||
* signing key rotations.
|
||||
*
|
||||
* @return quorum and NodeIDs.
|
||||
*/
|
||||
QuorumKeys
|
||||
getQuorumKeys() const
|
||||
{
|
||||
SharedLock const readLock{mutex_};
|
||||
return {quorum_, trustedSigningKeys_};
|
||||
}
|
||||
getQuorumKeys() const;
|
||||
|
||||
/**
|
||||
* get the trusted master public keys
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <xrpl/protocol/STValidation.h>
|
||||
#include <xrpl/protocol/SystemParameters.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/protocol/tokens.h>
|
||||
#include <xrpl/server/Wallet.h>
|
||||
@@ -107,8 +108,9 @@ parseSection(Section const& section)
|
||||
class TrustedVotes
|
||||
{
|
||||
private:
|
||||
// Associates each trusted validator with the last votes we saw from them
|
||||
// and an expiration for that record.
|
||||
// Associates each trusted validator's NodeID, which a signing key rotation
|
||||
// does not change, with the last votes we saw from them and an expiration
|
||||
// for that record.
|
||||
struct UpvotesAndTimeout
|
||||
{
|
||||
std::vector<UInt256> upVotes;
|
||||
@@ -120,7 +122,7 @@ private:
|
||||
*/
|
||||
std::optional<NetClock::time_point> timeout;
|
||||
};
|
||||
HashMap<PublicKey, UpvotesAndTimeout> recordedVotes_;
|
||||
HashMap<NodeID, UpvotesAndTimeout> recordedVotes_;
|
||||
|
||||
public:
|
||||
TrustedVotes() = default;
|
||||
@@ -132,14 +134,14 @@ public:
|
||||
//
|
||||
// Call with AmendmentTable::mutex_ locked.
|
||||
void
|
||||
trustChanged(HashSet<PublicKey> const& allTrusted, std::scoped_lock<std::mutex> const& lock)
|
||||
trustChanged(HashSet<NodeID> const& allTrusted, std::scoped_lock<std::mutex> const& lock)
|
||||
{
|
||||
decltype(recordedVotes_) newRecordedVotes;
|
||||
newRecordedVotes.reserve(allTrusted.size());
|
||||
|
||||
// Make sure every PublicKey in allTrusted is represented in
|
||||
// Make sure every NodeID in allTrusted is represented in
|
||||
// recordedVotes_. Also make sure recordedVotes_ contains
|
||||
// no additional PublicKeys.
|
||||
// no additional NodeIDs.
|
||||
for (auto& trusted : allTrusted)
|
||||
{
|
||||
if (recordedVotes_.contains(trusted))
|
||||
@@ -192,7 +194,7 @@ public:
|
||||
{
|
||||
auto const pkHuman = toBase58(TokenType::NodePublic, val->getSignerPublic());
|
||||
// If this validation comes from one of our trusted validators...
|
||||
if (auto const iter = recordedVotes_.find(val->getSignerPublic());
|
||||
if (auto const iter = recordedVotes_.find(val->getNodeID());
|
||||
iter != recordedVotes_.end())
|
||||
{
|
||||
iter->second.timeout = newTimeout;
|
||||
@@ -230,7 +232,7 @@ public:
|
||||
std::ranges::for_each(
|
||||
recordedVotes_,
|
||||
[&closeTime, newTimeout, &j](decltype(recordedVotes_)::value_type& votes) {
|
||||
auto const pkHuman = toBase58(TokenType::NodePublic, votes.first);
|
||||
auto const nodeHuman = to_string(votes.first);
|
||||
if (!votes.second.timeout)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
@@ -239,11 +241,11 @@ public:
|
||||
"upvotes");
|
||||
JLOG(j.debug()) << "recordVotes: Have not received any "
|
||||
"amendment votes from "
|
||||
<< pkHuman << " since last timeout or startup";
|
||||
<< nodeHuman << " since last timeout or startup";
|
||||
}
|
||||
else if (closeTime > votes.second.timeout)
|
||||
{
|
||||
JLOG(j.debug()) << "recordVotes: Timeout: Clearing votes from " << pkHuman;
|
||||
JLOG(j.debug()) << "recordVotes: Timeout: Clearing votes from " << nodeHuman;
|
||||
votes.second.timeout.reset();
|
||||
votes.second.upVotes.clear();
|
||||
}
|
||||
@@ -256,7 +258,7 @@ public:
|
||||
using namespace std::chrono;
|
||||
auto const age = duration_cast<minutes>(newTimeout - *votes.second.timeout);
|
||||
JLOG(j.debug()) << "recordVotes: Using " << age.count()
|
||||
<< "min old cached votes from " << pkHuman;
|
||||
<< "min old cached votes from " << nodeHuman;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -499,7 +501,7 @@ public:
|
||||
MajorityAmendmentsT const& majority) override;
|
||||
|
||||
void
|
||||
trustChanged(HashSet<PublicKey> const& allTrusted) override;
|
||||
trustChanged(HashSet<NodeID> const& allTrusted) override;
|
||||
|
||||
std::vector<UInt256>
|
||||
doValidation(std::set<UInt256> const& enabledAmendments) const override;
|
||||
@@ -953,7 +955,7 @@ AmendmentTableImpl::doValidatedLedger(
|
||||
}
|
||||
|
||||
void
|
||||
AmendmentTableImpl::trustChanged(HashSet<PublicKey> const& allTrusted)
|
||||
AmendmentTableImpl::trustChanged(HashSet<NodeID> const& allTrusted)
|
||||
{
|
||||
std::scoped_lock const lock(mutex_);
|
||||
previousTrustedVotes_.trustChanged(allTrusted, lock);
|
||||
|
||||
@@ -1916,23 +1916,6 @@ ValidatorList::updateTrusted(
|
||||
trustChanges.added.insert(calcNodeID(val.first));
|
||||
}
|
||||
|
||||
// If there were any changes, we need to update the ephemeral signing
|
||||
// keys:
|
||||
if (!trustChanges.added.empty() || !trustChanges.removed.empty())
|
||||
{
|
||||
trustedSigningKeys_.clear();
|
||||
|
||||
// trustedMasterKeys_ contain non-revoked manifests only. Hence the
|
||||
// manifests must contain a valid signingKey
|
||||
for (auto const& k : trustedMasterKeys_)
|
||||
{
|
||||
std::optional<PublicKey> const signingKey = validatorManifests_.getSigningKey(k);
|
||||
XRPL_ASSERT(signingKey, "xrpl::ValidatorList::updateTrusted : found signing key");
|
||||
trustedSigningKeys_.insert(
|
||||
*signingKey); // NOLINT(bugprone-unchecked-optional-access) assert above
|
||||
}
|
||||
}
|
||||
|
||||
JLOG(j_.debug()) << trustedMasterKeys_.size() << " of " << keyListings_.size()
|
||||
<< " listed validators eligible for inclusion in the trusted set";
|
||||
|
||||
@@ -1978,6 +1961,17 @@ ValidatorList::updateTrusted(
|
||||
return trustChanges;
|
||||
}
|
||||
|
||||
ValidatorList::QuorumKeys
|
||||
ValidatorList::getQuorumKeys() const
|
||||
{
|
||||
SharedLock const readLock{mutex_};
|
||||
HashSet<NodeID> nodeIDs;
|
||||
nodeIDs.reserve(trustedMasterKeys_.size());
|
||||
for (auto const& k : trustedMasterKeys_)
|
||||
nodeIDs.insert(calcNodeID(k));
|
||||
return {quorum_, std::move(nodeIDs)};
|
||||
}
|
||||
|
||||
HashSet<PublicKey>
|
||||
ValidatorList::getTrustedMasterKeys() const
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user