mirror of
				https://github.com/XRPLF/clio.git
				synced 2025-11-04 11:55:51 +00:00 
			
		
		
		
	@@ -165,6 +165,7 @@ target_sources (clio PRIVATE
 | 
			
		||||
  src/util/Random.cpp
 | 
			
		||||
  src/util/Taggable.cpp
 | 
			
		||||
  src/util/TerminationHandler.cpp
 | 
			
		||||
  src/util/TxUtil.cpp
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
# Clio server
 | 
			
		||||
@@ -190,6 +191,7 @@ if (tests)
 | 
			
		||||
    unittests/util/TestGlobals.cpp
 | 
			
		||||
    unittests/util/AssertTests.cpp
 | 
			
		||||
    unittests/util/BatchingTests.cpp
 | 
			
		||||
    unittests/util/TxUtilTests.cpp
 | 
			
		||||
    unittests/util/TestObject.cpp
 | 
			
		||||
    unittests/util/StringUtils.cpp
 | 
			
		||||
    unittests/util/prometheus/CounterTests.cpp
 | 
			
		||||
 
 | 
			
		||||
@@ -39,68 +39,17 @@
 | 
			
		||||
#include <ripple/protocol/AccountID.h>
 | 
			
		||||
#include <ripple/protocol/ErrorCodes.h>
 | 
			
		||||
#include <ripple/protocol/LedgerHeader.h>
 | 
			
		||||
#include <ripple/protocol/TxFormats.h>
 | 
			
		||||
#include <ripple/protocol/jss.h>
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <iterator>
 | 
			
		||||
#include <limits>
 | 
			
		||||
#include <optional>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <unordered_map>
 | 
			
		||||
#include <unordered_set>
 | 
			
		||||
#include <utility>
 | 
			
		||||
#include <variant>
 | 
			
		||||
 | 
			
		||||
namespace rpc {
 | 
			
		||||
 | 
			
		||||
// found here : https://xrpl.org/transaction-types.html
 | 
			
		||||
std::unordered_map<std::string, ripple::TxType> const AccountTxHandler::TYPESMAP{
 | 
			
		||||
    {JSL(AccountSet), ripple::ttACCOUNT_SET},
 | 
			
		||||
    {JSL(AccountDelete), ripple::ttACCOUNT_DELETE},
 | 
			
		||||
    {JSL(AMMBid), ripple::ttAMM_BID},
 | 
			
		||||
    {JSL(AMMCreate), ripple::ttAMM_CREATE},
 | 
			
		||||
    {JSL(AMMDelete), ripple::ttAMM_DELETE},
 | 
			
		||||
    {JSL(AMMDeposit), ripple::ttAMM_DEPOSIT},
 | 
			
		||||
    {JSL(AMMVote), ripple::ttAMM_VOTE},
 | 
			
		||||
    {JSL(AMMWithdraw), ripple::ttAMM_WITHDRAW},
 | 
			
		||||
    {JSL(CheckCancel), ripple::ttCHECK_CANCEL},
 | 
			
		||||
    {JSL(CheckCash), ripple::ttCHECK_CASH},
 | 
			
		||||
    {JSL(CheckCreate), ripple::ttCHECK_CREATE},
 | 
			
		||||
    {JSL(Clawback), ripple::ttCLAWBACK},
 | 
			
		||||
    {JSL(DepositPreauth), ripple::ttDEPOSIT_PREAUTH},
 | 
			
		||||
    {JSL(EscrowCancel), ripple::ttESCROW_CANCEL},
 | 
			
		||||
    {JSL(EscrowCreate), ripple::ttESCROW_CREATE},
 | 
			
		||||
    {JSL(EscrowFinish), ripple::ttESCROW_FINISH},
 | 
			
		||||
    {JSL(NFTokenAcceptOffer), ripple::ttNFTOKEN_ACCEPT_OFFER},
 | 
			
		||||
    {JSL(NFTokenBurn), ripple::ttNFTOKEN_BURN},
 | 
			
		||||
    {JSL(NFTokenCancelOffer), ripple::ttNFTOKEN_CANCEL_OFFER},
 | 
			
		||||
    {JSL(NFTokenCreateOffer), ripple::ttNFTOKEN_CREATE_OFFER},
 | 
			
		||||
    {JSL(NFTokenMint), ripple::ttNFTOKEN_MINT},
 | 
			
		||||
    {JSL(OfferCancel), ripple::ttOFFER_CANCEL},
 | 
			
		||||
    {JSL(OfferCreate), ripple::ttOFFER_CREATE},
 | 
			
		||||
    {JSL(Payment), ripple::ttPAYMENT},
 | 
			
		||||
    {JSL(PaymentChannelClaim), ripple::ttPAYCHAN_CLAIM},
 | 
			
		||||
    {JSL(PaymentChannelCreate), ripple::ttCHECK_CREATE},
 | 
			
		||||
    {JSL(PaymentChannelFund), ripple::ttPAYCHAN_FUND},
 | 
			
		||||
    {JSL(SetRegularKey), ripple::ttREGULAR_KEY_SET},
 | 
			
		||||
    {JSL(SignerListSet), ripple::ttSIGNER_LIST_SET},
 | 
			
		||||
    {JSL(TicketCreate), ripple::ttTICKET_CREATE},
 | 
			
		||||
    {JSL(TrustSet), ripple::ttTRUST_SET},
 | 
			
		||||
    {JSL(DIDSet), ripple::ttDID_SET},
 | 
			
		||||
    {JSL(DIDDelete), ripple::ttDID_DELETE},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// TODO: should be std::views::keys when clang supports it
 | 
			
		||||
std::unordered_set<std::string> const AccountTxHandler::TYPES_KEYS = [] {
 | 
			
		||||
    std::unordered_set<std::string> keys;
 | 
			
		||||
    std::transform(TYPESMAP.begin(), TYPESMAP.end(), std::inserter(keys, keys.begin()), [](auto const& pair) {
 | 
			
		||||
        return pair.first;
 | 
			
		||||
    });
 | 
			
		||||
    return keys;
 | 
			
		||||
}();
 | 
			
		||||
 | 
			
		||||
// TODO: this is currently very similar to nft_history but its own copy for time
 | 
			
		||||
// being. we should aim to reuse common logic in some way in the future.
 | 
			
		||||
AccountTxHandler::Result
 | 
			
		||||
@@ -197,19 +146,13 @@ AccountTxHandler::process(AccountTxHandler::Input input, Context const& ctx) con
 | 
			
		||||
 | 
			
		||||
        boost::json::object obj;
 | 
			
		||||
 | 
			
		||||
        // if binary is true or transactionType is specified, we need to expand the transaction
 | 
			
		||||
        if (!input.binary || input.transactionType.has_value()) {
 | 
			
		||||
        // if binary is false or transactionType is specified, we need to expand the transaction
 | 
			
		||||
        if (!input.binary || input.transactionTypeInLowercase.has_value()) {
 | 
			
		||||
            auto [txn, meta] = toExpandedJson(txnPlusMeta, ctx.apiVersion, NFTokenjson::ENABLE);
 | 
			
		||||
 | 
			
		||||
            if (txn.contains(JS(TransactionType))) {
 | 
			
		||||
                auto const objTransactionType = txn[JS(TransactionType)];
 | 
			
		||||
                auto const strType = util::toLower(objTransactionType.as_string().c_str());
 | 
			
		||||
 | 
			
		||||
                // if transactionType does not match
 | 
			
		||||
                if (input.transactionType.has_value() && AccountTxHandler::TYPESMAP.contains(strType) &&
 | 
			
		||||
                    AccountTxHandler::TYPESMAP.at(strType) != input.transactionType.value())
 | 
			
		||||
                    continue;
 | 
			
		||||
            }
 | 
			
		||||
            if (txn.contains(JS(TransactionType)) && input.transactionTypeInLowercase.has_value() &&
 | 
			
		||||
                util::toLower(txn[JS(TransactionType)].as_string().c_str()) != input.transactionTypeInLowercase.value())
 | 
			
		||||
                continue;
 | 
			
		||||
 | 
			
		||||
            if (!input.binary) {
 | 
			
		||||
                auto const txKey = ctx.apiVersion < 2u ? JS(tx) : JS(tx_json);
 | 
			
		||||
@@ -324,10 +267,8 @@ tag_invoke(boost::json::value_to_tag<AccountTxHandler::Input>, boost::json::valu
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (jsonObject.contains("tx_type")) {
 | 
			
		||||
        auto objTransactionType = jsonObject.at("tx_type");
 | 
			
		||||
        input.transactionType = AccountTxHandler::TYPESMAP.at(objTransactionType.as_string().c_str());
 | 
			
		||||
    }
 | 
			
		||||
    if (jsonObject.contains("tx_type"))
 | 
			
		||||
        input.transactionTypeInLowercase = jsonObject.at("tx_type").as_string().c_str();
 | 
			
		||||
 | 
			
		||||
    return input;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -20,14 +20,31 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "data/BackendInterface.h"
 | 
			
		||||
#include "rpc/RPCHelpers.h"
 | 
			
		||||
#include "rpc/Errors.h"
 | 
			
		||||
#include "rpc/JS.h"
 | 
			
		||||
#include "rpc/common/JsonBool.h"
 | 
			
		||||
#include "rpc/common/MetaProcessors.h"
 | 
			
		||||
#include "rpc/common/Modifiers.h"
 | 
			
		||||
#include "rpc/common/Types.h"
 | 
			
		||||
#include "rpc/common/Validators.h"
 | 
			
		||||
#include "util/TxUtil.h"
 | 
			
		||||
#include "util/log/Logger.h"
 | 
			
		||||
 | 
			
		||||
#include <boost/json/array.hpp>
 | 
			
		||||
#include <boost/json/conversion.hpp>
 | 
			
		||||
#include <boost/json/object.hpp>
 | 
			
		||||
#include <boost/json/value.hpp>
 | 
			
		||||
#include <ripple/protocol/ErrorCodes.h>
 | 
			
		||||
#include <ripple/protocol/TxFormats.h>
 | 
			
		||||
#include <ripple/protocol/jss.h>
 | 
			
		||||
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <limits>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <optional>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <unordered_set>
 | 
			
		||||
 | 
			
		||||
namespace rpc {
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -39,9 +56,6 @@ class AccountTxHandler {
 | 
			
		||||
    util::Logger log_{"RPC"};
 | 
			
		||||
    std::shared_ptr<BackendInterface> sharedPtrBackend_;
 | 
			
		||||
 | 
			
		||||
    static std::unordered_map<std::string, ripple::TxType> const TYPESMAP;
 | 
			
		||||
    static std::unordered_set<std::string> const TYPES_KEYS;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    // no max limit
 | 
			
		||||
    static auto constexpr LIMIT_MIN = 1;
 | 
			
		||||
@@ -77,7 +91,7 @@ public:
 | 
			
		||||
        JsonBool forward{false};
 | 
			
		||||
        std::optional<uint32_t> limit;
 | 
			
		||||
        std::optional<Marker> marker;
 | 
			
		||||
        std::optional<ripple::TxType> transactionType;
 | 
			
		||||
        std::optional<std::string> transactionTypeInLowercase;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    using Result = HandlerReturnType<Output>;
 | 
			
		||||
@@ -89,6 +103,7 @@ public:
 | 
			
		||||
    static RpcSpecConstRef
 | 
			
		||||
    spec([[maybe_unused]] uint32_t apiVersion)
 | 
			
		||||
    {
 | 
			
		||||
        auto const& typesKeysInLowercase = util::getTxTypesInLowercase();
 | 
			
		||||
        static auto const rpcSpecForV1 = RpcSpec{
 | 
			
		||||
            {JS(account), validation::Required{}, validation::AccountValidator},
 | 
			
		||||
            {JS(ledger_hash), validation::Uint256HexStringValidator},
 | 
			
		||||
@@ -112,7 +127,7 @@ public:
 | 
			
		||||
                "tx_type",
 | 
			
		||||
                validation::Type<std::string>{},
 | 
			
		||||
                modifiers::ToLower{},
 | 
			
		||||
                validation::OneOf<std::string>(TYPES_KEYS.cbegin(), TYPES_KEYS.cend()),
 | 
			
		||||
                validation::OneOf<std::string>(typesKeysInLowercase.cbegin(), typesKeysInLowercase.cend()),
 | 
			
		||||
            },
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										47
									
								
								src/util/TxUtil.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/util/TxUtil.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    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 "util/JsonUtils.h"
 | 
			
		||||
 | 
			
		||||
#include <ripple/protocol/TxFormats.h>
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <iterator>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <unordered_set>
 | 
			
		||||
 | 
			
		||||
namespace util {
 | 
			
		||||
 | 
			
		||||
std::unordered_set<std::string> const&
 | 
			
		||||
getTxTypesInLowercase()
 | 
			
		||||
{
 | 
			
		||||
    static std::unordered_set<std::string> const typesKeysInLowercase = []() {
 | 
			
		||||
        std::unordered_set<std::string> keys;
 | 
			
		||||
        std::transform(
 | 
			
		||||
            ripple::TxFormats::getInstance().begin(),
 | 
			
		||||
            ripple::TxFormats::getInstance().end(),
 | 
			
		||||
            std::inserter(keys, keys.begin()),
 | 
			
		||||
            [](auto const& pair) { return util::toLower(pair.getName()); }
 | 
			
		||||
        );
 | 
			
		||||
        return keys;
 | 
			
		||||
    }();
 | 
			
		||||
 | 
			
		||||
    return typesKeysInLowercase;
 | 
			
		||||
}
 | 
			
		||||
}  // namespace util
 | 
			
		||||
							
								
								
									
										28
									
								
								src/util/TxUtil.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/util/TxUtil.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    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 <string>
 | 
			
		||||
#include <unordered_set>
 | 
			
		||||
 | 
			
		||||
namespace util {
 | 
			
		||||
std::unordered_set<std::string> const&
 | 
			
		||||
getTxTypesInLowercase();
 | 
			
		||||
}  // namespace util
 | 
			
		||||
@@ -81,88 +81,88 @@ struct AccountTxParameterTest : public RPCAccountTxHandlerTest,
 | 
			
		||||
            },
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "BinaryNotBool",
 | 
			
		||||
                R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "binary": 1})",
 | 
			
		||||
                R"({"account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "binary": 1})",
 | 
			
		||||
                "invalidParams",
 | 
			
		||||
                "Invalid parameters."
 | 
			
		||||
            },
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "BinaryNotBool_API_v1",
 | 
			
		||||
                R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "binary": 1})",
 | 
			
		||||
                R"({"account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "binary": 1})",
 | 
			
		||||
                std::nullopt,
 | 
			
		||||
                std::nullopt,
 | 
			
		||||
                1u
 | 
			
		||||
            },
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "ForwardNotBool",
 | 
			
		||||
                R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "forward": 1})",
 | 
			
		||||
                R"({"account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "forward": 1})",
 | 
			
		||||
                "invalidParams",
 | 
			
		||||
                "Invalid parameters."
 | 
			
		||||
            },
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "ForwardNotBool_API_v1",
 | 
			
		||||
                R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "forward": 1})",
 | 
			
		||||
                R"({"account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "forward": 1})",
 | 
			
		||||
                std::nullopt,
 | 
			
		||||
                std::nullopt,
 | 
			
		||||
                1u
 | 
			
		||||
            },
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "ledger_index_minNotInt",
 | 
			
		||||
                R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_index_min": "x"})",
 | 
			
		||||
                R"({"account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_index_min": "x"})",
 | 
			
		||||
                "invalidParams",
 | 
			
		||||
                "Invalid parameters."
 | 
			
		||||
            },
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "ledger_index_maxNotInt",
 | 
			
		||||
                R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_index_max": "x"})",
 | 
			
		||||
                R"({"account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_index_max": "x"})",
 | 
			
		||||
                "invalidParams",
 | 
			
		||||
                "Invalid parameters."
 | 
			
		||||
            },
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "ledger_indexInvalid",
 | 
			
		||||
                R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_index": "x"})",
 | 
			
		||||
                R"({"account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_index": "x"})",
 | 
			
		||||
                "invalidParams",
 | 
			
		||||
                "ledgerIndexMalformed"
 | 
			
		||||
            },
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "ledger_hashInvalid",
 | 
			
		||||
                R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_hash": "x"})",
 | 
			
		||||
                R"({"account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_hash": "x"})",
 | 
			
		||||
                "invalidParams",
 | 
			
		||||
                "ledger_hashMalformed"
 | 
			
		||||
            },
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "ledger_hashNotString",
 | 
			
		||||
                R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_hash": 123})",
 | 
			
		||||
                R"({"account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_hash": 123})",
 | 
			
		||||
                "invalidParams",
 | 
			
		||||
                "ledger_hashNotString"
 | 
			
		||||
            },
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "limitNotInt",
 | 
			
		||||
                R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "limit": "123"})",
 | 
			
		||||
                R"({"account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "limit": "123"})",
 | 
			
		||||
                "invalidParams",
 | 
			
		||||
                "Invalid parameters."
 | 
			
		||||
            },
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "limitNegative",
 | 
			
		||||
                R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "limit": -1})",
 | 
			
		||||
                R"({"account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "limit": -1})",
 | 
			
		||||
                "invalidParams",
 | 
			
		||||
                "Invalid parameters."
 | 
			
		||||
            },
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "limitZero",
 | 
			
		||||
                R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "limit": 0})",
 | 
			
		||||
                R"({"account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "limit": 0})",
 | 
			
		||||
                "invalidParams",
 | 
			
		||||
                "Invalid parameters."
 | 
			
		||||
            },
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "MarkerNotObject",
 | 
			
		||||
                R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "marker": 101})",
 | 
			
		||||
                R"({"account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "marker": 101})",
 | 
			
		||||
                "invalidParams",
 | 
			
		||||
                "invalidMarker"
 | 
			
		||||
            },
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "MarkerMissingSeq",
 | 
			
		||||
                R"({
 | 
			
		||||
                "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "marker": {"ledger": 123}
 | 
			
		||||
            })",
 | 
			
		||||
                "invalidParams",
 | 
			
		||||
@@ -171,8 +171,8 @@ struct AccountTxParameterTest : public RPCAccountTxHandlerTest,
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "MarkerMissingLedger",
 | 
			
		||||
                R"({
 | 
			
		||||
                "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "marker":{"seq": 123}
 | 
			
		||||
                "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "marker": {"seq": 123}
 | 
			
		||||
            })",
 | 
			
		||||
                "invalidParams",
 | 
			
		||||
                "Required field 'ledger' missing"
 | 
			
		||||
@@ -180,7 +180,7 @@ struct AccountTxParameterTest : public RPCAccountTxHandlerTest,
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "MarkerLedgerNotInt",
 | 
			
		||||
                R"({
 | 
			
		||||
                "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "marker": 
 | 
			
		||||
                {
 | 
			
		||||
                    "seq": "string",
 | 
			
		||||
@@ -193,7 +193,7 @@ struct AccountTxParameterTest : public RPCAccountTxHandlerTest,
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "MarkerSeqNotInt",
 | 
			
		||||
                R"({
 | 
			
		||||
                "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "marker": 
 | 
			
		||||
                {
 | 
			
		||||
                    "ledger": "string",
 | 
			
		||||
@@ -206,7 +206,7 @@ struct AccountTxParameterTest : public RPCAccountTxHandlerTest,
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "LedgerIndexMinLessThanMinSeq",
 | 
			
		||||
                R"({
 | 
			
		||||
                "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "ledger_index_min": 9
 | 
			
		||||
            })",
 | 
			
		||||
                "lgrIdxMalformed",
 | 
			
		||||
@@ -215,7 +215,7 @@ struct AccountTxParameterTest : public RPCAccountTxHandlerTest,
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "LedgerIndexMaxLargeThanMaxSeq",
 | 
			
		||||
                R"({
 | 
			
		||||
                "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "ledger_index_max": 31
 | 
			
		||||
            })",
 | 
			
		||||
                "lgrIdxMalformed",
 | 
			
		||||
@@ -224,7 +224,7 @@ struct AccountTxParameterTest : public RPCAccountTxHandlerTest,
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "LedgerIndexMaxLargeThanMaxSeq_API_v1",
 | 
			
		||||
                R"({
 | 
			
		||||
                "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "ledger_index_max": 31
 | 
			
		||||
            })",
 | 
			
		||||
                std::nullopt,
 | 
			
		||||
@@ -234,7 +234,7 @@ struct AccountTxParameterTest : public RPCAccountTxHandlerTest,
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "LedgerIndexMaxSmallerThanMinSeq",
 | 
			
		||||
                R"({
 | 
			
		||||
                "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "ledger_index_max": 9
 | 
			
		||||
            })",
 | 
			
		||||
                "lgrIdxMalformed",
 | 
			
		||||
@@ -243,7 +243,7 @@ struct AccountTxParameterTest : public RPCAccountTxHandlerTest,
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "LedgerIndexMaxSmallerThanMinSeq_API_v1",
 | 
			
		||||
                R"({
 | 
			
		||||
                "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "ledger_index_max": 9
 | 
			
		||||
            })",
 | 
			
		||||
                "lgrIdxsInvalid",
 | 
			
		||||
@@ -253,7 +253,7 @@ struct AccountTxParameterTest : public RPCAccountTxHandlerTest,
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "LedgerIndexMinSmallerThanMinSeq",
 | 
			
		||||
                R"({
 | 
			
		||||
                "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "ledger_index_min": 9
 | 
			
		||||
            })",
 | 
			
		||||
                "lgrIdxMalformed",
 | 
			
		||||
@@ -262,7 +262,7 @@ struct AccountTxParameterTest : public RPCAccountTxHandlerTest,
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "LedgerIndexMinSmallerThanMinSeq_API_v1",
 | 
			
		||||
                R"({
 | 
			
		||||
                "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "ledger_index_min": 9
 | 
			
		||||
            })",
 | 
			
		||||
                std::nullopt,
 | 
			
		||||
@@ -272,7 +272,7 @@ struct AccountTxParameterTest : public RPCAccountTxHandlerTest,
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "LedgerIndexMinLargerThanMaxSeq",
 | 
			
		||||
                R"({
 | 
			
		||||
                "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "ledger_index_min": 31
 | 
			
		||||
            })",
 | 
			
		||||
                "lgrIdxMalformed",
 | 
			
		||||
@@ -281,7 +281,7 @@ struct AccountTxParameterTest : public RPCAccountTxHandlerTest,
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "LedgerIndexMinLargerThanMaxSeq_API_v1",
 | 
			
		||||
                R"({
 | 
			
		||||
                "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "ledger_index_min": 31
 | 
			
		||||
            })",
 | 
			
		||||
                "lgrIdxsInvalid",
 | 
			
		||||
@@ -291,7 +291,7 @@ struct AccountTxParameterTest : public RPCAccountTxHandlerTest,
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "LedgerIndexMaxLessThanLedgerIndexMin",
 | 
			
		||||
                R"({
 | 
			
		||||
                "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "ledger_index_max": 11,
 | 
			
		||||
                "ledger_index_min": 20
 | 
			
		||||
            })",
 | 
			
		||||
@@ -301,7 +301,7 @@ struct AccountTxParameterTest : public RPCAccountTxHandlerTest,
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "LedgerIndexMaxLessThanLedgerIndexMin_API_v1",
 | 
			
		||||
                R"({
 | 
			
		||||
                "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "ledger_index_max": 11,
 | 
			
		||||
                "ledger_index_min": 20
 | 
			
		||||
            })",
 | 
			
		||||
@@ -312,7 +312,7 @@ struct AccountTxParameterTest : public RPCAccountTxHandlerTest,
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "LedgerIndexMaxMinAndLedgerIndex",
 | 
			
		||||
                R"({
 | 
			
		||||
                "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", 
 | 
			
		||||
                "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", 
 | 
			
		||||
                "ledger_index_max": 20,
 | 
			
		||||
                "ledger_index_min": 11,
 | 
			
		||||
                "ledger_index": 10
 | 
			
		||||
@@ -323,7 +323,7 @@ struct AccountTxParameterTest : public RPCAccountTxHandlerTest,
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "LedgerIndexMaxMinAndLedgerIndexValidated",
 | 
			
		||||
                R"({
 | 
			
		||||
                "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "ledger_index_max": 20,
 | 
			
		||||
                "ledger_index_min": 11,
 | 
			
		||||
                "ledger_index": "validated"
 | 
			
		||||
@@ -334,7 +334,7 @@ struct AccountTxParameterTest : public RPCAccountTxHandlerTest,
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "LedgerIndexMaxMinAndLedgerIndex_API_v1",
 | 
			
		||||
                R"({
 | 
			
		||||
                "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "ledger_index_max": 20,
 | 
			
		||||
                "ledger_index_min": 11,
 | 
			
		||||
                "ledger_index": 10
 | 
			
		||||
@@ -347,7 +347,7 @@ struct AccountTxParameterTest : public RPCAccountTxHandlerTest,
 | 
			
		||||
                "LedgerIndexMaxMinAndLedgerHash",
 | 
			
		||||
                fmt::format(
 | 
			
		||||
                    R"({{
 | 
			
		||||
                "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", 
 | 
			
		||||
                "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", 
 | 
			
		||||
                "ledger_index_max": 20,
 | 
			
		||||
                "ledger_index_min": 11,
 | 
			
		||||
                "ledger_hash": "{}"
 | 
			
		||||
@@ -361,7 +361,7 @@ struct AccountTxParameterTest : public RPCAccountTxHandlerTest,
 | 
			
		||||
                "LedgerIndexMaxMinAndLedgerHash_API_v1",
 | 
			
		||||
                fmt::format(
 | 
			
		||||
                    R"({{
 | 
			
		||||
                "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", 
 | 
			
		||||
                "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", 
 | 
			
		||||
                "ledger_index_max": 20,
 | 
			
		||||
                "ledger_index_min": 11,
 | 
			
		||||
                "ledger_hash": "{}"
 | 
			
		||||
@@ -375,7 +375,7 @@ struct AccountTxParameterTest : public RPCAccountTxHandlerTest,
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "LedgerIndexMaxMinAndLedgerIndexValidated_API_v1",
 | 
			
		||||
                R"({
 | 
			
		||||
                "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "ledger_index_max": 20,
 | 
			
		||||
                "ledger_index_min": 11,
 | 
			
		||||
                "ledger_index": "validated"
 | 
			
		||||
@@ -384,6 +384,15 @@ struct AccountTxParameterTest : public RPCAccountTxHandlerTest,
 | 
			
		||||
                std::nullopt,
 | 
			
		||||
                1u
 | 
			
		||||
            },
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "InvalidTxType",
 | 
			
		||||
                R"({
 | 
			
		||||
                "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", 
 | 
			
		||||
                "tx_type": "unknow"
 | 
			
		||||
            })",
 | 
			
		||||
                "invalidParams",
 | 
			
		||||
                "Invalid field 'tx_type'."
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
@@ -1839,6 +1848,58 @@ generateTransactionTypeTestValues()
 | 
			
		||||
            ])",
 | 
			
		||||
            1u
 | 
			
		||||
        },
 | 
			
		||||
        AccountTxTransactionBundle{
 | 
			
		||||
            "Lowercase_Payment",
 | 
			
		||||
            R"({
 | 
			
		||||
                "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "ledger_index": "validated",
 | 
			
		||||
                "tx_type": "payment"
 | 
			
		||||
            })",
 | 
			
		||||
            R"([
 | 
			
		||||
                {
 | 
			
		||||
                    "meta": {
 | 
			
		||||
                        "AffectedNodes": [
 | 
			
		||||
                        {
 | 
			
		||||
                            "ModifiedNode": {
 | 
			
		||||
                                "FinalFields": {
 | 
			
		||||
                                    "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                                    "Balance": "22"
 | 
			
		||||
                                },
 | 
			
		||||
                                "LedgerEntryType": "AccountRoot"
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                            "ModifiedNode": {
 | 
			
		||||
                                "FinalFields": {
 | 
			
		||||
                                    "Account": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun",
 | 
			
		||||
                                    "Balance": "23"
 | 
			
		||||
                                },
 | 
			
		||||
                                "LedgerEntryType": "AccountRoot"
 | 
			
		||||
                            }
 | 
			
		||||
                        }],
 | 
			
		||||
                        "TransactionIndex": 0,
 | 
			
		||||
                        "TransactionResult": "tesSUCCESS",
 | 
			
		||||
                        "delivered_amount": "unavailable"
 | 
			
		||||
                    },
 | 
			
		||||
                    "tx": {
 | 
			
		||||
                        "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                        "Amount": "1",
 | 
			
		||||
                        "DeliverMax": "1",
 | 
			
		||||
                        "Destination": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun",
 | 
			
		||||
                        "Fee": "1",
 | 
			
		||||
                        "Sequence": 32,
 | 
			
		||||
                        "SigningPubKey": "74657374",
 | 
			
		||||
                        "TransactionType": "Payment",
 | 
			
		||||
                        "hash": "51D2AAA6B8E4E16EF22F6424854283D8391B56875858A711B8CE4D5B9A422CC2",
 | 
			
		||||
                        "ledger_index": 30,
 | 
			
		||||
                        "inLedger": 30,
 | 
			
		||||
                        "date": 1
 | 
			
		||||
                    },
 | 
			
		||||
                    "validated": true
 | 
			
		||||
                }
 | 
			
		||||
            ])",
 | 
			
		||||
            1u
 | 
			
		||||
        },
 | 
			
		||||
        AccountTxTransactionBundle{
 | 
			
		||||
            "Payment_API_v2",
 | 
			
		||||
            R"({
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										43
									
								
								unittests/util/TxUtilTests.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								unittests/util/TxUtilTests.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    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 "util/JsonUtils.h"
 | 
			
		||||
#include "util/TxUtil.h"
 | 
			
		||||
 | 
			
		||||
#include <gtest/gtest.h>
 | 
			
		||||
#include <ripple/protocol/TxFormats.h>
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <cstddef>
 | 
			
		||||
#include <iterator>
 | 
			
		||||
 | 
			
		||||
TEST(TxUtilTests, txTypesInLowercase)
 | 
			
		||||
{
 | 
			
		||||
    auto const& types = util::getTxTypesInLowercase();
 | 
			
		||||
    ASSERT_TRUE(
 | 
			
		||||
        std::size_t(std::distance(ripple::TxFormats::getInstance().begin(), ripple::TxFormats::getInstance().end())) ==
 | 
			
		||||
        types.size()
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    std::for_each(
 | 
			
		||||
        ripple::TxFormats::getInstance().begin(),
 | 
			
		||||
        ripple::TxFormats::getInstance().end(),
 | 
			
		||||
        [&](auto const& pair) { EXPECT_TRUE(types.find(util::toLower(pair.getName())) != types.end()); }
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user