mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
1246 lines
48 KiB
C++
1246 lines
48 KiB
C++
//------------------------------------------------------------------------------
|
|
/*
|
|
This file is part of rippled: https://github.com/ripple/rippled
|
|
Copyright (c) 2016 Ripple Labs Inc.
|
|
|
|
Permission to use, copy, modify, and/or 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 <ripple/beast/unit_test.h>
|
|
#include <ripple/protocol/ErrorCodes.h>
|
|
#include <ripple/protocol/TxFlags.h>
|
|
#include <ripple/protocol/jss.h>
|
|
#include <test/jtx.h>
|
|
|
|
namespace ripple {
|
|
|
|
namespace RPC {
|
|
|
|
class AccountLinesRPC_test : public beast::unit_test::suite
|
|
{
|
|
public:
|
|
void
|
|
testAccountLines()
|
|
{
|
|
testcase("acccount_lines");
|
|
|
|
using namespace test::jtx;
|
|
Env env(*this);
|
|
{
|
|
// account_lines with no account.
|
|
auto const lines = env.rpc("json", "account_lines", "{ }");
|
|
BEAST_EXPECT(
|
|
lines[jss::result][jss::error_message] ==
|
|
RPC::missing_field_error(jss::account)[jss::error_message]);
|
|
}
|
|
{
|
|
// account_lines with a malformed account.
|
|
auto const lines = env.rpc(
|
|
"json",
|
|
"account_lines",
|
|
R"({"account": )"
|
|
R"("n9MJkEKHDhy5eTLuHUQeAAjo382frHNbFK4C8hcwN4nwM2SrLdBj"})");
|
|
BEAST_EXPECT(
|
|
lines[jss::result][jss::error_message] ==
|
|
RPC::make_error(rpcBAD_SEED)[jss::error_message]);
|
|
}
|
|
Account const alice{"alice"};
|
|
{
|
|
// account_lines on an unfunded account.
|
|
auto const lines = env.rpc(
|
|
"json",
|
|
"account_lines",
|
|
R"({"account": ")" + alice.human() + R"("})");
|
|
BEAST_EXPECT(
|
|
lines[jss::result][jss::error_message] ==
|
|
RPC::make_error(rpcACT_NOT_FOUND)[jss::error_message]);
|
|
}
|
|
env.fund(XRP(10000), alice);
|
|
env.close();
|
|
LedgerInfo const ledger3Info = env.closed()->info();
|
|
BEAST_EXPECT(ledger3Info.seq == 3);
|
|
|
|
{
|
|
// alice is funded but has no lines. An empty array is returned.
|
|
auto const lines = env.rpc(
|
|
"json",
|
|
"account_lines",
|
|
R"({"account": ")" + alice.human() + R"("})");
|
|
BEAST_EXPECT(lines[jss::result][jss::lines].isArray());
|
|
BEAST_EXPECT(lines[jss::result][jss::lines].size() == 0);
|
|
}
|
|
{
|
|
// Specify a ledger that doesn't exist.
|
|
auto const lines = env.rpc(
|
|
"json",
|
|
"account_lines",
|
|
R"({"account": ")" + alice.human() +
|
|
R"(", )"
|
|
R"("ledger_index": "nonsense"})");
|
|
BEAST_EXPECT(
|
|
lines[jss::result][jss::error_message] ==
|
|
"ledgerIndexMalformed");
|
|
}
|
|
{
|
|
// Specify a different ledger that doesn't exist.
|
|
auto const lines = env.rpc(
|
|
"json",
|
|
"account_lines",
|
|
R"({"account": ")" + alice.human() +
|
|
R"(", )"
|
|
R"("ledger_index": 50000})");
|
|
BEAST_EXPECT(
|
|
lines[jss::result][jss::error_message] == "ledgerNotFound");
|
|
}
|
|
// Create trust lines to share with alice.
|
|
Account const gw1{"gw1"};
|
|
env.fund(XRP(10000), gw1);
|
|
std::vector<IOU> gw1Currencies;
|
|
|
|
for (char c = 0; c <= ('Z' - 'A'); ++c)
|
|
{
|
|
// gw1 currencies have names "YAA" -> "YAZ".
|
|
gw1Currencies.push_back(
|
|
gw1[std::string("YA") + static_cast<char>('A' + c)]);
|
|
IOU const& gw1Currency = gw1Currencies.back();
|
|
|
|
// Establish trust lines.
|
|
env(trust(alice, gw1Currency(100 + c)));
|
|
env(pay(gw1, alice, gw1Currency(50 + c)));
|
|
}
|
|
env.close();
|
|
LedgerInfo const ledger4Info = env.closed()->info();
|
|
BEAST_EXPECT(ledger4Info.seq == 4);
|
|
|
|
// Add another set of trust lines in another ledger so we can see
|
|
// differences in historic ledgers.
|
|
Account const gw2{"gw2"};
|
|
env.fund(XRP(10000), gw2);
|
|
|
|
// gw2 requires authorization.
|
|
env(fset(gw2, asfRequireAuth));
|
|
env.close();
|
|
std::vector<IOU> gw2Currencies;
|
|
|
|
for (char c = 0; c <= ('Z' - 'A'); ++c)
|
|
{
|
|
// gw2 currencies have names "ZAA" -> "ZAZ".
|
|
gw2Currencies.push_back(
|
|
gw2[std::string("ZA") + static_cast<char>('A' + c)]);
|
|
IOU const& gw2Currency = gw2Currencies.back();
|
|
|
|
// Establish trust lines.
|
|
env(trust(alice, gw2Currency(200 + c)));
|
|
env(trust(gw2, gw2Currency(0), alice, tfSetfAuth));
|
|
env.close();
|
|
env(pay(gw2, alice, gw2Currency(100 + c)));
|
|
env.close();
|
|
|
|
// Set flags on gw2 trust lines so we can look for them.
|
|
env(trust(alice, gw2Currency(0), gw2, tfSetNoRipple | tfSetFreeze));
|
|
}
|
|
env.close();
|
|
LedgerInfo const ledger58Info = env.closed()->info();
|
|
BEAST_EXPECT(ledger58Info.seq == 58);
|
|
|
|
// A re-usable test for historic ledgers.
|
|
auto testAccountLinesHistory = [this, &env](
|
|
Account const& account,
|
|
LedgerInfo const& info,
|
|
int count) {
|
|
// Get account_lines by ledger index.
|
|
auto const linesSeq = env.rpc(
|
|
"json",
|
|
"account_lines",
|
|
R"({"account": ")" + account.human() +
|
|
R"(", )"
|
|
R"("ledger_index": )" +
|
|
std::to_string(info.seq) + "}");
|
|
BEAST_EXPECT(linesSeq[jss::result][jss::lines].isArray());
|
|
BEAST_EXPECT(linesSeq[jss::result][jss::lines].size() == count);
|
|
|
|
// Get account_lines by ledger hash.
|
|
auto const linesHash = env.rpc(
|
|
"json",
|
|
"account_lines",
|
|
R"({"account": ")" + account.human() +
|
|
R"(", )"
|
|
R"("ledger_hash": ")" +
|
|
to_string(info.hash) + R"("})");
|
|
BEAST_EXPECT(linesHash[jss::result][jss::lines].isArray());
|
|
BEAST_EXPECT(linesHash[jss::result][jss::lines].size() == count);
|
|
};
|
|
|
|
// Alice should have no trust lines in ledger 3.
|
|
testAccountLinesHistory(alice, ledger3Info, 0);
|
|
|
|
// Alice should have 26 trust lines in ledger 4.
|
|
testAccountLinesHistory(alice, ledger4Info, 26);
|
|
|
|
// Alice should have 52 trust lines in ledger 58.
|
|
testAccountLinesHistory(alice, ledger58Info, 52);
|
|
|
|
{
|
|
// Surprisingly, it's valid to specify both index and hash, in
|
|
// which case the hash wins.
|
|
auto const lines = env.rpc(
|
|
"json",
|
|
"account_lines",
|
|
R"({"account": ")" + alice.human() +
|
|
R"(", )"
|
|
R"("ledger_hash": ")" +
|
|
to_string(ledger4Info.hash) +
|
|
R"(", )"
|
|
R"("ledger_index": )" +
|
|
std::to_string(ledger58Info.seq) + "}");
|
|
BEAST_EXPECT(lines[jss::result][jss::lines].isArray());
|
|
BEAST_EXPECT(lines[jss::result][jss::lines].size() == 26);
|
|
}
|
|
{
|
|
// alice should have 52 trust lines in the current ledger.
|
|
auto const lines = env.rpc(
|
|
"json",
|
|
"account_lines",
|
|
R"({"account": ")" + alice.human() + R"("})");
|
|
BEAST_EXPECT(lines[jss::result][jss::lines].isArray());
|
|
BEAST_EXPECT(lines[jss::result][jss::lines].size() == 52);
|
|
}
|
|
{
|
|
// alice should have 26 trust lines with gw1.
|
|
auto const lines = env.rpc(
|
|
"json",
|
|
"account_lines",
|
|
R"({"account": ")" + alice.human() +
|
|
R"(", )"
|
|
R"("peer": ")" +
|
|
gw1.human() + R"("})");
|
|
BEAST_EXPECT(lines[jss::result][jss::lines].isArray());
|
|
BEAST_EXPECT(lines[jss::result][jss::lines].size() == 26);
|
|
}
|
|
{
|
|
// Use a malformed peer.
|
|
auto const lines = env.rpc(
|
|
"json",
|
|
"account_lines",
|
|
R"({"account": ")" + alice.human() +
|
|
R"(", )"
|
|
R"("peer": )"
|
|
R"("n9MJkEKHDhy5eTLuHUQeAAjo382frHNbFK4C8hcwN4nwM2SrLdBj"})");
|
|
BEAST_EXPECT(
|
|
lines[jss::result][jss::error_message] ==
|
|
RPC::make_error(rpcBAD_SEED)[jss::error_message]);
|
|
}
|
|
{
|
|
// A negative limit should fail.
|
|
auto const lines = env.rpc(
|
|
"json",
|
|
"account_lines",
|
|
R"({"account": ")" + alice.human() +
|
|
R"(", )"
|
|
R"("limit": -1})");
|
|
BEAST_EXPECT(
|
|
lines[jss::result][jss::error_message] ==
|
|
RPC::expected_field_message(jss::limit, "unsigned integer"));
|
|
}
|
|
{
|
|
// Limit the response to 1 trust line.
|
|
auto const linesA = env.rpc(
|
|
"json",
|
|
"account_lines",
|
|
R"({"account": ")" + alice.human() +
|
|
R"(", )"
|
|
R"("limit": 1})");
|
|
BEAST_EXPECT(linesA[jss::result][jss::lines].isArray());
|
|
BEAST_EXPECT(linesA[jss::result][jss::lines].size() == 1);
|
|
|
|
// Pick up from where the marker left off. We should get 51.
|
|
auto marker = linesA[jss::result][jss::marker].asString();
|
|
auto const linesB = env.rpc(
|
|
"json",
|
|
"account_lines",
|
|
R"({"account": ")" + alice.human() +
|
|
R"(", )"
|
|
R"("marker": ")" +
|
|
marker + R"("})");
|
|
BEAST_EXPECT(linesB[jss::result][jss::lines].isArray());
|
|
BEAST_EXPECT(linesB[jss::result][jss::lines].size() == 51);
|
|
|
|
// Go again from where the marker left off, but set a limit of 3.
|
|
auto const linesC = env.rpc(
|
|
"json",
|
|
"account_lines",
|
|
R"({"account": ")" + alice.human() +
|
|
R"(", )"
|
|
R"("limit": 3, )"
|
|
R"("marker": ")" +
|
|
marker + R"("})");
|
|
BEAST_EXPECT(linesC[jss::result][jss::lines].isArray());
|
|
BEAST_EXPECT(linesC[jss::result][jss::lines].size() == 3);
|
|
|
|
// Mess with the marker so it becomes bad and check for the error.
|
|
marker[5] = marker[5] == '7' ? '8' : '7';
|
|
auto const linesD = env.rpc(
|
|
"json",
|
|
"account_lines",
|
|
R"({"account": ")" + alice.human() +
|
|
R"(", )"
|
|
R"("marker": ")" +
|
|
marker + R"("})");
|
|
BEAST_EXPECT(
|
|
linesD[jss::result][jss::error_message] ==
|
|
RPC::make_error(rpcINVALID_PARAMS)[jss::error_message]);
|
|
}
|
|
{
|
|
// A non-string marker should also fail.
|
|
auto const lines = env.rpc(
|
|
"json",
|
|
"account_lines",
|
|
R"({"account": ")" + alice.human() +
|
|
R"(", )"
|
|
R"("marker": true})");
|
|
BEAST_EXPECT(
|
|
lines[jss::result][jss::error_message] ==
|
|
RPC::expected_field_message(jss::marker, "string"));
|
|
}
|
|
{
|
|
// Check that the flags we expect from alice to gw2 are present.
|
|
auto const lines = env.rpc(
|
|
"json",
|
|
"account_lines",
|
|
R"({"account": ")" + alice.human() +
|
|
R"(", )"
|
|
R"("limit": 10, )"
|
|
R"("peer": ")" +
|
|
gw2.human() + R"("})");
|
|
auto const& line = lines[jss::result][jss::lines][0u];
|
|
BEAST_EXPECT(line[jss::freeze].asBool() == true);
|
|
BEAST_EXPECT(line[jss::no_ripple].asBool() == true);
|
|
BEAST_EXPECT(line[jss::peer_authorized].asBool() == true);
|
|
}
|
|
{
|
|
// Check that the flags we expect from gw2 to alice are present.
|
|
auto const linesA = env.rpc(
|
|
"json",
|
|
"account_lines",
|
|
R"({"account": ")" + gw2.human() +
|
|
R"(", )"
|
|
R"("limit": 1, )"
|
|
R"("peer": ")" +
|
|
alice.human() + R"("})");
|
|
auto const& lineA = linesA[jss::result][jss::lines][0u];
|
|
BEAST_EXPECT(lineA[jss::freeze_peer].asBool() == true);
|
|
BEAST_EXPECT(lineA[jss::no_ripple_peer].asBool() == true);
|
|
BEAST_EXPECT(lineA[jss::authorized].asBool() == true);
|
|
|
|
// Continue from the returned marker to make sure that works.
|
|
BEAST_EXPECT(linesA[jss::result].isMember(jss::marker));
|
|
auto const marker = linesA[jss::result][jss::marker].asString();
|
|
auto const linesB = env.rpc(
|
|
"json",
|
|
"account_lines",
|
|
R"({"account": ")" + gw2.human() +
|
|
R"(", )"
|
|
R"("limit": 25, )"
|
|
R"("marker": ")" +
|
|
marker +
|
|
R"(", )"
|
|
R"("peer": ")" +
|
|
alice.human() + R"("})");
|
|
BEAST_EXPECT(linesB[jss::result][jss::lines].isArray());
|
|
BEAST_EXPECT(linesB[jss::result][jss::lines].size() == 25);
|
|
BEAST_EXPECT(!linesB[jss::result].isMember(jss::marker));
|
|
}
|
|
}
|
|
|
|
void
|
|
testAccountLinesMarker()
|
|
{
|
|
testcase("Entry pointed to by marker is not owned by account");
|
|
using namespace test::jtx;
|
|
Env env(*this);
|
|
|
|
// The goal of this test is observe account_lines RPC calls return an
|
|
// error message when the SLE pointed to by the marker is not owned by
|
|
// the Account being traversed.
|
|
//
|
|
// To start, we'll create an environment with some trust lines, offers
|
|
// and a signers list.
|
|
Account const alice{"alice"};
|
|
Account const becky{"becky"};
|
|
Account const gw1{"gw1"};
|
|
env.fund(XRP(10000), alice, becky, gw1);
|
|
env.close();
|
|
|
|
// Give alice a SignerList.
|
|
Account const bogie{"bogie"};
|
|
env(signers(alice, 2, {{bogie, 3}}));
|
|
env.close();
|
|
|
|
auto const EUR = gw1["EUR"];
|
|
env(trust(alice, EUR(200)));
|
|
env(trust(becky, EUR(200)));
|
|
env.close();
|
|
|
|
// Get all account objects for alice and verify that her
|
|
// signerlist is first. This is only a (reliable) coincidence of
|
|
// object naming. So if any of alice's objects are renamed this
|
|
// may fail.
|
|
Json::Value const aliceObjects = env.rpc(
|
|
"json",
|
|
"account_objects",
|
|
R"({"account": ")" + alice.human() +
|
|
R"(", )"
|
|
R"("limit": 10})");
|
|
Json::Value const& aliceSignerList =
|
|
aliceObjects[jss::result][jss::account_objects][0u];
|
|
if (!(aliceSignerList[sfLedgerEntryType.jsonName] == jss::SignerList))
|
|
{
|
|
fail(
|
|
"alice's account objects are misordered. "
|
|
"Please reorder the objects so the SignerList is first.",
|
|
__FILE__,
|
|
__LINE__);
|
|
return;
|
|
}
|
|
|
|
// Get account_lines for alice. Limit at 1, so we get a marker
|
|
// pointing to her SignerList.
|
|
auto const aliceLines1 = env.rpc(
|
|
"json",
|
|
"account_lines",
|
|
R"({"account": ")" + alice.human() + R"(", "limit": 1})");
|
|
BEAST_EXPECT(aliceLines1[jss::result].isMember(jss::marker));
|
|
|
|
// Verify that the marker points at the signer list.
|
|
std::string const aliceMarker =
|
|
aliceLines1[jss::result][jss::marker].asString();
|
|
std::string const markerIndex =
|
|
aliceMarker.substr(0, aliceMarker.find(','));
|
|
BEAST_EXPECT(markerIndex == aliceSignerList[jss::index].asString());
|
|
|
|
// When we fetch Alice's remaining lines we should find one and no more.
|
|
auto const aliceLines2 = env.rpc(
|
|
"json",
|
|
"account_lines",
|
|
R"({"account": ")" + alice.human() + R"(", "marker": ")" +
|
|
aliceMarker + R"("})");
|
|
BEAST_EXPECT(aliceLines2[jss::result][jss::lines].size() == 1);
|
|
BEAST_EXPECT(!aliceLines2[jss::result].isMember(jss::marker));
|
|
|
|
// Get account lines for beckys account, using alices SignerList as a
|
|
// marker. This should cause an error.
|
|
auto const beckyLines = env.rpc(
|
|
"json",
|
|
"account_lines",
|
|
R"({"account": ")" + becky.human() + R"(", "marker": ")" +
|
|
aliceMarker + R"("})");
|
|
BEAST_EXPECT(beckyLines[jss::result].isMember(jss::error_message));
|
|
}
|
|
|
|
void
|
|
testAccountLineDelete()
|
|
{
|
|
testcase("Entry pointed to by marker is removed");
|
|
using namespace test::jtx;
|
|
Env env(*this);
|
|
|
|
// The goal here is to observe account_lines marker behavior if the
|
|
// entry pointed at by a returned marker is removed from the ledger.
|
|
//
|
|
// It isn't easy to explicitly delete a trust line, so we do so in a
|
|
// round-about fashion. It takes 4 actors:
|
|
// o Gateway gw1 issues USD
|
|
// o alice offers to buy 100 USD for 100 XRP.
|
|
// o becky offers to sell 100 USD for 100 XRP.
|
|
// There will now be an inferred trustline between alice and gw1.
|
|
// o alice pays her 100 USD to cheri.
|
|
// alice should now have no USD and no trustline to gw1.
|
|
Account const alice{"alice"};
|
|
Account const becky{"becky"};
|
|
Account const cheri{"cheri"};
|
|
Account const gw1{"gw1"};
|
|
Account const gw2{"gw2"};
|
|
env.fund(XRP(10000), alice, becky, cheri, gw1, gw2);
|
|
env.close();
|
|
|
|
auto const USD = gw1["USD"];
|
|
auto const AUD = gw1["AUD"];
|
|
auto const EUR = gw2["EUR"];
|
|
env(trust(alice, USD(200)));
|
|
env(trust(alice, AUD(200)));
|
|
env(trust(becky, EUR(200)));
|
|
env(trust(cheri, EUR(200)));
|
|
env.close();
|
|
|
|
// becky gets 100 USD from gw1.
|
|
env(pay(gw2, becky, EUR(100)));
|
|
env.close();
|
|
|
|
// alice offers to buy 100 EUR for 100 XRP.
|
|
env(offer(alice, EUR(100), XRP(100)));
|
|
env.close();
|
|
|
|
// becky offers to buy 100 XRP for 100 EUR.
|
|
env(offer(becky, XRP(100), EUR(100)));
|
|
env.close();
|
|
|
|
// Get account_lines for alice. Limit at 1, so we get a marker.
|
|
auto const linesBeg = env.rpc(
|
|
"json",
|
|
"account_lines",
|
|
R"({"account": ")" + alice.human() +
|
|
R"(", )"
|
|
R"("limit": 2})");
|
|
BEAST_EXPECT(
|
|
linesBeg[jss::result][jss::lines][0u][jss::currency] == "USD");
|
|
BEAST_EXPECT(linesBeg[jss::result].isMember(jss::marker));
|
|
|
|
// alice pays 100 EUR to cheri.
|
|
env(pay(alice, cheri, EUR(100)));
|
|
env.close();
|
|
|
|
// Since alice paid all her EUR to cheri, alice should no longer
|
|
// have a trust line to gw1. So the old marker should now be invalid.
|
|
auto const linesEnd = env.rpc(
|
|
"json",
|
|
"account_lines",
|
|
R"({"account": ")" + alice.human() +
|
|
R"(", )"
|
|
R"("marker": ")" +
|
|
linesBeg[jss::result][jss::marker].asString() + R"("})");
|
|
BEAST_EXPECT(
|
|
linesEnd[jss::result][jss::error_message] ==
|
|
RPC::make_error(rpcINVALID_PARAMS)[jss::error_message]);
|
|
}
|
|
|
|
// test API V2
|
|
void
|
|
testAccountLines2()
|
|
{
|
|
testcase("V2: acccount_lines");
|
|
|
|
using namespace test::jtx;
|
|
Env env(*this);
|
|
{
|
|
// account_lines with mal-formed json2 (missing id field).
|
|
auto const lines = env.rpc(
|
|
"json2",
|
|
"{ "
|
|
R"("method" : "account_lines",)"
|
|
R"("jsonrpc" : "2.0",)"
|
|
R"("ripplerpc" : "2.0")"
|
|
" }");
|
|
BEAST_EXPECT(
|
|
lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
|
|
BEAST_EXPECT(
|
|
lines.isMember(jss::ripplerpc) &&
|
|
lines[jss::ripplerpc] == "2.0");
|
|
}
|
|
{
|
|
// account_lines with no account.
|
|
auto const lines = env.rpc(
|
|
"json2",
|
|
"{ "
|
|
R"("method" : "account_lines",)"
|
|
R"("jsonrpc" : "2.0",)"
|
|
R"("ripplerpc" : "2.0",)"
|
|
R"("id" : 5)"
|
|
" }");
|
|
BEAST_EXPECT(
|
|
lines[jss::error][jss::message] ==
|
|
RPC::missing_field_error(jss::account)[jss::error_message]);
|
|
BEAST_EXPECT(
|
|
lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
|
|
BEAST_EXPECT(
|
|
lines.isMember(jss::ripplerpc) &&
|
|
lines[jss::ripplerpc] == "2.0");
|
|
BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
|
|
}
|
|
{
|
|
// account_lines with a malformed account.
|
|
auto const lines = env.rpc(
|
|
"json2",
|
|
"{ "
|
|
R"("method" : "account_lines",)"
|
|
R"("jsonrpc" : "2.0",)"
|
|
R"("ripplerpc" : "2.0",)"
|
|
R"("id" : 5,)"
|
|
R"("params": )"
|
|
R"({"account": )"
|
|
R"("n9MJkEKHDhy5eTLuHUQeAAjo382frHNbFK4C8hcwN4nwM2SrLdBj"}})");
|
|
BEAST_EXPECT(
|
|
lines[jss::error][jss::message] ==
|
|
RPC::make_error(rpcBAD_SEED)[jss::error_message]);
|
|
BEAST_EXPECT(
|
|
lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
|
|
BEAST_EXPECT(
|
|
lines.isMember(jss::ripplerpc) &&
|
|
lines[jss::ripplerpc] == "2.0");
|
|
BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
|
|
}
|
|
Account const alice{"alice"};
|
|
{
|
|
// account_lines on an unfunded account.
|
|
auto const lines = env.rpc(
|
|
"json2",
|
|
"{ "
|
|
R"("method" : "account_lines",)"
|
|
R"("jsonrpc" : "2.0",)"
|
|
R"("ripplerpc" : "2.0",)"
|
|
R"("id" : 5,)"
|
|
R"("params": )"
|
|
R"({"account": ")" +
|
|
alice.human() + R"("}})");
|
|
BEAST_EXPECT(
|
|
lines[jss::error][jss::message] ==
|
|
RPC::make_error(rpcACT_NOT_FOUND)[jss::error_message]);
|
|
BEAST_EXPECT(
|
|
lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
|
|
BEAST_EXPECT(
|
|
lines.isMember(jss::ripplerpc) &&
|
|
lines[jss::ripplerpc] == "2.0");
|
|
BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
|
|
}
|
|
env.fund(XRP(10000), alice);
|
|
env.close();
|
|
LedgerInfo const ledger3Info = env.closed()->info();
|
|
BEAST_EXPECT(ledger3Info.seq == 3);
|
|
|
|
{
|
|
// alice is funded but has no lines. An empty array is returned.
|
|
auto const lines = env.rpc(
|
|
"json2",
|
|
"{ "
|
|
R"("method" : "account_lines",)"
|
|
R"("jsonrpc" : "2.0",)"
|
|
R"("ripplerpc" : "2.0",)"
|
|
R"("id" : 5,)"
|
|
R"("params": )"
|
|
R"({"account": ")" +
|
|
alice.human() + R"("}})");
|
|
BEAST_EXPECT(lines[jss::result][jss::lines].isArray());
|
|
BEAST_EXPECT(lines[jss::result][jss::lines].size() == 0);
|
|
BEAST_EXPECT(
|
|
lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
|
|
BEAST_EXPECT(
|
|
lines.isMember(jss::ripplerpc) &&
|
|
lines[jss::ripplerpc] == "2.0");
|
|
BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
|
|
}
|
|
{
|
|
// Specify a ledger that doesn't exist.
|
|
auto const lines = env.rpc(
|
|
"json2",
|
|
"{ "
|
|
R"("method" : "account_lines",)"
|
|
R"("jsonrpc" : "2.0",)"
|
|
R"("ripplerpc" : "2.0",)"
|
|
R"("id" : 5,)"
|
|
R"("params": )"
|
|
R"({"account": ")" +
|
|
alice.human() +
|
|
R"(", )"
|
|
R"("ledger_index": "nonsense"}})");
|
|
BEAST_EXPECT(
|
|
lines[jss::error][jss::message] == "ledgerIndexMalformed");
|
|
BEAST_EXPECT(
|
|
lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
|
|
BEAST_EXPECT(
|
|
lines.isMember(jss::ripplerpc) &&
|
|
lines[jss::ripplerpc] == "2.0");
|
|
BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
|
|
}
|
|
{
|
|
// Specify a different ledger that doesn't exist.
|
|
auto const lines = env.rpc(
|
|
"json2",
|
|
"{ "
|
|
R"("method" : "account_lines",)"
|
|
R"("jsonrpc" : "2.0",)"
|
|
R"("ripplerpc" : "2.0",)"
|
|
R"("id" : 5,)"
|
|
R"("params": )"
|
|
R"({"account": ")" +
|
|
alice.human() +
|
|
R"(", )"
|
|
R"("ledger_index": 50000}})");
|
|
BEAST_EXPECT(lines[jss::error][jss::message] == "ledgerNotFound");
|
|
BEAST_EXPECT(
|
|
lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
|
|
BEAST_EXPECT(
|
|
lines.isMember(jss::ripplerpc) &&
|
|
lines[jss::ripplerpc] == "2.0");
|
|
BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
|
|
}
|
|
// Create trust lines to share with alice.
|
|
Account const gw1{"gw1"};
|
|
env.fund(XRP(10000), gw1);
|
|
std::vector<IOU> gw1Currencies;
|
|
|
|
for (char c = 0; c <= ('Z' - 'A'); ++c)
|
|
{
|
|
// gw1 currencies have names "YAA" -> "YAZ".
|
|
gw1Currencies.push_back(
|
|
gw1[std::string("YA") + static_cast<char>('A' + c)]);
|
|
IOU const& gw1Currency = gw1Currencies.back();
|
|
|
|
// Establish trust lines.
|
|
env(trust(alice, gw1Currency(100 + c)));
|
|
env(pay(gw1, alice, gw1Currency(50 + c)));
|
|
}
|
|
env.close();
|
|
LedgerInfo const ledger4Info = env.closed()->info();
|
|
BEAST_EXPECT(ledger4Info.seq == 4);
|
|
|
|
// Add another set of trust lines in another ledger so we can see
|
|
// differences in historic ledgers.
|
|
Account const gw2{"gw2"};
|
|
env.fund(XRP(10000), gw2);
|
|
|
|
// gw2 requires authorization.
|
|
env(fset(gw2, asfRequireAuth));
|
|
env.close();
|
|
std::vector<IOU> gw2Currencies;
|
|
|
|
for (char c = 0; c <= ('Z' - 'A'); ++c)
|
|
{
|
|
// gw2 currencies have names "ZAA" -> "ZAZ".
|
|
gw2Currencies.push_back(
|
|
gw2[std::string("ZA") + static_cast<char>('A' + c)]);
|
|
IOU const& gw2Currency = gw2Currencies.back();
|
|
|
|
// Establish trust lines.
|
|
env(trust(alice, gw2Currency(200 + c)));
|
|
env(trust(gw2, gw2Currency(0), alice, tfSetfAuth));
|
|
env.close();
|
|
env(pay(gw2, alice, gw2Currency(100 + c)));
|
|
env.close();
|
|
|
|
// Set flags on gw2 trust lines so we can look for them.
|
|
env(trust(alice, gw2Currency(0), gw2, tfSetNoRipple | tfSetFreeze));
|
|
}
|
|
env.close();
|
|
LedgerInfo const ledger58Info = env.closed()->info();
|
|
BEAST_EXPECT(ledger58Info.seq == 58);
|
|
|
|
// A re-usable test for historic ledgers.
|
|
auto testAccountLinesHistory = [this, &env](
|
|
Account const& account,
|
|
LedgerInfo const& info,
|
|
int count) {
|
|
// Get account_lines by ledger index.
|
|
auto const linesSeq = env.rpc(
|
|
"json2",
|
|
"{ "
|
|
R"("method" : "account_lines",)"
|
|
R"("jsonrpc" : "2.0",)"
|
|
R"("ripplerpc" : "2.0",)"
|
|
R"("id" : 5,)"
|
|
R"("params": )"
|
|
R"({"account": ")" +
|
|
account.human() +
|
|
R"(", )"
|
|
R"("ledger_index": )" +
|
|
std::to_string(info.seq) + "}}");
|
|
BEAST_EXPECT(linesSeq[jss::result][jss::lines].isArray());
|
|
BEAST_EXPECT(linesSeq[jss::result][jss::lines].size() == count);
|
|
BEAST_EXPECT(
|
|
linesSeq.isMember(jss::jsonrpc) &&
|
|
linesSeq[jss::jsonrpc] == "2.0");
|
|
BEAST_EXPECT(
|
|
linesSeq.isMember(jss::ripplerpc) &&
|
|
linesSeq[jss::ripplerpc] == "2.0");
|
|
BEAST_EXPECT(linesSeq.isMember(jss::id) && linesSeq[jss::id] == 5);
|
|
|
|
// Get account_lines by ledger hash.
|
|
auto const linesHash = env.rpc(
|
|
"json2",
|
|
"{ "
|
|
R"("method" : "account_lines",)"
|
|
R"("jsonrpc" : "2.0",)"
|
|
R"("ripplerpc" : "2.0",)"
|
|
R"("id" : 5,)"
|
|
R"("params": )"
|
|
R"({"account": ")" +
|
|
account.human() +
|
|
R"(", )"
|
|
R"("ledger_hash": ")" +
|
|
to_string(info.hash) + R"("}})");
|
|
BEAST_EXPECT(linesHash[jss::result][jss::lines].isArray());
|
|
BEAST_EXPECT(linesHash[jss::result][jss::lines].size() == count);
|
|
BEAST_EXPECT(
|
|
linesHash.isMember(jss::jsonrpc) &&
|
|
linesHash[jss::jsonrpc] == "2.0");
|
|
BEAST_EXPECT(
|
|
linesHash.isMember(jss::ripplerpc) &&
|
|
linesHash[jss::ripplerpc] == "2.0");
|
|
BEAST_EXPECT(
|
|
linesHash.isMember(jss::id) && linesHash[jss::id] == 5);
|
|
};
|
|
|
|
// Alice should have no trust lines in ledger 3.
|
|
testAccountLinesHistory(alice, ledger3Info, 0);
|
|
|
|
// Alice should have 26 trust lines in ledger 4.
|
|
testAccountLinesHistory(alice, ledger4Info, 26);
|
|
|
|
// Alice should have 52 trust lines in ledger 58.
|
|
testAccountLinesHistory(alice, ledger58Info, 52);
|
|
|
|
{
|
|
// Surprisingly, it's valid to specify both index and hash, in
|
|
// which case the hash wins.
|
|
auto const lines = env.rpc(
|
|
"json2",
|
|
"{ "
|
|
R"("method" : "account_lines",)"
|
|
R"("jsonrpc" : "2.0",)"
|
|
R"("ripplerpc" : "2.0",)"
|
|
R"("id" : 5,)"
|
|
R"("params": )"
|
|
R"({"account": ")" +
|
|
alice.human() +
|
|
R"(", )"
|
|
R"("ledger_hash": ")" +
|
|
to_string(ledger4Info.hash) +
|
|
R"(", )"
|
|
R"("ledger_index": )" +
|
|
std::to_string(ledger58Info.seq) + "}}");
|
|
BEAST_EXPECT(lines[jss::result][jss::lines].isArray());
|
|
BEAST_EXPECT(lines[jss::result][jss::lines].size() == 26);
|
|
BEAST_EXPECT(
|
|
lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
|
|
BEAST_EXPECT(
|
|
lines.isMember(jss::ripplerpc) &&
|
|
lines[jss::ripplerpc] == "2.0");
|
|
BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
|
|
}
|
|
{
|
|
// alice should have 52 trust lines in the current ledger.
|
|
auto const lines = env.rpc(
|
|
"json2",
|
|
"{ "
|
|
R"("method" : "account_lines",)"
|
|
R"("jsonrpc" : "2.0",)"
|
|
R"("ripplerpc" : "2.0",)"
|
|
R"("id" : 5,)"
|
|
R"("params": )"
|
|
R"({"account": ")" +
|
|
alice.human() + R"("}})");
|
|
BEAST_EXPECT(lines[jss::result][jss::lines].isArray());
|
|
BEAST_EXPECT(lines[jss::result][jss::lines].size() == 52);
|
|
BEAST_EXPECT(
|
|
lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
|
|
BEAST_EXPECT(
|
|
lines.isMember(jss::ripplerpc) &&
|
|
lines[jss::ripplerpc] == "2.0");
|
|
BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
|
|
}
|
|
{
|
|
// alice should have 26 trust lines with gw1.
|
|
auto const lines = env.rpc(
|
|
"json2",
|
|
"{ "
|
|
R"("method" : "account_lines",)"
|
|
R"("jsonrpc" : "2.0",)"
|
|
R"("ripplerpc" : "2.0",)"
|
|
R"("id" : 5,)"
|
|
R"("params": )"
|
|
R"({"account": ")" +
|
|
alice.human() +
|
|
R"(", )"
|
|
R"("peer": ")" +
|
|
gw1.human() + R"("}})");
|
|
BEAST_EXPECT(lines[jss::result][jss::lines].isArray());
|
|
BEAST_EXPECT(lines[jss::result][jss::lines].size() == 26);
|
|
BEAST_EXPECT(
|
|
lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
|
|
BEAST_EXPECT(
|
|
lines.isMember(jss::ripplerpc) &&
|
|
lines[jss::ripplerpc] == "2.0");
|
|
BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
|
|
}
|
|
{
|
|
// Use a malformed peer.
|
|
auto const lines = env.rpc(
|
|
"json2",
|
|
"{ "
|
|
R"("method" : "account_lines",)"
|
|
R"("jsonrpc" : "2.0",)"
|
|
R"("ripplerpc" : "2.0",)"
|
|
R"("id" : 5,)"
|
|
R"("params": )"
|
|
R"({"account": ")" +
|
|
alice.human() +
|
|
R"(", )"
|
|
R"("peer": )"
|
|
R"("n9MJkEKHDhy5eTLuHUQeAAjo382frHNbFK4C8hcwN4nwM2SrLdBj"}})");
|
|
BEAST_EXPECT(
|
|
lines[jss::error][jss::message] ==
|
|
RPC::make_error(rpcBAD_SEED)[jss::error_message]);
|
|
BEAST_EXPECT(
|
|
lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
|
|
BEAST_EXPECT(
|
|
lines.isMember(jss::ripplerpc) &&
|
|
lines[jss::ripplerpc] == "2.0");
|
|
BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
|
|
}
|
|
{
|
|
// A negative limit should fail.
|
|
auto const lines = env.rpc(
|
|
"json2",
|
|
"{ "
|
|
R"("method" : "account_lines",)"
|
|
R"("jsonrpc" : "2.0",)"
|
|
R"("ripplerpc" : "2.0",)"
|
|
R"("id" : 5,)"
|
|
R"("params": )"
|
|
R"({"account": ")" +
|
|
alice.human() +
|
|
R"(", )"
|
|
R"("limit": -1}})");
|
|
BEAST_EXPECT(
|
|
lines[jss::error][jss::message] ==
|
|
RPC::expected_field_message(jss::limit, "unsigned integer"));
|
|
BEAST_EXPECT(
|
|
lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
|
|
BEAST_EXPECT(
|
|
lines.isMember(jss::ripplerpc) &&
|
|
lines[jss::ripplerpc] == "2.0");
|
|
BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
|
|
}
|
|
{
|
|
// Limit the response to 1 trust line.
|
|
auto const linesA = env.rpc(
|
|
"json2",
|
|
"{ "
|
|
R"("method" : "account_lines",)"
|
|
R"("jsonrpc" : "2.0",)"
|
|
R"("ripplerpc" : "2.0",)"
|
|
R"("id" : 5,)"
|
|
R"("params": )"
|
|
R"({"account": ")" +
|
|
alice.human() +
|
|
R"(", )"
|
|
R"("limit": 1}})");
|
|
BEAST_EXPECT(linesA[jss::result][jss::lines].isArray());
|
|
BEAST_EXPECT(linesA[jss::result][jss::lines].size() == 1);
|
|
BEAST_EXPECT(
|
|
linesA.isMember(jss::jsonrpc) && linesA[jss::jsonrpc] == "2.0");
|
|
BEAST_EXPECT(
|
|
linesA.isMember(jss::ripplerpc) &&
|
|
linesA[jss::ripplerpc] == "2.0");
|
|
BEAST_EXPECT(linesA.isMember(jss::id) && linesA[jss::id] == 5);
|
|
|
|
// Pick up from where the marker left off. We should get 51.
|
|
auto marker = linesA[jss::result][jss::marker].asString();
|
|
auto const linesB = env.rpc(
|
|
"json2",
|
|
"{ "
|
|
R"("method" : "account_lines",)"
|
|
R"("jsonrpc" : "2.0",)"
|
|
R"("ripplerpc" : "2.0",)"
|
|
R"("id" : 5,)"
|
|
R"("params": )"
|
|
R"({"account": ")" +
|
|
alice.human() +
|
|
R"(", )"
|
|
R"("marker": ")" +
|
|
marker + R"("}})");
|
|
BEAST_EXPECT(linesB[jss::result][jss::lines].isArray());
|
|
BEAST_EXPECT(linesB[jss::result][jss::lines].size() == 51);
|
|
BEAST_EXPECT(
|
|
linesB.isMember(jss::jsonrpc) && linesB[jss::jsonrpc] == "2.0");
|
|
BEAST_EXPECT(
|
|
linesB.isMember(jss::ripplerpc) &&
|
|
linesB[jss::ripplerpc] == "2.0");
|
|
BEAST_EXPECT(linesB.isMember(jss::id) && linesB[jss::id] == 5);
|
|
|
|
// Go again from where the marker left off, but set a limit of 3.
|
|
auto const linesC = env.rpc(
|
|
"json2",
|
|
"{ "
|
|
R"("method" : "account_lines",)"
|
|
R"("jsonrpc" : "2.0",)"
|
|
R"("ripplerpc" : "2.0",)"
|
|
R"("id" : 5,)"
|
|
R"("params": )"
|
|
R"({"account": ")" +
|
|
alice.human() +
|
|
R"(", )"
|
|
R"("limit": 3, )"
|
|
R"("marker": ")" +
|
|
marker + R"("}})");
|
|
BEAST_EXPECT(linesC[jss::result][jss::lines].isArray());
|
|
BEAST_EXPECT(linesC[jss::result][jss::lines].size() == 3);
|
|
BEAST_EXPECT(
|
|
linesC.isMember(jss::jsonrpc) && linesC[jss::jsonrpc] == "2.0");
|
|
BEAST_EXPECT(
|
|
linesC.isMember(jss::ripplerpc) &&
|
|
linesC[jss::ripplerpc] == "2.0");
|
|
BEAST_EXPECT(linesC.isMember(jss::id) && linesC[jss::id] == 5);
|
|
|
|
// Mess with the marker so it becomes bad and check for the error.
|
|
marker[5] = marker[5] == '7' ? '8' : '7';
|
|
auto const linesD = env.rpc(
|
|
"json2",
|
|
"{ "
|
|
R"("method" : "account_lines",)"
|
|
R"("jsonrpc" : "2.0",)"
|
|
R"("ripplerpc" : "2.0",)"
|
|
R"("id" : 5,)"
|
|
R"("params": )"
|
|
R"({"account": ")" +
|
|
alice.human() +
|
|
R"(", )"
|
|
R"("marker": ")" +
|
|
marker + R"("}})");
|
|
BEAST_EXPECT(
|
|
linesD[jss::error][jss::message] ==
|
|
RPC::make_error(rpcINVALID_PARAMS)[jss::error_message]);
|
|
BEAST_EXPECT(
|
|
linesD.isMember(jss::jsonrpc) && linesD[jss::jsonrpc] == "2.0");
|
|
BEAST_EXPECT(
|
|
linesD.isMember(jss::ripplerpc) &&
|
|
linesD[jss::ripplerpc] == "2.0");
|
|
BEAST_EXPECT(linesD.isMember(jss::id) && linesD[jss::id] == 5);
|
|
}
|
|
{
|
|
// A non-string marker should also fail.
|
|
auto const lines = env.rpc(
|
|
"json2",
|
|
"{ "
|
|
R"("method" : "account_lines",)"
|
|
R"("jsonrpc" : "2.0",)"
|
|
R"("ripplerpc" : "2.0",)"
|
|
R"("id" : 5,)"
|
|
R"("params": )"
|
|
R"({"account": ")" +
|
|
alice.human() +
|
|
R"(", )"
|
|
R"("marker": true}})");
|
|
BEAST_EXPECT(
|
|
lines[jss::error][jss::message] ==
|
|
RPC::expected_field_message(jss::marker, "string"));
|
|
BEAST_EXPECT(
|
|
lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
|
|
BEAST_EXPECT(
|
|
lines.isMember(jss::ripplerpc) &&
|
|
lines[jss::ripplerpc] == "2.0");
|
|
BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
|
|
}
|
|
{
|
|
// Check that the flags we expect from alice to gw2 are present.
|
|
auto const lines = env.rpc(
|
|
"json2",
|
|
"{ "
|
|
R"("method" : "account_lines",)"
|
|
R"("jsonrpc" : "2.0",)"
|
|
R"("ripplerpc" : "2.0",)"
|
|
R"("id" : 5,)"
|
|
R"("params": )"
|
|
R"({"account": ")" +
|
|
alice.human() +
|
|
R"(", )"
|
|
R"("limit": 10, )"
|
|
R"("peer": ")" +
|
|
gw2.human() + R"("}})");
|
|
auto const& line = lines[jss::result][jss::lines][0u];
|
|
BEAST_EXPECT(line[jss::freeze].asBool() == true);
|
|
BEAST_EXPECT(line[jss::no_ripple].asBool() == true);
|
|
BEAST_EXPECT(line[jss::peer_authorized].asBool() == true);
|
|
BEAST_EXPECT(
|
|
lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
|
|
BEAST_EXPECT(
|
|
lines.isMember(jss::ripplerpc) &&
|
|
lines[jss::ripplerpc] == "2.0");
|
|
BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
|
|
}
|
|
{
|
|
// Check that the flags we expect from gw2 to alice are present.
|
|
auto const linesA = env.rpc(
|
|
"json2",
|
|
"{ "
|
|
R"("method" : "account_lines",)"
|
|
R"("jsonrpc" : "2.0",)"
|
|
R"("ripplerpc" : "2.0",)"
|
|
R"("id" : 5,)"
|
|
R"("params": )"
|
|
R"({"account": ")" +
|
|
gw2.human() +
|
|
R"(", )"
|
|
R"("limit": 1, )"
|
|
R"("peer": ")" +
|
|
alice.human() + R"("}})");
|
|
auto const& lineA = linesA[jss::result][jss::lines][0u];
|
|
BEAST_EXPECT(lineA[jss::freeze_peer].asBool() == true);
|
|
BEAST_EXPECT(lineA[jss::no_ripple_peer].asBool() == true);
|
|
BEAST_EXPECT(lineA[jss::authorized].asBool() == true);
|
|
BEAST_EXPECT(
|
|
linesA.isMember(jss::jsonrpc) && linesA[jss::jsonrpc] == "2.0");
|
|
BEAST_EXPECT(
|
|
linesA.isMember(jss::ripplerpc) &&
|
|
linesA[jss::ripplerpc] == "2.0");
|
|
BEAST_EXPECT(linesA.isMember(jss::id) && linesA[jss::id] == 5);
|
|
|
|
// Continue from the returned marker to make sure that works.
|
|
BEAST_EXPECT(linesA[jss::result].isMember(jss::marker));
|
|
auto const marker = linesA[jss::result][jss::marker].asString();
|
|
auto const linesB = env.rpc(
|
|
"json2",
|
|
"{ "
|
|
R"("method" : "account_lines",)"
|
|
R"("jsonrpc" : "2.0",)"
|
|
R"("ripplerpc" : "2.0",)"
|
|
R"("id" : 5,)"
|
|
R"("params": )"
|
|
R"({"account": ")" +
|
|
gw2.human() +
|
|
R"(", )"
|
|
R"("limit": 25, )"
|
|
R"("marker": ")" +
|
|
marker +
|
|
R"(", )"
|
|
R"("peer": ")" +
|
|
alice.human() + R"("}})");
|
|
BEAST_EXPECT(linesB[jss::result][jss::lines].isArray());
|
|
BEAST_EXPECT(linesB[jss::result][jss::lines].size() == 25);
|
|
BEAST_EXPECT(!linesB[jss::result].isMember(jss::marker));
|
|
BEAST_EXPECT(
|
|
linesB.isMember(jss::jsonrpc) && linesB[jss::jsonrpc] == "2.0");
|
|
BEAST_EXPECT(
|
|
linesB.isMember(jss::ripplerpc) &&
|
|
linesB[jss::ripplerpc] == "2.0");
|
|
BEAST_EXPECT(linesB.isMember(jss::id) && linesB[jss::id] == 5);
|
|
}
|
|
}
|
|
|
|
// test API V2
|
|
void
|
|
testAccountLineDelete2()
|
|
{
|
|
testcase("V2: account_lines with removed marker");
|
|
|
|
using namespace test::jtx;
|
|
Env env(*this);
|
|
|
|
// The goal here is to observe account_lines marker behavior if the
|
|
// entry pointed at by a returned marker is removed from the ledger.
|
|
//
|
|
// It isn't easy to explicitly delete a trust line, so we do so in a
|
|
// round-about fashion. It takes 4 actors:
|
|
// o Gateway gw1 issues EUR
|
|
// o alice offers to buy 100 EUR for 100 XRP.
|
|
// o becky offers to sell 100 EUR for 100 XRP.
|
|
// There will now be an inferred trustline between alice and gw2.
|
|
// o alice pays her 100 EUR to cheri.
|
|
// alice should now have no EUR and no trustline to gw2.
|
|
Account const alice{"alice"};
|
|
Account const becky{"becky"};
|
|
Account const cheri{"cheri"};
|
|
Account const gw1{"gw1"};
|
|
Account const gw2{"gw2"};
|
|
env.fund(XRP(10000), alice, becky, cheri, gw1, gw2);
|
|
env.close();
|
|
|
|
auto const USD = gw1["USD"];
|
|
auto const AUD = gw1["AUD"];
|
|
auto const EUR = gw2["EUR"];
|
|
env(trust(alice, USD(200)));
|
|
env(trust(alice, AUD(200)));
|
|
env(trust(becky, EUR(200)));
|
|
env(trust(cheri, EUR(200)));
|
|
env.close();
|
|
|
|
// becky gets 100 EUR from gw1.
|
|
env(pay(gw2, becky, EUR(100)));
|
|
env.close();
|
|
|
|
// alice offers to buy 100 EUR for 100 XRP.
|
|
env(offer(alice, EUR(100), XRP(100)));
|
|
env.close();
|
|
|
|
// becky offers to buy 100 XRP for 100 EUR.
|
|
env(offer(becky, XRP(100), EUR(100)));
|
|
env.close();
|
|
|
|
// Get account_lines for alice. Limit at 1, so we get a marker.
|
|
auto const linesBeg = env.rpc(
|
|
"json2",
|
|
"{ "
|
|
R"("method" : "account_lines",)"
|
|
R"("jsonrpc" : "2.0",)"
|
|
R"("ripplerpc" : "2.0",)"
|
|
R"("id" : 5,)"
|
|
R"("params": )"
|
|
R"({"account": ")" +
|
|
alice.human() +
|
|
R"(", )"
|
|
R"("limit": 2}})");
|
|
BEAST_EXPECT(
|
|
linesBeg[jss::result][jss::lines][0u][jss::currency] == "USD");
|
|
BEAST_EXPECT(linesBeg[jss::result].isMember(jss::marker));
|
|
BEAST_EXPECT(
|
|
linesBeg.isMember(jss::jsonrpc) && linesBeg[jss::jsonrpc] == "2.0");
|
|
BEAST_EXPECT(
|
|
linesBeg.isMember(jss::ripplerpc) &&
|
|
linesBeg[jss::ripplerpc] == "2.0");
|
|
BEAST_EXPECT(linesBeg.isMember(jss::id) && linesBeg[jss::id] == 5);
|
|
|
|
// alice pays 100 USD to cheri.
|
|
env(pay(alice, cheri, EUR(100)));
|
|
env.close();
|
|
|
|
// Since alice paid all her EUR to cheri, alice should no longer
|
|
// have a trust line to gw1. So the old marker should now be invalid.
|
|
auto const linesEnd = env.rpc(
|
|
"json2",
|
|
"{ "
|
|
R"("method" : "account_lines",)"
|
|
R"("jsonrpc" : "2.0",)"
|
|
R"("ripplerpc" : "2.0",)"
|
|
R"("id" : 5,)"
|
|
R"("params": )"
|
|
R"({"account": ")" +
|
|
alice.human() +
|
|
R"(", )"
|
|
R"("marker": ")" +
|
|
linesBeg[jss::result][jss::marker].asString() + R"("}})");
|
|
BEAST_EXPECT(
|
|
linesEnd[jss::error][jss::message] ==
|
|
RPC::make_error(rpcINVALID_PARAMS)[jss::error_message]);
|
|
BEAST_EXPECT(
|
|
linesEnd.isMember(jss::jsonrpc) && linesEnd[jss::jsonrpc] == "2.0");
|
|
BEAST_EXPECT(
|
|
linesEnd.isMember(jss::ripplerpc) &&
|
|
linesEnd[jss::ripplerpc] == "2.0");
|
|
BEAST_EXPECT(linesEnd.isMember(jss::id) && linesEnd[jss::id] == 5);
|
|
}
|
|
|
|
void
|
|
run() override
|
|
{
|
|
testAccountLines();
|
|
testAccountLinesMarker();
|
|
testAccountLineDelete();
|
|
testAccountLines2();
|
|
testAccountLineDelete2();
|
|
}
|
|
};
|
|
|
|
BEAST_DEFINE_TESTSUITE(AccountLinesRPC, app, ripple);
|
|
|
|
} // namespace RPC
|
|
} // namespace ripple
|