feat: Add Support Credentials for Clio (#1712)

Rippled PR: [here](https://github.com/XRPLF/rippled/pull/5103)
This commit is contained in:
Peter Chen
2024-11-14 14:52:03 -05:00
committed by GitHub
parent 0e25c0cabc
commit 67d99457f2
23 changed files with 1560 additions and 99 deletions

View File

@@ -24,6 +24,7 @@
#include "etl/NetworkValidatedLedgersInterface.hpp" #include "etl/NetworkValidatedLedgersInterface.hpp"
#include "etl/Source.hpp" #include "etl/Source.hpp"
#include "feed/SubscriptionManagerInterface.hpp" #include "feed/SubscriptionManagerInterface.hpp"
#include "rpc/Errors.hpp"
#include "util/Mutex.hpp" #include "util/Mutex.hpp"
#include "util/ResponseExpirationCache.hpp" #include "util/ResponseExpirationCache.hpp"
#include "util/config/Config.hpp" #include "util/config/Config.hpp"

View File

@@ -6,6 +6,7 @@ target_sources(
Factories.cpp Factories.cpp
AMMHelpers.cpp AMMHelpers.cpp
RPCHelpers.cpp RPCHelpers.cpp
CredentialHelpers.cpp
Counters.cpp Counters.cpp
WorkQueue.cpp WorkQueue.cpp
common/Specs.cpp common/Specs.cpp

View File

@@ -0,0 +1,162 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2024, the clio developers.
Permission to use, copy, modify, and 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 "data/BackendInterface.hpp"
#include "rpc/Errors.hpp"
#include "rpc/JS.hpp"
#include "rpc/common/Types.hpp"
#include "util/Assert.hpp"
#include <boost/asio/spawn.hpp>
#include <boost/json/array.hpp>
#include <boost/json/value_to.hpp>
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/StringUtilities.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/LedgerHeader.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STArray.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STObject.h>
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/jss.h>
#include <cstdint>
#include <expected>
#include <optional>
#include <set>
#include <string>
#include <string_view>
#include <unordered_set>
#include <utility>
namespace rpc::credentials {
bool
checkExpired(ripple::SLE const& sleCred, ripple::LedgerHeader const& ledger)
{
if (sleCred.isFieldPresent(ripple::sfExpiration)) {
std::uint32_t const exp = sleCred.getFieldU32(ripple::sfExpiration);
std::uint32_t const now = ledger.parentCloseTime.time_since_epoch().count();
return now > exp;
}
return false;
}
std::set<std::pair<ripple::AccountID, ripple::Slice>>
createAuthCredentials(ripple::STArray const& in)
{
std::set<std::pair<ripple::AccountID, ripple::Slice>> out;
for (auto const& cred : in)
out.insert({cred[ripple::sfIssuer], cred[ripple::sfCredentialType]});
return out;
}
ripple::STArray
parseAuthorizeCredentials(boost::json::array const& jv)
{
ripple::STArray arr;
for (auto const& jo : jv) {
ASSERT(
jo.at(JS(issuer)).is_string(),
"issuer must be string, should already be checked in AuthorizeCredentialValidator"
);
auto const issuer =
ripple::parseBase58<ripple::AccountID>(static_cast<std::string>(jo.at(JS(issuer)).as_string()));
ASSERT(
issuer.has_value(), "issuer must be present, should already be checked in AuthorizeCredentialValidator."
);
ASSERT(
jo.at(JS(credential_type)).is_string(),
"credential_type must be string, should already be checked in AuthorizeCredentialValidator"
);
auto const credentialType = ripple::strUnHex(static_cast<std::string>(jo.at(JS(credential_type)).as_string()));
ASSERT(
credentialType.has_value(),
"credential_type must be present, should already be checked in AuthorizeCredentialValidator."
);
auto credential = ripple::STObject::makeInnerObject(ripple::sfCredential);
credential.setAccountID(ripple::sfIssuer, *issuer);
credential.setFieldVL(ripple::sfCredentialType, *credentialType);
arr.push_back(std::move(credential));
}
return arr;
}
std::expected<ripple::STArray, Status>
fetchCredentialArray(
std::optional<boost::json::array> const& credID,
ripple::AccountID const& srcAcc,
BackendInterface const& backend,
ripple::LedgerHeader const& info,
boost::asio::yield_context const& yield
)
{
ripple::STArray authCreds;
std::unordered_set<std::string_view> elems;
for (auto const& elem : credID.value()) {
ASSERT(elem.is_string(), "should already be checked in validators.hpp that elem is a string.");
if (elems.contains(elem.as_string()))
return Error{Status{RippledError::rpcBAD_CREDENTIALS, "duplicates in credentials."}};
elems.insert(elem.as_string());
ripple::uint256 credHash;
ASSERT(
credHash.parseHex(boost::json::value_to<std::string>(elem)),
"should already be checked in validators.hpp that elem is a uint256 hex"
);
auto const credKeylet = ripple::keylet::credential(credHash).key;
auto const credLedgerObject = backend.fetchLedgerObject(credKeylet, info.seq, yield);
if (!credLedgerObject)
return Error{Status{RippledError::rpcBAD_CREDENTIALS, "credentials don't exist."}};
auto credIt = ripple::SerialIter{credLedgerObject->data(), credLedgerObject->size()};
auto const sleCred = ripple::SLE{credIt, credKeylet};
if ((sleCred.getType() != ripple::ltCREDENTIAL) ||
((sleCred.getFieldU32(ripple::sfFlags) & ripple::lsfAccepted) == 0u))
return Error{Status{RippledError::rpcBAD_CREDENTIALS, "credentials aren't accepted"}};
if (credentials::checkExpired(sleCred, info))
return Error{Status{RippledError::rpcBAD_CREDENTIALS, "credentials are expired"}};
if (sleCred.getAccountID(ripple::sfSubject) != srcAcc)
return Error{Status{RippledError::rpcBAD_CREDENTIALS, "credentials don't belong to the root account"}};
auto credential = ripple::STObject::makeInnerObject(ripple::sfCredential);
credential.setAccountID(ripple::sfIssuer, sleCred.getAccountID(ripple::sfIssuer));
credential.setFieldVL(ripple::sfCredentialType, sleCred.getFieldVL(ripple::sfCredentialType));
authCreds.push_back(std::move(credential));
}
return authCreds;
}
} // namespace rpc::credentials

View File

@@ -0,0 +1,89 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2024, the clio developers.
Permission to use, copy, modify, and 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.
*/
//==============================================================================
#pragma once
#include "data/BackendInterface.hpp"
#include "rpc/Errors.hpp"
#include <boost/asio/spawn.hpp>
#include <boost/json/array.hpp>
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/LedgerHeader.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STObject.h>
#include <expected>
#include <optional>
#include <set>
#include <utility>
namespace rpc::credentials {
/**
* @brief Check if credential is expired
*
* @param sleCred The credential to check
* @param ledger The ledger to check the closed time of
* @return true if credential not expired, false otherwise
*/
bool
checkExpired(ripple::SLE const& sleCred, ripple::LedgerHeader const& ledger);
/**
* @brief Creates authentication credential field (which is a set of pairs of AccountID and Credential ID)
*
* @param in The array of Credential objects to check
* @return Auth Credential array
*/
std::set<std::pair<ripple::AccountID, ripple::Slice>>
createAuthCredentials(ripple::STArray const& in);
/**
* @brief Parses each credential object and makes sure the credential type and values are correct
*
* @param jv The boost json array of credentials to parse
* @return Array of credentials after parsing
*/
ripple::STArray
parseAuthorizeCredentials(boost::json::array const& jv);
/**
* @brief Get Array of Credential objects
*
* @param credID Array of CredentialID's to parse
* @param srcAcc The Source Account
* @param backend backend interface
* @param info The ledger header
* @param yield The coroutine context
* @return Array of credential objects, error if failed otherwise
*/
std::expected<ripple::STArray, Status>
fetchCredentialArray(
std::optional<boost::json::array> const& credID,
ripple::AccountID const& srcAcc,
BackendInterface const& backend,
ripple::LedgerHeader const& info,
boost::asio::yield_context const& yield
);
} // namespace rpc::credentials

View File

@@ -83,6 +83,9 @@ getErrorInfo(ClioError code)
{ClioError::rpcUNKNOWN_OPTION, "unknownOption", "Unknown option."}, {ClioError::rpcUNKNOWN_OPTION, "unknownOption", "Unknown option."},
{ClioError::rpcFIELD_NOT_FOUND_TRANSACTION, "fieldNotFoundTransaction", "Missing field."}, {ClioError::rpcFIELD_NOT_FOUND_TRANSACTION, "fieldNotFoundTransaction", "Missing field."},
{ClioError::rpcMALFORMED_ORACLE_DOCUMENT_ID, "malformedDocumentID", "Malformed oracle_document_id."}, {ClioError::rpcMALFORMED_ORACLE_DOCUMENT_ID, "malformedDocumentID", "Malformed oracle_document_id."},
{ClioError::rpcMALFORMED_AUTHORIZED_CREDENTIALS,
"malformedAuthorizedCredentials",
"Malformed authorized credentials."},
// special system errors // special system errors
{ClioError::rpcINVALID_API_VERSION, JS(invalid_API_version), "Invalid API version."}, {ClioError::rpcINVALID_API_VERSION, JS(invalid_API_version), "Invalid API version."},
{ClioError::rpcCOMMAND_IS_MISSING, JS(missingCommand), "Method is not specified or is not a string."}, {ClioError::rpcCOMMAND_IS_MISSING, JS(missingCommand), "Method is not specified or is not a string."},

View File

@@ -43,6 +43,7 @@ enum class ClioError {
rpcUNKNOWN_OPTION = 5005, rpcUNKNOWN_OPTION = 5005,
rpcFIELD_NOT_FOUND_TRANSACTION = 5006, rpcFIELD_NOT_FOUND_TRANSACTION = 5006,
rpcMALFORMED_ORACLE_DOCUMENT_ID = 5007, rpcMALFORMED_ORACLE_DOCUMENT_ID = 5007,
rpcMALFORMED_AUTHORIZED_CREDENTIALS = 5008,
// special system errors start with 6000 // special system errors start with 6000
rpcINVALID_API_VERSION = 6000, rpcINVALID_API_VERSION = 6000,

View File

@@ -29,8 +29,10 @@
#include <boost/json/value.hpp> #include <boost/json/value.hpp>
#include <boost/json/value_to.hpp> #include <boost/json/value_to.hpp>
#include <fmt/core.h> #include <fmt/core.h>
#include <xrpl/basics/StringUtilities.h>
#include <xrpl/basics/base_uint.h> #include <xrpl/basics/base_uint.h>
#include <xrpl/protocol/AccountID.h> #include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/UintTypes.h> #include <xrpl/protocol/UintTypes.h>
#include <charconv> #include <charconv>
@@ -253,4 +255,67 @@ CustomValidator CustomValidators::CurrencyIssueValidator =
return MaybeError{}; return MaybeError{};
}}; }};
CustomValidator CustomValidators::CredentialTypeValidator =
CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError {
if (not value.is_string())
return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + " NotString"}};
auto const& credTypeHex = ripple::strViewUnHex(value.as_string());
if (!credTypeHex.has_value())
return Error{Status{ClioError::rpcMALFORMED_AUTHORIZED_CREDENTIALS, std::string(key) + " NotHexString"}};
if (credTypeHex->empty())
return Error{Status{ClioError::rpcMALFORMED_AUTHORIZED_CREDENTIALS, std::string(key) + " is empty"}};
if (credTypeHex->size() > ripple::maxCredentialTypeLength)
return Error{
Status{ClioError::rpcMALFORMED_AUTHORIZED_CREDENTIALS, std::string(key) + " greater than max length"}
};
return MaybeError{};
}};
CustomValidator CustomValidators::AuthorizeCredentialValidator =
CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError {
if (not value.is_array())
return Error{Status{ClioError::rpcMALFORMED_REQUEST, std::string(key) + " not array"}};
auto const& authCred = value.as_array();
if (authCred.size() == 0) {
return Error{Status{
ClioError::rpcMALFORMED_AUTHORIZED_CREDENTIALS,
fmt::format("Requires at least one element in authorized_credentials array")
}};
}
if (authCred.size() > ripple::maxCredentialsArraySize) {
return Error{Status{
ClioError::rpcMALFORMED_AUTHORIZED_CREDENTIALS,
fmt::format(
"Max {} number of credentials in authorized_credentials array", ripple::maxCredentialsArraySize
)
}};
}
for (auto const& credObj : value.as_array()) {
auto const& obj = credObj.as_object();
if (!obj.contains("issuer"))
return Error{Status{ClioError::rpcMALFORMED_REQUEST, "Field 'Issuer' is required but missing."}};
if (auto const err = IssuerValidator.verify(credObj, "issuer"); !err)
return err;
if (!obj.contains("credential_type")) {
return Error{Status{ClioError::rpcMALFORMED_REQUEST, "Field 'CredentialType' is required but missing."}
};
}
if (auto const err = CredentialTypeValidator.verify(credObj, "credential_type"); !err)
return err;
}
return MaybeError{};
}};
} // namespace rpc::validation } // namespace rpc::validation

View File

@@ -27,15 +27,13 @@
#include <boost/json/object.hpp> #include <boost/json/object.hpp>
#include <boost/json/value.hpp> #include <boost/json/value.hpp>
#include <fmt/core.h> #include <fmt/core.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/protocol/ErrorCodes.h> #include <xrpl/protocol/ErrorCodes.h>
#include <concepts> #include <concepts>
#include <cstdint>
#include <ctime> #include <ctime>
#include <functional> #include <functional>
#include <initializer_list> #include <initializer_list>
#include <iomanip>
#include <sstream>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <utility> #include <utility>
@@ -153,7 +151,7 @@ struct Type final {
verify(boost::json::value const& value, std::string_view key) const verify(boost::json::value const& value, std::string_view key) const
{ {
if (not value.is_object() or not value.as_object().contains(key.data())) if (not value.is_object() or not value.as_object().contains(key.data()))
return {}; // ignore. field does not exist, let 'required' fail instead return {}; // ignore. If field is supposed to exist, let 'required' fail instead
auto const& res = value.as_object().at(key.data()); auto const& res = value.as_object().at(key.data());
auto const convertible = (checkType<Types>(res) || ...); auto const convertible = (checkType<Types>(res) || ...);
@@ -559,6 +557,51 @@ struct CustomValidators final {
* Used by amm_info. * Used by amm_info.
*/ */
static CustomValidator CurrencyIssueValidator; static CustomValidator CurrencyIssueValidator;
/**
* @brief Provides a validator for validating authorized_credentials json array.
*
* Used by deposit_preauth.
*/
static CustomValidator AuthorizeCredentialValidator;
/**
* @brief Provides a validator for validating credential_type.
*
* Used by AuthorizeCredentialValidator in deposit_preauth.
*/
static CustomValidator CredentialTypeValidator;
};
/**
* @brief Validates that the elements of the array is of type Hex256 uint
*/
struct Hex256ItemType final {
/**
* @brief Validates given the prerequisite that the type of the json value is an array,
* verifies all values within the array is of uint256 hash
*
* @param value the value to verify
* @param key The key used to retrieve the tested value from the outer object
* @return `RippledError::rpcINVALID_PARAMS` if validation failed; otherwise no error is returned
*/
[[nodiscard]] static MaybeError
verify(boost::json::value const& value, std::string_view key)
{
if (not value.is_object() or not value.as_object().contains(key.data()))
return {}; // ignore. If field is supposed to exist, let 'required' fail instead
auto const& res = value.as_object().at(key.data());
// loop through each item in the array and make sure it is uint256 hex string
for (auto const& elem : res.as_array()) {
ripple::uint256 num;
if (!elem.is_string() || !num.parseHex(elem.as_string())) {
return Error{Status{RippledError::rpcINVALID_PARAMS, "Item is not a valid uint256 type."}};
}
}
return {};
}
}; };
} // namespace rpc::validation } // namespace rpc::validation

View File

@@ -19,25 +19,34 @@
#include "rpc/handlers/DepositAuthorized.hpp" #include "rpc/handlers/DepositAuthorized.hpp"
#include "rpc/CredentialHelpers.hpp"
#include "rpc/Errors.hpp" #include "rpc/Errors.hpp"
#include "rpc/JS.hpp" #include "rpc/JS.hpp"
#include "rpc/RPCHelpers.hpp" #include "rpc/RPCHelpers.hpp"
#include "rpc/common/Types.hpp" #include "rpc/common/Types.hpp"
#include "util/Assert.hpp"
#include <boost/json/array.hpp>
#include <boost/json/conversion.hpp> #include <boost/json/conversion.hpp>
#include <boost/json/object.hpp> #include <boost/json/object.hpp>
#include <boost/json/value.hpp> #include <boost/json/value.hpp>
#include <boost/json/value_to.hpp> #include <boost/json/value_to.hpp>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/basics/strHex.h> #include <xrpl/basics/strHex.h>
#include <xrpl/protocol/Indexes.h> #include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h> #include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/LedgerHeader.h> #include <xrpl/protocol/LedgerHeader.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/SField.h> #include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STLedgerEntry.h> #include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STObject.h>
#include <xrpl/protocol/Serializer.h> #include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/jss.h> #include <xrpl/protocol/jss.h>
#include <memory>
#include <string> #include <string>
#include <utility>
#include <variant> #include <variant>
namespace rpc { namespace rpc {
@@ -71,26 +80,55 @@ DepositAuthorizedHandler::process(DepositAuthorizedHandler::Input input, Context
Output response; Output response;
auto it = ripple::SerialIter{dstAccountLedgerObject->data(), dstAccountLedgerObject->size()};
auto const sleDest = ripple::SLE{it, dstKeylet};
bool const reqAuth = sleDest.isFlag(ripple::lsfDepositAuth) && (sourceAccountID != destinationAccountID);
auto const& creds = input.credentials;
bool const credentialsPresent = creds.has_value();
ripple::STArray authCreds;
if (credentialsPresent) {
if (creds.value().empty()) {
return Error{Status{RippledError::rpcINVALID_PARAMS, "credential array has no elements."}};
}
if (creds.value().size() > ripple::maxCredentialsArraySize) {
return Error{Status{RippledError::rpcINVALID_PARAMS, "credential array too long."}};
}
auto const credArray = credentials::fetchCredentialArray(
input.credentials, *sourceAccountID, *sharedPtrBackend_, lgrInfo, ctx.yield
);
if (!credArray.has_value())
return Error{std::move(credArray).error()};
authCreds = std::move(credArray).value();
}
// If the two accounts are the same OR if that flag is
// not set, then the deposit should be fine.
bool depositAuthorized = true;
if (reqAuth) {
ripple::uint256 hashKey;
if (credentialsPresent) {
auto const sortedAuthCreds = credentials::createAuthCredentials(authCreds);
ASSERT(
sortedAuthCreds.size() == authCreds.size(), "should already be checked above that there is no duplicate"
);
hashKey = ripple::keylet::depositPreauth(*destinationAccountID, sortedAuthCreds).key;
} else {
hashKey = ripple::keylet::depositPreauth(*destinationAccountID, *sourceAccountID).key;
}
depositAuthorized = sharedPtrBackend_->fetchLedgerObject(hashKey, lgrInfo.seq, ctx.yield).has_value();
}
response.sourceAccount = input.sourceAccount; response.sourceAccount = input.sourceAccount;
response.destinationAccount = input.destinationAccount; response.destinationAccount = input.destinationAccount;
response.ledgerHash = ripple::strHex(lgrInfo.hash); response.ledgerHash = ripple::strHex(lgrInfo.hash);
response.ledgerIndex = lgrInfo.seq; response.ledgerIndex = lgrInfo.seq;
response.depositAuthorized = depositAuthorized;
// If the two accounts are the same, then the deposit should be fine. if (credentialsPresent)
if (sourceAccountID != destinationAccountID) { response.credentials = input.credentials.value();
auto it = ripple::SerialIter{dstAccountLedgerObject->data(), dstAccountLedgerObject->size()};
auto sle = ripple::SLE{it, dstKeylet};
// Check destination for the DepositAuth flag.
// If that flag is not set then a deposit should be just fine.
if ((sle.getFieldU32(ripple::sfFlags) & ripple::lsfDepositAuth) != 0u) {
// See if a preauthorization entry is in the ledger.
auto const depositPreauthKeylet = ripple::keylet::depositPreauth(*destinationAccountID, *sourceAccountID);
auto const sleDepositAuth =
sharedPtrBackend_->fetchLedgerObject(depositPreauthKeylet.key, lgrInfo.seq, ctx.yield);
response.depositAuthorized = static_cast<bool>(sleDepositAuth);
}
}
return response; return response;
} }
@@ -115,6 +153,10 @@ tag_invoke(boost::json::value_to_tag<DepositAuthorizedHandler::Input>, boost::js
} }
} }
if (jsonObject.contains(JS(credentials))) {
input.credentials = boost::json::value_to<boost::json::array>(jv.at(JS(credentials)));
}
return input; return input;
} }
@@ -127,8 +169,10 @@ tag_invoke(boost::json::value_from_tag, boost::json::value& jv, DepositAuthorize
{JS(destination_account), output.destinationAccount}, {JS(destination_account), output.destinationAccount},
{JS(ledger_hash), output.ledgerHash}, {JS(ledger_hash), output.ledgerHash},
{JS(ledger_index), output.ledgerIndex}, {JS(ledger_index), output.ledgerIndex},
{JS(validated), output.validated}, {JS(validated), output.validated}
}; };
if (output.credentials)
jv.as_object()[JS(credentials)] = *output.credentials;
} }
} // namespace rpc } // namespace rpc

View File

@@ -25,8 +25,10 @@
#include "rpc/common/Types.hpp" #include "rpc/common/Types.hpp"
#include "rpc/common/Validators.hpp" #include "rpc/common/Validators.hpp"
#include <boost/json/array.hpp>
#include <boost/json/conversion.hpp> #include <boost/json/conversion.hpp>
#include <boost/json/value.hpp> #include <boost/json/value.hpp>
#include <xrpl/protocol/STArray.h>
#include <xrpl/protocol/jss.h> #include <xrpl/protocol/jss.h>
#include <cstdint> #include <cstdint>
@@ -59,6 +61,8 @@ public:
std::string destinationAccount; std::string destinationAccount;
std::string ledgerHash; std::string ledgerHash;
uint32_t ledgerIndex{}; uint32_t ledgerIndex{};
std::optional<boost::json::array> credentials;
// validated should be sent via framework // validated should be sent via framework
bool validated = true; bool validated = true;
}; };
@@ -71,6 +75,7 @@ public:
std::string destinationAccount; std::string destinationAccount;
std::optional<std::string> ledgerHash; std::optional<std::string> ledgerHash;
std::optional<uint32_t> ledgerIndex; std::optional<uint32_t> ledgerIndex;
std::optional<boost::json::array> credentials;
}; };
using Result = HandlerReturnType<Output>; using Result = HandlerReturnType<Output>;
@@ -99,6 +104,7 @@ public:
{JS(destination_account), validation::Required{}, validation::CustomValidators::AccountValidator}, {JS(destination_account), validation::Required{}, validation::CustomValidators::AccountValidator},
{JS(ledger_hash), validation::CustomValidators::Uint256HexStringValidator}, {JS(ledger_hash), validation::CustomValidators::Uint256HexStringValidator},
{JS(ledger_index), validation::CustomValidators::LedgerIndexValidator}, {JS(ledger_index), validation::CustomValidators::LedgerIndexValidator},
{JS(credentials), validation::Type<boost::json::array>{}, validation::Hex256ItemType()}
}; };
return rpcSpec; return rpcSpec;

View File

@@ -19,6 +19,7 @@
#include "rpc/handlers/LedgerEntry.hpp" #include "rpc/handlers/LedgerEntry.hpp"
#include "rpc/CredentialHelpers.hpp"
#include "rpc/Errors.hpp" #include "rpc/Errors.hpp"
#include "rpc/JS.hpp" #include "rpc/JS.hpp"
#include "rpc/RPCHelpers.hpp" #include "rpc/RPCHelpers.hpp"
@@ -30,6 +31,8 @@
#include <boost/json/object.hpp> #include <boost/json/object.hpp>
#include <boost/json/value.hpp> #include <boost/json/value.hpp>
#include <boost/json/value_to.hpp> #include <boost/json/value_to.hpp>
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/StringUtilities.h>
#include <xrpl/basics/base_uint.h> #include <xrpl/basics/base_uint.h>
#include <xrpl/basics/strHex.h> #include <xrpl/basics/strHex.h>
#include <xrpl/json/json_value.h> #include <xrpl/json/json_value.h>
@@ -38,6 +41,7 @@
#include <xrpl/protocol/Issue.h> #include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/LedgerFormats.h> #include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/LedgerHeader.h> #include <xrpl/protocol/LedgerHeader.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/SField.h> #include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STLedgerEntry.h> #include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STXChainBridge.h> #include <xrpl/protocol/STXChainBridge.h>
@@ -97,11 +101,30 @@ LedgerEntryHandler::process(LedgerEntryHandler::Input input, Context const& ctx)
auto const owner = util::parseBase58Wrapper<ripple::AccountID>( auto const owner = util::parseBase58Wrapper<ripple::AccountID>(
boost::json::value_to<std::string>(input.depositPreauth->at(JS(owner))) boost::json::value_to<std::string>(input.depositPreauth->at(JS(owner)))
); );
auto const authorized = util::parseBase58Wrapper<ripple::AccountID>( // Only one of authorize or authorized_credentials MUST exist;
boost::json::value_to<std::string>(input.depositPreauth->at(JS(authorized))) if (input.depositPreauth->contains(JS(authorized)) ==
); input.depositPreauth->contains(JS(authorized_credentials))) {
return Error{
Status{ClioError::rpcMALFORMED_REQUEST, "Must have one of authorized or authorized_credentials."}
};
}
key = ripple::keylet::depositPreauth(*owner, *authorized).key; if (input.depositPreauth->contains(JS(authorized))) {
auto const authorized = util::parseBase58Wrapper<ripple::AccountID>(
boost::json::value_to<std::string>(input.depositPreauth->at(JS(authorized)))
);
key = ripple::keylet::depositPreauth(*owner, *authorized).key;
} else {
auto const authorizedCredentials = rpc::credentials::parseAuthorizeCredentials(
input.depositPreauth->at(JS(authorized_credentials)).as_array()
);
auto const authCreds = credentials::createAuthCredentials(authorizedCredentials);
if (authCreds.size() != authorizedCredentials.size())
return Error{Status{ClioError::rpcMALFORMED_AUTHORIZED_CREDENTIALS, "duplicates in credentials."}};
key = ripple::keylet::depositPreauth(owner.value(), authCreds).key;
}
} else if (input.ticket) { } else if (input.ticket) {
auto const id = auto const id =
util::parseBase58Wrapper<ripple::AccountID>(boost::json::value_to<std::string>(input.ticket->at(JS(account)) util::parseBase58Wrapper<ripple::AccountID>(boost::json::value_to<std::string>(input.ticket->at(JS(account))
@@ -145,6 +168,8 @@ LedgerEntryHandler::process(LedgerEntryHandler::Input input, Context const& ctx)
} }
} else if (input.oracleNode) { } else if (input.oracleNode) {
key = input.oracleNode.value(); key = input.oracleNode.value();
} else if (input.credential) {
key = input.credential.value();
} else if (input.mptIssuance) { } else if (input.mptIssuance) {
auto const mptIssuanceID = ripple::uint192{std::string_view(*(input.mptIssuance))}; auto const mptIssuanceID = ripple::uint192{std::string_view(*(input.mptIssuance))};
key = ripple::keylet::mptIssuance(mptIssuanceID).key; key = ripple::keylet::mptIssuance(mptIssuanceID).key;
@@ -287,6 +312,7 @@ tag_invoke(boost::json::value_to_tag<LedgerEntryHandler::Input>, boost::json::va
{JS(xchain_owned_create_account_claim_id), ripple::ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID}, {JS(xchain_owned_create_account_claim_id), ripple::ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID},
{JS(xchain_owned_claim_id), ripple::ltXCHAIN_OWNED_CLAIM_ID}, {JS(xchain_owned_claim_id), ripple::ltXCHAIN_OWNED_CLAIM_ID},
{JS(oracle), ripple::ltORACLE}, {JS(oracle), ripple::ltORACLE},
{JS(credential), ripple::ltCREDENTIAL},
{JS(mptoken), ripple::ltMPTOKEN}, {JS(mptoken), ripple::ltMPTOKEN},
}; };
@@ -313,6 +339,16 @@ tag_invoke(boost::json::value_to_tag<LedgerEntryHandler::Input>, boost::json::va
return ripple::keylet::oracle(*account, documentId).key; return ripple::keylet::oracle(*account, documentId).key;
}; };
auto const parseCredentialFromJson = [](boost::json::value const& json) {
auto const subject =
util::parseBase58Wrapper<ripple::AccountID>(boost::json::value_to<std::string>(json.at(JS(subject))));
auto const issuer =
util::parseBase58Wrapper<ripple::AccountID>(boost::json::value_to<std::string>(json.at(JS(issuer))));
auto const credType = ripple::strUnHex(boost::json::value_to<std::string>(json.at(JS(credential_type))));
return ripple::keylet::credential(*subject, *issuer, ripple::Slice(credType->data(), credType->size())).key;
};
auto const indexFieldType = auto const indexFieldType =
std::find_if(indexFieldTypeMap.begin(), indexFieldTypeMap.end(), [&jsonObject](auto const& pair) { std::find_if(indexFieldTypeMap.begin(), indexFieldTypeMap.end(), [&jsonObject](auto const& pair) {
auto const& [field, _] = pair; auto const& [field, _] = pair;
@@ -361,6 +397,8 @@ tag_invoke(boost::json::value_to_tag<LedgerEntryHandler::Input>, boost::json::va
); );
} else if (jsonObject.contains(JS(oracle))) { } else if (jsonObject.contains(JS(oracle))) {
input.oracleNode = parseOracleFromJson(jv.at(JS(oracle))); input.oracleNode = parseOracleFromJson(jv.at(JS(oracle)));
} else if (jsonObject.contains(JS(credential))) {
input.credential = parseCredentialFromJson(jv.at(JS(credential)));
} else if (jsonObject.contains(JS(mptoken))) { } else if (jsonObject.contains(JS(mptoken))) {
input.mptoken = jv.at(JS(mptoken)).as_object(); input.mptoken = jv.at(JS(mptoken)).as_object();
} }

View File

@@ -30,6 +30,7 @@
#include "rpc/common/Validators.hpp" #include "rpc/common/Validators.hpp"
#include "util/AccountUtils.hpp" #include "util/AccountUtils.hpp"
#include <boost/json/array.hpp>
#include <boost/json/conversion.hpp> #include <boost/json/conversion.hpp>
#include <boost/json/object.hpp> #include <boost/json/object.hpp>
#include <boost/json/value.hpp> #include <boost/json/value.hpp>
@@ -107,6 +108,7 @@ public:
std::optional<uint32_t> chainClaimId; std::optional<uint32_t> chainClaimId;
std::optional<uint32_t> createAccountClaimId; std::optional<uint32_t> createAccountClaimId;
std::optional<ripple::uint256> oracleNode; std::optional<ripple::uint256> oracleNode;
std::optional<ripple::uint256> credential;
bool includeDeleted = false; bool includeDeleted = false;
}; };
@@ -197,7 +199,8 @@ public:
meta::WithCustomError{ meta::WithCustomError{
validation::CustomValidators::AccountBase58Validator, Status(ClioError::rpcMALFORMED_OWNER) validation::CustomValidators::AccountBase58Validator, Status(ClioError::rpcMALFORMED_OWNER)
}}, }},
{JS(authorized), validation::Required{}, validation::CustomValidators::AccountBase58Validator}, {JS(authorized), validation::CustomValidators::AccountBase58Validator},
{JS(authorized_credentials), validation::CustomValidators::AuthorizeCredentialValidator}
}, },
}}, }},
{JS(directory), {JS(directory),
@@ -318,6 +321,30 @@ public:
}, },
meta::WithCustomError{modifiers::ToNumber{}, Status(ClioError::rpcMALFORMED_ORACLE_DOCUMENT_ID)}}, meta::WithCustomError{modifiers::ToNumber{}, Status(ClioError::rpcMALFORMED_ORACLE_DOCUMENT_ID)}},
}}}, }}},
{JS(credential),
meta::WithCustomError{
validation::Type<std::string, boost::json::object>{}, Status(ClioError::rpcMALFORMED_REQUEST)
},
meta::IfType<std::string>{
meta::WithCustomError{malformedRequestHexStringValidator, Status(ClioError::rpcMALFORMED_ADDRESS)}
},
meta::IfType<boost::json::object>{meta::Section{
{JS(subject),
meta::WithCustomError{validation::Required{}, Status(ClioError::rpcMALFORMED_REQUEST)},
meta::WithCustomError{
validation::CustomValidators::AccountBase58Validator, Status(ClioError::rpcMALFORMED_ADDRESS)
}},
{JS(issuer),
meta::WithCustomError{validation::Required{}, Status(ClioError::rpcMALFORMED_REQUEST)},
meta::WithCustomError{
validation::CustomValidators::AccountBase58Validator, Status(ClioError::rpcMALFORMED_ADDRESS)
}},
{
JS(credential_type),
meta::WithCustomError{validation::Required{}, Status(ClioError::rpcMALFORMED_REQUEST)},
meta::WithCustomError{validation::Type<std::string>{}, Status(ClioError::rpcMALFORMED_REQUEST)},
},
}}},
{JS(mpt_issuance), {JS(mpt_issuance),
meta::WithCustomError{ meta::WithCustomError{
validation::CustomValidators::Uint192HexStringValidator, Status(ClioError::rpcMALFORMED_REQUEST) validation::CustomValidators::Uint192HexStringValidator, Status(ClioError::rpcMALFORMED_REQUEST)

View File

@@ -19,6 +19,8 @@
#pragma once #pragma once
#include <xrpl/basics/StringUtilities.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/tokens.h> #include <xrpl/protocol/tokens.h>
#include <cctype> #include <cctype>

View File

@@ -112,6 +112,7 @@ class LedgerTypes {
), ),
LedgerTypeAttribute::AccountOwnedLedgerType(JS(did), ripple::ltDID), LedgerTypeAttribute::AccountOwnedLedgerType(JS(did), ripple::ltDID),
LedgerTypeAttribute::AccountOwnedLedgerType(JS(oracle), ripple::ltORACLE), LedgerTypeAttribute::AccountOwnedLedgerType(JS(oracle), ripple::ltORACLE),
LedgerTypeAttribute::AccountOwnedLedgerType(JS(credential), ripple::ltCREDENTIAL),
LedgerTypeAttribute::ChainLedgerType(JS(nunl), ripple::ltNEGATIVE_UNL), LedgerTypeAttribute::ChainLedgerType(JS(nunl), ripple::ltNEGATIVE_UNL),
LedgerTypeAttribute::DeletionBlockerLedgerType(JS(mpt_issuance), ripple::ltMPTOKEN_ISSUANCE), LedgerTypeAttribute::DeletionBlockerLedgerType(JS(mpt_issuance), ripple::ltMPTOKEN_ISSUANCE),
LedgerTypeAttribute::DeletionBlockerLedgerType(JS(mptoken), ripple::ltMPTOKEN), LedgerTypeAttribute::DeletionBlockerLedgerType(JS(mptoken), ripple::ltMPTOKEN),

View File

@@ -90,6 +90,7 @@ public:
case rpc::ClioError::rpcINVALID_HOT_WALLET: case rpc::ClioError::rpcINVALID_HOT_WALLET:
case rpc::ClioError::rpcFIELD_NOT_FOUND_TRANSACTION: case rpc::ClioError::rpcFIELD_NOT_FOUND_TRANSACTION:
case rpc::ClioError::rpcMALFORMED_ORACLE_DOCUMENT_ID: case rpc::ClioError::rpcMALFORMED_ORACLE_DOCUMENT_ID:
case rpc::ClioError::rpcMALFORMED_AUTHORIZED_CREDENTIALS:
case rpc::ClioError::etlCONNECTION_ERROR: case rpc::ClioError::etlCONNECTION_ERROR:
case rpc::ClioError::etlREQUEST_ERROR: case rpc::ClioError::etlREQUEST_ERROR:
case rpc::ClioError::etlREQUEST_TIMEOUT: case rpc::ClioError::etlREQUEST_TIMEOUT:

View File

@@ -26,6 +26,7 @@
#include <xrpl/basics/Blob.h> #include <xrpl/basics/Blob.h>
#include <xrpl/basics/Slice.h> #include <xrpl/basics/Slice.h>
#include <xrpl/basics/StringUtilities.h>
#include <xrpl/basics/base_uint.h> #include <xrpl/basics/base_uint.h>
#include <xrpl/basics/chrono.h> #include <xrpl/basics/chrono.h>
#include <xrpl/json/json_value.h> #include <xrpl/json/json_value.h>
@@ -48,6 +49,7 @@
#include <algorithm> #include <algorithm>
#include <chrono> #include <chrono>
#include <cstddef>
#include <cstdint> #include <cstdint>
#include <optional> #include <optional>
#include <string> #include <string>
@@ -540,7 +542,7 @@ CreateCheckLedgerObject(std::string_view account, std::string_view dest)
} }
ripple::STObject ripple::STObject
CreateDepositPreauthLedgerObject(std::string_view account, std::string_view auth) CreateDepositPreauthLedgerObjectByAuth(std::string_view account, std::string_view auth)
{ {
ripple::STObject depositPreauth(ripple::sfLedgerEntry); ripple::STObject depositPreauth(ripple::sfLedgerEntry);
depositPreauth.setFieldU16(ripple::sfLedgerEntryType, ripple::ltDEPOSIT_PREAUTH); depositPreauth.setFieldU16(ripple::sfLedgerEntryType, ripple::ltDEPOSIT_PREAUTH);
@@ -553,6 +555,27 @@ CreateDepositPreauthLedgerObject(std::string_view account, std::string_view auth
return depositPreauth; return depositPreauth;
} }
ripple::STObject
CreateDepositPreauthLedgerObjectByAuthCredentials(
std::string_view account,
std::string_view issuer,
std::string_view credType
)
{
ripple::STObject depositPreauth(ripple::sfLedgerEntry);
depositPreauth.setFieldU16(ripple::sfLedgerEntryType, ripple::ltDEPOSIT_PREAUTH);
depositPreauth.setAccountID(ripple::sfAccount, GetAccountIDWithString(account));
depositPreauth.setFieldArray(
ripple::sfAuthorizeCredentials,
CreateAuthCredentialArray(std::vector<std::string_view>{issuer}, std::vector<std::string_view>{credType})
);
depositPreauth.setFieldU32(ripple::sfFlags, 0);
depositPreauth.setFieldU64(ripple::sfOwnerNode, 0);
depositPreauth.setFieldH256(ripple::sfPreviousTxnID, ripple::uint256{});
depositPreauth.setFieldU32(ripple::sfPreviousTxnLgrSeq, 0);
return depositPreauth;
}
data::NFT data::NFT
CreateNFT(std::string_view tokenID, std::string_view account, ripple::LedgerIndex seq, ripple::Blob uri, bool isBurned) CreateNFT(std::string_view tokenID, std::string_view account, ripple::LedgerIndex seq, ripple::Blob uri, bool isBurned)
{ {
@@ -1192,3 +1215,47 @@ CreateOracleObject(
return ledgerObject; return ledgerObject;
} }
// acc2 issue credential for acc1 so acc2 is issuer
ripple::STObject
CreateCredentialObject(
std::string_view acc1,
std::string_view acc2,
std::string_view credType,
bool accept,
std::optional<uint32_t> expiration
)
{
ripple::STObject credObj(ripple::sfCredential);
credObj.setFieldU16(ripple::sfLedgerEntryType, ripple::ltCREDENTIAL);
credObj.setFieldVL(ripple::sfCredentialType, ripple::Blob{credType.begin(), credType.end()});
credObj.setAccountID(ripple::sfSubject, GetAccountIDWithString(acc1));
credObj.setAccountID(ripple::sfIssuer, GetAccountIDWithString(acc2));
if (expiration.has_value())
credObj.setFieldU32(ripple::sfExpiration, expiration.value());
if (accept) {
credObj.setFieldU32(ripple::sfFlags, ripple::lsfAccepted);
} else {
credObj.setFieldU32(ripple::sfFlags, 0);
}
credObj.setFieldU64(ripple::sfSubjectNode, 0);
credObj.setFieldU64(ripple::sfIssuerNode, 0);
credObj.setFieldH256(ripple::sfPreviousTxnID, ripple::uint256{});
credObj.setFieldU32(ripple::sfPreviousTxnLgrSeq, 0);
return credObj;
}
ripple::STArray
CreateAuthCredentialArray(std::vector<std::string_view> issuer, std::vector<std::string_view> credType)
{
ripple::STArray arr;
ASSERT(issuer.size() == credType.size(), "issuer and credtype vector must be same length");
for (std::size_t i = 0; i < issuer.size(); ++i) {
auto credential = ripple::STObject::makeInnerObject(ripple::sfCredential);
credential.setAccountID(ripple::sfIssuer, GetAccountIDWithString(issuer[i]));
credential.setFieldVL(ripple::sfCredentialType, ripple::strUnHex(std::string(credType[i])).value());
arr.push_back(credential);
}
return arr;
}

View File

@@ -22,6 +22,7 @@
#include "data/Types.hpp" #include "data/Types.hpp"
#include <xrpl/basics/Blob.h> #include <xrpl/basics/Blob.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/base_uint.h> #include <xrpl/basics/base_uint.h>
#include <xrpl/protocol/AccountID.h> #include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Issue.h> #include <xrpl/protocol/Issue.h>
@@ -268,7 +269,14 @@ CreateEscrowLedgerObject(std::string_view account, std::string_view dest);
CreateCheckLedgerObject(std::string_view account, std::string_view dest); CreateCheckLedgerObject(std::string_view account, std::string_view dest);
[[nodiscard]] ripple::STObject [[nodiscard]] ripple::STObject
CreateDepositPreauthLedgerObject(std::string_view account, std::string_view auth); CreateDepositPreauthLedgerObjectByAuth(std::string_view account, std::string_view auth);
[[nodiscard]] ripple::STObject
CreateDepositPreauthLedgerObjectByAuthCredentials(
std::string_view account,
std::string_view issuer,
std::string_view credType
);
[[nodiscard]] data::NFT [[nodiscard]] data::NFT
CreateNFT( CreateNFT(
@@ -435,3 +443,15 @@ CreateOracleSetTxWithMetadata(
bool created, bool created,
std::string_view previousTxnId std::string_view previousTxnId
); );
[[nodiscard]] ripple::STObject
CreateCredentialObject(
std::string_view acc1,
std::string_view acc2,
std::string_view credType,
bool accept = true,
std::optional<uint32_t> expiration = std::nullopt
);
[[nodiscard]] ripple::STArray
CreateAuthCredentialArray(std::vector<std::string_view> issuer, std::vector<std::string_view> credType);

View File

@@ -67,6 +67,7 @@ target_sources(
rpc/handlers/AMMInfoTests.cpp rpc/handlers/AMMInfoTests.cpp
rpc/handlers/BookChangesTests.cpp rpc/handlers/BookChangesTests.cpp
rpc/handlers/BookOffersTests.cpp rpc/handlers/BookOffersTests.cpp
rpc/handlers/CredentialHelpersTests.cpp
rpc/handlers/DefaultProcessorTests.cpp rpc/handlers/DefaultProcessorTests.cpp
rpc/handlers/DepositAuthorizedTests.cpp rpc/handlers/DepositAuthorizedTests.cpp
rpc/handlers/FeatureTests.cpp rpc/handlers/FeatureTests.cpp

View File

@@ -30,10 +30,12 @@
#include <boost/asio/impl/spawn.hpp> #include <boost/asio/impl/spawn.hpp>
#include <boost/asio/spawn.hpp> #include <boost/asio/spawn.hpp>
#include <boost/json/array.hpp>
#include <boost/json/parse.hpp> #include <boost/json/parse.hpp>
#include <fmt/core.h> #include <fmt/core.h>
#include <gmock/gmock.h> #include <gmock/gmock.h>
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <xrpl/basics/Blob.h>
#include <xrpl/basics/base_uint.h> #include <xrpl/basics/base_uint.h>
#include <xrpl/protocol/ErrorCodes.h> #include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Indexes.h> #include <xrpl/protocol/Indexes.h>
@@ -47,6 +49,7 @@
#include <cstdint> #include <cstdint>
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
#include <string_view>
#include <tuple> #include <tuple>
#include <variant> #include <variant>
#include <vector> #include <vector>

View File

@@ -0,0 +1,153 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2024, the clio developers.
Permission to use, copy, modify, and 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 "rpc/CredentialHelpers.hpp"
#include "rpc/Errors.hpp"
#include "rpc/JS.hpp"
#include "util/AsioContextTestFixture.hpp"
#include "util/MockBackendTestFixture.hpp"
#include "util/MockPrometheus.hpp"
#include "util/TestObject.hpp"
#include <boost/asio/spawn.hpp>
#include <boost/json/array.hpp>
#include <boost/json/object.hpp>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <xrpl/basics/Blob.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/StringUtilities.h>
#include <xrpl/basics/strHex.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STArray.h>
#include <xrpl/protocol/STObject.h>
#include <xrpl/protocol/jss.h>
#include <string>
#include <string_view>
#include <utility>
using namespace rpc;
using namespace testing;
constexpr static auto Account = "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn";
constexpr static auto Account2 = "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun";
constexpr static auto Index1 = "E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321";
constexpr static auto CredentialID = "c7a14f6b9d5d4a9cb9c223a61b8e5c7df58e8b7ad1c6b4f8e7a321fa4e5b4c9d";
constexpr static std::string_view CredentialType = "credType";
TEST(CreateAuthCredentialsTest, UniqueCredentials)
{
ripple::STArray credentials;
auto const cred1 = CreateCredentialObject(Account, Account2, CredentialType);
auto const cred2 = CreateCredentialObject(Account2, Account, CredentialType);
credentials.push_back(cred1);
credentials.push_back(cred2);
auto const result = credentials::createAuthCredentials(credentials);
// Validate that the result contains the correct set of credentials
ASSERT_EQ(result.size(), 2);
auto const cred1Type = cred1.getFieldVL(ripple::sfCredentialType);
auto const cred2Type = cred2.getFieldVL(ripple::sfCredentialType);
auto const expected_cred1 =
std::make_pair(cred1.getAccountID(ripple::sfIssuer), ripple::Slice{cred1Type.data(), cred1Type.size()});
auto const expected_cred2 =
std::make_pair(cred2.getAccountID(ripple::sfIssuer), ripple::Slice{cred2Type.data(), cred2Type.size()});
EXPECT_TRUE(result.count(expected_cred1));
EXPECT_TRUE(result.count(expected_cred2));
}
TEST(ParseAuthorizeCredentialsTest, ValidCredentialsArray)
{
boost::json::array credentials;
boost::json::object credential1;
credential1[JS(issuer)] = Account;
credential1[JS(credential_type)] = ripple::strHex(CredentialType);
credentials.push_back(credential1);
ripple::STArray const parsedCredentials = credentials::parseAuthorizeCredentials(credentials);
ASSERT_EQ(parsedCredentials.size(), 1);
ripple::STObject const& cred = parsedCredentials[0];
ASSERT_TRUE(cred.isFieldPresent(ripple::sfIssuer));
ASSERT_TRUE(cred.isFieldPresent(ripple::sfCredentialType));
auto const expectedIssuer =
*ripple::parseBase58<ripple::AccountID>(static_cast<std::string>(credential1[JS(issuer)].as_string()));
auto const expectedCredentialType =
ripple::strUnHex(static_cast<std::string>(credential1[JS(credential_type)].as_string())).value();
EXPECT_EQ(cred.getAccountID(ripple::sfIssuer), expectedIssuer);
EXPECT_EQ(cred.getFieldVL(ripple::sfCredentialType), expectedCredentialType);
}
class CredentialHelperTest : public util::prometheus::WithPrometheus,
public MockBackendTest,
public SyncAsioContextTest {};
TEST_F(CredentialHelperTest, GetInvalidCredentialArray)
{
boost::json::array credentialsArray = {CredentialID};
auto const info = CreateLedgerHeader(Index1, 30);
boost::asio::spawn(ctx, [&](boost::asio::yield_context yield) {
auto const ret =
credentials::fetchCredentialArray(credentialsArray, GetAccountIDWithString(Account), *backend, info, yield);
ASSERT_FALSE(ret.has_value());
auto const status = ret.error();
EXPECT_EQ(status, RippledError::rpcBAD_CREDENTIALS);
EXPECT_EQ(status.message, "credentials don't exist.");
});
ctx.run();
}
TEST_F(CredentialHelperTest, GetValidCredentialArray)
{
backend->setRange(10, 30);
auto ledgerHeader = CreateLedgerHeader(Index1, 30);
auto const credLedgerObject = CreateCredentialObject(Account, Account2, CredentialType, true);
ON_CALL(*backend, doFetchLedgerObject(_, _, _)).WillByDefault(Return(credLedgerObject.getSerializer().peekData()));
EXPECT_CALL(*backend, doFetchLedgerObject).Times(1);
boost::json::array credentialsArray = {CredentialID};
ripple::STArray expectedAuthCreds;
ripple::STObject credential(ripple::sfCredential);
credential.setAccountID(ripple::sfIssuer, GetAccountIDWithString(Account2));
credential.setFieldVL(ripple::sfCredentialType, ripple::Blob{std::begin(CredentialType), std::end(CredentialType)});
expectedAuthCreds.push_back(std::move(credential));
boost::asio::spawn(ctx, [&](boost::asio::yield_context yield) {
auto const result = credentials::fetchCredentialArray(
credentialsArray, GetAccountIDWithString(Account), *backend, ledgerHeader, yield
);
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result.value(), expectedAuthCreds);
});
ctx.run();
}

View File

@@ -28,24 +28,34 @@
#include <boost/json/parse.hpp> #include <boost/json/parse.hpp>
#include <fmt/core.h> #include <fmt/core.h>
#include <fmt/format.h>
#include <gmock/gmock.h> #include <gmock/gmock.h>
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/base_uint.h> #include <xrpl/basics/base_uint.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/basics/strHex.h>
#include <xrpl/protocol/Indexes.h> #include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h> #include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/STArray.h>
#include <chrono>
#include <optional> #include <optional>
#include <ranges>
#include <string> #include <string>
#include <string_view>
#include <vector> #include <vector>
constexpr static auto ACCOUNT = "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"; constexpr static auto Account = "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn";
constexpr static auto ACCOUNT2 = "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun"; constexpr static auto Account2 = "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun";
constexpr static auto LEDGERHASH = "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652"; constexpr static auto LedgerHash = "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652";
constexpr static auto INDEX1 = "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC"; constexpr static auto Index1 = "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC";
constexpr static auto INDEX2 = "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515B1"; constexpr static auto Index2 = "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515B1";
constexpr static std::string_view CredentialType = "credType";
constexpr static auto CredentialHash = "F245428267E6177AEEFDD4FEA3533285712A4B1091CF82A7EA7BC39A62C3FB1A";
constexpr static auto RANGEMIN = 10; constexpr static auto RangeMin = 10;
constexpr static auto RANGEMAX = 30; constexpr static auto RangeMax = 30;
using namespace rpc; using namespace rpc;
namespace json = boost::json; namespace json = boost::json;
@@ -156,6 +166,38 @@ generateTestValuesForParametersTest()
"invalidParams", "invalidParams",
"ledgerIndexMalformed", "ledgerIndexMalformed",
}, },
{
"CredentialsNotArray",
R"({
"source_account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"destination_account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"credentials": "x"
})",
"invalidParams",
"Invalid parameters.",
},
{
"CredentialsNotStringsInArray",
R"({
"source_account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"destination_account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652",
"credentials": [123]
})",
"invalidParams",
"Item is not a valid uint256 type.",
},
{
"CredentialsNotHexedStringInArray",
R"({
"source_account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"destination_account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652",
"credentials": ["234", "432"]
})",
"invalidParams",
"Item is not a valid uint256 type.",
}
}; };
} }
@@ -184,10 +226,10 @@ TEST_P(DepositAuthorizedParameterTest, InvalidParams)
TEST_F(RPCDepositAuthorizedTest, LedgerNotExistViaIntSequence) TEST_F(RPCDepositAuthorizedTest, LedgerNotExistViaIntSequence)
{ {
backend->setRange(RANGEMIN, RANGEMAX); backend->setRange(RangeMin, RangeMax);
EXPECT_CALL(*backend, fetchLedgerBySequence).Times(1); EXPECT_CALL(*backend, fetchLedgerBySequence).Times(1);
ON_CALL(*backend, fetchLedgerBySequence(RANGEMAX, _)).WillByDefault(Return(std::nullopt)); ON_CALL(*backend, fetchLedgerBySequence(RangeMax, _)).WillByDefault(Return(std::nullopt));
runSpawn([&, this](auto yield) { runSpawn([&, this](auto yield) {
auto const handler = AnyHandler{DepositAuthorizedHandler{backend}}; auto const handler = AnyHandler{DepositAuthorizedHandler{backend}};
@@ -197,9 +239,9 @@ TEST_F(RPCDepositAuthorizedTest, LedgerNotExistViaIntSequence)
"destination_account": "{}", "destination_account": "{}",
"ledger_index": {} "ledger_index": {}
}})", }})",
ACCOUNT, Account,
ACCOUNT2, Account2,
RANGEMAX RangeMax
)); ));
auto const output = handler.process(req, Context{yield}); auto const output = handler.process(req, Context{yield});
@@ -213,10 +255,10 @@ TEST_F(RPCDepositAuthorizedTest, LedgerNotExistViaIntSequence)
TEST_F(RPCDepositAuthorizedTest, LedgerNotExistViaStringSequence) TEST_F(RPCDepositAuthorizedTest, LedgerNotExistViaStringSequence)
{ {
backend->setRange(RANGEMIN, RANGEMAX); backend->setRange(RangeMin, RangeMax);
EXPECT_CALL(*backend, fetchLedgerBySequence).Times(1); EXPECT_CALL(*backend, fetchLedgerBySequence).Times(1);
ON_CALL(*backend, fetchLedgerBySequence(RANGEMAX, _)).WillByDefault(Return(std::nullopt)); ON_CALL(*backend, fetchLedgerBySequence(RangeMax, _)).WillByDefault(Return(std::nullopt));
runSpawn([&, this](auto yield) { runSpawn([&, this](auto yield) {
auto const handler = AnyHandler{DepositAuthorizedHandler{backend}}; auto const handler = AnyHandler{DepositAuthorizedHandler{backend}};
@@ -226,9 +268,9 @@ TEST_F(RPCDepositAuthorizedTest, LedgerNotExistViaStringSequence)
"destination_account": "{}", "destination_account": "{}",
"ledger_index": "{}" "ledger_index": "{}"
}})", }})",
ACCOUNT, Account,
ACCOUNT2, Account2,
RANGEMAX RangeMax
)); ));
auto const output = handler.process(req, Context{yield}); auto const output = handler.process(req, Context{yield});
@@ -242,10 +284,10 @@ TEST_F(RPCDepositAuthorizedTest, LedgerNotExistViaStringSequence)
TEST_F(RPCDepositAuthorizedTest, LedgerNotExistViaHash) TEST_F(RPCDepositAuthorizedTest, LedgerNotExistViaHash)
{ {
backend->setRange(RANGEMIN, RANGEMAX); backend->setRange(RangeMin, RangeMax);
EXPECT_CALL(*backend, fetchLedgerByHash).Times(1); EXPECT_CALL(*backend, fetchLedgerByHash).Times(1);
ON_CALL(*backend, fetchLedgerByHash(ripple::uint256{LEDGERHASH}, _)).WillByDefault(Return(std::nullopt)); ON_CALL(*backend, fetchLedgerByHash(ripple::uint256{LedgerHash}, _)).WillByDefault(Return(std::nullopt));
runSpawn([&, this](auto yield) { runSpawn([&, this](auto yield) {
auto const handler = AnyHandler{DepositAuthorizedHandler{backend}}; auto const handler = AnyHandler{DepositAuthorizedHandler{backend}};
@@ -255,9 +297,9 @@ TEST_F(RPCDepositAuthorizedTest, LedgerNotExistViaHash)
"destination_account": "{}", "destination_account": "{}",
"ledger_hash": "{}" "ledger_hash": "{}"
}})", }})",
ACCOUNT, Account,
ACCOUNT2, Account2,
LEDGERHASH LedgerHash
)); ));
auto const output = handler.process(req, Context{yield}); auto const output = handler.process(req, Context{yield});
@@ -273,9 +315,9 @@ TEST_F(RPCDepositAuthorizedTest, SourceAccountDoesNotExist)
{ {
backend->setRange(10, 30); backend->setRange(10, 30);
auto ledgerHeader = CreateLedgerHeader(LEDGERHASH, 30); auto ledgerHeader = CreateLedgerHeader(LedgerHash, 30);
ON_CALL(*backend, fetchLedgerByHash(ripple::uint256{LEDGERHASH}, _)).WillByDefault(Return(ledgerHeader)); ON_CALL(*backend, fetchLedgerByHash(ripple::uint256{LedgerHash}, _)).WillByDefault(Return(ledgerHeader));
EXPECT_CALL(*backend, fetchLedgerByHash).Times(1); EXPECT_CALL(*backend, fetchLedgerByHash).Times(1);
ON_CALL(*backend, doFetchLedgerObject).WillByDefault(Return(std::optional<Blob>{})); ON_CALL(*backend, doFetchLedgerObject).WillByDefault(Return(std::optional<Blob>{}));
@@ -287,9 +329,9 @@ TEST_F(RPCDepositAuthorizedTest, SourceAccountDoesNotExist)
"destination_account": "{}", "destination_account": "{}",
"ledger_hash": "{}" "ledger_hash": "{}"
}})", }})",
ACCOUNT, Account,
ACCOUNT2, Account2,
LEDGERHASH LedgerHash
)); ));
runSpawn([&, this](auto yield) { runSpawn([&, this](auto yield) {
@@ -308,14 +350,14 @@ TEST_F(RPCDepositAuthorizedTest, DestinationAccountDoesNotExist)
{ {
backend->setRange(10, 30); backend->setRange(10, 30);
auto ledgerHeader = CreateLedgerHeader(LEDGERHASH, 30); auto ledgerHeader = CreateLedgerHeader(LedgerHash, 30);
ON_CALL(*backend, fetchLedgerByHash(ripple::uint256{LEDGERHASH}, _)).WillByDefault(Return(ledgerHeader)); ON_CALL(*backend, fetchLedgerByHash(ripple::uint256{LedgerHash}, _)).WillByDefault(Return(ledgerHeader));
EXPECT_CALL(*backend, fetchLedgerByHash).Times(1); EXPECT_CALL(*backend, fetchLedgerByHash).Times(1);
auto const accountRoot = CreateAccountRootObject(ACCOUNT, 0, 2, 200, 2, INDEX1, 2); auto const accountRoot = CreateAccountRootObject(Account, 0, 2, 200, 2, Index1, 2);
ON_CALL(*backend, doFetchLedgerObject(_, _, _)).WillByDefault(Return(accountRoot.getSerializer().peekData())); ON_CALL(*backend, doFetchLedgerObject(_, _, _)).WillByDefault(Return(accountRoot.getSerializer().peekData()));
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(ACCOUNT2)).key, _, _)) ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account2)).key, _, _))
.WillByDefault(Return(std::optional<Blob>{})); .WillByDefault(Return(std::optional<Blob>{}));
EXPECT_CALL(*backend, doFetchLedgerObject).Times(2); EXPECT_CALL(*backend, doFetchLedgerObject).Times(2);
@@ -326,9 +368,9 @@ TEST_F(RPCDepositAuthorizedTest, DestinationAccountDoesNotExist)
"destination_account": "{}", "destination_account": "{}",
"ledger_hash": "{}" "ledger_hash": "{}"
}})", }})",
ACCOUNT, Account,
ACCOUNT2, Account2,
LEDGERHASH LedgerHash
)); ));
runSpawn([&, this](auto yield) { runSpawn([&, this](auto yield) {
@@ -357,12 +399,12 @@ TEST_F(RPCDepositAuthorizedTest, AccountsAreEqual)
backend->setRange(10, 30); backend->setRange(10, 30);
auto ledgerHeader = CreateLedgerHeader(LEDGERHASH, 30); auto ledgerHeader = CreateLedgerHeader(LedgerHash, 30);
ON_CALL(*backend, fetchLedgerByHash(ripple::uint256{LEDGERHASH}, _)).WillByDefault(Return(ledgerHeader)); ON_CALL(*backend, fetchLedgerByHash(ripple::uint256{LedgerHash}, _)).WillByDefault(Return(ledgerHeader));
EXPECT_CALL(*backend, fetchLedgerByHash).Times(1); EXPECT_CALL(*backend, fetchLedgerByHash).Times(1);
auto const accountRoot = CreateAccountRootObject(ACCOUNT, 0, 2, 200, 2, INDEX1, 2); auto const accountRoot = CreateAccountRootObject(Account, 0, 2, 200, 2, Index1, 2);
ON_CALL(*backend, doFetchLedgerObject).WillByDefault(Return(accountRoot.getSerializer().peekData())); ON_CALL(*backend, doFetchLedgerObject).WillByDefault(Return(accountRoot.getSerializer().peekData()));
EXPECT_CALL(*backend, doFetchLedgerObject).Times(2); EXPECT_CALL(*backend, doFetchLedgerObject).Times(2);
@@ -372,9 +414,9 @@ TEST_F(RPCDepositAuthorizedTest, AccountsAreEqual)
"destination_account": "{}", "destination_account": "{}",
"ledger_hash": "{}" "ledger_hash": "{}"
}})", }})",
ACCOUNT, Account,
ACCOUNT, Account,
LEDGERHASH LedgerHash
)); ));
runSpawn([&, this](auto yield) { runSpawn([&, this](auto yield) {
@@ -400,17 +442,17 @@ TEST_F(RPCDepositAuthorizedTest, DifferentAccountsNoDepositAuthFlag)
backend->setRange(10, 30); backend->setRange(10, 30);
auto ledgerHeader = CreateLedgerHeader(LEDGERHASH, 30); auto ledgerHeader = CreateLedgerHeader(LedgerHash, 30);
ON_CALL(*backend, fetchLedgerByHash(ripple::uint256{LEDGERHASH}, _)).WillByDefault(Return(ledgerHeader)); ON_CALL(*backend, fetchLedgerByHash(ripple::uint256{LedgerHash}, _)).WillByDefault(Return(ledgerHeader));
EXPECT_CALL(*backend, fetchLedgerByHash).Times(1); EXPECT_CALL(*backend, fetchLedgerByHash).Times(1);
auto const account1Root = CreateAccountRootObject(ACCOUNT, 0, 2, 200, 2, INDEX1, 2); auto const account1Root = CreateAccountRootObject(Account, 0, 2, 200, 2, Index1, 2);
auto const account2Root = CreateAccountRootObject(ACCOUNT2, 0, 2, 200, 2, INDEX2, 2); auto const account2Root = CreateAccountRootObject(Account2, 0, 2, 200, 2, Index2, 2);
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(ACCOUNT)).key, _, _)) ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account)).key, _, _))
.WillByDefault(Return(account1Root.getSerializer().peekData())); .WillByDefault(Return(account1Root.getSerializer().peekData()));
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(ACCOUNT2)).key, _, _)) ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account2)).key, _, _))
.WillByDefault(Return(account2Root.getSerializer().peekData())); .WillByDefault(Return(account2Root.getSerializer().peekData()));
EXPECT_CALL(*backend, doFetchLedgerObject).Times(2); EXPECT_CALL(*backend, doFetchLedgerObject).Times(2);
@@ -420,9 +462,9 @@ TEST_F(RPCDepositAuthorizedTest, DifferentAccountsNoDepositAuthFlag)
"destination_account": "{}", "destination_account": "{}",
"ledger_hash": "{}" "ledger_hash": "{}"
}})", }})",
ACCOUNT, Account,
ACCOUNT2, Account2,
LEDGERHASH LedgerHash
)); ));
runSpawn([&, this](auto yield) { runSpawn([&, this](auto yield) {
@@ -448,18 +490,18 @@ TEST_F(RPCDepositAuthorizedTest, DifferentAccountsWithDepositAuthFlagReturnsFals
backend->setRange(10, 30); backend->setRange(10, 30);
auto ledgerHeader = CreateLedgerHeader(LEDGERHASH, 30); auto ledgerHeader = CreateLedgerHeader(LedgerHash, 30);
ON_CALL(*backend, fetchLedgerByHash(ripple::uint256{LEDGERHASH}, _)).WillByDefault(Return(ledgerHeader)); ON_CALL(*backend, fetchLedgerByHash(ripple::uint256{LedgerHash}, _)).WillByDefault(Return(ledgerHeader));
EXPECT_CALL(*backend, fetchLedgerByHash).Times(1); EXPECT_CALL(*backend, fetchLedgerByHash).Times(1);
auto const account1Root = CreateAccountRootObject(ACCOUNT, 0, 2, 200, 2, INDEX1, 2); auto const account1Root = CreateAccountRootObject(Account, 0, 2, 200, 2, Index1, 2);
auto const account2Root = CreateAccountRootObject(ACCOUNT2, ripple::lsfDepositAuth, 2, 200, 2, INDEX2, 2); auto const account2Root = CreateAccountRootObject(Account2, ripple::lsfDepositAuth, 2, 200, 2, Index2, 2);
ON_CALL(*backend, doFetchLedgerObject(_, _, _)).WillByDefault(Return(std::nullopt)); ON_CALL(*backend, doFetchLedgerObject(_, _, _)).WillByDefault(Return(std::nullopt));
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(ACCOUNT)).key, _, _)) ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account)).key, _, _))
.WillByDefault(Return(account1Root.getSerializer().peekData())); .WillByDefault(Return(account1Root.getSerializer().peekData()));
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(ACCOUNT2)).key, _, _)) ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account2)).key, _, _))
.WillByDefault(Return(account2Root.getSerializer().peekData())); .WillByDefault(Return(account2Root.getSerializer().peekData()));
EXPECT_CALL(*backend, doFetchLedgerObject).Times(3); EXPECT_CALL(*backend, doFetchLedgerObject).Times(3);
@@ -469,9 +511,9 @@ TEST_F(RPCDepositAuthorizedTest, DifferentAccountsWithDepositAuthFlagReturnsFals
"destination_account": "{}", "destination_account": "{}",
"ledger_hash": "{}" "ledger_hash": "{}"
}})", }})",
ACCOUNT, Account,
ACCOUNT2, Account2,
LEDGERHASH LedgerHash
)); ));
runSpawn([&, this](auto yield) { runSpawn([&, this](auto yield) {
@@ -497,18 +539,18 @@ TEST_F(RPCDepositAuthorizedTest, DifferentAccountsWithDepositAuthFlagReturnsTrue
backend->setRange(10, 30); backend->setRange(10, 30);
auto ledgerHeader = CreateLedgerHeader(LEDGERHASH, 30); auto ledgerHeader = CreateLedgerHeader(LedgerHash, 30);
ON_CALL(*backend, fetchLedgerByHash(ripple::uint256{LEDGERHASH}, _)).WillByDefault(Return(ledgerHeader)); ON_CALL(*backend, fetchLedgerByHash(ripple::uint256{LedgerHash}, _)).WillByDefault(Return(ledgerHeader));
EXPECT_CALL(*backend, fetchLedgerByHash).Times(1); EXPECT_CALL(*backend, fetchLedgerByHash).Times(1);
auto const account1Root = CreateAccountRootObject(ACCOUNT, 0, 2, 200, 2, INDEX1, 2); auto const account1Root = CreateAccountRootObject(Account, 0, 2, 200, 2, Index1, 2);
auto const account2Root = CreateAccountRootObject(ACCOUNT2, ripple::lsfDepositAuth, 2, 200, 2, INDEX2, 2); auto const account2Root = CreateAccountRootObject(Account2, ripple::lsfDepositAuth, 2, 200, 2, Index2, 2);
ON_CALL(*backend, doFetchLedgerObject(_, _, _)).WillByDefault(Return(std::optional<Blob>{{1, 2, 3}})); ON_CALL(*backend, doFetchLedgerObject(_, _, _)).WillByDefault(Return(std::optional<Blob>{{1, 2, 3}}));
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(ACCOUNT)).key, _, _)) ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account)).key, _, _))
.WillByDefault(Return(account1Root.getSerializer().peekData())); .WillByDefault(Return(account1Root.getSerializer().peekData()));
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(ACCOUNT2)).key, _, _)) ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account2)).key, _, _))
.WillByDefault(Return(account2Root.getSerializer().peekData())); .WillByDefault(Return(account2Root.getSerializer().peekData()));
EXPECT_CALL(*backend, doFetchLedgerObject).Times(3); EXPECT_CALL(*backend, doFetchLedgerObject).Times(3);
@@ -518,9 +560,9 @@ TEST_F(RPCDepositAuthorizedTest, DifferentAccountsWithDepositAuthFlagReturnsTrue
"destination_account": "{}", "destination_account": "{}",
"ledger_hash": "{}" "ledger_hash": "{}"
}})", }})",
ACCOUNT, Account,
ACCOUNT2, Account2,
LEDGERHASH LedgerHash
)); ));
runSpawn([&, this](auto yield) { runSpawn([&, this](auto yield) {
@@ -531,3 +573,380 @@ TEST_F(RPCDepositAuthorizedTest, DifferentAccountsWithDepositAuthFlagReturnsTrue
EXPECT_EQ(*output.result, json::parse(expectedOut)); EXPECT_EQ(*output.result, json::parse(expectedOut));
}); });
} }
TEST_F(RPCDepositAuthorizedTest, CredentialAcceptedAndNotExpiredReturnsTrue)
{
static auto const expectedOut = fmt::format(
R"({{
"ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652",
"ledger_index": 30,
"validated": true,
"deposit_authorized": true,
"source_account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"destination_account": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun",
"credentials": ["{}"]
}})",
CredentialHash // CREDENTIALHASH should match credentialIndex
);
backend->setRange(10, 30);
auto ledgerHeader = CreateLedgerHeader(LedgerHash, 30);
EXPECT_CALL(*backend, fetchLedgerByHash(ripple::uint256{LedgerHash}, _)).WillOnce(Return(ledgerHeader));
auto const account1Root = CreateAccountRootObject(Account, 0, 2, 200, 2, Index1, 2);
auto const account2Root = CreateAccountRootObject(Account2, ripple::lsfDepositAuth, 2, 200, 2, Index2, 2);
auto const credential = CreateCredentialObject(Account, Account2, CredentialType);
auto const credentialIndex = ripple::keylet::credential(
GetAccountIDWithString(Account),
GetAccountIDWithString(Account2),
ripple::Slice(CredentialType.data(), CredentialType.size())
)
.key;
ON_CALL(*backend, doFetchLedgerObject(_, _, _)).WillByDefault(Return(std::optional<Blob>{{1, 2, 3}}));
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account)).key, _, _))
.WillByDefault(Return(account1Root.getSerializer().peekData()));
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account2)).key, _, _))
.WillByDefault(Return(account2Root.getSerializer().peekData()));
ON_CALL(*backend, doFetchLedgerObject(credentialIndex, _, _))
.WillByDefault(Return(credential.getSerializer().peekData()));
EXPECT_CALL(*backend, doFetchLedgerObject).Times(4);
auto const input = json::parse(fmt::format(
R"({{
"source_account": "{}",
"destination_account": "{}",
"ledger_hash": "{}",
"credentials": ["{}"]
}})",
Account,
Account2,
LedgerHash,
ripple::strHex(credentialIndex)
));
runSpawn([&, this](auto yield) {
auto const handler = AnyHandler{DepositAuthorizedHandler{backend}};
auto const output = handler.process(input, Context{yield});
ASSERT_TRUE(output);
EXPECT_EQ(*output.result, json::parse(expectedOut));
});
}
TEST_F(RPCDepositAuthorizedTest, CredentialNotAuthorizedReturnsFalse)
{
backend->setRange(10, 30);
auto ledgerHeader = CreateLedgerHeader(LedgerHash, 30);
EXPECT_CALL(*backend, fetchLedgerByHash(ripple::uint256{LedgerHash}, _)).WillOnce(Return(ledgerHeader));
auto const account1Root = CreateAccountRootObject(Account, 0, 2, 200, 2, Index1, 2);
auto const account2Root = CreateAccountRootObject(Account2, ripple::lsfDepositAuth, 2, 200, 2, Index2, 2);
auto const credential = CreateCredentialObject(Account, Account2, CredentialType, false);
auto const credentialIndex = ripple::keylet::credential(
GetAccountIDWithString(Account),
GetAccountIDWithString(Account2),
ripple::Slice(CredentialType.data(), CredentialType.size())
)
.key;
ON_CALL(*backend, doFetchLedgerObject(_, _, _)).WillByDefault(Return(std::optional<Blob>{{1, 2, 3}}));
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account)).key, _, _))
.WillByDefault(Return(account1Root.getSerializer().peekData()));
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account2)).key, _, _))
.WillByDefault(Return(account2Root.getSerializer().peekData()));
ON_CALL(*backend, doFetchLedgerObject(credentialIndex, _, _))
.WillByDefault(Return(credential.getSerializer().peekData()));
EXPECT_CALL(*backend, doFetchLedgerObject).Times(3);
auto const input = json::parse(fmt::format(
R"({{
"source_account": "{}",
"destination_account": "{}",
"ledger_hash": "{}",
"credentials": ["{}"]
}})",
Account,
Account2,
LedgerHash,
ripple::strHex(credentialIndex)
));
runSpawn([&, this](auto yield) {
auto const handler = AnyHandler{DepositAuthorizedHandler{backend}};
auto const output = handler.process(input, Context{yield});
ASSERT_FALSE(output);
auto const err = rpc::makeError(output.result.error());
EXPECT_EQ(err.at("error").as_string(), "badCredentials");
EXPECT_EQ(err.at("error_message").as_string(), "credentials aren't accepted");
});
}
TEST_F(RPCDepositAuthorizedTest, CredentialExpiredReturnsFalse)
{
backend->setRange(10, 30);
auto ledgerHeader = CreateLedgerHeader(LedgerHash, 30, 100);
// set parent close time to 500 seconds
ledgerHeader.parentCloseTime = ripple::NetClock::time_point{std::chrono::seconds{500}};
EXPECT_CALL(*backend, fetchLedgerByHash(ripple::uint256{LedgerHash}, _)).WillOnce(Return(ledgerHeader));
auto const account1Root = CreateAccountRootObject(Account, 0, 2, 200, 2, Index1, 2);
auto const account2Root = CreateAccountRootObject(Account2, ripple::lsfDepositAuth, 2, 200, 2, Index2, 2);
// credential expire time is 23 seconds, so credential will fail
auto const expiredCredential = CreateCredentialObject(Account, Account2, CredentialType, true, 23);
auto const credentialIndex = ripple::keylet::credential(
GetAccountIDWithString(Account),
GetAccountIDWithString(Account2),
ripple::Slice(CredentialType.data(), CredentialType.size())
)
.key;
ON_CALL(*backend, doFetchLedgerObject(_, _, _)).WillByDefault(Return(std::optional<Blob>{{1, 2, 3}}));
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account)).key, _, _))
.WillByDefault(Return(account1Root.getSerializer().peekData()));
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account2)).key, _, _))
.WillByDefault(Return(account2Root.getSerializer().peekData()));
ON_CALL(*backend, doFetchLedgerObject(credentialIndex, _, _))
.WillByDefault(Return(expiredCredential.getSerializer().peekData()));
EXPECT_CALL(*backend, doFetchLedgerObject).Times(3);
auto const input = json::parse(fmt::format(
R"({{
"source_account": "{}",
"destination_account": "{}",
"ledger_hash": "{}",
"credentials": ["{}"]
}})",
Account,
Account2,
LedgerHash,
ripple::strHex(credentialIndex)
));
runSpawn([&, this](auto yield) {
auto const handler = AnyHandler{DepositAuthorizedHandler{backend}};
auto const output = handler.process(input, Context{yield});
ASSERT_FALSE(output);
auto const err = rpc::makeError(output.result.error());
EXPECT_EQ(err.at("error").as_string(), "badCredentials");
EXPECT_EQ(err.at("error_message").as_string(), "credentials are expired");
});
}
TEST_F(RPCDepositAuthorizedTest, DuplicateCredentialsReturnsFalse)
{
backend->setRange(10, 30);
auto ledgerHeader = CreateLedgerHeader(LedgerHash, 30, 34);
EXPECT_CALL(*backend, fetchLedgerByHash(ripple::uint256{LedgerHash}, _)).WillOnce(Return(ledgerHeader));
auto const account1Root = CreateAccountRootObject(Account, 0, 2, 200, 2, Index1, 2);
auto const account2Root = CreateAccountRootObject(Account2, ripple::lsfDepositAuth, 2, 200, 2, Index2, 2);
auto const credential = CreateCredentialObject(Account, Account2, CredentialType);
auto const credentialIndex = ripple::keylet::credential(
GetAccountIDWithString(Account),
GetAccountIDWithString(Account2),
ripple::Slice(CredentialType.data(), CredentialType.size())
)
.key;
ON_CALL(*backend, doFetchLedgerObject(_, _, _)).WillByDefault(Return(std::optional<Blob>{{1, 2, 3}}));
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account)).key, _, _))
.WillByDefault(Return(account1Root.getSerializer().peekData()));
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account2)).key, _, _))
.WillByDefault(Return(account2Root.getSerializer().peekData()));
ON_CALL(*backend, doFetchLedgerObject(credentialIndex, _, _))
.WillByDefault(Return(credential.getSerializer().peekData()));
EXPECT_CALL(*backend, doFetchLedgerObject).Times(3);
auto const input = json::parse(fmt::format(
R"({{
"source_account": "{}",
"destination_account": "{}",
"ledger_hash": "{}",
"credentials": ["{}", "{}"]
}})",
Account,
Account2,
LedgerHash,
ripple::strHex(credentialIndex),
ripple::strHex(credentialIndex)
));
runSpawn([&, this](auto yield) {
auto const handler = AnyHandler{DepositAuthorizedHandler{backend}};
auto const output = handler.process(input, Context{yield});
ASSERT_FALSE(output);
auto const err = rpc::makeError(output.result.error());
EXPECT_EQ(err.at("error").as_string(), "badCredentials");
EXPECT_EQ(err.at("error_message").as_string(), "duplicates in credentials.");
});
}
TEST_F(RPCDepositAuthorizedTest, NoElementsInCredentialsReturnsFalse)
{
backend->setRange(10, 30);
auto ledgerHeader = CreateLedgerHeader(LedgerHash, 30, 34);
EXPECT_CALL(*backend, fetchLedgerByHash(ripple::uint256{LedgerHash}, _)).WillOnce(Return(ledgerHeader));
auto const account1Root = CreateAccountRootObject(Account, 0, 2, 200, 2, Index1, 2);
auto const account2Root = CreateAccountRootObject(Account2, ripple::lsfDepositAuth, 2, 200, 2, Index2, 2);
ON_CALL(*backend, doFetchLedgerObject(_, _, _)).WillByDefault(Return(std::optional<Blob>{{1, 2, 3}}));
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account)).key, _, _))
.WillByDefault(Return(account1Root.getSerializer().peekData()));
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account2)).key, _, _))
.WillByDefault(Return(account2Root.getSerializer().peekData()));
EXPECT_CALL(*backend, doFetchLedgerObject).Times(2);
auto const input = json::parse(fmt::format(
R"({{
"source_account": "{}",
"destination_account": "{}",
"ledger_hash": "{}",
"credentials": []
}})",
Account,
Account2,
LedgerHash
));
runSpawn([&, this](auto yield) {
auto const handler = AnyHandler{DepositAuthorizedHandler{backend}};
auto const output = handler.process(input, Context{yield});
ASSERT_FALSE(output);
auto const err = rpc::makeError(output.result.error());
EXPECT_EQ(err.at("error").as_string(), "invalidParams");
EXPECT_EQ(err.at("error_message").as_string(), "credential array has no elements.");
});
}
TEST_F(RPCDepositAuthorizedTest, MoreThanMaxNumberOfCredentialsReturnsFalse)
{
backend->setRange(10, 30);
auto ledgerHeader = CreateLedgerHeader(LedgerHash, 30, 34);
EXPECT_CALL(*backend, fetchLedgerByHash(ripple::uint256{LedgerHash}, _)).WillOnce(Return(ledgerHeader));
auto const account1Root = CreateAccountRootObject(Account, 0, 2, 200, 2, Index1, 2);
auto const account2Root = CreateAccountRootObject(Account2, ripple::lsfDepositAuth, 2, 200, 2, Index2, 2);
auto const credential = CreateCredentialObject(Account, Account2, CredentialType);
auto const credentialIndex = ripple::keylet::credential(
GetAccountIDWithString(Account),
GetAccountIDWithString(Account2),
ripple::Slice(CredentialType.data(), CredentialType.size())
)
.key;
ON_CALL(*backend, doFetchLedgerObject(_, _, _)).WillByDefault(Return(std::optional<Blob>{{1, 2, 3}}));
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account)).key, _, _))
.WillByDefault(Return(account1Root.getSerializer().peekData()));
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account2)).key, _, _))
.WillByDefault(Return(account2Root.getSerializer().peekData()));
ON_CALL(*backend, doFetchLedgerObject(credentialIndex, _, _))
.WillByDefault(Return(credential.getSerializer().peekData()));
EXPECT_CALL(*backend, doFetchLedgerObject).Times(2);
std::vector<std::string> credentials(9, ripple::strHex(credentialIndex));
auto const input = json::parse(fmt::format(
R"({{
"source_account": "{}",
"destination_account": "{}",
"ledger_hash": "{}",
"credentials": [{}]
}})",
Account,
Account2,
LedgerHash,
fmt::join(
credentials | std::views::transform([](std::string const& cred) { return fmt::format("\"{}\"", cred); }),
", "
)
));
runSpawn([&, this](auto yield) {
auto const handler = AnyHandler{DepositAuthorizedHandler{backend}};
auto const output = handler.process(input, Context{yield});
ASSERT_FALSE(output);
auto const err = rpc::makeError(output.result.error());
EXPECT_EQ(err.at("error").as_string(), "invalidParams");
EXPECT_EQ(err.at("error_message").as_string(), "credential array too long.");
});
}
TEST_F(RPCDepositAuthorizedTest, DifferenSubjectAccountForCredentialReturnsFalse)
{
backend->setRange(10, 30);
auto ledgerHeader = CreateLedgerHeader(LedgerHash, 30);
EXPECT_CALL(*backend, fetchLedgerByHash(ripple::uint256{LedgerHash}, _)).WillOnce(Return(ledgerHeader));
auto const account1Root = CreateAccountRootObject(Account, 0, 2, 200, 2, Index1, 2);
auto const account2Root = CreateAccountRootObject(Account2, ripple::lsfDepositAuth, 2, 200, 2, Index2, 2);
// reverse the subject and issuer account. Now subject is ACCOUNT2
auto const credential = CreateCredentialObject(Account2, Account, CredentialType);
auto const credentialIndex = ripple::keylet::credential(
GetAccountIDWithString(Account2),
GetAccountIDWithString(Account),
ripple::Slice(CredentialType.data(), CredentialType.size())
)
.key;
ON_CALL(*backend, doFetchLedgerObject(_, _, _)).WillByDefault(Return(std::optional<Blob>{{1, 2, 3}}));
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account)).key, _, _))
.WillByDefault(Return(account1Root.getSerializer().peekData()));
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account2)).key, _, _))
.WillByDefault(Return(account2Root.getSerializer().peekData()));
ON_CALL(*backend, doFetchLedgerObject(credentialIndex, _, _))
.WillByDefault(Return(credential.getSerializer().peekData()));
EXPECT_CALL(*backend, doFetchLedgerObject).Times(3);
auto const input = json::parse(fmt::format(
R"({{
"source_account": "{}",
"destination_account": "{}",
"ledger_hash": "{}",
"credentials": ["{}"]
}})",
Account,
Account2,
LedgerHash,
ripple::strHex(credentialIndex)
));
runSpawn([&, this](auto yield) {
auto const handler = AnyHandler{DepositAuthorizedHandler{backend}};
auto const output = handler.process(input, Context{yield});
ASSERT_FALSE(output);
auto const err = rpc::makeError(output.result.error());
EXPECT_EQ(err.at("error").as_string(), "badCredentials");
EXPECT_EQ(err.at("error_message").as_string(), "credentials don't belong to the root account");
});
}

View File

@@ -18,6 +18,7 @@
//============================================================================== //==============================================================================
#include "data/Types.hpp" #include "data/Types.hpp"
#include "rpc/CredentialHelpers.hpp"
#include "rpc/Errors.hpp" #include "rpc/Errors.hpp"
#include "rpc/common/AnyHandler.hpp" #include "rpc/common/AnyHandler.hpp"
#include "rpc/common/Types.hpp" #include "rpc/common/Types.hpp"
@@ -29,6 +30,8 @@
#include <boost/asio/executor_work_guard.hpp> #include <boost/asio/executor_work_guard.hpp>
#include <boost/asio/io_context.hpp> #include <boost/asio/io_context.hpp>
#include <boost/asio/spawn.hpp> #include <boost/asio/spawn.hpp>
#include <boost/json/array.hpp>
#include <boost/json/object.hpp>
#include <boost/json/parse.hpp> #include <boost/json/parse.hpp>
#include <boost/json/value.hpp> #include <boost/json/value.hpp>
#include <boost/json/value_to.hpp> #include <boost/json/value_to.hpp>
@@ -36,6 +39,8 @@
#include <gmock/gmock.h> #include <gmock/gmock.h>
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <xrpl/basics/Blob.h> #include <xrpl/basics/Blob.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/StringUtilities.h>
#include <xrpl/basics/base_uint.h> #include <xrpl/basics/base_uint.h>
#include <xrpl/basics/strHex.h> #include <xrpl/basics/strHex.h>
#include <xrpl/protocol/AccountID.h> #include <xrpl/protocol/AccountID.h>
@@ -49,6 +54,7 @@
#include <cstdint> #include <cstdint>
#include <optional> #include <optional>
#include <string> #include <string>
#include <string_view>
#include <utility> #include <utility>
#include <vector> #include <vector>
@@ -66,6 +72,7 @@ constexpr static auto LEDGERHASH = "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A
constexpr static auto TOKENID = "000827103B94ECBB7BF0A0A6ED62B3607801A27B65F4679F4AD1D4850000C0EA"; constexpr static auto TOKENID = "000827103B94ECBB7BF0A0A6ED62B3607801A27B65F4679F4AD1D4850000C0EA";
constexpr static auto NFTID = "00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004"; constexpr static auto NFTID = "00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004";
constexpr static auto TXNID = "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD"; constexpr static auto TXNID = "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD";
constexpr static auto CREDENTIALTYPE = "4B5943";
class RPCLedgerEntryTest : public HandlerBaseTest {}; class RPCLedgerEntryTest : public HandlerBaseTest {};
@@ -200,6 +207,206 @@ generateTestValuesForParametersTest()
"authorizedNotString" "authorizedNotString"
}, },
ParamTestCaseBundle{
"InvalidDepositPreauthJsonAuthorizeCredentialsNotArray",
fmt::format(
R"({{
"deposit_preauth": {{
"owner": "{}",
"authorized_credentials": "asdf"
}}
}})",
ACCOUNT
),
"malformedRequest",
"authorized_credentials not array"
},
ParamTestCaseBundle{
"DepositPreauthBothAuthAndAuthCredentialsDoesNotExists",
fmt::format(
R"({{
"deposit_preauth": {{
"owner": "{}"
}}
}})",
ACCOUNT
),
"malformedRequest",
"Must have one of authorized or authorized_credentials."
},
ParamTestCaseBundle{
"DepositPreauthBothAuthAndAuthCredentialsExists",
fmt::format(
R"({{
"deposit_preauth": {{
"owner": "{}",
"authorized": "{}",
"authorized_credentials": [
{{
"issuer": "{}",
"credential_type": "{}"
}}
]
}}
}})",
ACCOUNT,
ACCOUNT2,
ACCOUNT3,
CREDENTIALTYPE
),
"malformedRequest",
"Must have one of authorized or authorized_credentials."
},
ParamTestCaseBundle{
"DepositPreauthEmptyAuthorizeCredentials",
fmt::format(
R"({{
"deposit_preauth": {{
"owner": "{}",
"authorized_credentials": [
]
}}
}})",
ACCOUNT
),
"malformedAuthorizedCredentials",
"Requires at least one element in authorized_credentials array"
},
ParamTestCaseBundle{
"DepositPreauthAuthorizeCredentialsMissingCredentialType",
fmt::format(
R"({{
"deposit_preauth": {{
"owner": "{}",
"authorized_credentials": [
{{
"issuer": "{}"
}}
]
}}
}})",
ACCOUNT,
ACCOUNT2
),
"malformedRequest",
"Field 'CredentialType' is required but missing."
},
ParamTestCaseBundle{
"DepositPreauthAuthorizeCredentialsMissingIssuer",
fmt::format(
R"({{
"deposit_preauth": {{
"owner": "{}",
"authorized_credentials": [
{{
"credential_type": "{}"
}}
]
}}
}})",
ACCOUNT,
CREDENTIALTYPE
),
"malformedRequest",
"Field 'Issuer' is required but missing."
},
ParamTestCaseBundle{
"DepositPreauthAuthorizeCredentialsIncorrectCredentialType",
fmt::format(
R"({{
"deposit_preauth": {{
"owner": "{}",
"authorized_credentials": [
{{
"issuer": "{}",
"credential_type": 432
}}
]
}}
}})",
ACCOUNT,
ACCOUNT2
),
"invalidParams",
"credential_type NotString"
},
ParamTestCaseBundle{
"DepositPreauthAuthorizeCredentialsCredentialTypeNotHex",
fmt::format(
R"({{
"deposit_preauth": {{
"owner": "{}",
"authorized_credentials": [
{{
"issuer": "{}",
"credential_type": "hello world"
}}
]
}}
}})",
ACCOUNT,
ACCOUNT2
),
"malformedAuthorizedCredentials",
"credential_type NotHexString"
},
ParamTestCaseBundle{
"DepositPreauthAuthorizeCredentialsCredentialTypeEmpty",
fmt::format(
R"({{
"deposit_preauth": {{
"owner": "{}",
"authorized_credentials": [
{{
"issuer": "{}",
"credential_type": ""
}}
]
}}
}})",
ACCOUNT,
ACCOUNT2
),
"malformedAuthorizedCredentials",
"credential_type is empty"
},
ParamTestCaseBundle{
"DepositPreauthDuplicateAuthorizeCredentials",
fmt::format(
R"({{
"deposit_preauth": {{
"owner": "{}",
"authorized_credentials": [
{{
"issuer": "{}",
"credential_type": "{}"
}},
{{
"issuer": "{}",
"credential_type": "{}"
}}
]
}}
}})",
ACCOUNT,
ACCOUNT2,
CREDENTIALTYPE,
ACCOUNT2,
CREDENTIALTYPE
),
"malformedAuthorizedCredentials",
"duplicates in credentials."
},
ParamTestCaseBundle{ ParamTestCaseBundle{
"InvalidTicketType", "InvalidTicketType",
R"({ R"({
@@ -1759,6 +1966,29 @@ generateTestValuesForParametersTest()
"malformedRequest", "malformedRequest",
"Malformed request." "Malformed request."
}, },
ParamTestCaseBundle{
"CredentialInvalidSubjectType",
R"({
"credential": {
"subject": 123
}
})",
"malformedAddress",
"Malformed address."
},
ParamTestCaseBundle{
"CredentialInvalidIssuerType",
fmt::format(
R"({{
"credential": {{
"issuer": ["{}"]
}}
}})",
ACCOUNT
),
"malformedRequest",
"Malformed request."
},
ParamTestCaseBundle{ ParamTestCaseBundle{
"InvalidMPTIssuanceStringIndex", "InvalidMPTIssuanceStringIndex",
R"({ R"({
@@ -1806,6 +2036,37 @@ generateTestValuesForParametersTest()
"malformedRequest", "malformedRequest",
"Malformed request." "Malformed request."
}, },
ParamTestCaseBundle{
"CredentialInvalidCredentialType",
fmt::format(
R"({{
"credential": {{
"subject": "{}",
"issuer": "{}",
"credential_type": 1234
}}
}})",
ACCOUNT,
ACCOUNT2
),
"malformedRequest",
"Malformed request."
},
ParamTestCaseBundle{
"CredentialMissingIssuerField",
fmt::format(
R"({{
"credential": {{
"subject": "{}",
"credential_type": "1234"
}}
}})",
ACCOUNT,
ACCOUNT2
),
"malformedRequest",
"Malformed request."
},
ParamTestCaseBundle{ ParamTestCaseBundle{
"InvalidMPTokenAccount", "InvalidMPTokenAccount",
fmt::format( fmt::format(
@@ -1828,7 +2089,7 @@ generateTestValuesForParametersTest()
), ),
"malformedRequest", "malformedRequest",
"Malformed request." "Malformed request."
}, }
}; };
} }
@@ -2068,7 +2329,7 @@ generateTestValuesForNormalPathTest()
INDEX1 INDEX1
), ),
ripple::uint256{INDEX1}, ripple::uint256{INDEX1},
CreateDepositPreauthLedgerObject(ACCOUNT, ACCOUNT2) CreateDepositPreauthLedgerObjectByAuth(ACCOUNT, ACCOUNT2)
}, },
NormalPathTestBundle{ NormalPathTestBundle{
"AccountRoot", "AccountRoot",
@@ -2155,7 +2416,7 @@ generateTestValuesForNormalPathTest()
CreateEscrowLedgerObject(ACCOUNT, ACCOUNT2) CreateEscrowLedgerObject(ACCOUNT, ACCOUNT2)
}, },
NormalPathTestBundle{ NormalPathTestBundle{
"DepositPreauth", "DepositPreauthByAuth",
fmt::format( fmt::format(
R"({{ R"({{
"binary": true, "binary": true,
@@ -2168,7 +2429,58 @@ generateTestValuesForNormalPathTest()
ACCOUNT2 ACCOUNT2
), ),
ripple::keylet::depositPreauth(account1, account2).key, ripple::keylet::depositPreauth(account1, account2).key,
CreateDepositPreauthLedgerObject(ACCOUNT, ACCOUNT2) CreateDepositPreauthLedgerObjectByAuth(ACCOUNT, ACCOUNT2)
},
NormalPathTestBundle{
"DepositPreauthByAuthCredentials",
fmt::format(
R"({{
"binary": true,
"deposit_preauth": {{
"owner": "{}",
"authorized_credentials": [
{{
"issuer": "{}",
"credential_type": "{}"
}}
]
}}
}})",
ACCOUNT,
ACCOUNT2,
CREDENTIALTYPE
),
ripple::keylet::depositPreauth(
account1,
credentials::createAuthCredentials(CreateAuthCredentialArray(
std::vector<std::string_view>{ACCOUNT2}, std::vector<std::string_view>{CREDENTIALTYPE}
))
)
.key,
CreateDepositPreauthLedgerObjectByAuthCredentials(ACCOUNT, ACCOUNT2, CREDENTIALTYPE)
},
NormalPathTestBundle{
"Credentials",
fmt::format(
R"({{
"binary": true,
"credential": {{
"subject": "{}",
"issuer": "{}",
"credential_type": "{}"
}}
}})",
ACCOUNT,
ACCOUNT2,
CREDENTIALTYPE
),
ripple::keylet::credential(
account1,
account2,
ripple::Slice(ripple::strUnHex(CREDENTIALTYPE)->data(), ripple::strUnHex(CREDENTIALTYPE)->size())
)
.key,
CreateCredentialObject(ACCOUNT, ACCOUNT2, CREDENTIALTYPE)
}, },
NormalPathTestBundle{ NormalPathTestBundle{
"RippleState", "RippleState",

View File

@@ -55,6 +55,7 @@ TEST(LedgerUtilsTests, LedgerObjectTypeList)
JS(mpt_issuance), JS(mpt_issuance),
JS(mptoken), JS(mptoken),
JS(oracle), JS(oracle),
JS(credential),
JS(nunl) JS(nunl)
}; };
@@ -86,6 +87,7 @@ TEST(LedgerUtilsTests, AccountOwnedTypeList)
JS(xchain_owned_create_account_claim_id), JS(xchain_owned_create_account_claim_id),
JS(did), JS(did),
JS(oracle), JS(oracle),
JS(credential),
JS(mpt_issuance), JS(mpt_issuance),
JS(mptoken) JS(mptoken)
}; };