Handler account_currencies (#524)

Fixes #525
This commit is contained in:
cyan317
2023-02-27 09:17:51 +00:00
committed by GitHub
parent 7d4e5ff0bd
commit a3211f4458
10 changed files with 592 additions and 19 deletions

View File

@@ -58,6 +58,7 @@ target_sources(clio PRIVATE
src/rpc/common/Validators.cpp src/rpc/common/Validators.cpp
## NextGen RPC handler ## NextGen RPC handler
src/rpc/ngHandlers/AccountChannels.cpp src/rpc/ngHandlers/AccountChannels.cpp
src/rpc/ngHandlers/AccountCurrencies.cpp
## RPC Methods ## RPC Methods
# Account # Account
src/rpc/handlers/AccountChannels.cpp src/rpc/handlers/AccountChannels.cpp
@@ -116,6 +117,7 @@ if(BUILD_TESTS)
unittests/rpc/BaseTests.cpp unittests/rpc/BaseTests.cpp
unittests/rpc/RPCHelpersTest.cpp unittests/rpc/RPCHelpersTest.cpp
unittests/rpc/handlers/TestHandlerTests.cpp unittests/rpc/handlers/TestHandlerTests.cpp
unittests/rpc/handlers/AccountCurrenciesTest.cpp
unittests/rpc/handlers/DefaultProcessorTests.cpp unittests/rpc/handlers/DefaultProcessorTests.cpp
unittests/rpc/handlers/PingTest.cpp unittests/rpc/handlers/PingTest.cpp
unittests/rpc/handlers/AccountChannelsTest.cpp) unittests/rpc/handlers/AccountChannelsTest.cpp)

View File

@@ -606,8 +606,10 @@ getLedgerInfoFromHashOrSeq(
RPC::Status{RPC::RippledError::rpcLGR_NOT_FOUND, "ledgerNotFound"}; RPC::Status{RPC::RippledError::rpcLGR_NOT_FOUND, "ledgerNotFound"};
if (ledgerHash) if (ledgerHash)
{ {
lgrInfo = // invoke uint256's constructor to parse the hex string , instead of
backend.fetchLedgerByHash(ripple::uint256{*ledgerHash}, yield); // copying buffer
ripple::uint256 ledgerHash256{std::string_view(*ledgerHash)};
lgrInfo = backend.fetchLedgerByHash(ledgerHash256, yield);
if (!lgrInfo || lgrInfo->seq > maxSeq) if (!lgrInfo || lgrInfo->seq > maxSeq)
return err; return err;

View File

@@ -75,7 +75,8 @@ public:
using Result = RPCng::HandlerReturnType<Output>; using Result = RPCng::HandlerReturnType<Output>;
AccountChannelsHandler(std::shared_ptr<BackendInterface>& sharedPtrBackend) AccountChannelsHandler(
std::shared_ptr<BackendInterface> const& sharedPtrBackend)
: sharedPtrBackend_(sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend)
{ {
} }

View File

@@ -0,0 +1,129 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2023, 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/RPCHelpers.h>
#include <rpc/ngHandlers/AccountCurrencies.h>
namespace RPCng {
AccountCurrenciesHandler::Result
AccountCurrenciesHandler::process(
AccountCurrenciesHandler::Input input,
boost::asio::yield_context& yield) const
{
auto const range = sharedPtrBackend_->fetchLedgerRange();
auto const lgrInfoOrStatus = RPC::getLedgerInfoFromHashOrSeq(
*sharedPtrBackend_,
yield,
input.ledgerHash,
input.ledgerIndex,
range->maxSequence);
if (auto const status = std::get_if<RPC::Status>(&lgrInfoOrStatus))
return Error{*status};
auto const lgrInfo = std::get<ripple::LedgerInfo>(lgrInfoOrStatus);
auto const accountID = RPC::accountFromStringStrict(input.account);
auto const accountLedgerObject = sharedPtrBackend_->fetchLedgerObject(
ripple::keylet::account(*accountID).key, lgrInfo.seq, yield);
if (!accountLedgerObject)
return Error{RPC::Status{
RPC::RippledError::rpcACT_NOT_FOUND, "accountNotFound"}};
Output response;
auto const addToResponse = [&](ripple::SLE&& sle) {
if (sle.getType() == ripple::ltRIPPLE_STATE)
{
ripple::STAmount balance = sle.getFieldAmount(ripple::sfBalance);
auto const lowLimit = sle.getFieldAmount(ripple::sfLowLimit);
auto const highLimit = sle.getFieldAmount(ripple::sfHighLimit);
bool const viewLowest = (lowLimit.getIssuer() == accountID);
auto const lineLimit = viewLowest ? lowLimit : highLimit;
auto const lineLimitPeer = !viewLowest ? lowLimit : highLimit;
if (!viewLowest)
balance.negate();
if (balance < lineLimit)
response.receiveCurrencies.insert(
ripple::to_string(balance.getCurrency()));
if ((-balance) < lineLimitPeer)
response.sendCurrencies.insert(
ripple::to_string(balance.getCurrency()));
}
return true;
};
// traverse all owned nodes, limit->max, marker->empty
RPC::ngTraverseOwnedNodes(
*sharedPtrBackend_,
*accountID,
lgrInfo.seq,
std::numeric_limits<std::uint32_t>::max(),
{},
yield,
addToResponse);
response.ledgerHash = ripple::strHex(lgrInfo.hash);
response.ledgerIndex = lgrInfo.seq;
return response;
}
void
tag_invoke(
boost::json::value_from_tag,
boost::json::value& jv,
AccountCurrenciesHandler::Output output)
{
jv = {
{"ledger_hash", output.ledgerHash},
{"ledger_index", output.ledgerIndex},
{"validated", output.validated},
{"receive_currencies",
boost::json::value_from(output.receiveCurrencies)},
{"send_currencies", boost::json::value_from(output.sendCurrencies)}};
}
AccountCurrenciesHandler::Input
tag_invoke(
boost::json::value_to_tag<AccountCurrenciesHandler::Input>,
boost::json::value const& jv)
{
auto const& jsonObject = jv.as_object();
AccountCurrenciesHandler::Input input;
input.account = jv.at("account").as_string().c_str();
if (jsonObject.contains("ledger_hash"))
{
input.ledgerHash = jv.at("ledger_hash").as_string().c_str();
}
if (jsonObject.contains("ledger_index"))
{
if (!jsonObject.at("ledger_index").is_string())
{
input.ledgerIndex = jv.at("ledger_index").as_int64();
}
else if (jsonObject.at("ledger_index").as_string() != "validated")
{
input.ledgerIndex =
std::stoi(jv.at("ledger_index").as_string().c_str());
}
}
return input;
}
} // namespace RPCng

View File

@@ -0,0 +1,87 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2023, 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 <backend/BackendInterface.h>
#include <rpc/common/Types.h>
#include <rpc/common/Validators.h>
#include <boost/asio/spawn.hpp>
#include <set>
namespace RPCng {
class AccountCurrenciesHandler
{
// dependencies
std::shared_ptr<BackendInterface> sharedPtrBackend_;
public:
struct Output
{
std::string ledgerHash;
uint32_t ledgerIndex;
std::set<std::string> receiveCurrencies;
std::set<std::string> sendCurrencies;
// validated should be sent via framework
bool validated = true;
};
// TODO:we did not implement the "strict" field
struct Input
{
std::string account;
std::optional<std::string> ledgerHash;
std::optional<uint32_t> ledgerIndex;
};
using Result = RPCng::HandlerReturnType<Output>;
AccountCurrenciesHandler(
std::shared_ptr<BackendInterface> const& sharedPtrBackend)
: sharedPtrBackend_(sharedPtrBackend)
{
}
RpcSpecConstRef
spec() const
{
static const RpcSpec rpcSpec = {
{"account", validation::Required{}, validation::AccountValidator},
{"ledger_hash", validation::LedgerHashValidator},
{"ledger_index", validation::LedgerIndexValidator}};
return rpcSpec;
}
Result
process(Input input, boost::asio::yield_context& yield) const;
};
void
tag_invoke(
boost::json::value_from_tag,
boost::json::value& jv,
AccountCurrenciesHandler::Output output);
AccountCurrenciesHandler::Input
tag_invoke(
boost::json::value_to_tag<AccountCurrenciesHandler::Input>,
boost::json::value const& jv);
} // namespace RPCng

View File

@@ -40,20 +40,8 @@ constexpr static auto INDEX2 =
constexpr static auto TXNID = constexpr static auto TXNID =
"05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD"; "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD";
class RPCAccountHandlerTest : public SyncAsioContextTest, public MockBackendTest class RPCAccountHandlerTest : public HandlerBaseTest
{ {
void
SetUp() override
{
SyncAsioContextTest::SetUp();
MockBackendTest::SetUp();
}
void
TearDown() override
{
MockBackendTest::TearDown();
SyncAsioContextTest::TearDown();
}
}; };
TEST_F(RPCAccountHandlerTest, NonHexLedgerHash) TEST_F(RPCAccountHandlerTest, NonHexLedgerHash)
@@ -250,7 +238,7 @@ TEST_F(RPCAccountHandlerTest, NonExistLedgerViaLedgerHash)
MockBackend* rawBackendPtr = MockBackend* rawBackendPtr =
static_cast<MockBackend*>(mockBackendPtr.get()); static_cast<MockBackend*>(mockBackendPtr.get());
// mock fetchLedgerByHash return empty // mock fetchLedgerByHash return empty
ON_CALL(*rawBackendPtr, fetchLedgerByHash) ON_CALL(*rawBackendPtr, fetchLedgerByHash(ripple::uint256{LEDGERHASH}, _))
.WillByDefault(Return(std::optional<ripple::LedgerInfo>{})); .WillByDefault(Return(std::optional<ripple::LedgerInfo>{}));
EXPECT_CALL(*rawBackendPtr, fetchLedgerByHash).Times(1); EXPECT_CALL(*rawBackendPtr, fetchLedgerByHash).Times(1);
@@ -311,7 +299,7 @@ TEST_F(RPCAccountHandlerTest, NonExistLedgerViaLedgerHash2)
mockBackendPtr->updateRange(30); // max mockBackendPtr->updateRange(30); // max
// mock fetchLedgerByHash return ledger but seq is 31 > 30 // mock fetchLedgerByHash return ledger but seq is 31 > 30
auto ledgerinfo = CreateLedgerInfo(LEDGERHASH, 31); auto ledgerinfo = CreateLedgerInfo(LEDGERHASH, 31);
ON_CALL(*rawBackendPtr, fetchLedgerByHash) ON_CALL(*rawBackendPtr, fetchLedgerByHash(ripple::uint256{LEDGERHASH}, _))
.WillByDefault(Return(ledgerinfo)); .WillByDefault(Return(ledgerinfo));
EXPECT_CALL(*rawBackendPtr, fetchLedgerByHash).Times(1); EXPECT_CALL(*rawBackendPtr, fetchLedgerByHash).Times(1);
auto const input = json::parse(fmt::format( auto const input = json::parse(fmt::format(
@@ -367,7 +355,7 @@ TEST_F(RPCAccountHandlerTest, NonExistAccount)
mockBackendPtr->updateRange(10); // min mockBackendPtr->updateRange(10); // min
mockBackendPtr->updateRange(30); // max mockBackendPtr->updateRange(30); // max
auto ledgerinfo = CreateLedgerInfo(LEDGERHASH, 30); auto ledgerinfo = CreateLedgerInfo(LEDGERHASH, 30);
ON_CALL(*rawBackendPtr, fetchLedgerByHash) ON_CALL(*rawBackendPtr, fetchLedgerByHash(ripple::uint256{LEDGERHASH}, _))
.WillByDefault(Return(ledgerinfo)); .WillByDefault(Return(ledgerinfo));
EXPECT_CALL(*rawBackendPtr, fetchLedgerByHash).Times(1); EXPECT_CALL(*rawBackendPtr, fetchLedgerByHash).Times(1);
// fetch account object return emtpy // fetch account object return emtpy

View File

@@ -0,0 +1,295 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2023, 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/common/AnyHandler.h>
#include <rpc/ngHandlers/AccountCurrencies.h>
#include <util/Fixtures.h>
#include <util/TestObject.h>
#include <fmt/core.h>
using namespace RPCng;
namespace json = boost::json;
using namespace testing;
constexpr static auto ACCOUNT = "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn";
constexpr static auto ACCOUNT2 = "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun";
constexpr static auto ISSUER = "rK9DrarGKnVEo2nYp5MfVRXRYf5yRX3mwD";
constexpr static auto LEDGERHASH =
"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652";
constexpr static auto INDEX1 =
"1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC";
constexpr static auto INDEX2 =
"E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321";
constexpr static auto TXNID =
"E3FE6EA3D48F0C2B639448020EA4F03D4F4F8FFDB243A852A0F59177921B4879";
class RPCAccountCurrenciesHandlerTest : public HandlerBaseTest
{
};
TEST_F(RPCAccountCurrenciesHandlerTest, AccountNotExsit)
{
auto const rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get());
mockBackendPtr->updateRange(10); // min
mockBackendPtr->updateRange(30); // max
auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, 30);
EXPECT_CALL(*rawBackendPtr, fetchLedgerBySequence).Times(1);
ON_CALL(*rawBackendPtr, fetchLedgerBySequence)
.WillByDefault(Return(ledgerinfo));
ON_CALL(*rawBackendPtr, doFetchLedgerObject)
.WillByDefault(Return(std::optional<Blob>{}));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(1);
auto const static input = boost::json::parse(fmt::format(
R"({{
"account":"{}"
}})",
ACCOUNT));
auto const handler = AnyHandler{AccountCurrenciesHandler{mockBackendPtr}};
boost::asio::spawn(ctx, [&](boost::asio::yield_context yield) {
auto const output = handler.process(input, yield);
ASSERT_FALSE(output);
auto const err = RPC::makeError(output.error());
EXPECT_EQ(err.at("error").as_string(), "actNotFound");
EXPECT_EQ(err.at("error_message").as_string(), "accountNotFound");
});
ctx.run();
}
TEST_F(RPCAccountCurrenciesHandlerTest, LedgerNonExistViaSequence)
{
auto const rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get());
mockBackendPtr->updateRange(10); // min
mockBackendPtr->updateRange(30); // max
EXPECT_CALL(*rawBackendPtr, fetchLedgerBySequence).Times(1);
// return empty ledgerinfo
ON_CALL(*rawBackendPtr, fetchLedgerBySequence(30, _))
.WillByDefault(Return(std::optional<ripple::LedgerInfo>{}));
auto const static input = boost::json::parse(fmt::format(
R"({{
"account":"{}"
}})",
ACCOUNT));
auto const handler = AnyHandler{AccountCurrenciesHandler{mockBackendPtr}};
boost::asio::spawn(ctx, [&](boost::asio::yield_context yield) {
auto const output = handler.process(input, yield);
ASSERT_FALSE(output);
auto const err = RPC::makeError(output.error());
EXPECT_EQ(err.at("error").as_string(), "lgrNotFound");
EXPECT_EQ(err.at("error_message").as_string(), "ledgerNotFound");
});
ctx.run();
}
TEST_F(RPCAccountCurrenciesHandlerTest, LedgerNonExistViaHash)
{
auto const rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get());
mockBackendPtr->updateRange(10); // min
mockBackendPtr->updateRange(30); // max
EXPECT_CALL(*rawBackendPtr, fetchLedgerByHash).Times(1);
// return empty ledgerinfo
ON_CALL(*rawBackendPtr, fetchLedgerByHash(ripple::uint256{LEDGERHASH}, _))
.WillByDefault(Return(std::optional<ripple::LedgerInfo>{}));
auto const static input = boost::json::parse(fmt::format(
R"({{
"account":"{}",
"ledger_hash":"{}"
}})",
ACCOUNT,
LEDGERHASH));
auto const handler = AnyHandler{AccountCurrenciesHandler{mockBackendPtr}};
boost::asio::spawn(ctx, [&](boost::asio::yield_context yield) {
auto const output = handler.process(input);
ASSERT_FALSE(output);
auto const err = RPC::makeError(output.error());
EXPECT_EQ(err.at("error").as_string(), "lgrNotFound");
EXPECT_EQ(err.at("error_message").as_string(), "ledgerNotFound");
});
ctx.run();
}
TEST_F(RPCAccountCurrenciesHandlerTest, DefaultParameter)
{
auto constexpr static OUTPUT = R"({
"ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652",
"ledger_index":30,
"validated":true,
"receive_currencies":[
"EUR",
"JPY"
],
"send_currencies":[
"EUR",
"USD"
]
})";
auto const rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get());
mockBackendPtr->updateRange(10); // min
mockBackendPtr->updateRange(30); // max
// return valid ledgerinfo
auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, 30);
EXPECT_CALL(*rawBackendPtr, fetchLedgerBySequence).Times(1);
ON_CALL(*rawBackendPtr, fetchLedgerBySequence(30, _))
.WillByDefault(Return(ledgerinfo));
// return valid account
auto const accountKk =
ripple::keylet::account(GetAccountIDWithString(ACCOUNT)).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(accountKk, 30, _))
.WillByDefault(Return(Blob{'f', 'a', 'k', 'e'}));
auto const ownerDir = CreateOwnerDirLedgerObject(
{ripple::uint256{INDEX1},
ripple::uint256{INDEX2},
ripple::uint256{INDEX2}},
INDEX1);
auto const ownerDirKk =
ripple::keylet::ownerDir(GetAccountIDWithString(ACCOUNT)).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(ownerDirKk, 30, _))
.WillByDefault(Return(ownerDir.getSerializer().peekData()));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(2);
// ACCOUNT can receive USD 10 from ACCOUNT2 and send USD 20 to ACCOUNT2, now
// the balance is 100, ACCOUNT can only send USD to ACCOUNT2
auto const line1 = CreateRippleStateLedgerObject(
ACCOUNT, "USD", ISSUER, 100, ACCOUNT, 10, ACCOUNT2, 20, TXNID, 123);
// ACCOUNT2 can receive JPY 10 from ACCOUNT and send JPY 20 to ACCOUNT, now
// the balance is 100, ACCOUNT2 can only send JPY to ACCOUNT
auto const line2 = CreateRippleStateLedgerObject(
ACCOUNT, "JPY", ISSUER, 100, ACCOUNT2, 10, ACCOUNT, 20, TXNID, 123);
// ACCOUNT can receive EUR 10 from ACCOUNT and send EUR 20 to ACCOUNT2, now
// the balance is 8, ACCOUNT can receive/send EUR to/from ACCOUNT2
auto const line3 = CreateRippleStateLedgerObject(
ACCOUNT, "EUR", ISSUER, 8, ACCOUNT, 10, ACCOUNT2, 20, TXNID, 123);
std::vector<Blob> bbs;
bbs.push_back(line1.getSerializer().peekData());
bbs.push_back(line2.getSerializer().peekData());
bbs.push_back(line3.getSerializer().peekData());
ON_CALL(*rawBackendPtr, doFetchLedgerObjects).WillByDefault(Return(bbs));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObjects).Times(1);
auto const static input = boost::json::parse(fmt::format(
R"({{
"account":"{}"
}})",
ACCOUNT));
auto const handler = AnyHandler{AccountCurrenciesHandler{mockBackendPtr}};
boost::asio::spawn(ctx, [&](boost::asio::yield_context yield) {
auto const output = handler.process(input, yield);
ASSERT_TRUE(output);
EXPECT_EQ(*output, json::parse(OUTPUT));
});
ctx.run();
}
TEST_F(RPCAccountCurrenciesHandlerTest, RequestViaLegderHash)
{
auto const rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get());
mockBackendPtr->updateRange(10); // min
mockBackendPtr->updateRange(30); // max
// return valid ledgerinfo
auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, 30);
EXPECT_CALL(*rawBackendPtr, fetchLedgerByHash).Times(1);
ON_CALL(*rawBackendPtr, fetchLedgerByHash(ripple::uint256{LEDGERHASH}, _))
.WillByDefault(Return(ledgerinfo));
// return valid account
auto const accountKk =
ripple::keylet::account(GetAccountIDWithString(ACCOUNT)).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(accountKk, 30, _))
.WillByDefault(Return(Blob{'f', 'a', 'k', 'e'}));
auto const ownerDir =
CreateOwnerDirLedgerObject({ripple::uint256{INDEX1}}, INDEX1);
auto const ownerDirKk =
ripple::keylet::ownerDir(GetAccountIDWithString(ACCOUNT)).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(ownerDirKk, 30, _))
.WillByDefault(Return(ownerDir.getSerializer().peekData()));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(2);
std::vector<Blob> bbs;
auto const line1 = CreateRippleStateLedgerObject(
ACCOUNT, "USD", ISSUER, 100, ACCOUNT, 10, ACCOUNT2, 20, TXNID, 123);
bbs.push_back(line1.getSerializer().peekData());
ON_CALL(*rawBackendPtr, doFetchLedgerObjects).WillByDefault(Return(bbs));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObjects).Times(1);
auto const static input = boost::json::parse(fmt::format(
R"({{
"account":"{}",
"ledger_hash":"{}"
}})",
ACCOUNT,
LEDGERHASH));
auto const handler = AnyHandler{AccountCurrenciesHandler{mockBackendPtr}};
boost::asio::spawn(ctx, [&](boost::asio::yield_context yield) {
auto const output = handler.process(input, yield);
ASSERT_TRUE(output);
});
ctx.run();
}
TEST_F(RPCAccountCurrenciesHandlerTest, RequestViaLegderSeq)
{
auto const rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get());
mockBackendPtr->updateRange(10); // min
mockBackendPtr->updateRange(30); // max
auto const ledgerSeq = 29;
// return valid ledgerinfo
auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, ledgerSeq);
EXPECT_CALL(*rawBackendPtr, fetchLedgerBySequence).Times(1);
ON_CALL(*rawBackendPtr, fetchLedgerBySequence(ledgerSeq, _))
.WillByDefault(Return(ledgerinfo));
// return valid account
auto const accountKk =
ripple::keylet::account(GetAccountIDWithString(ACCOUNT)).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(accountKk, ledgerSeq, _))
.WillByDefault(Return(Blob{'f', 'a', 'k', 'e'}));
auto const ownerDir =
CreateOwnerDirLedgerObject({ripple::uint256{INDEX1}}, INDEX1);
auto const ownerDirKk =
ripple::keylet::ownerDir(GetAccountIDWithString(ACCOUNT)).key;
ON_CALL(*rawBackendPtr, doFetchLedgerObject(ownerDirKk, ledgerSeq, _))
.WillByDefault(Return(ownerDir.getSerializer().peekData()));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(2);
std::vector<Blob> bbs;
auto const line1 = CreateRippleStateLedgerObject(
ACCOUNT, "USD", ISSUER, 100, ACCOUNT, 10, ACCOUNT2, 20, TXNID, 123);
bbs.push_back(line1.getSerializer().peekData());
ON_CALL(*rawBackendPtr, doFetchLedgerObjects).WillByDefault(Return(bbs));
EXPECT_CALL(*rawBackendPtr, doFetchLedgerObjects).Times(1);
auto const static input = boost::json::parse(fmt::format(
R"({{
"account":"{}",
"ledger_index":{}
}})",
ACCOUNT,
ledgerSeq));
auto const handler = AnyHandler{AccountCurrenciesHandler{mockBackendPtr}};
boost::asio::spawn(ctx, [&](boost::asio::yield_context yield) {
auto const output = handler.process(input, yield);
ASSERT_TRUE(output);
EXPECT_EQ(
(*output).as_object().at("ledger_index").as_uint64(), ledgerSeq);
});
ctx.run();
}

View File

@@ -177,3 +177,23 @@ struct MockBackendTest : virtual public NoLoggerFixture
protected: protected:
std::shared_ptr<BackendInterface> mockBackendPtr; std::shared_ptr<BackendInterface> mockBackendPtr;
}; };
/**
* @brief Fixture with an mock backend and an embedded boost::asio context
* Handler unittest base class
*/
class HandlerBaseTest : public MockBackendTest, public SyncAsioContextTest
{
void
SetUp() override
{
MockBackendTest::SetUp();
SyncAsioContextTest::SetUp();
}
void
TearDown() override
{
SyncAsioContextTest::TearDown();
MockBackendTest::TearDown();
}
};

View File

@@ -153,6 +153,12 @@ CreateCreateOfferTransactionObject(
ripple::Issue ripple::Issue
GetIssue(std::string_view currency, std::string_view issuerId) GetIssue(std::string_view currency, std::string_view issuerId)
{ {
// standard currency
if (currency.size() == 3)
return ripple::Issue(
ripple::to_currency(std::string(currency)),
ripple::parseBase58<ripple::AccountID>(std::string(issuerId))
.value());
return ripple::Issue( return ripple::Issue(
ripple::Currency{currency}, ripple::Currency{currency},
ripple::parseBase58<ripple::AccountID>(std::string(issuerId)).value()); ripple::parseBase58<ripple::AccountID>(std::string(issuerId)).value());
@@ -285,3 +291,33 @@ CreatePaymentChannelLedgerObject(
channel.setFieldVL(ripple::sfPublicKey, slice); channel.setFieldVL(ripple::sfPublicKey, slice);
return channel; return channel;
} }
[[nodiscard]] ripple::STObject
CreateRippleStateLedgerObject(
std::string_view accountId,
std::string_view currency,
std::string_view issuerId,
int balance,
std::string_view lowNodeAccountId,
int lowLimit,
std::string_view highNodeAccountId,
int highLimit,
std::string_view previousTxnId,
uint32_t previousTxnSeq)
{
auto line = ripple::STObject(ripple::sfLedgerEntry);
line.setFieldU16(ripple::sfLedgerEntryType, ripple::ltRIPPLE_STATE);
line.setFieldU32(ripple::sfFlags, 0);
line.setFieldAmount(
ripple::sfBalance,
ripple::STAmount(GetIssue(currency, issuerId), balance));
line.setFieldAmount(
ripple::sfHighLimit,
ripple::STAmount(GetIssue(currency, highNodeAccountId), highLimit));
line.setFieldAmount(
ripple::sfLowLimit,
ripple::STAmount(GetIssue(currency, lowNodeAccountId), lowLimit));
line.setFieldH256(ripple::sfPreviousTxnID, ripple::uint256{previousTxnId});
line.setFieldU32(ripple::sfPreviousTxnLgrSeq, previousTxnSeq);
return line;
}

View File

@@ -156,3 +156,16 @@ CreatePaymentChannelLedgerObject(
uint32_t settleDelay, uint32_t settleDelay,
std::string_view previousTxnId, std::string_view previousTxnId,
uint32_t previousTxnSeq); uint32_t previousTxnSeq);
[[nodiscard]] ripple::STObject
CreateRippleStateLedgerObject(
std::string_view accountId,
std::string_view currency,
std::string_view issuerId,
int balance,
std::string_view lowNodeAccountId,
int lowLimit,
std::string_view highNodeAccountId,
int highLimit,
std::string_view previousTxnId,
uint32_t previousTxnSeq);