Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow account_objects RPC to filter by "check" (RIPD-1589) #2356

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/ripple/protocol/JsonFields.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ JSS ( cancel_after ); // out: AccountChannels
JSS ( can_delete ); // out: CanDelete
JSS ( channel_id ); // out: AccountChannels
JSS ( channels ); // out: AccountChannels
JSS ( check ); // in: AccountObjects
JSS ( check_nodes ); // in: LedgerCleaner
JSS ( clear ); // in/out: FetchInfo
JSS ( close_flags ); // out: LedgerToJson
Expand Down
11 changes: 6 additions & 5 deletions src/ripple/rpc/impl/RPCHelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ getAccountObjects(ReadView const& ledger, AccountID const& account,
return false;

std::uint32_t i = 0;
auto& jvObjects = jvResult[jss::account_objects];
auto& jvObjects = (jvResult[jss::account_objects] = Json::arrayValue);
for (;;)
{
auto const& entries = dir->getFieldV256 (sfIndexes);
Expand Down Expand Up @@ -671,19 +671,20 @@ chooseLedgerEntryType(Json::Value const& params)
if (params.isMember(jss::type))
{
static
std::array<std::pair<char const *, LedgerEntryType>, 11> const types
std::array<std::pair<char const *, LedgerEntryType>, 12> const types
{ {
{ jss::account, ltACCOUNT_ROOT },
{ jss::amendments, ltAMENDMENTS },
{ jss::check, ltCHECK },
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just adding 1 new type (check) and reordering a few others?

{ jss::directory, ltDIR_NODE },
{ jss::escrow, ltESCROW },
{ jss::fee, ltFEE_SETTINGS },
{ jss::hashes, ltLEDGER_HASHES },
{ jss::offer, ltOFFER },
{ jss::payment_channel, ltPAYCHAN },
{ jss::signer_list, ltSIGNER_LIST },
{ jss::state, ltRIPPLE_STATE },
{ jss::ticket, ltTICKET },
{ jss::escrow, ltESCROW },
{ jss::payment_channel, ltPAYCHAN }
{ jss::ticket, ltTICKET }
} };

auto const& p = params[jss::type];
Expand Down
190 changes: 190 additions & 0 deletions src/test/rpc/AccountObjects_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -316,10 +316,200 @@ class AccountObjects_test : public beast::unit_test::suite
}
}

void testObjectTypes()
{
testcase("object types");

// Give gw a bunch of ledger objects and make sure we can retrieve
// them by type.
using namespace jtx;

Account const alice { "alice" };
Account const gw{ "gateway" };
auto const USD = gw["USD"];

// Test for ticket account objects when they are supported.
Env env(*this, supported_amendments().set(featureTickets));

// Make a lambda we can use to get "account_objects" easily.
auto acct_objs = [&env] (Account const& acct, char const* type)
{
Json::Value params;
params[jss::account] = acct.human();
params[jss::type] = type;
params[jss::ledger_index] = "validated";
return env.rpc("json", "account_objects", to_string(params));
};

// Make a lambda that easily identifies the size of account objects.
auto acct_objs_is_size = [&env, &acct_objs]
(Json::Value const& resp, unsigned size)
{
return resp[jss::result][jss::account_objects].isArray() &&
(resp[jss::result][jss::account_objects].size() == size);
};

env.fund(XRP(10000), gw, alice);
env.close();

// Since the account is empty now, all account objects should come
// back empty.
BEAST_EXPECT (acct_objs_is_size (acct_objs (gw, jss::account), 0));
BEAST_EXPECT (acct_objs_is_size (acct_objs (gw, jss::amendments), 0));
BEAST_EXPECT (acct_objs_is_size (acct_objs (gw, jss::check), 0));
BEAST_EXPECT (acct_objs_is_size (acct_objs (gw, jss::directory), 0));
BEAST_EXPECT (acct_objs_is_size (acct_objs (gw, jss::escrow), 0));
BEAST_EXPECT (acct_objs_is_size (acct_objs (gw, jss::fee), 0));
BEAST_EXPECT (acct_objs_is_size (acct_objs (gw, jss::hashes), 0));
BEAST_EXPECT (acct_objs_is_size (acct_objs (gw, jss::offer), 0));
BEAST_EXPECT (acct_objs_is_size (acct_objs (gw, jss::payment_channel), 0));
BEAST_EXPECT (acct_objs_is_size (acct_objs (gw, jss::signer_list), 0));
BEAST_EXPECT (acct_objs_is_size (acct_objs (gw, jss::state), 0));
BEAST_EXPECT (acct_objs_is_size (acct_objs (gw, jss::ticket), 0));

// Set up a trust line so we can find it.
env.trust(USD(1000), alice);
env.close();
env(pay(gw, alice, USD(5)));
env.close();
{
// Find the trustline and make sure it's the right one.
Json::Value const resp = acct_objs (gw, jss::state);
BEAST_EXPECT (acct_objs_is_size (resp, 1));

auto const& state = resp[jss::result][jss::account_objects][0u];
BEAST_EXPECT (state[sfBalance.jsonName][jss::value].asInt() == -5);
BEAST_EXPECT (state[sfHighLimit.jsonName][jss::value].asUInt() == 1000);
}
{
// gw writes a check for USD(10) to alice.
Json::Value jvCheck;
jvCheck[sfAccount.jsonName] = gw.human();
jvCheck[sfSendMax.jsonName] = USD(10).value().getJson(0);
jvCheck[sfDestination.jsonName] = alice.human();
jvCheck[sfTransactionType.jsonName] = "CheckCreate";
jvCheck[sfFlags.jsonName] = tfUniversal;
env (jvCheck);
env.close();
}
{
// Find the check.
Json::Value const resp = acct_objs (gw, jss::check);
BEAST_EXPECT (acct_objs_is_size (resp, 1));

auto const& check = resp[jss::result][jss::account_objects][0u];
BEAST_EXPECT (check[sfAccount.jsonName] == gw.human());
BEAST_EXPECT (check[sfDestination.jsonName] == alice.human());
BEAST_EXPECT (check[sfSendMax.jsonName][jss::value].asUInt() == 10);
}
{
// gw creates an escrow that we can look for in the ledger.
Json::Value jvEscrow;
jvEscrow[jss::TransactionType] = "EscrowCreate";
jvEscrow[jss::Flags] = tfUniversal;
jvEscrow[jss::Account] = gw.human();
jvEscrow[jss::Destination] = gw.human();
jvEscrow[jss::Amount] = XRP(100).value().getJson(0);
jvEscrow[sfFinishAfter.jsonName] =
env.now().time_since_epoch().count() + 1;
env (jvEscrow);
env.close();
}
{
// Find the escrow.
Json::Value const resp = acct_objs (gw, jss::escrow);
BEAST_EXPECT (acct_objs_is_size (resp, 1));

auto const& escrow = resp[jss::result][jss::account_objects][0u];
BEAST_EXPECT (escrow[sfAccount.jsonName] == gw.human());
BEAST_EXPECT (escrow[sfDestination.jsonName] == gw.human());
BEAST_EXPECT (escrow[sfAmount.jsonName].asUInt() == 100'000'000);
}
// gw creates an offer that we can look for in the ledger.
env (offer (gw, USD (7), XRP (14)));
env.close();
{
// Find the offer.
Json::Value const resp = acct_objs (gw, jss::offer);
BEAST_EXPECT (acct_objs_is_size (resp, 1));

auto const& offer = resp[jss::result][jss::account_objects][0u];
BEAST_EXPECT (offer[sfAccount.jsonName] == gw.human());
BEAST_EXPECT (offer[sfTakerGets.jsonName].asUInt() == 14'000'000);
BEAST_EXPECT (offer[sfTakerPays.jsonName][jss::value].asUInt() == 7);
}
{
// Create a payment channel from qw to alice that we can look for.
Json::Value jvPayChan;
jvPayChan[jss::TransactionType] = "PaymentChannelCreate";
jvPayChan[jss::Flags] = tfUniversal;
jvPayChan[jss::Account] = gw.human ();
jvPayChan[jss::Destination] = alice.human ();
jvPayChan[jss::Amount] = XRP (300).value().getJson (0);
jvPayChan[sfSettleDelay.jsonName] = 24 * 60 * 60;
jvPayChan[sfPublicKey.jsonName] = strHex (gw.pk().slice ());
env (jvPayChan);
env.close();
}
{
// Find the payment channel.
Json::Value const resp = acct_objs (gw, jss::payment_channel);
BEAST_EXPECT (acct_objs_is_size (resp, 1));

auto const& payChan = resp[jss::result][jss::account_objects][0u];
BEAST_EXPECT (payChan[sfAccount.jsonName] == gw.human());
BEAST_EXPECT (payChan[sfAmount.jsonName].asUInt() == 300'000'000);
BEAST_EXPECT (payChan[sfSettleDelay.jsonName].asUInt() == 24 * 60 * 60);
}
// Make gw multisigning by adding a signerList.
env (signers (gw, 6, { { alice, 7} }));
env.close();
{
// Find the signer list.
Json::Value const resp = acct_objs (gw, jss::signer_list);
BEAST_EXPECT (acct_objs_is_size (resp, 1));

auto const& signerList = resp[jss::result][jss::account_objects][0u];
BEAST_EXPECT (signerList[sfSignerQuorum.jsonName] == 6);
auto const& entry =
signerList[sfSignerEntries.jsonName][0u][sfSignerEntry.jsonName];
BEAST_EXPECT (entry[sfAccount.jsonName] == alice.human());
BEAST_EXPECT (entry[sfSignerWeight.jsonName].asUInt() == 7);
}
// Create a Ticket for gw.
env (ticket::create (gw, gw));
env.close();
{
// Find the ticket.
Json::Value const resp = acct_objs (gw, jss::ticket);
BEAST_EXPECT (acct_objs_is_size (resp, 1));

auto const& ticket = resp[jss::result][jss::account_objects][0u];
BEAST_EXPECT (ticket[sfAccount.jsonName] == gw.human());
BEAST_EXPECT (ticket[sfLedgerEntryType.jsonName] == "Ticket");
BEAST_EXPECT (ticket[sfSequence.jsonName].asUInt() == 8);
}
// Run up the number of directory entries so gw has two
// directory nodes.
for (int d = 1'000'032; d >= 1'000'000; --d)
{
env (offer (gw, USD (1), drops (d)));
env.close();
}

// Verify that the non-returning types still don't return anything.
BEAST_EXPECT (acct_objs_is_size (acct_objs (gw, jss::account), 0));
BEAST_EXPECT (acct_objs_is_size (acct_objs (gw, jss::amendments), 0));
BEAST_EXPECT (acct_objs_is_size (acct_objs (gw, jss::directory), 0));
BEAST_EXPECT (acct_objs_is_size (acct_objs (gw, jss::fee), 0));
BEAST_EXPECT (acct_objs_is_size (acct_objs (gw, jss::hashes), 0));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests are great. I'm was going to suggest you do one more unfiltered, but it looks like testUnsteppedThenStepped has that covered.

}

void run() override
{
testErrors();
testUnsteppedThenStepped();
testObjectTypes();
}
};

Expand Down