diff --git a/src/test/consensus/ConsensusExtensions_test.cpp b/src/test/consensus/ConsensusExtensions_test.cpp new file mode 100644 index 000000000..417031357 --- /dev/null +++ b/src/test/consensus/ConsensusExtensions_test.cpp @@ -0,0 +1,120 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include +#include +#include + +namespace ripple { +namespace test { + +class ConsensusExtensions_test : public beast::unit_test::suite +{ + std::vector + makeValidatorKeys() const + { + std::vector const rawKeys = { + "0388935426E0D08083314842EDFBB2D517BD47699F9A4527318A8E10468C97C05" + "2", + "02691AC5AE1C4C333AE5DF8A93BDC495F0EEBFC6DB0DA7EB6EF808F3AFC006E3F" + "E"}; + + std::vector keys; + keys.reserve(rawKeys.size()); + for (auto const& rawKey : rawKeys) + { + auto const pkHex = strUnHex(rawKey); + keys.emplace_back(makeSlice(*pkHex)); + } + return keys; + } + + void + testActiveValidatorViewAppliesNegativeUNL() + { + testcase("Active validator view applies NegativeUNL"); + + using namespace jtx; + Env env{ + *this, + envconfig(), + supported_amendments() | featureNegativeUNL, + nullptr}; + + auto const vlKeys = makeValidatorKeys(); + auto const genesis = std::make_shared( + create_genesis, + env.app().config(), + std::vector{}, + env.app().getNodeFamily()); + auto l = std::make_shared( + *genesis, env.app().timeKeeper().closeTime()); + BEAST_EXPECT(l->rules().enabled(featureNegativeUNL)); + + auto report = std::make_shared(keylet::UNLReport()); + std::vector activeValidators; + for (auto const& pk : vlKeys) + { + activeValidators.push_back( + STObject::makeInnerObject(sfActiveValidator)); + activeValidators.back().setFieldVL(sfPublicKey, pk); + } + report->setFieldArray( + sfActiveValidators, STArray(activeValidators, sfActiveValidators)); + + auto negUnl = std::make_shared(keylet::negativeUNL()); + std::vector disabledValidators; + disabledValidators.push_back( + STObject::makeInnerObject(sfDisabledValidator)); + disabledValidators.back().setFieldVL(sfPublicKey, vlKeys[0]); + disabledValidators.back().setFieldU32(sfFirstLedgerSequence, l->seq()); + negUnl->setFieldArray( + sfDisabledValidators, + STArray(disabledValidators, sfDisabledValidators)); + OpenView accum(&*l); + accum.rawInsert(report); + accum.rawInsert(negUnl); + accum.apply(*l); + + ConsensusExtensions ce{env.app(), env.journal}; + auto const view = ce.makeActiveValidatorView(l); + + BEAST_EXPECT(view->fromUNLReport); + BEAST_EXPECT(view->size() == 1); + BEAST_EXPECT(!view->containsMaster(vlKeys[0])); + BEAST_EXPECT(!view->containsNode(calcNodeID(vlKeys[0]))); + BEAST_EXPECT(view->containsMaster(vlKeys[1])); + BEAST_EXPECT(view->containsNode(calcNodeID(vlKeys[1]))); + } + +public: + void + run() override + { + testActiveValidatorViewAppliesNegativeUNL(); + } +}; + +BEAST_DEFINE_TESTSUITE(ConsensusExtensions, consensus, ripple); + +} // namespace test +} // namespace ripple diff --git a/src/xrpld/app/consensus/ConsensusExtensions.cpp b/src/xrpld/app/consensus/ConsensusExtensions.cpp index a61d05279..fcd24791a 100644 --- a/src/xrpld/app/consensus/ConsensusExtensions.cpp +++ b/src/xrpld/app/consensus/ConsensusExtensions.cpp @@ -92,22 +92,29 @@ buildActiveValidatorView( } } - // TODO: If this fork still supports Negative UNL, fold that policy into - // this active view before quorum sizing or sidecar eligibility decisions. - if (!view.masterKeys.empty()) - return view; + if (view.masterKeys.empty()) + { + // Fallback exists for early ledgers and dev/test networks before the + // report object is available. It is deliberately the configured trusted + // master-key set so manifest signing keys still resolve through trust. + for (auto const& masterKey : app.validators().getTrustedMasterKeys()) + view.insertMaster(masterKey); - // Fallback exists for early ledgers and dev/test networks before the - // report object is available. It is deliberately the configured trusted - // master-key set so manifest signing keys still resolve through trust. - for (auto const& masterKey : app.validators().getTrustedMasterKeys()) - view.insertMaster(masterKey); + // Some standalone/dev configurations trust local validation implicitly. + // insertMaster() makes this idempotent if self is already trusted. + auto const& valKeys = app.getValidatorKeys(); + if (valKeys.keys && valKeys.nodeID != beast::zero) + view.insertMaster(valKeys.keys->masterPublicKey); + } - // Some standalone/dev configurations trust local validation implicitly. - // insertMaster() makes this idempotent if self is already trusted. - auto const& valKeys = app.getValidatorKeys(); - if (valKeys.keys && valKeys.nodeID != beast::zero) - view.insertMaster(valKeys.keys->masterPublicKey); + if (sourceLedger && sourceLedger->rules().enabled(featureNegativeUNL)) + { + // UNLReport records recently active validators; NegativeUNL is the + // separate ledger policy overlay that core consensus applies to quorum. + // Apply it to either source so sidecar quorum matches that policy. + for (auto const& masterKey : sourceLedger->negativeUNL()) + view.eraseMaster(masterKey); + } return view; } diff --git a/src/xrpld/app/consensus/ConsensusExtensions.h b/src/xrpld/app/consensus/ConsensusExtensions.h index 93cbcbb54..43a25b72d 100644 --- a/src/xrpld/app/consensus/ConsensusExtensions.h +++ b/src/xrpld/app/consensus/ConsensusExtensions.h @@ -58,6 +58,13 @@ public: nodeIds.insert(calcNodeID(masterKey)); } + void + eraseMaster(PublicKey const& masterKey) + { + masterKeys.erase(masterKey); + nodeIds.erase(calcNodeID(masterKey)); + } + std::size_t size() const {