Skip to content

Commit

Permalink
feat: Permissioned domains (#1841)
Browse files Browse the repository at this point in the history
Fixes #1833.
  • Loading branch information
kuznetsss authored Jan 31, 2025
1 parent 1753c95 commit 89af8fe
Show file tree
Hide file tree
Showing 9 changed files with 159 additions and 10 deletions.
1 change: 0 additions & 1 deletion src/data/AmendmentCenter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,6 @@ struct Amendments {
REGISTER(AMMClawback);
REGISTER(Credentials);
REGISTER(DynamicNFT);
// TODO: Add PermissionedDomains related RPC changes
REGISTER(PermissionedDomains);

// Obsolete but supported by libxrpl
Expand Down
1 change: 1 addition & 0 deletions src/rpc/Errors.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ getErrorInfo(ClioError code)
{.code = ClioError::RpcMalformedAuthorizedCredentials,
.error = "malformedAuthorizedCredentials",
.message = "Malformed authorized credentials."},

// special system errors
{.code = ClioError::RpcInvalidApiVersion, .error = JS(invalid_API_version), .message = "Invalid API version."},
{.code = ClioError::RpcCommandIsMissing,
Expand Down
9 changes: 9 additions & 0 deletions src/rpc/handlers/LedgerEntry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,12 @@ LedgerEntryHandler::process(LedgerEntryHandler::Input input, Context const& ctx)
ripple::uint192{std::string_view(boost::json::value_to<std::string>(input.mptoken->at(JS(mpt_issuance_id))))
};
key = ripple::keylet::mptoken(mptIssuanceID, *holder).key;
} else if (input.permissionedDomain) {
auto const account = ripple::parseBase58<ripple::AccountID>(
boost::json::value_to<std::string>(input.permissionedDomain->at(JS(account)))
);
auto const seq = input.permissionedDomain->at(JS(seq)).as_int64();
key = ripple::keylet::permissionedDomain(*account, seq).key;
} else {
// Must specify 1 of the following fields to indicate what type
if (ctx.apiVersion == 1)
Expand Down Expand Up @@ -313,6 +319,7 @@ tag_invoke(boost::json::value_to_tag<LedgerEntryHandler::Input>, boost::json::va
{JS(oracle), ripple::ltORACLE},
{JS(credential), ripple::ltCREDENTIAL},
{JS(mptoken), ripple::ltMPTOKEN},
{JS(permissioned_domain), ripple::ltPERMISSIONED_DOMAIN}
};

auto const parseBridgeFromJson = [](boost::json::value const& bridgeJson) {
Expand Down Expand Up @@ -399,6 +406,8 @@ tag_invoke(boost::json::value_to_tag<LedgerEntryHandler::Input>, boost::json::va
input.credential = parseCredentialFromJson(jv.at(JS(credential)));
} else if (jsonObject.contains(JS(mptoken))) {
input.mptoken = jv.at(JS(mptoken)).as_object();
} else if (jsonObject.contains(JS(permissioned_domain))) {
input.permissionedDomain = jv.at(JS(permissioned_domain)).as_object();
}

if (jsonObject.contains("include_deleted"))
Expand Down
18 changes: 18 additions & 0 deletions src/rpc/handlers/LedgerEntry.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ class LedgerEntryHandler {
std::optional<boost::json::object> ticket;
std::optional<boost::json::object> amm;
std::optional<boost::json::object> mptoken;
std::optional<boost::json::object> permissionedDomain;
std::optional<ripple::STXChainBridge> bridge;
std::optional<std::string> bridgeAccount;
std::optional<uint32_t> chainClaimId;
Expand Down Expand Up @@ -374,6 +375,23 @@ class LedgerEntryHandler {
},
},
}},
{JS(permissioned_domain),
meta::WithCustomError{
validation::Type<std::string, boost::json::object>{}, Status(ClioError::RpcMalformedRequest)
},
meta::IfType<std::string>{kMALFORMED_REQUEST_HEX_STRING_VALIDATOR},
meta::IfType<boost::json::object>{meta::Section{
{JS(seq),
meta::WithCustomError{validation::Required{}, Status(ClioError::RpcMalformedRequest)},
meta::WithCustomError{validation::Type<uint32_t>{}, Status(ClioError::RpcMalformedRequest)}},
{
JS(account),
meta::WithCustomError{validation::Required{}, Status(ClioError::RpcMalformedRequest)},
meta::WithCustomError{
validation::CustomValidators::accountBase58Validator, Status(ClioError::RpcMalformedAddress)
},
},
}}},
{JS(ledger), check::Deprecated{}},
{"include_deleted", validation::Type<bool>{}},
};
Expand Down
1 change: 1 addition & 0 deletions src/util/LedgerUtils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ class LedgerTypes {
LedgerTypeAttribute::chainLedgerType(JS(nunl), ripple::ltNEGATIVE_UNL),
LedgerTypeAttribute::deletionBlockerLedgerType(JS(mpt_issuance), ripple::ltMPTOKEN_ISSUANCE),
LedgerTypeAttribute::deletionBlockerLedgerType(JS(mptoken), ripple::ltMPTOKEN),
LedgerTypeAttribute::deletionBlockerLedgerType(JS(permissioned_domain), ripple::ltPERMISSIONED_DOMAIN),
};

public:
Expand Down
24 changes: 24 additions & 0 deletions tests/common/util/TestObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1485,6 +1485,30 @@ createMpTokenObject(std::string_view accountId, ripple::uint192 issuanceID, std:
return mptoken;
}

ripple::STObject
createPermissionedDomainObject(
std::string_view accountId,
std::string_view ledgerIndex,
ripple::LedgerIndex seq,
uint64_t ownerNode,
ripple::uint256 previousTxId,
uint32_t previousTxSeq
)
{
ripple::STObject object(ripple::sfLedgerEntry);
object.setFieldH256(ripple::sfLedgerIndex, ripple::uint256(ledgerIndex));
object.setAccountID(ripple::sfOwner, getAccountIdWithString(accountId));
object.setFieldU32(ripple::sfSequence, seq);
object.setFieldArray(ripple::sfAcceptedCredentials, ripple::STArray{});
object.setFieldU64(ripple::sfOwnerNode, ownerNode);
object.setFieldH256(ripple::sfPreviousTxnID, previousTxId);
object.setFieldU32(ripple::sfPreviousTxnLgrSeq, previousTxSeq);
object.setFieldU32(ripple::sfFlags, 0);
object.setFieldU16(ripple::sfLedgerEntryType, ripple::ltPERMISSIONED_DOMAIN);

return object;
}

ripple::STObject
createOraclePriceData(
uint64_t assetPrice,
Expand Down
10 changes: 10 additions & 0 deletions tests/common/util/TestObject.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,16 @@ createMptIssuanceObject(std::string_view accountId, std::uint32_t seq, std::stri
[[nodiscard]] ripple::STObject
createMpTokenObject(std::string_view accountId, ripple::uint192 issuanceID, std::uint64_t mptAmount = 1);

[[nodiscard]] ripple::STObject
createPermissionedDomainObject(
std::string_view accountId,
std::string_view ledgerIndex,
ripple::LedgerIndex seq,
uint64_t ownerNode,
ripple::uint256 previousTxId,
uint32_t previousTxSeq
);

[[nodiscard]] ripple::STObject
createOraclePriceData(
uint64_t assetPrice,
Expand Down
98 changes: 91 additions & 7 deletions tests/unit/rpc/handlers/LedgerEntryTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2136,7 +2136,62 @@ generateTestValuesForParametersTest()
),
.expectedError = "malformedRequest",
.expectedErrorMessage = "Malformed request."
}
},
ParamTestCaseBundle{
.testName = "InvalidPermissionedDomain_NotObject",
.testJson = R"json({"permissioned_domain": []})json",
.expectedError = "malformedRequest",
.expectedErrorMessage = "Malformed request.",
},
ParamTestCaseBundle{
.testName = "InvalidPermissionedDomain_InvalidString",
.testJson = R"json({"permissioned_domain": "invalid_string"})json",
.expectedError = "malformedRequest",
.expectedErrorMessage = "Malformed request.",
},
ParamTestCaseBundle{
.testName = "InvalidPermissionedDomain_EmptyObject",
.testJson = R"json({"permissioned_domain": {}})json",
.expectedError = "malformedRequest",
.expectedErrorMessage = "Malformed request.",
},
ParamTestCaseBundle{
.testName = "InvalidPermissionedDomain_BadAccount",
.testJson = R"json({"permissioned_domain": {"account": "1234", "seq": 1234}})json",
.expectedError = "malformedAddress",
.expectedErrorMessage = "Malformed address.",
},
ParamTestCaseBundle{
.testName = "InvalidPermissionedDomain_MissingSeq",
.testJson = fmt::format(
R"json({{
"permissioned_domain": {{ "account": "{}" }}
}})json",
kACCOUNT
),
.expectedError = "malformedRequest",
.expectedErrorMessage = "Malformed request.",
},
ParamTestCaseBundle{
.testName = "InvalidPermissionedDomain_SeqIsNotUint",
.testJson = fmt::format(
R"json({{
"permissioned_domain": {{ "account": "{}", "seq": -1 }}
}})json",
kACCOUNT
),
.expectedError = "malformedRequest",
.expectedErrorMessage = "Malformed request.",
},
ParamTestCaseBundle{
.testName = "InvalidPermissionedDomain_BothAccountAndSeqAreInvalid",
.testJson =
R"json({
"permissioned_domain": { "account": "", "seq": -1 }
})json",
.expectedError = "malformedRequest",
.expectedErrorMessage = "Malformed request.",
},
};
}

Expand Down Expand Up @@ -2872,6 +2927,36 @@ generateTestValuesForNormalPathTest()
.expectedIndex = ripple::keylet::mptoken(ripple::makeMptID(2, account1), account1).key,
.mockedEntity = createMpTokenObject(kACCOUNT, ripple::makeMptID(2, account1))
},
NormalPathTestBundle{
.testName = "PermissionedDomainViaString",
.testJson = fmt::format(
R"json({{
"binary": true,
"permissioned_domain": "{}"
}})json",
kINDEX1
),
.expectedIndex = ripple::uint256(kINDEX1),
.mockedEntity = createPermissionedDomainObject(kACCOUNT, kINDEX1, kRANGE_MAX, 0, ripple::uint256{0}, 0)
},
NormalPathTestBundle{
.testName = "PermissionedDomainViaObject",
.testJson = fmt::format(
R"json({{
"binary": true,
"permissioned_domain": {{
"account": "{}",
"seq": {}
}}
}})json",
kACCOUNT,
kRANGE_MAX
),
.expectedIndex =
ripple::keylet::permissionedDomain(ripple::parseBase58<ripple::AccountID>(kACCOUNT).value(), kRANGE_MAX)
.key,
.mockedEntity = createPermissionedDomainObject(kACCOUNT, kINDEX1, kRANGE_MAX, 0, ripple::uint256{0}, 0)
}
};
}

Expand Down Expand Up @@ -2900,15 +2985,14 @@ TEST_P(RPCLedgerEntryNormalPathTest, NormalPath)
auto const req = json::parse(testBundle.testJson);
auto const output = handler.process(req, Context{yield});
ASSERT_TRUE(output);
EXPECT_EQ(output.result.value().at("ledger_hash").as_string(), kLEDGER_HASH);
EXPECT_EQ(output.result.value().at("ledger_index").as_uint64(), kRANGE_MAX);
auto const& outputJson = output.result.value();
EXPECT_EQ(outputJson.at("ledger_hash").as_string(), kLEDGER_HASH);
EXPECT_EQ(outputJson.at("ledger_index").as_uint64(), kRANGE_MAX);
EXPECT_EQ(
output.result.value().at("node_binary").as_string(),
ripple::strHex(testBundle.mockedEntity.getSerializer().peekData())
outputJson.at("node_binary").as_string(), ripple::strHex(testBundle.mockedEntity.getSerializer().peekData())
);
EXPECT_EQ(
ripple::uint256(boost::json::value_to<std::string>(output.result.value().at("index")).data()),
testBundle.expectedIndex
ripple::uint256(boost::json::value_to<std::string>(outputJson.at("index")).data()), testBundle.expectedIndex
);
});
}
Expand Down
7 changes: 5 additions & 2 deletions tests/unit/util/LedgerUtilsTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ TEST(LedgerUtilsTests, LedgerObjectTypeList)
JS(did),
JS(mpt_issuance),
JS(mptoken),
JS(permissioned_domain),
JS(oracle),
JS(credential),
JS(nunl)
Expand Down Expand Up @@ -88,7 +89,8 @@ TEST(LedgerUtilsTests, AccountOwnedTypeList)
JS(oracle),
JS(credential),
JS(mpt_issuance),
JS(mptoken)
JS(mptoken),
JS(permissioned_domain),
};

static_assert(std::size(kCORRECT_TYPES) == kACCOUNT_OWNED.size());
Expand Down Expand Up @@ -123,7 +125,8 @@ TEST(LedgerUtilsTests, DeletionBlockerTypes)
ripple::ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID,
ripple::ltBRIDGE,
ripple::ltMPTOKEN_ISSUANCE,
ripple::ltMPTOKEN
ripple::ltMPTOKEN,
ripple::ltPERMISSIONED_DOMAIN
};

static_assert(std::size(kDELETION_BLOCKERS) == kTESTED_TYPES.size());
Expand Down

0 comments on commit 89af8fe

Please sign in to comment.