mirror of
https://github.com/XRPLF/clio.git
synced 2025-11-23 05:05:54 +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/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"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
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::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."},
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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)))
|
||||||
);
|
);
|
||||||
|
// 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>(
|
auto const authorized = util::parseBase58Wrapper<ripple::AccountID>(
|
||||||
boost::json::value_to<std::string>(input.depositPreauth->at(JS(authorized)))
|
boost::json::value_to<std::string>(input.depositPreauth->at(JS(authorized)))
|
||||||
);
|
);
|
||||||
|
|
||||||
key = ripple::keylet::depositPreauth(*owner, *authorized).key;
|
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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
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 <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");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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)
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user