mirror of
https://github.com/XRPLF/clio.git
synced 2025-11-22 04:35:50 +00:00
feat: Add Support Credentials for Clio (#1712)
Rippled PR: [here](https://github.com/XRPLF/rippled/pull/5103)
This commit is contained in:
@@ -24,6 +24,7 @@
|
||||
#include "etl/NetworkValidatedLedgersInterface.hpp"
|
||||
#include "etl/Source.hpp"
|
||||
#include "feed/SubscriptionManagerInterface.hpp"
|
||||
#include "rpc/Errors.hpp"
|
||||
#include "util/Mutex.hpp"
|
||||
#include "util/ResponseExpirationCache.hpp"
|
||||
#include "util/config/Config.hpp"
|
||||
|
||||
@@ -6,6 +6,7 @@ target_sources(
|
||||
Factories.cpp
|
||||
AMMHelpers.cpp
|
||||
RPCHelpers.cpp
|
||||
CredentialHelpers.cpp
|
||||
Counters.cpp
|
||||
WorkQueue.cpp
|
||||
common/Specs.cpp
|
||||
|
||||
162
src/rpc/CredentialHelpers.cpp
Normal file
162
src/rpc/CredentialHelpers.cpp
Normal 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
|
||||
89
src/rpc/CredentialHelpers.hpp
Normal file
89
src/rpc/CredentialHelpers.hpp
Normal 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
|
||||
@@ -83,6 +83,9 @@ getErrorInfo(ClioError code)
|
||||
{ClioError::rpcUNKNOWN_OPTION, "unknownOption", "Unknown option."},
|
||||
{ClioError::rpcFIELD_NOT_FOUND_TRANSACTION, "fieldNotFoundTransaction", "Missing field."},
|
||||
{ClioError::rpcMALFORMED_ORACLE_DOCUMENT_ID, "malformedDocumentID", "Malformed oracle_document_id."},
|
||||
{ClioError::rpcMALFORMED_AUTHORIZED_CREDENTIALS,
|
||||
"malformedAuthorizedCredentials",
|
||||
"Malformed authorized credentials."},
|
||||
// special system errors
|
||||
{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."},
|
||||
|
||||
@@ -43,6 +43,7 @@ enum class ClioError {
|
||||
rpcUNKNOWN_OPTION = 5005,
|
||||
rpcFIELD_NOT_FOUND_TRANSACTION = 5006,
|
||||
rpcMALFORMED_ORACLE_DOCUMENT_ID = 5007,
|
||||
rpcMALFORMED_AUTHORIZED_CREDENTIALS = 5008,
|
||||
|
||||
// special system errors start with 6000
|
||||
rpcINVALID_API_VERSION = 6000,
|
||||
|
||||
@@ -29,8 +29,10 @@
|
||||
#include <boost/json/value.hpp>
|
||||
#include <boost/json/value_to.hpp>
|
||||
#include <fmt/core.h>
|
||||
#include <xrpl/basics/StringUtilities.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
|
||||
#include <charconv>
|
||||
@@ -253,4 +255,67 @@ CustomValidator CustomValidators::CurrencyIssueValidator =
|
||||
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
|
||||
|
||||
@@ -27,15 +27,13 @@
|
||||
#include <boost/json/object.hpp>
|
||||
#include <boost/json/value.hpp>
|
||||
#include <fmt/core.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
|
||||
#include <concepts>
|
||||
#include <cstdint>
|
||||
#include <ctime>
|
||||
#include <functional>
|
||||
#include <initializer_list>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
@@ -153,7 +151,7 @@ struct Type final {
|
||||
verify(boost::json::value const& value, std::string_view key) const
|
||||
{
|
||||
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 convertible = (checkType<Types>(res) || ...);
|
||||
@@ -559,6 +557,51 @@ struct CustomValidators final {
|
||||
* Used by amm_info.
|
||||
*/
|
||||
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
|
||||
|
||||
@@ -19,25 +19,34 @@
|
||||
|
||||
#include "rpc/handlers/DepositAuthorized.hpp"
|
||||
|
||||
#include "rpc/CredentialHelpers.hpp"
|
||||
#include "rpc/Errors.hpp"
|
||||
#include "rpc/JS.hpp"
|
||||
#include "rpc/RPCHelpers.hpp"
|
||||
#include "rpc/common/Types.hpp"
|
||||
#include "util/Assert.hpp"
|
||||
|
||||
#include <boost/json/array.hpp>
|
||||
#include <boost/json/conversion.hpp>
|
||||
#include <boost/json/object.hpp>
|
||||
#include <boost/json/value.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/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/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
namespace rpc {
|
||||
@@ -71,26 +80,55 @@ DepositAuthorizedHandler::process(DepositAuthorizedHandler::Input input, Context
|
||||
|
||||
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.destinationAccount = input.destinationAccount;
|
||||
response.ledgerHash = ripple::strHex(lgrInfo.hash);
|
||||
response.ledgerIndex = lgrInfo.seq;
|
||||
|
||||
// If the two accounts are the same, then the deposit should be fine.
|
||||
if (sourceAccountID != destinationAccountID) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
response.depositAuthorized = depositAuthorized;
|
||||
if (credentialsPresent)
|
||||
response.credentials = input.credentials.value();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -127,8 +169,10 @@ tag_invoke(boost::json::value_from_tag, boost::json::value& jv, DepositAuthorize
|
||||
{JS(destination_account), output.destinationAccount},
|
||||
{JS(ledger_hash), output.ledgerHash},
|
||||
{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
|
||||
|
||||
@@ -25,8 +25,10 @@
|
||||
#include "rpc/common/Types.hpp"
|
||||
#include "rpc/common/Validators.hpp"
|
||||
|
||||
#include <boost/json/array.hpp>
|
||||
#include <boost/json/conversion.hpp>
|
||||
#include <boost/json/value.hpp>
|
||||
#include <xrpl/protocol/STArray.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
#include <cstdint>
|
||||
@@ -59,6 +61,8 @@ public:
|
||||
std::string destinationAccount;
|
||||
std::string ledgerHash;
|
||||
uint32_t ledgerIndex{};
|
||||
std::optional<boost::json::array> credentials;
|
||||
|
||||
// validated should be sent via framework
|
||||
bool validated = true;
|
||||
};
|
||||
@@ -71,6 +75,7 @@ public:
|
||||
std::string destinationAccount;
|
||||
std::optional<std::string> ledgerHash;
|
||||
std::optional<uint32_t> ledgerIndex;
|
||||
std::optional<boost::json::array> credentials;
|
||||
};
|
||||
|
||||
using Result = HandlerReturnType<Output>;
|
||||
@@ -99,6 +104,7 @@ public:
|
||||
{JS(destination_account), validation::Required{}, validation::CustomValidators::AccountValidator},
|
||||
{JS(ledger_hash), validation::CustomValidators::Uint256HexStringValidator},
|
||||
{JS(ledger_index), validation::CustomValidators::LedgerIndexValidator},
|
||||
{JS(credentials), validation::Type<boost::json::array>{}, validation::Hex256ItemType()}
|
||||
};
|
||||
|
||||
return rpcSpec;
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include "rpc/handlers/LedgerEntry.hpp"
|
||||
|
||||
#include "rpc/CredentialHelpers.hpp"
|
||||
#include "rpc/Errors.hpp"
|
||||
#include "rpc/JS.hpp"
|
||||
#include "rpc/RPCHelpers.hpp"
|
||||
@@ -30,6 +31,8 @@
|
||||
#include <boost/json/object.hpp>
|
||||
#include <boost/json/value.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/strHex.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
@@ -38,6 +41,7 @@
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/LedgerHeader.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.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>(
|
||||
boost::json::value_to<std::string>(input.depositPreauth->at(JS(owner)))
|
||||
);
|
||||
// Only one of authorize or authorized_credentials MUST exist;
|
||||
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."}
|
||||
};
|
||||
}
|
||||
|
||||
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) {
|
||||
auto const id =
|
||||
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) {
|
||||
key = input.oracleNode.value();
|
||||
} else if (input.credential) {
|
||||
key = input.credential.value();
|
||||
} else if (input.mptIssuance) {
|
||||
auto const mptIssuanceID = ripple::uint192{std::string_view(*(input.mptIssuance))};
|
||||
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_claim_id), ripple::ltXCHAIN_OWNED_CLAIM_ID},
|
||||
{JS(oracle), ripple::ltORACLE},
|
||||
{JS(credential), ripple::ltCREDENTIAL},
|
||||
{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;
|
||||
};
|
||||
|
||||
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 =
|
||||
std::find_if(indexFieldTypeMap.begin(), indexFieldTypeMap.end(), [&jsonObject](auto const& 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))) {
|
||||
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))) {
|
||||
input.mptoken = jv.at(JS(mptoken)).as_object();
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "rpc/common/Validators.hpp"
|
||||
#include "util/AccountUtils.hpp"
|
||||
|
||||
#include <boost/json/array.hpp>
|
||||
#include <boost/json/conversion.hpp>
|
||||
#include <boost/json/object.hpp>
|
||||
#include <boost/json/value.hpp>
|
||||
@@ -107,6 +108,7 @@ public:
|
||||
std::optional<uint32_t> chainClaimId;
|
||||
std::optional<uint32_t> createAccountClaimId;
|
||||
std::optional<ripple::uint256> oracleNode;
|
||||
std::optional<ripple::uint256> credential;
|
||||
bool includeDeleted = false;
|
||||
};
|
||||
|
||||
@@ -197,7 +199,8 @@ public:
|
||||
meta::WithCustomError{
|
||||
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),
|
||||
@@ -318,6 +321,30 @@ public:
|
||||
},
|
||||
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),
|
||||
meta::WithCustomError{
|
||||
validation::CustomValidators::Uint192HexStringValidator, Status(ClioError::rpcMALFORMED_REQUEST)
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/StringUtilities.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/tokens.h>
|
||||
|
||||
#include <cctype>
|
||||
|
||||
@@ -112,6 +112,7 @@ class LedgerTypes {
|
||||
),
|
||||
LedgerTypeAttribute::AccountOwnedLedgerType(JS(did), ripple::ltDID),
|
||||
LedgerTypeAttribute::AccountOwnedLedgerType(JS(oracle), ripple::ltORACLE),
|
||||
LedgerTypeAttribute::AccountOwnedLedgerType(JS(credential), ripple::ltCREDENTIAL),
|
||||
LedgerTypeAttribute::ChainLedgerType(JS(nunl), ripple::ltNEGATIVE_UNL),
|
||||
LedgerTypeAttribute::DeletionBlockerLedgerType(JS(mpt_issuance), ripple::ltMPTOKEN_ISSUANCE),
|
||||
LedgerTypeAttribute::DeletionBlockerLedgerType(JS(mptoken), ripple::ltMPTOKEN),
|
||||
|
||||
@@ -90,6 +90,7 @@ public:
|
||||
case rpc::ClioError::rpcINVALID_HOT_WALLET:
|
||||
case rpc::ClioError::rpcFIELD_NOT_FOUND_TRANSACTION:
|
||||
case rpc::ClioError::rpcMALFORMED_ORACLE_DOCUMENT_ID:
|
||||
case rpc::ClioError::rpcMALFORMED_AUTHORIZED_CREDENTIALS:
|
||||
case rpc::ClioError::etlCONNECTION_ERROR:
|
||||
case rpc::ClioError::etlREQUEST_ERROR:
|
||||
case rpc::ClioError::etlREQUEST_TIMEOUT:
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
#include <xrpl/basics/Blob.h>
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/StringUtilities.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
@@ -48,6 +49,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
@@ -540,7 +542,7 @@ CreateCheckLedgerObject(std::string_view account, std::string_view dest)
|
||||
}
|
||||
|
||||
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);
|
||||
depositPreauth.setFieldU16(ripple::sfLedgerEntryType, ripple::ltDEPOSIT_PREAUTH);
|
||||
@@ -553,6 +555,27 @@ CreateDepositPreauthLedgerObject(std::string_view account, std::string_view auth
|
||||
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
|
||||
CreateNFT(std::string_view tokenID, std::string_view account, ripple::LedgerIndex seq, ripple::Blob uri, bool isBurned)
|
||||
{
|
||||
@@ -1192,3 +1215,47 @@ CreateOracleObject(
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "data/Types.hpp"
|
||||
|
||||
#include <xrpl/basics/Blob.h>
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/protocol/AccountID.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);
|
||||
|
||||
[[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
|
||||
CreateNFT(
|
||||
@@ -435,3 +443,15 @@ CreateOracleSetTxWithMetadata(
|
||||
bool created,
|
||||
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);
|
||||
|
||||
@@ -67,6 +67,7 @@ target_sources(
|
||||
rpc/handlers/AMMInfoTests.cpp
|
||||
rpc/handlers/BookChangesTests.cpp
|
||||
rpc/handlers/BookOffersTests.cpp
|
||||
rpc/handlers/CredentialHelpersTests.cpp
|
||||
rpc/handlers/DefaultProcessorTests.cpp
|
||||
rpc/handlers/DepositAuthorizedTests.cpp
|
||||
rpc/handlers/FeatureTests.cpp
|
||||
|
||||
@@ -30,10 +30,12 @@
|
||||
|
||||
#include <boost/asio/impl/spawn.hpp>
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/json/array.hpp>
|
||||
#include <boost/json/parse.hpp>
|
||||
#include <fmt/core.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <xrpl/basics/Blob.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
@@ -47,6 +49,7 @@
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
153
tests/unit/rpc/handlers/CredentialHelpersTests.cpp
Normal file
153
tests/unit/rpc/handlers/CredentialHelpersTests.cpp
Normal 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();
|
||||
}
|
||||
@@ -28,24 +28,34 @@
|
||||
|
||||
#include <boost/json/parse.hpp>
|
||||
#include <fmt/core.h>
|
||||
#include <fmt/format.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <xrpl/basics/Slice.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/LedgerFormats.h>
|
||||
#include <xrpl/protocol/STArray.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
#include <ranges>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
constexpr static auto ACCOUNT = "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn";
|
||||
constexpr static auto ACCOUNT2 = "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun";
|
||||
constexpr static auto LEDGERHASH = "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652";
|
||||
constexpr static auto INDEX1 = "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC";
|
||||
constexpr static auto INDEX2 = "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515B1";
|
||||
constexpr static auto Account = "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn";
|
||||
constexpr static auto Account2 = "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun";
|
||||
constexpr static auto LedgerHash = "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652";
|
||||
constexpr static auto Index1 = "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC";
|
||||
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 RANGEMAX = 30;
|
||||
constexpr static auto RangeMin = 10;
|
||||
constexpr static auto RangeMax = 30;
|
||||
|
||||
using namespace rpc;
|
||||
namespace json = boost::json;
|
||||
@@ -156,6 +166,38 @@ generateTestValuesForParametersTest()
|
||||
"invalidParams",
|
||||
"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)
|
||||
{
|
||||
backend->setRange(RANGEMIN, RANGEMAX);
|
||||
backend->setRange(RangeMin, RangeMax);
|
||||
|
||||
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) {
|
||||
auto const handler = AnyHandler{DepositAuthorizedHandler{backend}};
|
||||
@@ -197,9 +239,9 @@ TEST_F(RPCDepositAuthorizedTest, LedgerNotExistViaIntSequence)
|
||||
"destination_account": "{}",
|
||||
"ledger_index": {}
|
||||
}})",
|
||||
ACCOUNT,
|
||||
ACCOUNT2,
|
||||
RANGEMAX
|
||||
Account,
|
||||
Account2,
|
||||
RangeMax
|
||||
));
|
||||
|
||||
auto const output = handler.process(req, Context{yield});
|
||||
@@ -213,10 +255,10 @@ TEST_F(RPCDepositAuthorizedTest, LedgerNotExistViaIntSequence)
|
||||
|
||||
TEST_F(RPCDepositAuthorizedTest, LedgerNotExistViaStringSequence)
|
||||
{
|
||||
backend->setRange(RANGEMIN, RANGEMAX);
|
||||
backend->setRange(RangeMin, RangeMax);
|
||||
|
||||
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) {
|
||||
auto const handler = AnyHandler{DepositAuthorizedHandler{backend}};
|
||||
@@ -226,9 +268,9 @@ TEST_F(RPCDepositAuthorizedTest, LedgerNotExistViaStringSequence)
|
||||
"destination_account": "{}",
|
||||
"ledger_index": "{}"
|
||||
}})",
|
||||
ACCOUNT,
|
||||
ACCOUNT2,
|
||||
RANGEMAX
|
||||
Account,
|
||||
Account2,
|
||||
RangeMax
|
||||
));
|
||||
|
||||
auto const output = handler.process(req, Context{yield});
|
||||
@@ -242,10 +284,10 @@ TEST_F(RPCDepositAuthorizedTest, LedgerNotExistViaStringSequence)
|
||||
|
||||
TEST_F(RPCDepositAuthorizedTest, LedgerNotExistViaHash)
|
||||
{
|
||||
backend->setRange(RANGEMIN, RANGEMAX);
|
||||
backend->setRange(RangeMin, RangeMax);
|
||||
|
||||
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) {
|
||||
auto const handler = AnyHandler{DepositAuthorizedHandler{backend}};
|
||||
@@ -255,9 +297,9 @@ TEST_F(RPCDepositAuthorizedTest, LedgerNotExistViaHash)
|
||||
"destination_account": "{}",
|
||||
"ledger_hash": "{}"
|
||||
}})",
|
||||
ACCOUNT,
|
||||
ACCOUNT2,
|
||||
LEDGERHASH
|
||||
Account,
|
||||
Account2,
|
||||
LedgerHash
|
||||
));
|
||||
|
||||
auto const output = handler.process(req, Context{yield});
|
||||
@@ -273,9 +315,9 @@ TEST_F(RPCDepositAuthorizedTest, SourceAccountDoesNotExist)
|
||||
{
|
||||
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);
|
||||
|
||||
ON_CALL(*backend, doFetchLedgerObject).WillByDefault(Return(std::optional<Blob>{}));
|
||||
@@ -287,9 +329,9 @@ TEST_F(RPCDepositAuthorizedTest, SourceAccountDoesNotExist)
|
||||
"destination_account": "{}",
|
||||
"ledger_hash": "{}"
|
||||
}})",
|
||||
ACCOUNT,
|
||||
ACCOUNT2,
|
||||
LEDGERHASH
|
||||
Account,
|
||||
Account2,
|
||||
LedgerHash
|
||||
));
|
||||
|
||||
runSpawn([&, this](auto yield) {
|
||||
@@ -308,14 +350,14 @@ TEST_F(RPCDepositAuthorizedTest, DestinationAccountDoesNotExist)
|
||||
{
|
||||
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);
|
||||
|
||||
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(ripple::keylet::account(GetAccountIDWithString(ACCOUNT2)).key, _, _))
|
||||
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account2)).key, _, _))
|
||||
.WillByDefault(Return(std::optional<Blob>{}));
|
||||
|
||||
EXPECT_CALL(*backend, doFetchLedgerObject).Times(2);
|
||||
@@ -326,9 +368,9 @@ TEST_F(RPCDepositAuthorizedTest, DestinationAccountDoesNotExist)
|
||||
"destination_account": "{}",
|
||||
"ledger_hash": "{}"
|
||||
}})",
|
||||
ACCOUNT,
|
||||
ACCOUNT2,
|
||||
LEDGERHASH
|
||||
Account,
|
||||
Account2,
|
||||
LedgerHash
|
||||
));
|
||||
|
||||
runSpawn([&, this](auto yield) {
|
||||
@@ -357,12 +399,12 @@ TEST_F(RPCDepositAuthorizedTest, AccountsAreEqual)
|
||||
|
||||
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);
|
||||
|
||||
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()));
|
||||
EXPECT_CALL(*backend, doFetchLedgerObject).Times(2);
|
||||
|
||||
@@ -372,9 +414,9 @@ TEST_F(RPCDepositAuthorizedTest, AccountsAreEqual)
|
||||
"destination_account": "{}",
|
||||
"ledger_hash": "{}"
|
||||
}})",
|
||||
ACCOUNT,
|
||||
ACCOUNT,
|
||||
LEDGERHASH
|
||||
Account,
|
||||
Account,
|
||||
LedgerHash
|
||||
));
|
||||
|
||||
runSpawn([&, this](auto yield) {
|
||||
@@ -400,17 +442,17 @@ TEST_F(RPCDepositAuthorizedTest, DifferentAccountsNoDepositAuthFlag)
|
||||
|
||||
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);
|
||||
|
||||
auto const account1Root = CreateAccountRootObject(ACCOUNT, 0, 2, 200, 2, INDEX1, 2);
|
||||
auto const account2Root = CreateAccountRootObject(ACCOUNT2, 0, 2, 200, 2, INDEX2, 2);
|
||||
auto const account1Root = CreateAccountRootObject(Account, 0, 2, 200, 2, Index1, 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()));
|
||||
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()));
|
||||
EXPECT_CALL(*backend, doFetchLedgerObject).Times(2);
|
||||
|
||||
@@ -420,9 +462,9 @@ TEST_F(RPCDepositAuthorizedTest, DifferentAccountsNoDepositAuthFlag)
|
||||
"destination_account": "{}",
|
||||
"ledger_hash": "{}"
|
||||
}})",
|
||||
ACCOUNT,
|
||||
ACCOUNT2,
|
||||
LEDGERHASH
|
||||
Account,
|
||||
Account2,
|
||||
LedgerHash
|
||||
));
|
||||
|
||||
runSpawn([&, this](auto yield) {
|
||||
@@ -448,18 +490,18 @@ TEST_F(RPCDepositAuthorizedTest, DifferentAccountsWithDepositAuthFlagReturnsFals
|
||||
|
||||
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);
|
||||
|
||||
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 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::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()));
|
||||
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()));
|
||||
EXPECT_CALL(*backend, doFetchLedgerObject).Times(3);
|
||||
|
||||
@@ -469,9 +511,9 @@ TEST_F(RPCDepositAuthorizedTest, DifferentAccountsWithDepositAuthFlagReturnsFals
|
||||
"destination_account": "{}",
|
||||
"ledger_hash": "{}"
|
||||
}})",
|
||||
ACCOUNT,
|
||||
ACCOUNT2,
|
||||
LEDGERHASH
|
||||
Account,
|
||||
Account2,
|
||||
LedgerHash
|
||||
));
|
||||
|
||||
runSpawn([&, this](auto yield) {
|
||||
@@ -497,18 +539,18 @@ TEST_F(RPCDepositAuthorizedTest, DifferentAccountsWithDepositAuthFlagReturnsTrue
|
||||
|
||||
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);
|
||||
|
||||
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 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, _, _))
|
||||
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, _, _))
|
||||
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account2)).key, _, _))
|
||||
.WillByDefault(Return(account2Root.getSerializer().peekData()));
|
||||
EXPECT_CALL(*backend, doFetchLedgerObject).Times(3);
|
||||
|
||||
@@ -518,9 +560,9 @@ TEST_F(RPCDepositAuthorizedTest, DifferentAccountsWithDepositAuthFlagReturnsTrue
|
||||
"destination_account": "{}",
|
||||
"ledger_hash": "{}"
|
||||
}})",
|
||||
ACCOUNT,
|
||||
ACCOUNT2,
|
||||
LEDGERHASH
|
||||
Account,
|
||||
Account2,
|
||||
LedgerHash
|
||||
));
|
||||
|
||||
runSpawn([&, this](auto yield) {
|
||||
@@ -531,3 +573,380 @@ TEST_F(RPCDepositAuthorizedTest, DifferentAccountsWithDepositAuthFlagReturnsTrue
|
||||
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");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
//==============================================================================
|
||||
|
||||
#include "data/Types.hpp"
|
||||
#include "rpc/CredentialHelpers.hpp"
|
||||
#include "rpc/Errors.hpp"
|
||||
#include "rpc/common/AnyHandler.hpp"
|
||||
#include "rpc/common/Types.hpp"
|
||||
@@ -29,6 +30,8 @@
|
||||
#include <boost/asio/executor_work_guard.hpp>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/json/array.hpp>
|
||||
#include <boost/json/object.hpp>
|
||||
#include <boost/json/parse.hpp>
|
||||
#include <boost/json/value.hpp>
|
||||
#include <boost/json/value_to.hpp>
|
||||
@@ -36,6 +39,8 @@
|
||||
#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/base_uint.h>
|
||||
#include <xrpl/basics/strHex.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
@@ -49,6 +54,7 @@
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -66,6 +72,7 @@ constexpr static auto LEDGERHASH = "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A
|
||||
constexpr static auto TOKENID = "000827103B94ECBB7BF0A0A6ED62B3607801A27B65F4679F4AD1D4850000C0EA";
|
||||
constexpr static auto NFTID = "00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004";
|
||||
constexpr static auto TXNID = "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD";
|
||||
constexpr static auto CREDENTIALTYPE = "4B5943";
|
||||
|
||||
class RPCLedgerEntryTest : public HandlerBaseTest {};
|
||||
|
||||
@@ -200,6 +207,206 @@ generateTestValuesForParametersTest()
|
||||
"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{
|
||||
"InvalidTicketType",
|
||||
R"({
|
||||
@@ -1759,6 +1966,29 @@ generateTestValuesForParametersTest()
|
||||
"malformedRequest",
|
||||
"Malformed request."
|
||||
},
|
||||
ParamTestCaseBundle{
|
||||
"CredentialInvalidSubjectType",
|
||||
R"({
|
||||
"credential": {
|
||||
"subject": 123
|
||||
}
|
||||
})",
|
||||
"malformedAddress",
|
||||
"Malformed address."
|
||||
},
|
||||
ParamTestCaseBundle{
|
||||
"CredentialInvalidIssuerType",
|
||||
fmt::format(
|
||||
R"({{
|
||||
"credential": {{
|
||||
"issuer": ["{}"]
|
||||
}}
|
||||
}})",
|
||||
ACCOUNT
|
||||
),
|
||||
"malformedRequest",
|
||||
"Malformed request."
|
||||
},
|
||||
ParamTestCaseBundle{
|
||||
"InvalidMPTIssuanceStringIndex",
|
||||
R"({
|
||||
@@ -1806,6 +2036,37 @@ generateTestValuesForParametersTest()
|
||||
"malformedRequest",
|
||||
"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{
|
||||
"InvalidMPTokenAccount",
|
||||
fmt::format(
|
||||
@@ -1828,7 +2089,7 @@ generateTestValuesForParametersTest()
|
||||
),
|
||||
"malformedRequest",
|
||||
"Malformed request."
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2068,7 +2329,7 @@ generateTestValuesForNormalPathTest()
|
||||
INDEX1
|
||||
),
|
||||
ripple::uint256{INDEX1},
|
||||
CreateDepositPreauthLedgerObject(ACCOUNT, ACCOUNT2)
|
||||
CreateDepositPreauthLedgerObjectByAuth(ACCOUNT, ACCOUNT2)
|
||||
},
|
||||
NormalPathTestBundle{
|
||||
"AccountRoot",
|
||||
@@ -2155,7 +2416,7 @@ generateTestValuesForNormalPathTest()
|
||||
CreateEscrowLedgerObject(ACCOUNT, ACCOUNT2)
|
||||
},
|
||||
NormalPathTestBundle{
|
||||
"DepositPreauth",
|
||||
"DepositPreauthByAuth",
|
||||
fmt::format(
|
||||
R"({{
|
||||
"binary": true,
|
||||
@@ -2168,7 +2429,58 @@ generateTestValuesForNormalPathTest()
|
||||
ACCOUNT2
|
||||
),
|
||||
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{
|
||||
"RippleState",
|
||||
|
||||
@@ -55,6 +55,7 @@ TEST(LedgerUtilsTests, LedgerObjectTypeList)
|
||||
JS(mpt_issuance),
|
||||
JS(mptoken),
|
||||
JS(oracle),
|
||||
JS(credential),
|
||||
JS(nunl)
|
||||
};
|
||||
|
||||
@@ -86,6 +87,7 @@ TEST(LedgerUtilsTests, AccountOwnedTypeList)
|
||||
JS(xchain_owned_create_account_claim_id),
|
||||
JS(did),
|
||||
JS(oracle),
|
||||
JS(credential),
|
||||
JS(mpt_issuance),
|
||||
JS(mptoken)
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user