mirror of
https://github.com/XRPLF/clio.git
synced 2026-08-19 03:30:53 +00:00
fix: Allow deleted libXRPL amendments to still be queried via feature (#3011)
This commit is contained in:
@@ -98,14 +98,26 @@ AmendmentCenter::AmendmentCenter(std::shared_ptr<data::BackendInterface> const&
|
||||
.name = name,
|
||||
.feature = Amendment::getAmendmentId(name),
|
||||
.isSupportedByXRPL = support != ripple::AmendmentSupport::Unsupported,
|
||||
.isSupportedByClio =
|
||||
rg::find(supportedAmendments(), name) != rg::end(supportedAmendments()),
|
||||
.isSupportedByClio = rg::contains(supportedAmendments(), name),
|
||||
.isRetired = support == ripple::AmendmentSupport::Retired
|
||||
};
|
||||
}),
|
||||
std::back_inserter(all_)
|
||||
);
|
||||
|
||||
// Mix in amendments that Clio registered but libXRPL no longer knows about (deleted upstream).
|
||||
for (auto const& name : supportedAmendments()) {
|
||||
if (not rg::contains(all_, name, &Amendment::name)) {
|
||||
all_.push_back({
|
||||
.name = name,
|
||||
.feature = Amendment::getAmendmentId(name),
|
||||
.isSupportedByXRPL = false,
|
||||
.isSupportedByClio = true,
|
||||
.isRetired = true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (auto const& am : all_ | vs::filter([](auto const& am) { return am.isSupportedByClio; }))
|
||||
supported_.insert_or_assign(am.name, am);
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ FeatureHandler::process(FeatureHandler::Input const& input, Context const& ctx)
|
||||
return Output::Feature{
|
||||
.name = feature.name,
|
||||
.key = ripple::to_string(feature.feature),
|
||||
.supported = feature.isSupportedByClio,
|
||||
.supported = feature.isSupportedByClio and feature.isSupportedByXRPL,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@@ -37,8 +37,9 @@ TEST_F(AmendmentCenterTest, AllAmendmentsFromLibXRPLAreSupported)
|
||||
<< "XRPL amendment not supported by Clio: " << name;
|
||||
}
|
||||
|
||||
ASSERT_EQ(amendmentCenter.getSupported().size(), ripple::allAmendments().size());
|
||||
ASSERT_EQ(amendmentCenter.getAll().size(), ripple::allAmendments().size());
|
||||
// We support at least all the amendments currently exposed by libXRPL
|
||||
ASSERT_GE(amendmentCenter.getSupported().size(), ripple::allAmendments().size());
|
||||
ASSERT_GE(amendmentCenter.getAll().size(), ripple::allAmendments().size());
|
||||
}
|
||||
|
||||
TEST_F(AmendmentCenterTest, Accessors)
|
||||
@@ -147,6 +148,32 @@ TEST_F(AmendmentCenterTest, IsEnabledReturnsVectorOfFalseWhenNoAmendments)
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(AmendmentCenterTest, DeletedLibXRPLAmendmentIsNotKnownToLibXRPL)
|
||||
{
|
||||
// OwnerPaysFee was removed from libXRPL in 2.6.0; confirm it's not present upstream
|
||||
EXPECT_FALSE(ripple::allAmendments().contains(std::string{Amendments::OwnerPaysFee}));
|
||||
}
|
||||
|
||||
TEST_F(AmendmentCenterTest, DeletedLibXRPLAmendmentIsPresentInGetAllWithCorrectFlags)
|
||||
{
|
||||
auto const& all = amendmentCenter.getAll();
|
||||
auto const it = std::ranges::find(all, std::string{Amendments::OwnerPaysFee}, &Amendment::name);
|
||||
|
||||
ASSERT_NE(
|
||||
it, all.end()
|
||||
) << "OwnerPaysFee must be present in getAll() even after libXRPL deleted it";
|
||||
EXPECT_FALSE(it->isSupportedByXRPL);
|
||||
EXPECT_TRUE(it->isSupportedByClio);
|
||||
EXPECT_TRUE(it->isRetired);
|
||||
}
|
||||
|
||||
TEST_F(AmendmentCenterTest, DeletedLibXRPLAmendmentIsSupportedByClio)
|
||||
{
|
||||
// Clio still registers OwnerPaysFee so isSupported() and getSupported() must include it
|
||||
EXPECT_TRUE(amendmentCenter.isSupported(Amendments::OwnerPaysFee));
|
||||
EXPECT_TRUE(amendmentCenter.getSupported().contains(std::string{Amendments::OwnerPaysFee}));
|
||||
}
|
||||
|
||||
TEST(AmendmentTest, GenerateAmendmentId)
|
||||
{
|
||||
// https://xrpl.org/known-amendments.html#disallowincoming refer to the published id
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/strHex.h>
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
@@ -386,6 +387,102 @@ TEST_F(RPCFeatureHandlerTest, BadFeaturePath)
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(RPCFeatureHandlerTest, DeletedLibXRPLAmendmentQueryByNameReturnsSupportedFalse)
|
||||
{
|
||||
auto const ownerPaysFeeKey =
|
||||
ripple::to_string(data::Amendment::getAmendmentId(Amendments::OwnerPaysFee));
|
||||
auto const all = std::vector<data::Amendment>{{
|
||||
.name = Amendments::OwnerPaysFee,
|
||||
.feature = data::Amendment::getAmendmentId(Amendments::OwnerPaysFee),
|
||||
.isSupportedByXRPL = false,
|
||||
.isSupportedByClio = true,
|
||||
.isRetired = true,
|
||||
}};
|
||||
auto const keys = std::vector<data::AmendmentKey>{Amendments::OwnerPaysFee};
|
||||
auto const enabled = std::vector<bool>{false};
|
||||
|
||||
EXPECT_CALL(*mockAmendmentCenterPtr_, getAll).WillOnce(testing::ReturnRef(all));
|
||||
EXPECT_CALL(*mockAmendmentCenterPtr_, isEnabled(testing::_, keys, kSEQ))
|
||||
.WillOnce(testing::Return(enabled));
|
||||
|
||||
auto const ledgerHeader = createLedgerHeader(kLEDGER_HASH, kSEQ);
|
||||
EXPECT_CALL(*backend_, fetchLedgerBySequence).WillOnce(testing::Return(ledgerHeader));
|
||||
|
||||
auto const expectedOutput = fmt::format(
|
||||
R"JSON({{
|
||||
"{}": {{
|
||||
"name": "OwnerPaysFee",
|
||||
"enabled": false,
|
||||
"supported": false
|
||||
}},
|
||||
"ledger_hash": "{}",
|
||||
"ledger_index": {},
|
||||
"validated": true
|
||||
}})JSON",
|
||||
ownerPaysFeeKey,
|
||||
kLEDGER_HASH,
|
||||
kSEQ
|
||||
);
|
||||
|
||||
runSpawn([this, &expectedOutput](auto yield) {
|
||||
auto const handler = AnyHandler{FeatureHandler{backend_, mockAmendmentCenterPtr_}};
|
||||
auto const output = handler.process(
|
||||
boost::json::parse(R"JSON({"feature": "OwnerPaysFee"})JSON"), Context{yield}
|
||||
);
|
||||
|
||||
ASSERT_TRUE(output);
|
||||
EXPECT_EQ(*output.result, boost::json::parse(expectedOutput));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(RPCFeatureHandlerTest, DeletedLibXRPLAmendmentQueryByHashReturnsSupportedFalse)
|
||||
{
|
||||
auto const ownerPaysFeeKey =
|
||||
ripple::to_string(data::Amendment::getAmendmentId(Amendments::OwnerPaysFee));
|
||||
auto const all = std::vector<data::Amendment>{{
|
||||
.name = Amendments::OwnerPaysFee,
|
||||
.feature = data::Amendment::getAmendmentId(Amendments::OwnerPaysFee),
|
||||
.isSupportedByXRPL = false,
|
||||
.isSupportedByClio = true,
|
||||
.isRetired = true,
|
||||
}};
|
||||
auto const keys = std::vector<data::AmendmentKey>{Amendments::OwnerPaysFee};
|
||||
auto const enabled = std::vector<bool>{true};
|
||||
|
||||
EXPECT_CALL(*mockAmendmentCenterPtr_, getAll).WillOnce(testing::ReturnRef(all));
|
||||
EXPECT_CALL(*mockAmendmentCenterPtr_, isEnabled(testing::_, keys, kSEQ))
|
||||
.WillOnce(testing::Return(enabled));
|
||||
|
||||
auto const ledgerHeader = createLedgerHeader(kLEDGER_HASH, kSEQ);
|
||||
EXPECT_CALL(*backend_, fetchLedgerBySequence).WillOnce(testing::Return(ledgerHeader));
|
||||
|
||||
auto const expectedOutput = fmt::format(
|
||||
R"JSON({{
|
||||
"{}": {{
|
||||
"name": "OwnerPaysFee",
|
||||
"enabled": true,
|
||||
"supported": false
|
||||
}},
|
||||
"ledger_hash": "{}",
|
||||
"ledger_index": {},
|
||||
"validated": true
|
||||
}})JSON",
|
||||
ownerPaysFeeKey,
|
||||
kLEDGER_HASH,
|
||||
kSEQ
|
||||
);
|
||||
|
||||
runSpawn([this, &ownerPaysFeeKey, &expectedOutput](auto yield) {
|
||||
auto const handler = AnyHandler{FeatureHandler{backend_, mockAmendmentCenterPtr_}};
|
||||
auto const req =
|
||||
boost::json::parse(fmt::format(R"JSON({{"feature": "{}"}})JSON", ownerPaysFeeKey));
|
||||
auto const output = handler.process(req, Context{yield});
|
||||
|
||||
ASSERT_TRUE(output);
|
||||
EXPECT_EQ(*output.result, boost::json::parse(expectedOutput));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(RPCFeatureHandlerTest, SuccessPathWithMultipleResults)
|
||||
{
|
||||
auto const all = std::vector<data::Amendment>{
|
||||
|
||||
Reference in New Issue
Block a user