mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-04 01:06:48 +00:00
This change renames all the `info()` functions to `header()`, since they return `LedgerHeader` structs. It also renames the underlying variables from `info_` to `header_`.
1407 lines
59 KiB
C++
1407 lines
59 KiB
C++
#include <test/jtx.h>
|
|
|
|
#include <xrpl/beast/unit_test.h>
|
|
#include <xrpl/protocol/ErrorCodes.h>
|
|
#include <xrpl/protocol/TxFlags.h>
|
|
#include <xrpl/protocol/jss.h>
|
|
|
|
namespace ripple {
|
|
namespace RPC {
|
|
|
|
class AccountLines_test : public beast::unit_test::suite
|
|
{
|
|
public:
|
|
void
|
|
testAccountLines()
|
|
{
|
|
testcase("account_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.
|
|
Json::Value params;
|
|
params[jss::account] =
|
|
"n9MJkEKHDhy5eTLuHUQeAAjo382frHNbFK4C8hcwN4nwM2SrLdBj";
|
|
auto const lines =
|
|
env.rpc("json", "account_lines", to_string(params));
|
|
BEAST_EXPECT(
|
|
lines[jss::result][jss::error_message] ==
|
|
RPC::make_error(rpcACT_MALFORMED)[jss::error_message]);
|
|
}
|
|
{
|
|
// test account non-string
|
|
auto testInvalidAccountParam = [&](auto const& param) {
|
|
Json::Value params;
|
|
params[jss::account] = param;
|
|
auto jrr = env.rpc(
|
|
"json", "account_lines", to_string(params))[jss::result];
|
|
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
|
|
BEAST_EXPECT(
|
|
jrr[jss::error_message] == "Invalid field 'account'.");
|
|
};
|
|
|
|
testInvalidAccountParam(1);
|
|
testInvalidAccountParam(1.1);
|
|
testInvalidAccountParam(true);
|
|
testInvalidAccountParam(Json::Value(Json::nullValue));
|
|
testInvalidAccountParam(Json::Value(Json::objectValue));
|
|
testInvalidAccountParam(Json::Value(Json::arrayValue));
|
|
}
|
|
Account const alice{"alice"};
|
|
{
|
|
// account_lines on an unfunded account.
|
|
Json::Value params;
|
|
params[jss::account] = alice.human();
|
|
auto const lines =
|
|
env.rpc("json", "account_lines", to_string(params));
|
|
BEAST_EXPECT(
|
|
lines[jss::result][jss::error_message] ==
|
|
RPC::make_error(rpcACT_NOT_FOUND)[jss::error_message]);
|
|
}
|
|
env.fund(XRP(10000), alice);
|
|
env.close();
|
|
LedgerHeader const ledger3Info = env.closed()->header();
|
|
BEAST_EXPECT(ledger3Info.seq == 3);
|
|
|
|
{
|
|
// alice is funded but has no lines. An empty array is returned.
|
|
Json::Value params;
|
|
params[jss::account] = alice.human();
|
|
auto const lines =
|
|
env.rpc("json", "account_lines", to_string(params));
|
|
BEAST_EXPECT(lines[jss::result][jss::lines].isArray());
|
|
BEAST_EXPECT(lines[jss::result][jss::lines].size() == 0);
|
|
}
|
|
{
|
|
// Specify a ledger that doesn't exist.
|
|
Json::Value params;
|
|
params[jss::account] = alice.human();
|
|
params[jss::ledger_index] = "nonsense";
|
|
auto const lines =
|
|
env.rpc("json", "account_lines", to_string(params));
|
|
BEAST_EXPECT(
|
|
lines[jss::result][jss::error_message] ==
|
|
"Invalid field 'ledger_index', not string or number.");
|
|
}
|
|
{
|
|
// Specify a different ledger that doesn't exist.
|
|
Json::Value params;
|
|
params[jss::account] = alice.human();
|
|
params[jss::ledger_index] = 50000;
|
|
auto const lines =
|
|
env.rpc("json", "account_lines", to_string(params));
|
|
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();
|
|
LedgerHeader const ledger4Info = env.closed()->header();
|
|
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 | tfSetDeepFreeze));
|
|
}
|
|
env.close();
|
|
LedgerHeader const ledger58Info = env.closed()->header();
|
|
BEAST_EXPECT(ledger58Info.seq == 58);
|
|
|
|
// A re-usable test for historic ledgers.
|
|
auto testAccountLinesHistory = [this, &env](
|
|
Account const& account,
|
|
LedgerHeader const& info,
|
|
int count) {
|
|
// Get account_lines by ledger index.
|
|
Json::Value paramsSeq;
|
|
paramsSeq[jss::account] = account.human();
|
|
paramsSeq[jss::ledger_index] = info.seq;
|
|
auto const linesSeq =
|
|
env.rpc("json", "account_lines", to_string(paramsSeq));
|
|
BEAST_EXPECT(linesSeq[jss::result][jss::lines].isArray());
|
|
BEAST_EXPECT(linesSeq[jss::result][jss::lines].size() == count);
|
|
|
|
// Get account_lines by ledger hash.
|
|
Json::Value paramsHash;
|
|
paramsHash[jss::account] = account.human();
|
|
paramsHash[jss::ledger_hash] = to_string(info.hash);
|
|
auto const linesHash =
|
|
env.rpc("json", "account_lines", to_string(paramsHash));
|
|
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);
|
|
|
|
{
|
|
// Invalid to specify both index and hash
|
|
Json::Value params;
|
|
params[jss::account] = alice.human();
|
|
params[jss::ledger_hash] = to_string(ledger4Info.hash);
|
|
params[jss::ledger_index] = ledger58Info.seq;
|
|
auto const lines = env.rpc(
|
|
"json", "account_lines", to_string(params))[jss::result];
|
|
BEAST_EXPECT(lines[jss::error] == "invalidParams");
|
|
BEAST_EXPECT(
|
|
lines[jss::error_message] ==
|
|
"Exactly one of 'ledger_hash' or 'ledger_index' can be "
|
|
"specified.");
|
|
}
|
|
{
|
|
// Invalid index
|
|
Json::Value params;
|
|
params[jss::account] = alice.human();
|
|
params[jss::ledger_index] = Json::objectValue;
|
|
auto const lines = env.rpc(
|
|
"json", "account_lines", to_string(params))[jss::result];
|
|
BEAST_EXPECT(lines[jss::error] == "invalidParams");
|
|
BEAST_EXPECT(
|
|
lines[jss::error_message] ==
|
|
"Invalid field 'ledger_index', not string or number.");
|
|
}
|
|
{
|
|
// alice should have 52 trust lines in the current ledger.
|
|
Json::Value params;
|
|
params[jss::account] = alice.human();
|
|
auto const lines =
|
|
env.rpc("json", "account_lines", to_string(params));
|
|
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.
|
|
Json::Value params;
|
|
params[jss::account] = alice.human();
|
|
params[jss::peer] = gw1.human();
|
|
auto const lines =
|
|
env.rpc("json", "account_lines", to_string(params));
|
|
BEAST_EXPECT(lines[jss::result][jss::lines].isArray());
|
|
BEAST_EXPECT(lines[jss::result][jss::lines].size() == 26);
|
|
|
|
// Check no ripple is not set for trustlines between alice and gw1
|
|
auto const& line = lines[jss::result][jss::lines][0u];
|
|
BEAST_EXPECT(!line[jss::no_ripple].isMember(jss::no_ripple));
|
|
}
|
|
{
|
|
// Use a malformed peer.
|
|
Json::Value params;
|
|
params[jss::account] = alice.human();
|
|
params[jss::peer] =
|
|
"n9MJkEKHDhy5eTLuHUQeAAjo382frHNbFK4C8hcwN4nwM2SrLdBj";
|
|
auto const lines =
|
|
env.rpc("json", "account_lines", to_string(params));
|
|
BEAST_EXPECT(
|
|
lines[jss::result][jss::error_message] ==
|
|
RPC::make_error(rpcACT_MALFORMED)[jss::error_message]);
|
|
}
|
|
{
|
|
// A negative limit should fail.
|
|
Json::Value params;
|
|
params[jss::account] = alice.human();
|
|
params[jss::limit] = -1;
|
|
auto const lines =
|
|
env.rpc("json", "account_lines", to_string(params));
|
|
BEAST_EXPECT(
|
|
lines[jss::result][jss::error_message] ==
|
|
RPC::expected_field_message(jss::limit, "unsigned integer"));
|
|
}
|
|
{
|
|
// Limit the response to 1 trust line.
|
|
Json::Value paramsA;
|
|
paramsA[jss::account] = alice.human();
|
|
paramsA[jss::limit] = 1;
|
|
auto const linesA =
|
|
env.rpc("json", "account_lines", to_string(paramsA));
|
|
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();
|
|
Json::Value paramsB;
|
|
paramsB[jss::account] = alice.human();
|
|
paramsB[jss::marker] = marker;
|
|
auto const linesB =
|
|
env.rpc("json", "account_lines", to_string(paramsB));
|
|
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.
|
|
Json::Value paramsC;
|
|
paramsC[jss::account] = alice.human();
|
|
paramsC[jss::limit] = 3;
|
|
paramsC[jss::marker] = marker;
|
|
auto const linesC =
|
|
env.rpc("json", "account_lines", to_string(paramsC));
|
|
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';
|
|
Json::Value paramsD;
|
|
paramsD[jss::account] = alice.human();
|
|
paramsD[jss::marker] = marker;
|
|
auto const linesD =
|
|
env.rpc("json", "account_lines", to_string(paramsD));
|
|
BEAST_EXPECT(
|
|
linesD[jss::result][jss::error_message] ==
|
|
RPC::make_error(rpcINVALID_PARAMS)[jss::error_message]);
|
|
}
|
|
{
|
|
// A non-string marker should also fail.
|
|
Json::Value params;
|
|
params[jss::account] = alice.human();
|
|
params[jss::marker] = true;
|
|
auto const lines =
|
|
env.rpc("json", "account_lines", to_string(params));
|
|
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.
|
|
Json::Value params;
|
|
params[jss::account] = alice.human();
|
|
params[jss::limit] = 10;
|
|
params[jss::peer] = gw2.human();
|
|
auto const lines =
|
|
env.rpc("json", "account_lines", to_string(params));
|
|
auto const& line = lines[jss::result][jss::lines][0u];
|
|
BEAST_EXPECT(line[jss::freeze].asBool() == true);
|
|
BEAST_EXPECT(line[jss::deep_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.
|
|
Json::Value paramsA;
|
|
paramsA[jss::account] = gw2.human();
|
|
paramsA[jss::limit] = 1;
|
|
paramsA[jss::peer] = alice.human();
|
|
auto const linesA =
|
|
env.rpc("json", "account_lines", to_string(paramsA));
|
|
auto const& lineA = linesA[jss::result][jss::lines][0u];
|
|
BEAST_EXPECT(lineA[jss::freeze_peer].asBool() == true);
|
|
BEAST_EXPECT(lineA[jss::deep_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();
|
|
Json::Value paramsB;
|
|
paramsB[jss::account] = gw2.human();
|
|
paramsB[jss::limit] = 25;
|
|
paramsB[jss::marker] = marker;
|
|
paramsB[jss::peer] = alice.human();
|
|
auto const linesB =
|
|
env.rpc("json", "account_lines", to_string(paramsB));
|
|
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 aliceObjectsParams;
|
|
aliceObjectsParams[jss::account] = alice.human();
|
|
aliceObjectsParams[jss::limit] = 10;
|
|
Json::Value const aliceObjects =
|
|
env.rpc("json", "account_objects", to_string(aliceObjectsParams));
|
|
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.
|
|
Json::Value aliceLines1Params;
|
|
aliceLines1Params[jss::account] = alice.human();
|
|
aliceLines1Params[jss::limit] = 1;
|
|
auto const aliceLines1 =
|
|
env.rpc("json", "account_lines", to_string(aliceLines1Params));
|
|
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.
|
|
Json::Value aliceLines2Params;
|
|
aliceLines2Params[jss::account] = alice.human();
|
|
aliceLines2Params[jss::marker] = aliceMarker;
|
|
auto const aliceLines2 =
|
|
env.rpc("json", "account_lines", to_string(aliceLines2Params));
|
|
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.
|
|
Json::Value beckyLinesParams;
|
|
beckyLinesParams[jss::account] = becky.human();
|
|
beckyLinesParams[jss::marker] = aliceMarker;
|
|
auto const beckyLines =
|
|
env.rpc("json", "account_lines", to_string(beckyLinesParams));
|
|
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.
|
|
Json::Value linesBegParams;
|
|
linesBegParams[jss::account] = alice.human();
|
|
linesBegParams[jss::limit] = 2;
|
|
auto const linesBeg =
|
|
env.rpc("json", "account_lines", to_string(linesBegParams));
|
|
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.
|
|
Json::Value linesEndParams;
|
|
linesEndParams[jss::account] = alice.human();
|
|
linesEndParams[jss::marker] = linesBeg[jss::result][jss::marker];
|
|
auto const linesEnd =
|
|
env.rpc("json", "account_lines", to_string(linesEndParams));
|
|
BEAST_EXPECT(
|
|
linesEnd[jss::result][jss::error_message] ==
|
|
RPC::make_error(rpcINVALID_PARAMS)[jss::error_message]);
|
|
}
|
|
|
|
void
|
|
testAccountLinesWalkMarkers()
|
|
{
|
|
testcase("Marker can point to any appropriate ledger entry type");
|
|
using namespace test::jtx;
|
|
using namespace std::chrono_literals;
|
|
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();
|
|
|
|
auto payChan = [](Account const& account,
|
|
Account const& to,
|
|
STAmount const& amount,
|
|
NetClock::duration const& settleDelay,
|
|
PublicKey const& pk) {
|
|
Json::Value jv;
|
|
jv[jss::TransactionType] = jss::PaymentChannelCreate;
|
|
jv[jss::Account] = account.human();
|
|
jv[jss::Destination] = to.human();
|
|
jv[jss::Amount] = amount.getJson(JsonOptions::none);
|
|
jv["SettleDelay"] = settleDelay.count();
|
|
jv["PublicKey"] = strHex(pk.slice());
|
|
return jv;
|
|
};
|
|
|
|
// Test all available object types. Not all of these objects will be
|
|
// included in the search, nor found by `account_objects`. If that ever
|
|
// changes for any reason, this test will help catch that.
|
|
//
|
|
// SignerList, for alice
|
|
Account const bogie{"bogie"};
|
|
env(signers(alice, 2, {{bogie, 3}}));
|
|
env.close();
|
|
|
|
// SignerList, includes alice
|
|
env(signers(becky, 2, {{alice, 3}}));
|
|
env.close();
|
|
|
|
// Trust lines
|
|
auto const EUR = gw1["EUR"];
|
|
env(trust(alice, EUR(200)));
|
|
env(trust(becky, EUR(200)));
|
|
env.close();
|
|
|
|
// Escrow, in each direction
|
|
env(escrow::create(alice, becky, XRP(1000)),
|
|
escrow::finish_time(env.now() + 1s));
|
|
env(escrow::create(becky, alice, XRP(1000)),
|
|
escrow::finish_time(env.now() + 1s));
|
|
|
|
// Pay channels, in each direction
|
|
env(payChan(alice, becky, XRP(1000), 100s, alice.pk()));
|
|
env(payChan(becky, alice, XRP(1000), 100s, becky.pk()));
|
|
|
|
// Mint NFTs, for each account
|
|
uint256 const aliceNFtokenID =
|
|
token::getNextID(env, alice, 0, tfTransferable);
|
|
env(token::mint(alice, 0), txflags(tfTransferable));
|
|
|
|
uint256 const beckyNFtokenID =
|
|
token::getNextID(env, becky, 0, tfTransferable);
|
|
env(token::mint(becky, 0), txflags(tfTransferable));
|
|
|
|
// NFT Offers, for each other's NFTs
|
|
env(token::createOffer(alice, beckyNFtokenID, drops(1)),
|
|
token::owner(becky));
|
|
env(token::createOffer(becky, aliceNFtokenID, drops(1)),
|
|
token::owner(alice));
|
|
|
|
env(token::createOffer(becky, beckyNFtokenID, drops(1)),
|
|
txflags(tfSellNFToken),
|
|
token::destination(alice));
|
|
env(token::createOffer(alice, aliceNFtokenID, drops(1)),
|
|
txflags(tfSellNFToken),
|
|
token::destination(becky));
|
|
|
|
env(token::createOffer(gw1, beckyNFtokenID, drops(1)),
|
|
token::owner(becky),
|
|
token::destination(alice));
|
|
env(token::createOffer(gw1, aliceNFtokenID, drops(1)),
|
|
token::owner(alice),
|
|
token::destination(becky));
|
|
|
|
env(token::createOffer(becky, beckyNFtokenID, drops(1)),
|
|
txflags(tfSellNFToken));
|
|
env(token::createOffer(alice, aliceNFtokenID, drops(1)),
|
|
txflags(tfSellNFToken));
|
|
|
|
// Checks, in each direction
|
|
env(check::create(alice, becky, XRP(50)));
|
|
env(check::create(becky, alice, XRP(50)));
|
|
|
|
// Deposit preauth, in each direction
|
|
env(deposit::auth(alice, becky));
|
|
env(deposit::auth(becky, alice));
|
|
|
|
// Offers, one where alice is the owner, and one where alice is the
|
|
// issuer
|
|
auto const USDalice = alice["USD"];
|
|
env(offer(alice, EUR(10), XRP(100)));
|
|
env(offer(becky, USDalice(10), XRP(100)));
|
|
|
|
// Tickets
|
|
env(ticket::create(alice, 2));
|
|
|
|
// Add another trustline for good measure
|
|
auto const BTCbecky = becky["BTC"];
|
|
env(trust(alice, BTCbecky(200)));
|
|
|
|
env.close();
|
|
|
|
{
|
|
// Now make repeated calls to `account_lines` with a limit of 1.
|
|
// That should iterate all of alice's relevant objects, even though
|
|
// the list will be empty for most calls.
|
|
auto getNextLine = [](Env& env,
|
|
Account const& alice,
|
|
std::optional<std::string> const marker) {
|
|
Json::Value params(Json::objectValue);
|
|
params[jss::account] = alice.human();
|
|
params[jss::limit] = 1;
|
|
if (marker)
|
|
params[jss::marker] = *marker;
|
|
|
|
return env.rpc("json", "account_lines", to_string(params));
|
|
};
|
|
|
|
auto aliceLines = getNextLine(env, alice, std::nullopt);
|
|
constexpr std::size_t expectedIterations = 16;
|
|
constexpr std::size_t expectedLines = 2;
|
|
constexpr std::size_t expectedNFTs = 1;
|
|
std::size_t foundLines = 0;
|
|
|
|
auto hasMarker = [](auto const& aliceLines) {
|
|
return aliceLines[jss::result].isMember(jss::marker);
|
|
};
|
|
auto marker = [](auto const& aliceLines) {
|
|
return aliceLines[jss::result][jss::marker].asString();
|
|
};
|
|
auto checkLines = [](auto const& aliceLines) {
|
|
return aliceLines.isMember(jss::result) &&
|
|
!aliceLines[jss::result].isMember(jss::error_message) &&
|
|
aliceLines[jss::result].isMember(jss::lines) &&
|
|
aliceLines[jss::result][jss::lines].isArray() &&
|
|
aliceLines[jss::result][jss::lines].size() <= 1;
|
|
};
|
|
|
|
BEAST_EXPECT(hasMarker(aliceLines));
|
|
BEAST_EXPECT(checkLines(aliceLines));
|
|
BEAST_EXPECT(aliceLines[jss::result][jss::lines].size() == 0);
|
|
|
|
int iterations = 1;
|
|
|
|
while (hasMarker(aliceLines))
|
|
{
|
|
// Iterate through the markers
|
|
aliceLines = getNextLine(env, alice, marker(aliceLines));
|
|
BEAST_EXPECT(checkLines(aliceLines));
|
|
foundLines += aliceLines[jss::result][jss::lines].size();
|
|
++iterations;
|
|
}
|
|
BEAST_EXPECT(expectedLines == foundLines);
|
|
|
|
Json::Value aliceObjectsParams2;
|
|
aliceObjectsParams2[jss::account] = alice.human();
|
|
aliceObjectsParams2[jss::limit] = 200;
|
|
Json::Value const aliceObjects = env.rpc(
|
|
"json", "account_objects", to_string(aliceObjectsParams2));
|
|
BEAST_EXPECT(aliceObjects.isMember(jss::result));
|
|
BEAST_EXPECT(
|
|
!aliceObjects[jss::result].isMember(jss::error_message));
|
|
BEAST_EXPECT(
|
|
aliceObjects[jss::result].isMember(jss::account_objects));
|
|
BEAST_EXPECT(
|
|
aliceObjects[jss::result][jss::account_objects].isArray());
|
|
// account_objects does not currently return NFTPages. If
|
|
// that ever changes, without also changing account_lines,
|
|
// this test will need to be updated.
|
|
BEAST_EXPECT(
|
|
aliceObjects[jss::result][jss::account_objects].size() ==
|
|
iterations + expectedNFTs);
|
|
// If ledger object association ever changes, for whatever
|
|
// reason, this test will need to be updated.
|
|
BEAST_EXPECTS(
|
|
iterations == expectedIterations, std::to_string(iterations));
|
|
|
|
// Get becky's objects just to confirm that they're symmetrical
|
|
Json::Value beckyObjectsParams;
|
|
beckyObjectsParams[jss::account] = becky.human();
|
|
beckyObjectsParams[jss::limit] = 200;
|
|
Json::Value const beckyObjects = env.rpc(
|
|
"json", "account_objects", to_string(beckyObjectsParams));
|
|
BEAST_EXPECT(beckyObjects.isMember(jss::result));
|
|
BEAST_EXPECT(
|
|
!beckyObjects[jss::result].isMember(jss::error_message));
|
|
BEAST_EXPECT(
|
|
beckyObjects[jss::result].isMember(jss::account_objects));
|
|
BEAST_EXPECT(
|
|
beckyObjects[jss::result][jss::account_objects].isArray());
|
|
// becky should have the same number of objects as alice, except the
|
|
// 2 tickets that only alice created.
|
|
BEAST_EXPECT(
|
|
beckyObjects[jss::result][jss::account_objects].size() ==
|
|
aliceObjects[jss::result][jss::account_objects].size() - 2);
|
|
}
|
|
}
|
|
|
|
// test API V2
|
|
void
|
|
testAccountLines2()
|
|
{
|
|
testcase("V2: account_lines");
|
|
|
|
using namespace test::jtx;
|
|
Env env(*this);
|
|
{
|
|
// account_lines with mal-formed json2 (missing id field).
|
|
Json::Value request;
|
|
request[jss::method] = "account_lines";
|
|
request[jss::jsonrpc] = "2.0";
|
|
request[jss::ripplerpc] = "2.0";
|
|
auto const lines = env.rpc("json2", to_string(request));
|
|
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.
|
|
Json::Value request;
|
|
request[jss::method] = "account_lines";
|
|
request[jss::jsonrpc] = "2.0";
|
|
request[jss::ripplerpc] = "2.0";
|
|
request[jss::id] = 5;
|
|
auto const lines = env.rpc("json2", to_string(request));
|
|
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.
|
|
Json::Value params;
|
|
params[jss::account] =
|
|
"n9MJkEKHDhy5eTLuHUQeAAjo382frHNbFK4C8hcwN4nwM2SrLdBj";
|
|
Json::Value request;
|
|
request[jss::method] = "account_lines";
|
|
request[jss::jsonrpc] = "2.0";
|
|
request[jss::ripplerpc] = "2.0";
|
|
request[jss::id] = 5;
|
|
request[jss::params] = params;
|
|
auto const lines = env.rpc("json2", to_string(request));
|
|
BEAST_EXPECT(
|
|
lines[jss::error][jss::message] ==
|
|
RPC::make_error(rpcACT_MALFORMED)[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.
|
|
Json::Value params;
|
|
params[jss::account] = alice.human();
|
|
Json::Value request;
|
|
request[jss::method] = "account_lines";
|
|
request[jss::jsonrpc] = "2.0";
|
|
request[jss::ripplerpc] = "2.0";
|
|
request[jss::id] = 5;
|
|
request[jss::params] = params;
|
|
auto const lines = env.rpc("json2", to_string(request));
|
|
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();
|
|
LedgerHeader const ledger3Info = env.closed()->header();
|
|
BEAST_EXPECT(ledger3Info.seq == 3);
|
|
|
|
{
|
|
// alice is funded but has no lines. An empty array is returned.
|
|
Json::Value params;
|
|
params[jss::account] = alice.human();
|
|
Json::Value request;
|
|
request[jss::method] = "account_lines";
|
|
request[jss::jsonrpc] = "2.0";
|
|
request[jss::ripplerpc] = "2.0";
|
|
request[jss::id] = 5;
|
|
request[jss::params] = params;
|
|
auto const lines = env.rpc("json2", to_string(request));
|
|
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.
|
|
Json::Value params;
|
|
params[jss::account] = alice.human();
|
|
params[jss::ledger_index] = "nonsense";
|
|
Json::Value request;
|
|
request[jss::method] = "account_lines";
|
|
request[jss::jsonrpc] = "2.0";
|
|
request[jss::ripplerpc] = "2.0";
|
|
request[jss::id] = 5;
|
|
request[jss::params] = params;
|
|
auto const lines = env.rpc("json2", to_string(request));
|
|
BEAST_EXPECT(
|
|
lines[jss::error][jss::message] ==
|
|
"Invalid field 'ledger_index', not string or number.");
|
|
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.
|
|
Json::Value params;
|
|
params[jss::account] = alice.human();
|
|
params[jss::ledger_index] = 50000;
|
|
Json::Value request;
|
|
request[jss::method] = "account_lines";
|
|
request[jss::jsonrpc] = "2.0";
|
|
request[jss::ripplerpc] = "2.0";
|
|
request[jss::id] = 5;
|
|
request[jss::params] = params;
|
|
auto const lines = env.rpc("json2", to_string(request));
|
|
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();
|
|
LedgerHeader const ledger4Info = env.closed()->header();
|
|
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 | tfSetDeepFreeze));
|
|
}
|
|
env.close();
|
|
LedgerHeader const ledger58Info = env.closed()->header();
|
|
BEAST_EXPECT(ledger58Info.seq == 58);
|
|
|
|
// A re-usable test for historic ledgers.
|
|
auto testAccountLinesHistory = [this, &env](
|
|
Account const& account,
|
|
LedgerHeader const& info,
|
|
int count) {
|
|
// Get account_lines by ledger index.
|
|
Json::Value paramsSeq;
|
|
paramsSeq[jss::account] = account.human();
|
|
paramsSeq[jss::ledger_index] = info.seq;
|
|
Json::Value requestSeq;
|
|
requestSeq[jss::method] = "account_lines";
|
|
requestSeq[jss::jsonrpc] = "2.0";
|
|
requestSeq[jss::ripplerpc] = "2.0";
|
|
requestSeq[jss::id] = 5;
|
|
requestSeq[jss::params] = paramsSeq;
|
|
auto const linesSeq = env.rpc("json2", to_string(requestSeq));
|
|
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.
|
|
Json::Value paramsHash;
|
|
paramsHash[jss::account] = account.human();
|
|
paramsHash[jss::ledger_hash] = to_string(info.hash);
|
|
Json::Value requestHash;
|
|
requestHash[jss::method] = "account_lines";
|
|
requestHash[jss::jsonrpc] = "2.0";
|
|
requestHash[jss::ripplerpc] = "2.0";
|
|
requestHash[jss::id] = 5;
|
|
requestHash[jss::params] = paramsHash;
|
|
auto const linesHash = env.rpc("json2", to_string(requestHash));
|
|
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.
|
|
Json::Value params;
|
|
params[jss::account] = alice.human();
|
|
params[jss::ledger_hash] = to_string(ledger4Info.hash);
|
|
params[jss::ledger_index] = ledger58Info.seq;
|
|
Json::Value request;
|
|
request[jss::method] = "account_lines";
|
|
request[jss::jsonrpc] = "2.0";
|
|
request[jss::ripplerpc] = "2.0";
|
|
request[jss::id] = 5;
|
|
request[jss::params] = params;
|
|
auto const lines = env.rpc("json2", to_string(request));
|
|
BEAST_EXPECT(lines[jss::error][jss::error] == "invalidParams");
|
|
BEAST_EXPECT(
|
|
lines[jss::error][jss::message] ==
|
|
"Exactly one of 'ledger_hash' or 'ledger_index' can be "
|
|
"specified.");
|
|
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.
|
|
Json::Value params;
|
|
params[jss::account] = alice.human();
|
|
Json::Value request;
|
|
request[jss::method] = "account_lines";
|
|
request[jss::jsonrpc] = "2.0";
|
|
request[jss::ripplerpc] = "2.0";
|
|
request[jss::id] = 5;
|
|
request[jss::params] = params;
|
|
auto const lines = env.rpc("json2", to_string(request));
|
|
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.
|
|
Json::Value params;
|
|
params[jss::account] = alice.human();
|
|
params[jss::peer] = gw1.human();
|
|
Json::Value request;
|
|
request[jss::method] = "account_lines";
|
|
request[jss::jsonrpc] = "2.0";
|
|
request[jss::ripplerpc] = "2.0";
|
|
request[jss::id] = 5;
|
|
request[jss::params] = params;
|
|
auto const lines = env.rpc("json2", to_string(request));
|
|
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.
|
|
Json::Value params;
|
|
params[jss::account] = alice.human();
|
|
params[jss::peer] =
|
|
"n9MJkEKHDhy5eTLuHUQeAAjo382frHNbFK4C8hcwN4nwM2SrLdBj";
|
|
Json::Value request;
|
|
request[jss::method] = "account_lines";
|
|
request[jss::jsonrpc] = "2.0";
|
|
request[jss::ripplerpc] = "2.0";
|
|
request[jss::id] = 5;
|
|
request[jss::params] = params;
|
|
auto const lines = env.rpc("json2", to_string(request));
|
|
BEAST_EXPECT(
|
|
lines[jss::error][jss::message] ==
|
|
RPC::make_error(rpcACT_MALFORMED)[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.
|
|
Json::Value params;
|
|
params[jss::account] = alice.human();
|
|
params[jss::limit] = -1;
|
|
Json::Value request;
|
|
request[jss::method] = "account_lines";
|
|
request[jss::jsonrpc] = "2.0";
|
|
request[jss::ripplerpc] = "2.0";
|
|
request[jss::id] = 5;
|
|
request[jss::params] = params;
|
|
auto const lines = env.rpc("json2", to_string(request));
|
|
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.
|
|
Json::Value paramsA;
|
|
paramsA[jss::account] = alice.human();
|
|
paramsA[jss::limit] = 1;
|
|
Json::Value requestA;
|
|
requestA[jss::method] = "account_lines";
|
|
requestA[jss::jsonrpc] = "2.0";
|
|
requestA[jss::ripplerpc] = "2.0";
|
|
requestA[jss::id] = 5;
|
|
requestA[jss::params] = paramsA;
|
|
auto const linesA = env.rpc("json2", to_string(requestA));
|
|
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();
|
|
Json::Value paramsB;
|
|
paramsB[jss::account] = alice.human();
|
|
paramsB[jss::marker] = marker;
|
|
Json::Value requestB;
|
|
requestB[jss::method] = "account_lines";
|
|
requestB[jss::jsonrpc] = "2.0";
|
|
requestB[jss::ripplerpc] = "2.0";
|
|
requestB[jss::id] = 5;
|
|
requestB[jss::params] = paramsB;
|
|
auto const linesB = env.rpc("json2", to_string(requestB));
|
|
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.
|
|
Json::Value paramsC;
|
|
paramsC[jss::account] = alice.human();
|
|
paramsC[jss::limit] = 3;
|
|
paramsC[jss::marker] = marker;
|
|
Json::Value requestC;
|
|
requestC[jss::method] = "account_lines";
|
|
requestC[jss::jsonrpc] = "2.0";
|
|
requestC[jss::ripplerpc] = "2.0";
|
|
requestC[jss::id] = 5;
|
|
requestC[jss::params] = paramsC;
|
|
auto const linesC = env.rpc("json2", to_string(requestC));
|
|
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';
|
|
Json::Value paramsD;
|
|
paramsD[jss::account] = alice.human();
|
|
paramsD[jss::marker] = marker;
|
|
Json::Value requestD;
|
|
requestD[jss::method] = "account_lines";
|
|
requestD[jss::jsonrpc] = "2.0";
|
|
requestD[jss::ripplerpc] = "2.0";
|
|
requestD[jss::id] = 5;
|
|
requestD[jss::params] = paramsD;
|
|
auto const linesD = env.rpc("json2", to_string(requestD));
|
|
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.
|
|
Json::Value params;
|
|
params[jss::account] = alice.human();
|
|
params[jss::marker] = true;
|
|
Json::Value request;
|
|
request[jss::method] = "account_lines";
|
|
request[jss::jsonrpc] = "2.0";
|
|
request[jss::ripplerpc] = "2.0";
|
|
request[jss::id] = 5;
|
|
request[jss::params] = params;
|
|
auto const lines = env.rpc("json2", to_string(request));
|
|
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.
|
|
Json::Value params;
|
|
params[jss::account] = alice.human();
|
|
params[jss::limit] = 10;
|
|
params[jss::peer] = gw2.human();
|
|
Json::Value request;
|
|
request[jss::method] = "account_lines";
|
|
request[jss::jsonrpc] = "2.0";
|
|
request[jss::ripplerpc] = "2.0";
|
|
request[jss::id] = 5;
|
|
request[jss::params] = params;
|
|
auto const lines = env.rpc("json2", to_string(request));
|
|
auto const& line = lines[jss::result][jss::lines][0u];
|
|
BEAST_EXPECT(line[jss::freeze].asBool() == true);
|
|
BEAST_EXPECT(line[jss::deep_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.
|
|
Json::Value paramsA;
|
|
paramsA[jss::account] = gw2.human();
|
|
paramsA[jss::limit] = 1;
|
|
paramsA[jss::peer] = alice.human();
|
|
Json::Value requestA;
|
|
requestA[jss::method] = "account_lines";
|
|
requestA[jss::jsonrpc] = "2.0";
|
|
requestA[jss::ripplerpc] = "2.0";
|
|
requestA[jss::id] = 5;
|
|
requestA[jss::params] = paramsA;
|
|
auto const linesA = env.rpc("json2", to_string(requestA));
|
|
auto const& lineA = linesA[jss::result][jss::lines][0u];
|
|
BEAST_EXPECT(lineA[jss::freeze_peer].asBool() == true);
|
|
BEAST_EXPECT(lineA[jss::deep_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();
|
|
Json::Value paramsB;
|
|
paramsB[jss::account] = gw2.human();
|
|
paramsB[jss::limit] = 25;
|
|
paramsB[jss::marker] = marker;
|
|
paramsB[jss::peer] = alice.human();
|
|
Json::Value requestB;
|
|
requestB[jss::method] = "account_lines";
|
|
requestB[jss::jsonrpc] = "2.0";
|
|
requestB[jss::ripplerpc] = "2.0";
|
|
requestB[jss::id] = 5;
|
|
requestB[jss::params] = paramsB;
|
|
auto const linesB = env.rpc("json2", to_string(requestB));
|
|
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.
|
|
Json::Value linesBegParams;
|
|
linesBegParams[jss::account] = alice.human();
|
|
linesBegParams[jss::limit] = 2;
|
|
Json::Value linesBegRequest;
|
|
linesBegRequest[jss::method] = "account_lines";
|
|
linesBegRequest[jss::jsonrpc] = "2.0";
|
|
linesBegRequest[jss::ripplerpc] = "2.0";
|
|
linesBegRequest[jss::id] = 5;
|
|
linesBegRequest[jss::params] = linesBegParams;
|
|
auto const linesBeg = env.rpc("json2", to_string(linesBegRequest));
|
|
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.
|
|
Json::Value linesEndParams;
|
|
linesEndParams[jss::account] = alice.human();
|
|
linesEndParams[jss::marker] = linesBeg[jss::result][jss::marker];
|
|
Json::Value linesEndRequest;
|
|
linesEndRequest[jss::method] = "account_lines";
|
|
linesEndRequest[jss::jsonrpc] = "2.0";
|
|
linesEndRequest[jss::ripplerpc] = "2.0";
|
|
linesEndRequest[jss::id] = 5;
|
|
linesEndRequest[jss::params] = linesEndParams;
|
|
auto const linesEnd = env.rpc("json2", to_string(linesEndRequest));
|
|
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();
|
|
testAccountLinesWalkMarkers();
|
|
testAccountLines2();
|
|
testAccountLineDelete2();
|
|
}
|
|
};
|
|
|
|
BEAST_DEFINE_TESTSUITE(AccountLines, rpc, ripple);
|
|
|
|
} // namespace RPC
|
|
} // namespace ripple
|