Don't include unit test sources in code coverage (RIPD-1132):

Most files containing unit test code are moved to
src/test. JTx and the test client code are not yet moved.
This commit is contained in:
Brad Chase
2016-09-02 15:25:05 -04:00
committed by Vinnie Falco
parent 8687f64429
commit 8f97889176
165 changed files with 2090 additions and 1693 deletions

View File

@@ -0,0 +1,175 @@
//------------------------------------------------------------------------------
/*
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/protocol/JsonFields.h> // jss:: definitions
#include <ripple/protocol/Feature.h>
#include <ripple/test/jtx.h>
namespace ripple {
namespace test {
class AccountInfo_test : public beast::unit_test::suite
{
public:
void testErrors()
{
using namespace jtx;
Env env(*this);
{
// account_info with no account.
auto const info = env.rpc ("json", "account_info", "{ }");
BEAST_EXPECT(info[jss::result][jss::error_message] ==
"Missing field 'account'.");
}
{
// account_info with a malformed account sting.
auto const info = env.rpc ("json", "account_info", "{\"account\": "
"\"n94JNrQYkDrpt62bbSR7nVEhdyAvcJXRAsjEkFYyqRkh9SUTYEqV\"}");
BEAST_EXPECT(info[jss::result][jss::error_message] ==
"Disallowed seed.");
}
{
// account_info with an account that's not in the ledger.
Account const bogie {"bogie"};
auto const info = env.rpc ("json", "account_info",
std::string ("{ ") + "\"account\": \"" + bogie.human() + "\"}");
BEAST_EXPECT(info[jss::result][jss::error_message] ==
"Account not found.");
}
}
// Test the "signer_lists" argument in account_info.
void testSignerLists()
{
using namespace jtx;
Env env(*this, features(featureMultiSign));
Account const alice {"alice"};
env.fund(XRP(1000), alice);
auto const withoutSigners = std::string ("{ ") +
"\"account\": \"" + alice.human() + "\"}";
auto const withSigners = std::string ("{ ") +
"\"account\": \"" + alice.human() + "\", " +
"\"signer_lists\": true }";
// Alice has no SignerList yet.
{
// account_info without the "signer_lists" argument.
auto const info = env.rpc ("json", "account_info", withoutSigners);
BEAST_EXPECT(info.isMember(jss::result) &&
info[jss::result].isMember(jss::account_data));
BEAST_EXPECT(! info[jss::result][jss::account_data].
isMember (jss::signer_lists));
}
{
// account_info with the "signer_lists" argument.
auto const info = env.rpc ("json", "account_info", withSigners);
BEAST_EXPECT(info.isMember(jss::result) &&
info[jss::result].isMember(jss::account_data));
auto const& data = info[jss::result][jss::account_data];
BEAST_EXPECT(data.isMember (jss::signer_lists));
auto const& signerLists = data[jss::signer_lists];
BEAST_EXPECT(signerLists.isArray());
BEAST_EXPECT(signerLists.size() == 0);
}
// Give alice a SignerList.
Account const bogie {"bogie"};
Json::Value const smallSigners = signers(alice, 2, { { bogie, 3 } });
env(smallSigners);
{
// account_info without the "signer_lists" argument.
auto const info = env.rpc ("json", "account_info", withoutSigners);
BEAST_EXPECT(info.isMember(jss::result) &&
info[jss::result].isMember(jss::account_data));
BEAST_EXPECT(! info[jss::result][jss::account_data].
isMember (jss::signer_lists));
}
{
// account_info with the "signer_lists" argument.
auto const info = env.rpc ("json", "account_info", withSigners);
BEAST_EXPECT(info.isMember(jss::result) &&
info[jss::result].isMember(jss::account_data));
auto const& data = info[jss::result][jss::account_data];
BEAST_EXPECT(data.isMember (jss::signer_lists));
auto const& signerLists = data[jss::signer_lists];
BEAST_EXPECT(signerLists.isArray());
BEAST_EXPECT(signerLists.size() == 1);
auto const& signers = signerLists[0u];
BEAST_EXPECT(signers.isObject());
BEAST_EXPECT(signers[sfSignerQuorum.jsonName] == 2);
auto const& signerEntries = signers[sfSignerEntries.jsonName];
BEAST_EXPECT(signerEntries.size() == 1);
auto const& entry0 = signerEntries[0u][sfSignerEntry.jsonName];
BEAST_EXPECT(entry0[sfSignerWeight.jsonName] == 3);
}
// Give alice a big signer list
Account const demon {"demon"};
Account const ghost {"ghost"};
Account const haunt {"haunt"};
Account const jinni {"jinni"};
Account const phase {"phase"};
Account const shade {"shade"};
Account const spook {"spook"};
Json::Value const bigSigners = signers(alice, 4, {
{bogie, 1}, {demon, 1}, {ghost, 1}, {haunt, 1},
{jinni, 1}, {phase, 1}, {shade, 1}, {spook, 1}, });
env(bigSigners);
{
// account_info with the "signer_lists" argument.
auto const info = env.rpc ("json", "account_info", withSigners);
BEAST_EXPECT(info.isMember(jss::result) &&
info[jss::result].isMember(jss::account_data));
auto const& data = info[jss::result][jss::account_data];
BEAST_EXPECT(data.isMember (jss::signer_lists));
auto const& signerLists = data[jss::signer_lists];
BEAST_EXPECT(signerLists.isArray());
BEAST_EXPECT(signerLists.size() == 1);
auto const& signers = signerLists[0u];
BEAST_EXPECT(signers.isObject());
BEAST_EXPECT(signers[sfSignerQuorum.jsonName] == 4);
auto const& signerEntries = signers[sfSignerEntries.jsonName];
BEAST_EXPECT(signerEntries.size() == 8);
for (unsigned i = 0u; i < 8; ++i)
{
auto const& entry = signerEntries[i][sfSignerEntry.jsonName];
BEAST_EXPECT(entry.size() == 2);
BEAST_EXPECT(entry.isMember(sfAccount.jsonName));
BEAST_EXPECT(entry[sfSignerWeight.jsonName] == 1);
}
}
}
void run()
{
testErrors();
testSignerLists();
}
};
BEAST_DEFINE_TESTSUITE(AccountInfo,app,ripple);
}
}

View File

@@ -0,0 +1,360 @@
//------------------------------------------------------------------------------
/*
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 <BeastConfig.h>
#include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/JsonFields.h>
#include <ripple/protocol/TxFlags.h>
#include <ripple/test/jtx.h>
#include <ripple/beast/unit_test.h>
namespace ripple {
namespace RPC {
class AccountLinesRPC_test : public beast::unit_test::suite
{
public:
void testAccountLines()
{
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": 1, )"
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 testAccountLineDelete()
{
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 EUR = gw2["EUR"];
env(trust(alice, EUR(200)));
env(trust(becky, USD(200)));
env(trust(cheri, USD(200)));
env.close();
// becky gets 100 USD from gw1.
env(pay(gw1, becky, USD(100)));
env.close();
// alice offers to buy 100 USD for 100 XRP.
env(offer(alice, USD(100), XRP(100)));
env.close();
// becky offers to buy 100 XRP for 100 USD.
env(offer(becky, XRP(100), USD(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": 1})");
BEAST_EXPECT(linesBeg[jss::result][jss::lines][0u][jss::currency] == "EUR");
BEAST_EXPECT(linesBeg[jss::result].isMember(jss::marker));
// alice pays 100 USD to cheri.
env(pay(alice, cheri, USD(100)));
env.close();
// Since alice paid all her USD 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]);
}
void run ()
{
testAccountLines();
testAccountLineDelete();
}
};
BEAST_DEFINE_TESTSUITE(AccountLinesRPC,app,ripple);
} // RPC
} // ripple

View File

@@ -0,0 +1,337 @@
//------------------------------------------------------------------------------
/*
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/protocol/JsonFields.h>
#include <ripple/json/json_value.h>
#include <ripple/json/to_string.h>
#include <ripple/json/json_reader.h>
#include <ripple/test/jtx.h>
#include <boost/utility/string_ref.hpp>
namespace ripple {
namespace test {
static char const* bobs_account_objects[] = {
R"json(
{
"Balance": {
"currency": "USD",
"issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
"value": "-1000"
},
"Flags": 131072,
"HighLimit": {
"currency": "USD",
"issuer": "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
"value": "1000"
},
"HighNode": "0000000000000000",
"LedgerEntryType": "RippleState",
"LowLimit": {
"currency": "USD",
"issuer": "r32rQHyesiTtdWFU7UJVtff4nCR5SHCbJW",
"value": "0"
},
"LowNode": "0000000000000000",
"index":
"D89BC239086183EB9458C396E643795C1134963E6550E682A190A5F021766D43"
})json"
,
R"json(
{
"Balance": {
"currency": "USD",
"issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
"value": "-1000"
},
"Flags": 131072,
"HighLimit": {
"currency": "USD",
"issuer": "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
"value": "1000"
},
"HighNode": "0000000000000000",
"LedgerEntryType": "RippleState",
"LowLimit": {
"currency": "USD",
"issuer": "r9cZvwKU3zzuZK9JFovGg1JC5n7QiqNL8L",
"value": "0"
},
"LowNode": "0000000000000000",
"index":
"D13183BCFFC9AAC9F96AEBB5F66E4A652AD1F5D10273AEB615478302BEBFD4A4"
})json"
,
R"json(
{
"Account": "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
"BookDirectory":
"50AD0A9E54D2B381288D535EB724E4275FFBF41580D28A925D038D7EA4C68000",
"BookNode": "0000000000000000",
"Flags": 65536,
"LedgerEntryType": "Offer",
"OwnerNode": "0000000000000000",
"Sequence": 4,
"TakerGets": {
"currency": "USD",
"issuer": "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
"value": "1"
},
"TakerPays": "100000000",
"index":
"A984D036A0E562433A8377CA57D1A1E056E58C0D04818F8DFD3A1AA3F217DD82"
})json"
,
R"json(
{
"Account": "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
"BookDirectory":
"B025997A323F5C3E03DDF1334471F5984ABDE31C59D463525D038D7EA4C68000",
"BookNode": "0000000000000000",
"Flags": 65536,
"LedgerEntryType": "Offer",
"OwnerNode": "0000000000000000",
"Sequence": 5,
"TakerGets": {
"currency": "USD",
"issuer" : "r32rQHyesiTtdWFU7UJVtff4nCR5SHCbJW",
"value" : "1"
},
"TakerPays" : "100000000",
"index" :
"CAFE32332D752387B01083B60CC63069BA4A969C9730836929F841450F6A718E"
}
)json"
};
class AccountObjects_test : public beast::unit_test::suite
{
public:
void testErrors()
{
testcase("error cases");
using namespace jtx;
Env env(*this);
// test error on no account
{
auto resp = env.rpc("account_objects");
BEAST_EXPECT( resp[jss::result][jss::error_message] ==
"Missing field 'account'.");
}
// test error on malformed account string.
{
Json::Value params;
params[jss::account] = "n94JNrQYkDrpt62bbSR7nVEhdyAvcJXRAsjEkFYyqRkh9SUTYEqV";
auto resp = env.rpc("json", "account_objects", to_string(params));
BEAST_EXPECT( resp[jss::result][jss::error_message] == "Disallowed seed.");
}
// test error on account that's not in the ledger.
{
Json::Value params;
params[jss::account] = Account{ "bogie" }.human();
auto resp = env.rpc("json", "account_objects", to_string(params));
BEAST_EXPECT( resp[jss::result][jss::error_message] == "Account not found.");
}
Account const bob{ "bob" };
// test error on large ledger_index.
{
Json::Value params;
params[jss::account] = bob.human();
params[jss::ledger_index] = 10;
auto resp = env.rpc("json", "account_objects", to_string(params));
BEAST_EXPECT( resp[jss::result][jss::error_message] == "ledgerNotFound");
}
env.fund(XRP(1000), bob);
// test error on type param not a string
{
Json::Value params;
params[jss::account] = bob.human();
params[jss::type] = 10;
auto resp = env.rpc("json", "account_objects", to_string(params));
BEAST_EXPECT( resp[jss::result][jss::error_message] ==
"Invalid field 'type', not string.");
}
// test error on type param not a valid type
{
Json::Value params;
params[jss::account] = bob.human();
params[jss::type] = "expedited";
auto resp = env.rpc("json", "account_objects", to_string(params));
BEAST_EXPECT( resp[jss::result][jss::error_message] == "Invalid field 'type'.");
}
// test error on limit -ve
{
Json::Value params;
params[jss::account] = bob.human();
params[jss::limit] = -1;
auto resp = env.rpc("json", "account_objects", to_string(params));
BEAST_EXPECT( resp[jss::result][jss::error_message] ==
"Invalid field 'limit', not unsigned integer.");
}
// test errors on marker
{
Account const gw{ "G" };
env.fund(XRP(1000), gw);
auto const USD = gw["USD"];
env.trust(USD(1000), bob);
env(pay(gw, bob, XRP(1)));
env(offer(bob, XRP(100), bob["USD"](1)), txflags(tfPassive));
Json::Value params;
params[jss::account] = bob.human();
params[jss::limit] = 1;
auto resp = env.rpc("json", "account_objects", to_string(params));
auto resume_marker = resp[jss::result][jss::marker];
std::string mark = to_string(resume_marker);
params[jss::marker] = 10;
resp = env.rpc("json", "account_objects", to_string(params));
BEAST_EXPECT( resp[jss::result][jss::error_message] == "Invalid field 'marker', not string.");
params[jss::marker] = "This is a string with no comma";
resp = env.rpc("json", "account_objects", to_string(params));
BEAST_EXPECT( resp[jss::result][jss::error_message] == "Invalid field 'marker'.");
params[jss::marker] = "This string has a comma, but is not hex";
resp = env.rpc("json", "account_objects", to_string(params));
BEAST_EXPECT( resp[jss::result][jss::error_message] == "Invalid field 'marker'.");
params[jss::marker] = std::string(&mark[1U], 64);
resp = env.rpc("json", "account_objects", to_string(params));
BEAST_EXPECT( resp[jss::result][jss::error_message] == "Invalid field 'marker'.");
params[jss::marker] = std::string(&mark[1U], 65);
resp = env.rpc("json", "account_objects", to_string(params));
BEAST_EXPECT( resp[jss::result][jss::error_message] == "Invalid field 'marker'.");
params[jss::marker] = std::string(&mark[1U], 65) + "not hex";
resp = env.rpc("json", "account_objects", to_string(params));
BEAST_EXPECT( resp[jss::result][jss::error_message] == "Invalid field 'marker'.");
// Should this be an error?
// A hex digit is absent from the end of marker.
// No account objects returned.
params[jss::marker] = std::string(&mark[1U], 128);
resp = env.rpc("json", "account_objects", to_string(params));
BEAST_EXPECT( resp[jss::result][jss::account_objects].size() == 0);
}
}
void testUnsteppedThenStepped()
{
testcase("unsteppedThenStepped");
using namespace jtx;
Env env(*this);
Account const gw1{ "G1" };
Account const gw2{ "G2" };
Account const bob{ "bob" };
auto const USD1 = gw1["USD"];
auto const USD2 = gw2["USD"];
env.fund(XRP(1000), gw1, gw2, bob);
env.trust(USD1(1000), bob);
env.trust(USD2(1000), bob);
env(pay(gw1, bob, USD1(1000)));
env(pay(gw2, bob, USD2(1000)));
env(offer(bob, XRP(100), bob["USD"](1)),txflags(tfPassive));
env(offer(bob, XRP(100), USD1(1)), txflags(tfPassive));
Json::Value bobj[4];
for (int i = 0; i < 4; ++i)
Json::Reader{}.parse(bobs_account_objects[i], bobj[i]);
// test 'unstepped'
// i.e. request account objects without explicit limit/marker paging
{
Json::Value params;
params[jss::account] = bob.human();
auto resp = env.rpc("json", "account_objects", to_string(params));
BEAST_EXPECT( !resp.isMember(jss::marker));
BEAST_EXPECT( resp[jss::result][jss::account_objects].size() == 4);
for (int i = 0; i < 4; ++i)
{
auto& aobj = resp[jss::result][jss::account_objects][i];
aobj.removeMember("PreviousTxnID");
aobj.removeMember("PreviousTxnLgrSeq");
BEAST_EXPECT( aobj == bobj[i]);
}
}
// test request with type parameter as filter, unstepped
{
Json::Value params;
params[jss::account] = bob.human();
params[jss::type] = "state";
auto resp = env.rpc("json", "account_objects", to_string(params));
BEAST_EXPECT( !resp.isMember(jss::marker));
BEAST_EXPECT( resp[jss::result][jss::account_objects].size() == 2);
for (int i = 0; i < 2; ++i)
{
auto& aobj = resp[jss::result][jss::account_objects][i];
aobj.removeMember("PreviousTxnID");
aobj.removeMember("PreviousTxnLgrSeq");
BEAST_EXPECT( aobj == bobj[i]);
}
}
// test stepped one-at-a-time with limit=1, resume from prev marker
{
Json::Value params;
params[jss::account] = bob.human();
params[jss::limit] = 1;
for (int i = 0; i < 4; ++i)
{
auto resp = env.rpc("json", "account_objects", to_string(params));
auto& aobjs = resp[jss::result][jss::account_objects];
BEAST_EXPECT( aobjs.size() == 1);
auto& aobj = aobjs[0U];
if (i < 3) BEAST_EXPECT( resp[jss::result][jss::limit] == 1);
aobj.removeMember("PreviousTxnID");
aobj.removeMember("PreviousTxnLgrSeq");
BEAST_EXPECT( aobj == bobj[i]);
auto resume_marker = resp[jss::result][jss::marker];
params[jss::marker] = resume_marker;
}
}
}
void run() override
{
testErrors();
testUnsteppedThenStepped();
}
};
BEAST_DEFINE_TESTSUITE(AccountObjects,app,ripple);
}
}

View File

@@ -0,0 +1,300 @@
//------------------------------------------------------------------------------
/*
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/protocol/JsonFields.h>
#include <ripple/test/jtx.h>
namespace ripple {
namespace test {
class AccountOffers_test : public beast::unit_test::suite
{
public:
static
std::unique_ptr<Config>
makeConfig(bool setup_admin)
{
auto p = std::make_unique<Config>();
setupConfigForUnitTests(*p);
// the default config has admin active
// ...we remove them if setup_admin is false
if (! setup_admin)
{
(*p)["port_rpc"].set("admin","");
(*p)["port_ws"].set("admin","");
}
return p;
}
// test helper
static bool checkArraySize(Json::Value const& val, unsigned int size)
{
return val.isArray() &&
val.size() == size;
}
// test helper
static bool checkMarker(Json::Value const& val)
{
return val.isMember(jss::marker) &&
val[jss::marker].isString() &&
val[jss::marker].asString().size() > 0;
}
void testNonAdminMinLimit()
{
using namespace jtx;
Env env(*this, makeConfig(false));
Account const gw ("G1");
auto const USD_gw = gw["USD"];
Account const bob ("bob");
auto const USD_bob = bob["USD"];
env.fund(XRP(10000), gw, bob);
env.trust(USD_gw(1000), bob);
// this is to provide some USD from gw in the
// bob account so that it can rightly
// make offers that give those USDs
env (pay (gw, bob, USD_gw (10)));
unsigned const offer_count = 12u;
for (auto i = 0u; i < offer_count; i++)
{
Json::Value jvo = offer(bob, XRP(100 + i), USD_gw(1));
jvo[sfExpiration.fieldName] = 10000000u;
env(jvo);
}
// make non-limited RPC call
auto const jro_nl = env.rpc("account_offers", bob.human())[jss::result][jss::offers];
BEAST_EXPECT(checkArraySize(jro_nl, offer_count));
// now make a low-limit query, should get "corrected"
// to a min of 10 results with a marker set since there
// are more than 10 total
Json::Value jvParams;
jvParams[jss::account] = bob.human();
jvParams[jss::limit] = 1u;
auto const jrr_l = env.rpc ("json", "account_offers", jvParams.toStyledString())[jss::result];
auto const& jro_l = jrr_l[jss::offers];
BEAST_EXPECT(checkMarker(jrr_l));
BEAST_EXPECT(checkArraySize(jro_l, 10u));
}
void testSequential(bool as_admin)
{
using namespace jtx;
Env env(*this, makeConfig(as_admin));
Account const gw ("G1");
auto const USD_gw = gw["USD"];
Account const bob ("bob");
auto const USD_bob = bob["USD"];
env.fund(XRP(10000), gw, bob);
env.trust(USD_gw(1000), bob);
// this is to provide some USD from gw in the
// bob account so that it can rightly
// make offers that give those USDs
env (pay (gw, bob, USD_gw (10)));
env(offer(bob, XRP(100), USD_bob(1)));
env(offer(bob, XRP(100), USD_gw(1)));
env(offer(bob, XRP(10), USD_gw(2)));
// make the RPC call
auto const jro = env.rpc("account_offers", bob.human())[jss::result][jss::offers];
if(BEAST_EXPECT(checkArraySize(jro, 3u)))
{
BEAST_EXPECT(jro[0u][jss::quality] == "100000000");
BEAST_EXPECT(jro[0u][jss::taker_gets][jss::currency] == "USD");
BEAST_EXPECT(jro[0u][jss::taker_gets][jss::issuer] == bob.human());
BEAST_EXPECT(jro[0u][jss::taker_gets][jss::value] == "1");
BEAST_EXPECT(jro[0u][jss::taker_pays] == "100000000");
BEAST_EXPECT(jro[1u][jss::quality] == "100000000");
BEAST_EXPECT(jro[1u][jss::taker_gets][jss::currency] == "USD");
BEAST_EXPECT(jro[1u][jss::taker_gets][jss::issuer] == gw.human());
BEAST_EXPECT(jro[1u][jss::taker_gets][jss::value] == "1");
BEAST_EXPECT(jro[1u][jss::taker_pays] == "100000000");
BEAST_EXPECT(jro[2u][jss::quality] == "5000000");
BEAST_EXPECT(jro[2u][jss::taker_gets][jss::currency] == "USD");
BEAST_EXPECT(jro[2u][jss::taker_gets][jss::issuer] == gw.human());
BEAST_EXPECT(jro[2u][jss::taker_gets][jss::value] == "2");
BEAST_EXPECT(jro[2u][jss::taker_pays] == "10000000");
}
{
// now make a limit (= 1) query for the same data
Json::Value jvParams;
jvParams[jss::account] = bob.human();
jvParams[jss::limit] = 1u;
auto const jrr_l_1 = env.rpc ("json", "account_offers", jvParams.toStyledString())[jss::result];
auto const& jro_l_1 = jrr_l_1[jss::offers];
// there is a difference in the validation of the limit param
// between admin and non-admin requests. with admin requests, the
// limit parameter is NOT subject to sane defaults, but with a
// non-admin there are pre-configured limit ranges applied. That's
// why we have different BEAST_EXPECT()s here for the two scenarios
BEAST_EXPECT(checkArraySize(jro_l_1, as_admin ? 1u : 3u));
BEAST_EXPECT(as_admin ? checkMarker(jrr_l_1) : (! jrr_l_1.isMember(jss::marker)));
if (as_admin)
{
BEAST_EXPECT(jro[0u] == jro_l_1[0u]);
// second item...with previous marker passed
jvParams[jss::marker] = jrr_l_1[jss::marker];
auto const jrr_l_2 = env.rpc ("json", "account_offers", jvParams.toStyledString())[jss::result];
auto const& jro_l_2 = jrr_l_2[jss::offers];
BEAST_EXPECT(checkMarker(jrr_l_2));
BEAST_EXPECT(checkArraySize(jro_l_2, 1u));
BEAST_EXPECT(jro[1u] == jro_l_2[0u]);
// last item...with previous marker passed
jvParams[jss::marker] = jrr_l_2[jss::marker];
auto const jrr_l_3 = env.rpc ("json", "account_offers", jvParams.toStyledString())[jss::result];
auto const& jro_l_3 = jrr_l_3[jss::offers];
BEAST_EXPECT(! jrr_l_3.isMember(jss::marker));
BEAST_EXPECT(checkArraySize(jro_l_3, 1u));
BEAST_EXPECT(jro[2u] == jro_l_3[0u]);
}
else
{
BEAST_EXPECT(jro == jro_l_1);
}
}
{
// now make a limit (= 0) query for the same data
// since we operate on the admin port, the limit
// value of 0 is not adjusted into tuned ranges for admin requests so
// we literally get 0 elements in that case. For non-admin
// requests, we get limit defaults applied thus all our results
// come back (we are below the min results limit)
Json::Value jvParams;
jvParams[jss::account] = bob.human();
jvParams[jss::limit] = 0u;
auto const jrr = env.rpc ("json", "account_offers", jvParams.toStyledString())[jss::result];
auto const& jro = jrr[jss::offers];
BEAST_EXPECT(checkArraySize(jro, as_admin ? 0u : 3u));
BEAST_EXPECT(as_admin ? checkMarker(jrr) : (! jrr.isMember(jss::marker)));
}
}
void testBadInput()
{
using namespace jtx;
Env env(*this);
Account const gw ("G1");
auto const USD_gw = gw["USD"];
Account const bob ("bob");
auto const USD_bob = bob["USD"];
env.fund(XRP(10000), gw, bob);
env.trust(USD_gw(1000), bob);
{
// no account field
auto const jrr = env.rpc ("account_offers")[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error_message] == "Missing field 'account'.");
}
{
// empty string account
Json::Value jvParams;
jvParams[jss::account] = "";
auto const jrr = env.rpc ("json", "account_offers", jvParams.toStyledString())[jss::result];
BEAST_EXPECT(jrr[jss::error] == "badSeed");
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error_message] == "Disallowed seed.");
}
{
// bogus account value
auto const jrr = env.rpc ("account_offers", "rNOT_AN_ACCOUNT")[jss::result];
BEAST_EXPECT(jrr[jss::error] == "actNotFound");
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error_message] == "Account not found.");
}
{
// bad limit
Json::Value jvParams;
jvParams[jss::account] = bob.human();
jvParams[jss::limit] = "0"; // NOT an integer
auto const jrr = env.rpc ("json", "account_offers", jvParams.toStyledString())[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'limit', not unsigned integer.");
}
{
// invalid marker
Json::Value jvParams;
jvParams[jss::account] = bob.human();
jvParams[jss::marker] = "NOT_A_MARKER";
auto const jrr = env.rpc ("json", "account_offers", jvParams.toStyledString())[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error_message] == "Invalid parameters.");
}
{
// invalid marker - not a string
Json::Value jvParams;
jvParams[jss::account] = bob.human();
jvParams[jss::marker] = 1;
auto const jrr = env.rpc ("json", "account_offers", jvParams.toStyledString())[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'marker', not string.");
}
{
// ask for a bad ledger index
Json::Value jvParams;
jvParams[jss::account] = bob.human();
jvParams[jss::ledger_index] = 10u;
auto const jrr = env.rpc ("json", "account_offers", jvParams.toStyledString())[jss::result];
BEAST_EXPECT(jrr[jss::error] == "lgrNotFound");
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error_message] == "ledgerNotFound");
}
}
void run()
{
testSequential(true);
testSequential(false);
testBadInput();
testNonAdminMinLimit();
}
};
BEAST_DEFINE_TESTSUITE(AccountOffers,app,ripple);
}
}

View File

@@ -0,0 +1,317 @@
//------------------------------------------------------------------------------
/*
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/protocol/JsonFields.h>
#include <ripple/protocol/Feature.h>
#include <ripple/test/jtx.h>
#include <ripple/basics/StringUtilities.h>
namespace ripple {
class AccountSet_test : public beast::unit_test::suite
{
public:
void testNullAccountSet()
{
using namespace test::jtx;
Env env(*this);
Account const alice ("alice");
env.fund(XRP(10000), noripple(alice));
//ask for the ledger entry - account root, to check its flags
auto const jrr = env.le(alice);
BEAST_EXPECT((*env.le(alice))[ sfFlags ] == 0u);
}
void testSetAndReset(unsigned int flag_val, std::string const& label)
{
using namespace test::jtx;
Env env(*this);
Account const alice ("alice");
env.fund(XRP(10000), noripple(alice));
env.memoize("eric");
env(regkey(alice, "eric"));
unsigned int orig_flags = (*env.le(alice))[ sfFlags ];
env.require(nflags(alice, flag_val));
env(fset(alice, flag_val), sig(alice));
env.require(flags(alice, flag_val));
env(fclear(alice, flag_val));
env.require(nflags(alice, flag_val));
uint32 now_flags = (*env.le(alice))[ sfFlags ];
BEAST_EXPECT(now_flags == orig_flags);
}
void testSetAndResetAccountTxnID()
{
using namespace test::jtx;
Env env(*this);
Account const alice ("alice");
env.fund(XRP(10000), noripple(alice));
unsigned int orig_flags = (*env.le(alice))[ sfFlags ];
// asfAccountTxnID is special and not actually set as a flag,
// so we check the field presence instead
BEAST_EXPECT(! env.le(alice)->isFieldPresent(sfAccountTxnID));
env(fset(alice, asfAccountTxnID), sig(alice));
BEAST_EXPECT(env.le(alice)->isFieldPresent(sfAccountTxnID));
env(fclear(alice, asfAccountTxnID));
BEAST_EXPECT(! env.le(alice)->isFieldPresent(sfAccountTxnID));
uint32 now_flags = (*env.le(alice))[ sfFlags ];
BEAST_EXPECT(now_flags == orig_flags);
}
void testSetNoFreeze()
{
using namespace test::jtx;
Env env(*this);
Account const alice ("alice");
env.fund(XRP(10000), noripple(alice));
env.memoize("eric");
env(regkey(alice, "eric"));
env.require(nflags(alice, asfNoFreeze));
env(fset(alice, asfNoFreeze), sig("eric"), ter(tecNEED_MASTER_KEY));
env(fset(alice, asfNoFreeze), sig(alice));
env.require(flags(alice, asfNoFreeze));
env(fclear(alice, asfNoFreeze), sig(alice));
// verify flag is still set (clear does not clear in this case)
env.require(flags(alice, asfNoFreeze));
}
void testDomain()
{
using namespace test::jtx;
Env env(*this);
Account const alice ("alice");
env.fund(XRP(10000), alice);
auto jt = noop(alice);
// The Domain field is represented as the hex string of the lowercase
// ASCII of the domain. For example, the domain example.com would be
// represented as "6578616d706c652e636f6d".
//
// To remove the Domain field from an account, send an AccountSet with
// the Domain set to an empty string.
std::string const domain = "example.com";
jt[sfDomain.fieldName] = strHex(domain);
env(jt);
BEAST_EXPECT((*env.le(alice))[ sfDomain ] == makeSlice(domain));
jt[sfDomain.fieldName] = "";
env(jt);
BEAST_EXPECT(! env.le(alice)->isFieldPresent(sfDomain));
// The upper limit on the length is 256 bytes
// (defined as DOMAIN_BYTES_MAX in SetAccount)
// test the edge cases: 255, 256, 257.
std::size_t const maxLength = 256;
for (std::size_t len = maxLength - 1; len <= maxLength + 1; ++len)
{
std::string domain2 =
std::string(len - domain.length() - 1, 'a') + "." + domain;
BEAST_EXPECT (domain2.length() == len);
jt[sfDomain.fieldName] = strHex(domain2);
if (len <= maxLength)
{
env(jt);
BEAST_EXPECT((*env.le(alice))[ sfDomain ] == makeSlice(domain2));
}
else
{
env(jt, ter(telBAD_DOMAIN));
}
}
}
void testMessageKey()
{
using namespace test::jtx;
Env env(*this);
Account const alice ("alice");
env.fund(XRP(10000), alice);
auto jt = noop(alice);
auto const rkp = randomKeyPair(KeyType::ed25519);
jt[sfMessageKey.fieldName] = strHex(rkp.first.slice());
env(jt);
BEAST_EXPECT(strHex((*env.le(alice))[ sfMessageKey ]) == strHex(rkp.first.slice()));
jt[sfMessageKey.fieldName] = "";
env(jt);
BEAST_EXPECT(! env.le(alice)->isFieldPresent(sfMessageKey));
jt[sfMessageKey.fieldName] = strHex("NOT_REALLY_A_PUBKEY");
env(jt, ter(telBAD_PUBLIC_KEY));
}
void testWalletID()
{
using namespace test::jtx;
Env env(*this);
Account const alice ("alice");
env.fund(XRP(10000), alice);
auto jt = noop(alice);
uint256 somehash = from_hex_text<uint256>("9633ec8af54f16b5286db1d7b519ef49eefc050c0c8ac4384f1d88acd1bfdf05");
jt[sfWalletLocator.fieldName] = to_string(somehash);
env(jt);
BEAST_EXPECT((*env.le(alice))[ sfWalletLocator ] == somehash);
jt[sfWalletLocator.fieldName] = "";
env(jt);
BEAST_EXPECT(! env.le(alice)->isFieldPresent(sfWalletLocator));
}
void testEmailHash()
{
using namespace test::jtx;
Env env(*this);
Account const alice ("alice");
env.fund(XRP(10000), alice);
auto jt = noop(alice);
uint128 somehash = from_hex_text<uint128>("fff680681c2f5e6095324e2e08838f221a72ab4f");
jt[sfEmailHash.fieldName] = to_string(somehash);
env(jt);
BEAST_EXPECT((*env.le(alice))[ sfEmailHash ] == somehash);
jt[sfEmailHash.fieldName] = "";
env(jt);
BEAST_EXPECT(! env.le(alice)->isFieldPresent(sfEmailHash));
}
void testTransferRate()
{
using namespace test::jtx;
Env env(*this);
Account const alice ("alice");
env.fund(XRP(10000), alice);
auto jt = noop(alice);
uint32 xfer_rate = 2000000000;
jt[sfTransferRate.fieldName] = xfer_rate;
env(jt);
BEAST_EXPECT((*env.le(alice))[ sfTransferRate ] == xfer_rate);
jt[sfTransferRate.fieldName] = 0u;
env(jt);
BEAST_EXPECT(! env.le(alice)->isFieldPresent(sfTransferRate));
// set a bad value (< QUALITY_ONE)
jt[sfTransferRate.fieldName] = 10u;
env(jt, ter(temBAD_TRANSFER_RATE));
}
void testBadInputs()
{
using namespace test::jtx;
Env env(*this);
Account const alice ("alice");
env.fund(XRP(10000), alice);
auto jt = fset(alice, asfDisallowXRP);
jt[jss::ClearFlag] = asfDisallowXRP;
env(jt, ter(temINVALID_FLAG));
jt = fset(alice, asfRequireAuth);
jt[jss::ClearFlag] = asfRequireAuth;
env(jt, ter(temINVALID_FLAG));
jt = fset(alice, asfRequireDest);
jt[jss::ClearFlag] = asfRequireDest;
env(jt, ter(temINVALID_FLAG));
jt = fset(alice, asfDisallowXRP);
jt[sfFlags.fieldName] = tfAllowXRP;
env(jt, ter(temINVALID_FLAG));
jt = fset(alice, asfRequireAuth);
jt[sfFlags.fieldName] = tfOptionalAuth;
env(jt, ter(temINVALID_FLAG));
jt = fset(alice, asfRequireDest);
jt[sfFlags.fieldName] = tfOptionalDestTag;
env(jt, ter(temINVALID_FLAG));
jt = fset(alice, asfRequireDest);
jt[sfFlags.fieldName] = tfAccountSetMask;
env(jt, ter(temINVALID_FLAG));
env(fset (alice, asfDisableMaster),
sig(alice), ter(tecNO_REGULAR_KEY));
}
void testRequireAuthWithDir()
{
using namespace test::jtx;
Env env(*this, features(featureMultiSign));
Account const alice ("alice");
Account const bob ("bob");
env.fund(XRP(10000), alice);
env.close();
// alice should have an empty directory.
BEAST_EXPECT(dirIsEmpty (*env.closed(), keylet::ownerDir(alice)));
// Give alice a signer list, then there will be stuff in the directory.
env(signers(alice, 1, { { bob, 1} }));
env.close();
BEAST_EXPECT(! dirIsEmpty (*env.closed(), keylet::ownerDir(alice)));
env(fset (alice, asfRequireAuth), ter(tecOWNERS));
}
void run()
{
testNullAccountSet();
for(auto const& flag_set : std::vector<std::pair<unsigned int, std::string>>({
{asfRequireDest, "RequireDestTag"},
{asfRequireAuth, "RequireAuth"},
{asfDisallowXRP, "DisallowXRP"},
{asfGlobalFreeze, "GlobalFreeze"},
{asfDisableMaster, "DisableMaster"},
{asfDefaultRipple, "DefaultRipple"}
}))
{
testSetAndReset(flag_set.first, flag_set.second);
}
testSetAndResetAccountTxnID();
testSetNoFreeze();
testDomain();
testMessageKey();
testWalletID();
testEmailHash();
testBadInputs();
testRequireAuthWithDir();
testTransferRate();
}
};
BEAST_DEFINE_TESTSUITE(AccountSet,app,ripple);
}

823
src/test/rpc/Book_test.cpp Normal file
View File

@@ -0,0 +1,823 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 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 <BeastConfig.h>
#include <ripple/protocol/JsonFields.h>
#include <ripple/test/WSClient.h>
#include <ripple/test/jtx.h>
#include <ripple/beast/unit_test.h>
namespace ripple {
namespace test {
class Book_test : public beast::unit_test::suite
{
public:
void
testOneSideEmptyBook()
{
using namespace std::chrono_literals;
using namespace jtx;
Env env(*this);
env.fund(XRP(10000), "alice");
auto USD = Account("alice")["USD"];
auto wsc = makeWSClient(env.app().config());
Json::Value books;
{
// RPC subscribe to books stream
books[jss::books] = Json::arrayValue;
{
auto &j = books[jss::books].append(Json::objectValue);
j[jss::snapshot] = true;
j[jss::taker_gets][jss::currency] = "XRP";
j[jss::taker_pays][jss::currency] = "USD";
j[jss::taker_pays][jss::issuer] = Account("alice").human();
}
auto jv = wsc->invoke("subscribe", books);
BEAST_EXPECT(jv[jss::status] == "success");
BEAST_EXPECT(jv[jss::result].isMember(jss::offers) &&
jv[jss::result][jss::offers].size() == 0);
BEAST_EXPECT(! jv[jss::result].isMember(jss::asks));
BEAST_EXPECT(! jv[jss::result].isMember(jss::bids));
}
{
// Create an ask: TakerPays 700, TakerGets 100/USD
env(offer("alice", XRP(700), USD(100)),
require(owners("alice", 1)));
env.close();
// Check stream update
BEAST_EXPECT(wsc->findMsg(5s,
[&](auto const& jv)
{
auto const& t = jv[jss::transaction];
return t[jss::TransactionType] == "OfferCreate" &&
t[jss::TakerGets] == USD(100).value().getJson(0) &&
t[jss::TakerPays] == XRP(700).value().getJson(0);
}));
}
{
// Create a bid: TakerPays 100/USD, TakerGets 75
env(offer("alice", USD(100), XRP(75)),
require(owners("alice", 2)));
env.close();
BEAST_EXPECT(! wsc->getMsg(10ms));
}
// RPC unsubscribe
BEAST_EXPECT(wsc->invoke("unsubscribe",
books)[jss::status] == "success");
}
void
testOneSideOffersInBook()
{
using namespace std::chrono_literals;
using namespace jtx;
Env env(*this);
env.fund(XRP(10000), "alice");
auto USD = Account("alice")["USD"];
auto wsc = makeWSClient(env.app().config());
Json::Value books;
// Create an ask: TakerPays 500, TakerGets 100/USD
env(offer("alice", XRP(500), USD(100)),
require(owners("alice", 1)));
// Create a bid: TakerPays 100/USD, TakerGets 200
env(offer("alice", USD(100), XRP(200)),
require(owners("alice", 2)));
env.close();
{
// RPC subscribe to books stream
books[jss::books] = Json::arrayValue;
{
auto &j = books[jss::books].append(Json::objectValue);
j[jss::snapshot] = true;
j[jss::taker_gets][jss::currency] = "XRP";
j[jss::taker_pays][jss::currency] = "USD";
j[jss::taker_pays][jss::issuer] = Account("alice").human();
}
auto jv = wsc->invoke("subscribe", books);
BEAST_EXPECT(jv[jss::status] == "success");
BEAST_EXPECT(jv[jss::result].isMember(jss::offers) &&
jv[jss::result][jss::offers].size() == 1);
BEAST_EXPECT(jv[jss::result][jss::offers][0u][jss::TakerGets] ==
XRP(200).value().getJson(0));
BEAST_EXPECT(jv[jss::result][jss::offers][0u][jss::TakerPays] ==
USD(100).value().getJson(0));
BEAST_EXPECT(! jv[jss::result].isMember(jss::asks));
BEAST_EXPECT(! jv[jss::result].isMember(jss::bids));
}
{
// Create an ask: TakerPays 700, TakerGets 100/USD
env(offer("alice", XRP(700), USD(100)),
require(owners("alice", 3)));
env.close();
// Check stream update
BEAST_EXPECT(wsc->findMsg(5s,
[&](auto const& jv)
{
auto const& t = jv[jss::transaction];
return t[jss::TransactionType] == "OfferCreate" &&
t[jss::TakerGets] == USD(100).value().getJson(0) &&
t[jss::TakerPays] == XRP(700).value().getJson(0);
}));
}
{
// Create a bid: TakerPays 100/USD, TakerGets 75
env(offer("alice", USD(100), XRP(75)),
require(owners("alice", 4)));
env.close();
BEAST_EXPECT(! wsc->getMsg(10ms));
}
// RPC unsubscribe
BEAST_EXPECT(wsc->invoke("unsubscribe",
books)[jss::status] == "success");
}
void
testBothSidesEmptyBook()
{
using namespace std::chrono_literals;
using namespace jtx;
Env env(*this);
env.fund(XRP(10000), "alice");
auto USD = Account("alice")["USD"];
auto wsc = makeWSClient(env.app().config());
Json::Value books;
{
// RPC subscribe to books stream
books[jss::books] = Json::arrayValue;
{
auto &j = books[jss::books].append(Json::objectValue);
j[jss::snapshot] = true;
j[jss::both] = true;
j[jss::taker_gets][jss::currency] = "XRP";
j[jss::taker_pays][jss::currency] = "USD";
j[jss::taker_pays][jss::issuer] = Account("alice").human();
}
auto jv = wsc->invoke("subscribe", books);
BEAST_EXPECT(jv[jss::status] == "success");
BEAST_EXPECT(jv[jss::result].isMember(jss::asks) &&
jv[jss::result][jss::asks].size() == 0);
BEAST_EXPECT(jv[jss::result].isMember(jss::bids) &&
jv[jss::result][jss::bids].size() == 0);
BEAST_EXPECT(! jv[jss::result].isMember(jss::offers));
}
{
// Create an ask: TakerPays 700, TakerGets 100/USD
env(offer("alice", XRP(700), USD(100)),
require(owners("alice", 1)));
env.close();
// Check stream update
BEAST_EXPECT(wsc->findMsg(5s,
[&](auto const& jv)
{
auto const& t = jv[jss::transaction];
return t[jss::TransactionType] == "OfferCreate" &&
t[jss::TakerGets] == USD(100).value().getJson(0) &&
t[jss::TakerPays] == XRP(700).value().getJson(0);
}));
}
{
// Create a bid: TakerPays 100/USD, TakerGets 75
env(offer("alice", USD(100), XRP(75)),
require(owners("alice", 2)));
env.close();
// Check stream update
BEAST_EXPECT(wsc->findMsg(5s,
[&](auto const& jv)
{
auto const& t = jv[jss::transaction];
return t[jss::TransactionType] == "OfferCreate" &&
t[jss::TakerGets] == XRP(75).value().getJson(0) &&
t[jss::TakerPays] == USD(100).value().getJson(0);
}));
}
// RPC unsubscribe
BEAST_EXPECT(wsc->invoke("unsubscribe",
books)[jss::status] == "success");
}
void
testBothSidesOffersInBook()
{
using namespace std::chrono_literals;
using namespace jtx;
Env env(*this);
env.fund(XRP(10000), "alice");
auto USD = Account("alice")["USD"];
auto wsc = makeWSClient(env.app().config());
Json::Value books;
// Create an ask: TakerPays 500, TakerGets 100/USD
env(offer("alice", XRP(500), USD(100)),
require(owners("alice", 1)));
// Create a bid: TakerPays 100/USD, TakerGets 200
env(offer("alice", USD(100), XRP(200)),
require(owners("alice", 2)));
env.close();
{
// RPC subscribe to books stream
books[jss::books] = Json::arrayValue;
{
auto &j = books[jss::books].append(Json::objectValue);
j[jss::snapshot] = true;
j[jss::both] = true;
j[jss::taker_gets][jss::currency] = "XRP";
j[jss::taker_pays][jss::currency] = "USD";
j[jss::taker_pays][jss::issuer] = Account("alice").human();
}
auto jv = wsc->invoke("subscribe", books);
BEAST_EXPECT(jv[jss::status] == "success");
BEAST_EXPECT(jv[jss::result].isMember(jss::asks) &&
jv[jss::result][jss::asks].size() == 1);
BEAST_EXPECT(jv[jss::result].isMember(jss::bids) &&
jv[jss::result][jss::bids].size() == 1);
BEAST_EXPECT(jv[jss::result][jss::asks][0u][jss::TakerGets] ==
USD(100).value().getJson(0));
BEAST_EXPECT(jv[jss::result][jss::asks][0u][jss::TakerPays] ==
XRP(500).value().getJson(0));
BEAST_EXPECT(jv[jss::result][jss::bids][0u][jss::TakerGets] ==
XRP(200).value().getJson(0));
BEAST_EXPECT(jv[jss::result][jss::bids][0u][jss::TakerPays] ==
USD(100).value().getJson(0));
BEAST_EXPECT(! jv[jss::result].isMember(jss::offers));
}
{
// Create an ask: TakerPays 700, TakerGets 100/USD
env(offer("alice", XRP(700), USD(100)),
require(owners("alice", 3)));
env.close();
// Check stream update
BEAST_EXPECT(wsc->findMsg(5s,
[&](auto const& jv)
{
auto const& t = jv[jss::transaction];
return t[jss::TransactionType] == "OfferCreate" &&
t[jss::TakerGets] == USD(100).value().getJson(0) &&
t[jss::TakerPays] == XRP(700).value().getJson(0);
}));
}
{
// Create a bid: TakerPays 100/USD, TakerGets 75
env(offer("alice", USD(100), XRP(75)),
require(owners("alice", 4)));
env.close();
// Check stream update
BEAST_EXPECT(wsc->findMsg(5s,
[&](auto const& jv)
{
auto const& t = jv[jss::transaction];
return t[jss::TransactionType] == "OfferCreate" &&
t[jss::TakerGets] == XRP(75).value().getJson(0) &&
t[jss::TakerPays] == USD(100).value().getJson(0);
}));
}
// RPC unsubscribe
BEAST_EXPECT(wsc->invoke("unsubscribe",
books)[jss::status] == "success");
}
void
testMultipleBooksOneSideEmptyBook()
{
using namespace std::chrono_literals;
using namespace jtx;
Env env(*this);
env.fund(XRP(10000), "alice");
auto USD = Account("alice")["USD"];
auto CNY = Account("alice")["CNY"];
auto JPY = Account("alice")["JPY"];
auto wsc = makeWSClient(env.app().config());
Json::Value books;
{
// RPC subscribe to books stream
books[jss::books] = Json::arrayValue;
{
auto &j = books[jss::books].append(Json::objectValue);
j[jss::snapshot] = true;
j[jss::taker_gets][jss::currency] = "XRP";
j[jss::taker_pays][jss::currency] = "USD";
j[jss::taker_pays][jss::issuer] = Account("alice").human();
}
{
auto &j = books[jss::books].append(Json::objectValue);
j[jss::snapshot] = true;
j[jss::taker_gets][jss::currency] = "CNY";
j[jss::taker_gets][jss::issuer] = Account("alice").human();
j[jss::taker_pays][jss::currency] = "JPY";
j[jss::taker_pays][jss::issuer] = Account("alice").human();
}
auto jv = wsc->invoke("subscribe", books);
BEAST_EXPECT(jv[jss::status] == "success");
BEAST_EXPECT(jv[jss::result].isMember(jss::offers) &&
jv[jss::result][jss::offers].size() == 0);
BEAST_EXPECT(! jv[jss::result].isMember(jss::asks));
BEAST_EXPECT(! jv[jss::result].isMember(jss::bids));
}
{
// Create an ask: TakerPays 700, TakerGets 100/USD
env(offer("alice", XRP(700), USD(100)),
require(owners("alice", 1)));
env.close();
// Check stream update
BEAST_EXPECT(wsc->findMsg(5s,
[&](auto const& jv)
{
auto const& t = jv[jss::transaction];
return t[jss::TransactionType] == "OfferCreate" &&
t[jss::TakerGets] == USD(100).value().getJson(0) &&
t[jss::TakerPays] == XRP(700).value().getJson(0);
}));
}
{
// Create a bid: TakerPays 100/USD, TakerGets 75
env(offer("alice", USD(100), XRP(75)),
require(owners("alice", 2)));
env.close();
BEAST_EXPECT(! wsc->getMsg(10ms));
}
{
// Create an ask: TakerPays 700/CNY, TakerGets 100/JPY
env(offer("alice", CNY(700), JPY(100)),
require(owners("alice", 3)));
env.close();
// Check stream update
BEAST_EXPECT(wsc->findMsg(5s,
[&](auto const& jv)
{
auto const& t = jv[jss::transaction];
return t[jss::TransactionType] == "OfferCreate" &&
t[jss::TakerGets] == JPY(100).value().getJson(0) &&
t[jss::TakerPays] == CNY(700).value().getJson(0);
}));
}
{
// Create a bid: TakerPays 100/JPY, TakerGets 75/CNY
env(offer("alice", JPY(100), CNY(75)),
require(owners("alice", 4)));
env.close();
BEAST_EXPECT(! wsc->getMsg(10ms));
}
// RPC unsubscribe
BEAST_EXPECT(wsc->invoke("unsubscribe",
books)[jss::status] == "success");
}
void
testMultipleBooksOneSideOffersInBook()
{
using namespace std::chrono_literals;
using namespace jtx;
Env env(*this);
env.fund(XRP(10000), "alice");
auto USD = Account("alice")["USD"];
auto CNY = Account("alice")["CNY"];
auto JPY = Account("alice")["JPY"];
auto wsc = makeWSClient(env.app().config());
Json::Value books;
// Create an ask: TakerPays 500, TakerGets 100/USD
env(offer("alice", XRP(500), USD(100)),
require(owners("alice", 1)));
// Create an ask: TakerPays 500/CNY, TakerGets 100/JPY
env(offer("alice", CNY(500), JPY(100)),
require(owners("alice", 2)));
// Create a bid: TakerPays 100/USD, TakerGets 200
env(offer("alice", USD(100), XRP(200)),
require(owners("alice", 3)));
// Create a bid: TakerPays 100/JPY, TakerGets 200/CNY
env(offer("alice", JPY(100), CNY(200)),
require(owners("alice", 4)));
env.close();
{
// RPC subscribe to books stream
books[jss::books] = Json::arrayValue;
{
auto &j = books[jss::books].append(Json::objectValue);
j[jss::snapshot] = true;
j[jss::taker_gets][jss::currency] = "XRP";
j[jss::taker_pays][jss::currency] = "USD";
j[jss::taker_pays][jss::issuer] = Account("alice").human();
}
{
auto &j = books[jss::books].append(Json::objectValue);
j[jss::snapshot] = true;
j[jss::taker_gets][jss::currency] = "CNY";
j[jss::taker_gets][jss::issuer] = Account("alice").human();
j[jss::taker_pays][jss::currency] = "JPY";
j[jss::taker_pays][jss::issuer] = Account("alice").human();
}
auto jv = wsc->invoke("subscribe", books);
BEAST_EXPECT(jv[jss::status] == "success");
BEAST_EXPECT(jv[jss::result].isMember(jss::offers) &&
jv[jss::result][jss::offers].size() == 2);
BEAST_EXPECT(jv[jss::result][jss::offers][0u][jss::TakerGets] ==
XRP(200).value().getJson(0));
BEAST_EXPECT(jv[jss::result][jss::offers][0u][jss::TakerPays] ==
USD(100).value().getJson(0));
BEAST_EXPECT(jv[jss::result][jss::offers][1u][jss::TakerGets] ==
CNY(200).value().getJson(0));
BEAST_EXPECT(jv[jss::result][jss::offers][1u][jss::TakerPays] ==
JPY(100).value().getJson(0));
BEAST_EXPECT(! jv[jss::result].isMember(jss::asks));
BEAST_EXPECT(! jv[jss::result].isMember(jss::bids));
}
{
// Create an ask: TakerPays 700, TakerGets 100/USD
env(offer("alice", XRP(700), USD(100)),
require(owners("alice", 5)));
env.close();
// Check stream update
BEAST_EXPECT(wsc->findMsg(5s,
[&](auto const& jv)
{
auto const& t = jv[jss::transaction];
return t[jss::TransactionType] == "OfferCreate" &&
t[jss::TakerGets] == USD(100).value().getJson(0) &&
t[jss::TakerPays] == XRP(700).value().getJson(0);
}));
}
{
// Create a bid: TakerPays 100/USD, TakerGets 75
env(offer("alice", USD(100), XRP(75)),
require(owners("alice", 6)));
env.close();
BEAST_EXPECT(! wsc->getMsg(10ms));
}
{
// Create an ask: TakerPays 700/CNY, TakerGets 100/JPY
env(offer("alice", CNY(700), JPY(100)),
require(owners("alice", 7)));
env.close();
// Check stream update
BEAST_EXPECT(wsc->findMsg(5s,
[&](auto const& jv)
{
auto const& t = jv[jss::transaction];
return t[jss::TransactionType] == "OfferCreate" &&
t[jss::TakerGets] == JPY(100).value().getJson(0) &&
t[jss::TakerPays] == CNY(700).value().getJson(0);
}));
}
{
// Create a bid: TakerPays 100/JPY, TakerGets 75/CNY
env(offer("alice", JPY(100), CNY(75)),
require(owners("alice", 8)));
env.close();
BEAST_EXPECT(! wsc->getMsg(10ms));
}
// RPC unsubscribe
BEAST_EXPECT(wsc->invoke("unsubscribe",
books)[jss::status] == "success");
}
void
testMultipleBooksBothSidesEmptyBook()
{
using namespace std::chrono_literals;
using namespace jtx;
Env env(*this);
env.fund(XRP(10000), "alice");
auto USD = Account("alice")["USD"];
auto CNY = Account("alice")["CNY"];
auto JPY = Account("alice")["JPY"];
auto wsc = makeWSClient(env.app().config());
Json::Value books;
{
// RPC subscribe to books stream
books[jss::books] = Json::arrayValue;
{
auto &j = books[jss::books].append(Json::objectValue);
j[jss::snapshot] = true;
j[jss::both] = true;
j[jss::taker_gets][jss::currency] = "XRP";
j[jss::taker_pays][jss::currency] = "USD";
j[jss::taker_pays][jss::issuer] = Account("alice").human();
}
{
auto &j = books[jss::books].append(Json::objectValue);
j[jss::snapshot] = true;
j[jss::both] = true;
j[jss::taker_gets][jss::currency] = "CNY";
j[jss::taker_gets][jss::issuer] = Account("alice").human();
j[jss::taker_pays][jss::currency] = "JPY";
j[jss::taker_pays][jss::issuer] = Account("alice").human();
}
auto jv = wsc->invoke("subscribe", books);
BEAST_EXPECT(jv[jss::status] == "success");
BEAST_EXPECT(jv[jss::result].isMember(jss::asks) &&
jv[jss::result][jss::asks].size() == 0);
BEAST_EXPECT(jv[jss::result].isMember(jss::bids) &&
jv[jss::result][jss::bids].size() == 0);
BEAST_EXPECT(! jv[jss::result].isMember(jss::offers));
}
{
// Create an ask: TakerPays 700, TakerGets 100/USD
env(offer("alice", XRP(700), USD(100)),
require(owners("alice", 1)));
env.close();
// Check stream update
BEAST_EXPECT(wsc->findMsg(5s,
[&](auto const& jv)
{
auto const& t = jv[jss::transaction];
return t[jss::TransactionType] == "OfferCreate" &&
t[jss::TakerGets] == USD(100).value().getJson(0) &&
t[jss::TakerPays] == XRP(700).value().getJson(0);
}));
}
{
// Create a bid: TakerPays 100/USD, TakerGets 75
env(offer("alice", USD(100), XRP(75)),
require(owners("alice", 2)));
env.close();
// Check stream update
BEAST_EXPECT(wsc->findMsg(5s,
[&](auto const& jv)
{
auto const& t = jv[jss::transaction];
return t[jss::TransactionType] == "OfferCreate" &&
t[jss::TakerGets] == XRP(75).value().getJson(0) &&
t[jss::TakerPays] == USD(100).value().getJson(0);
}));
}
{
// Create an ask: TakerPays 700/CNY, TakerGets 100/JPY
env(offer("alice", CNY(700), JPY(100)),
require(owners("alice", 3)));
env.close();
// Check stream update
BEAST_EXPECT(wsc->findMsg(5s,
[&](auto const& jv)
{
auto const& t = jv[jss::transaction];
return t[jss::TransactionType] == "OfferCreate" &&
t[jss::TakerGets] == JPY(100).value().getJson(0) &&
t[jss::TakerPays] == CNY(700).value().getJson(0);
}));
}
{
// Create a bid: TakerPays 100/JPY, TakerGets 75/CNY
env(offer("alice", JPY(100), CNY(75)),
require(owners("alice", 4)));
env.close();
// Check stream update
BEAST_EXPECT(wsc->findMsg(5s,
[&](auto const& jv)
{
auto const& t = jv[jss::transaction];
return t[jss::TransactionType] == "OfferCreate" &&
t[jss::TakerGets] == CNY(75).value().getJson(0) &&
t[jss::TakerPays] == JPY(100).value().getJson(0);
}));
}
// RPC unsubscribe
BEAST_EXPECT(wsc->invoke("unsubscribe",
books)[jss::status] == "success");
}
void
testMultipleBooksBothSidesOffersInBook()
{
using namespace std::chrono_literals;
using namespace jtx;
Env env(*this);
env.fund(XRP(10000), "alice");
auto USD = Account("alice")["USD"];
auto CNY = Account("alice")["CNY"];
auto JPY = Account("alice")["JPY"];
auto wsc = makeWSClient(env.app().config());
Json::Value books;
// Create an ask: TakerPays 500, TakerGets 100/USD
env(offer("alice", XRP(500), USD(100)),
require(owners("alice", 1)));
// Create an ask: TakerPays 500/CNY, TakerGets 100/JPY
env(offer("alice", CNY(500), JPY(100)),
require(owners("alice", 2)));
// Create a bid: TakerPays 100/USD, TakerGets 200
env(offer("alice", USD(100), XRP(200)),
require(owners("alice", 3)));
// Create a bid: TakerPays 100/JPY, TakerGets 200/CNY
env(offer("alice", JPY(100), CNY(200)),
require(owners("alice", 4)));
env.close();
{
// RPC subscribe to books stream
books[jss::books] = Json::arrayValue;
{
auto &j = books[jss::books].append(Json::objectValue);
j[jss::snapshot] = true;
j[jss::both] = true;
j[jss::taker_gets][jss::currency] = "XRP";
j[jss::taker_pays][jss::currency] = "USD";
j[jss::taker_pays][jss::issuer] = Account("alice").human();
}
// RPC subscribe to books stream
{
auto &j = books[jss::books].append(Json::objectValue);
j[jss::snapshot] = true;
j[jss::both] = true;
j[jss::taker_gets][jss::currency] = "CNY";
j[jss::taker_gets][jss::issuer] = Account("alice").human();
j[jss::taker_pays][jss::currency] = "JPY";
j[jss::taker_pays][jss::issuer] = Account("alice").human();
}
auto jv = wsc->invoke("subscribe", books);
BEAST_EXPECT(jv[jss::status] == "success");
BEAST_EXPECT(jv[jss::result].isMember(jss::asks) &&
jv[jss::result][jss::asks].size() == 2);
BEAST_EXPECT(jv[jss::result].isMember(jss::bids) &&
jv[jss::result][jss::bids].size() == 2);
BEAST_EXPECT(jv[jss::result][jss::asks][0u][jss::TakerGets] ==
USD(100).value().getJson(0));
BEAST_EXPECT(jv[jss::result][jss::asks][0u][jss::TakerPays] ==
XRP(500).value().getJson(0));
BEAST_EXPECT(jv[jss::result][jss::asks][1u][jss::TakerGets] ==
JPY(100).value().getJson(0));
BEAST_EXPECT(jv[jss::result][jss::asks][1u][jss::TakerPays] ==
CNY(500).value().getJson(0));
BEAST_EXPECT(jv[jss::result][jss::bids][0u][jss::TakerGets] ==
XRP(200).value().getJson(0));
BEAST_EXPECT(jv[jss::result][jss::bids][0u][jss::TakerPays] ==
USD(100).value().getJson(0));
BEAST_EXPECT(jv[jss::result][jss::bids][1u][jss::TakerGets] ==
CNY(200).value().getJson(0));
BEAST_EXPECT(jv[jss::result][jss::bids][1u][jss::TakerPays] ==
JPY(100).value().getJson(0));
BEAST_EXPECT(! jv[jss::result].isMember(jss::offers));
}
{
// Create an ask: TakerPays 700, TakerGets 100/USD
env(offer("alice", XRP(700), USD(100)),
require(owners("alice", 5)));
env.close();
// Check stream update
BEAST_EXPECT(wsc->findMsg(5s,
[&](auto const& jv)
{
auto const& t = jv[jss::transaction];
return t[jss::TransactionType] == "OfferCreate" &&
t[jss::TakerGets] == USD(100).value().getJson(0) &&
t[jss::TakerPays] == XRP(700).value().getJson(0);
}));
}
{
// Create a bid: TakerPays 100/USD, TakerGets 75
env(offer("alice", USD(100), XRP(75)),
require(owners("alice", 6)));
env.close();
// Check stream update
BEAST_EXPECT(wsc->findMsg(5s,
[&](auto const& jv)
{
auto const& t = jv[jss::transaction];
return t[jss::TransactionType] == "OfferCreate" &&
t[jss::TakerGets] == XRP(75).value().getJson(0) &&
t[jss::TakerPays] == USD(100).value().getJson(0);
}));
}
{
// Create an ask: TakerPays 700/CNY, TakerGets 100/JPY
env(offer("alice", CNY(700), JPY(100)),
require(owners("alice", 7)));
env.close();
// Check stream update
BEAST_EXPECT(wsc->findMsg(5s,
[&](auto const& jv)
{
auto const& t = jv[jss::transaction];
return t[jss::TransactionType] == "OfferCreate" &&
t[jss::TakerGets] == JPY(100).value().getJson(0) &&
t[jss::TakerPays] == CNY(700).value().getJson(0);
}));
}
{
// Create a bid: TakerPays 100/JPY, TakerGets 75/CNY
env(offer("alice", JPY(100), CNY(75)),
require(owners("alice", 8)));
env.close();
// Check stream update
BEAST_EXPECT(wsc->findMsg(5s,
[&](auto const& jv)
{
auto const& t = jv[jss::transaction];
return t[jss::TransactionType] == "OfferCreate" &&
t[jss::TakerGets] == CNY(75).value().getJson(0) &&
t[jss::TakerPays] == JPY(100).value().getJson(0);
}));
}
// RPC unsubscribe
BEAST_EXPECT(wsc->invoke("unsubscribe",
books)[jss::status] == "success");
}
void
run() override
{
testOneSideEmptyBook();
testOneSideOffersInBook();
testBothSidesEmptyBook();
testBothSidesOffersInBook();
testMultipleBooksOneSideEmptyBook();
testMultipleBooksOneSideOffersInBook();
testMultipleBooksBothSidesEmptyBook();
testMultipleBooksBothSidesOffersInBook();
}
};
BEAST_DEFINE_TESTSUITE(Book,app,ripple);
} // test
} // ripple

View File

@@ -0,0 +1,156 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 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 <BeastConfig.h>
#include <ripple/protocol/JsonFields.h>
#include <ripple/test/WSClient.h>
#include <ripple/test/jtx.h>
#include <ripple/beast/unit_test.h>
namespace ripple {
namespace test {
class GatewayBalances_test : public beast::unit_test::suite
{
public:
void
testGWB()
{
using namespace std::chrono_literals;
using namespace jtx;
Env env(*this);
// Gateway account and assets
Account const alice {"alice"};
env.fund(XRP(10000), "alice");
auto USD = alice["USD"];
auto CNY = alice["CNY"];
auto JPY = alice["JPY"];
// Create a hotwallet
Account const hw {"hw"};
env.fund(XRP(10000), "hw");
env(trust(hw, USD(10000)));
env(trust(hw, JPY(10000)));
env(pay(alice, hw, USD(5000)));
env(pay(alice, hw, JPY(5000)));
// Create some clients
Account const bob {"bob"};
env.fund(XRP(10000), "bob");
env(trust(bob, USD(100)));
env(trust(bob, CNY(100)));
env(pay(alice, bob, USD(50)));
Account const charley {"charley"};
env.fund(XRP(10000), "charley");
env(trust(charley, CNY(500)));
env(trust(charley, JPY(500)));
env(pay(alice, charley, CNY(250)));
env(pay(alice, charley, JPY(250)));
Account const dave {"dave"};
env.fund(XRP(10000), "dave");
env(trust(dave, CNY(100)));
env(pay(alice, dave, CNY(30)));
// give the gateway an asset
env(trust(alice, charley["USD"](50)));
env(pay(charley, alice, USD(10)));
// freeze dave
env(trust(alice, dave["CNY"](0), dave, tfSetFreeze));
env.close();
auto wsc = makeWSClient(env.app().config());
Json::Value qry;
qry[jss::account] = alice.human();
qry[jss::hotwallet] = hw.human();
auto jv = wsc->invoke("gateway_balances", qry);
expect(jv[jss::status] == "success");
auto const& result = jv[jss::result];
expect(result[jss::account] == alice.human());
expect(result[jss::status] == "success");
{
auto const& balances = result[jss::balances];
expect (balances.isObject(), "balances is not an object");
expect (balances.size() == 1, "balances size is not 1");
auto const& hwBalance = balances[hw.human()];
expect (hwBalance.isArray(), "hwBalance is not an array");
expect (hwBalance.size() == 2);
auto c1 = hwBalance[0u][jss::currency];
auto c2 = hwBalance[1u][jss::currency];
expect (c1 == "USD" || c2 == "USD");
expect (c1 == "JPY" || c2 == "JPY");
expect (hwBalance[0u][jss::value] == "5000" &&
hwBalance[1u][jss::value] == "5000");
}
{
auto const& fBalances = result[jss::frozen_balances];
expect (fBalances.isObject());
expect (fBalances.size() == 1);
auto const& fBal = fBalances[dave.human()];
expect (fBal.isArray());
expect (fBal.size() == 1);
expect (fBal[0u].isObject());
expect (fBal[0u][jss::currency] == "CNY");
expect (fBal[0u][jss::value] == "30");
}
{
auto const& assets = result[jss::assets];
expect (assets.isObject(), "assets it not an object");
expect (assets.size() == 1, "assets size is not 1");
auto const& cAssets = assets[charley.human()];
expect (cAssets.isArray());
expect (cAssets.size() == 1);
expect (cAssets[0u][jss::currency] == "USD");
expect (cAssets[0u][jss::value] == "10");
}
{
auto const& obligations = result[jss::obligations];
expect (obligations.isObject(), "obligations is not an object");
expect (obligations.size() == 3);
expect (obligations["CNY"] == "250");
expect (obligations["JPY"] == "250");
expect (obligations["USD"] == "50");
}
}
void
run() override
{
testGWB();
}
};
BEAST_DEFINE_TESTSUITE(GatewayBalances,app,ripple);
} // test
} // ripple

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,697 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2015 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 <BeastConfig.h>
#include <ripple/basics/StringUtilities.h>
#include <ripple/basics/TestSuite.h>
#include <ripple/json/json_value.h>
#include <ripple/json/json_writer.h>
#include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/JsonFields.h>
#include <ripple/rpc/handlers/WalletPropose.h>
#include <ripple/rpc/impl/RPCHelpers.h>
namespace ripple {
namespace RPC {
struct key_strings
{
char const* account_id;
char const* master_key;
char const* master_seed;
char const* master_seed_hex;
char const* public_key;
char const* public_key_hex;
char const* secret_key_hex;
};
namespace common {
static char const* passphrase = "REINDEER FLOTILLA";
static char const* master_key = "SCAT BERN ISLE FOR ROIL BUS SOAK AQUA FREE FOR DRAM BRIG";
static char const* master_seed = "snMwVWs2hZzfDUF3p2tHZ3EgmyhFs";
static char const* master_seed_hex = "BE6A670A19B209E112146D0A7ED2AAD7";
}
static key_strings const secp256k1_strings =
{
"r4Vtj2jrfmTVZGfSP3gH9hQPMqFPQFin8f",
common::master_key,
common::master_seed,
common::master_seed_hex,
"aBQxK2YFNqzmAaXNczYcjqDjfiKkLsJUizsr1UBf44RCF8FHdrmX",
"038AAE247B2344B1837FBED8F57389C8C11774510A3F7D784F2A09F0CB6843236C",
"1949ECD889EA71324BC7A30C8E81F4E93CB73EE19D59E9082111E78CC3DDABC2",
};
static key_strings const ed25519_strings =
{
"r4qV6xTXerqaZav3MJfSY79ynmc1BSBev1",
common::master_key,
common::master_seed,
common::master_seed_hex,
"aKEQmgLMyZPMruJFejUuedp169LgW6DbJt1rej1DJ5hWUMH4pHJ7",
"ED54C3F5BEDA8BD588B203D23A27398FAD9D20F88A974007D6994659CD7273FE1D",
"77AAED2698D56D6676323629160F4EEF21CFD9EE3D0745CC78FA291461F98278",
};
class WalletPropose_test : public ripple::TestSuite
{
public:
void testRandomWallet(boost::optional<std::string> const& keyType)
{
Json::Value params;
if (keyType)
params[jss::key_type] = *keyType;
Json::Value result = walletPropose (params);
BEAST_EXPECT(! contains_error (result));
BEAST_EXPECT(result.isMember (jss::account_id));
BEAST_EXPECT(result.isMember (jss::master_key));
BEAST_EXPECT(result.isMember (jss::master_seed));
BEAST_EXPECT(result.isMember (jss::master_seed_hex));
BEAST_EXPECT(result.isMember (jss::public_key));
BEAST_EXPECT(result.isMember (jss::public_key_hex));
BEAST_EXPECT(result.isMember (jss::key_type));
expectEquals (result[jss::key_type],
params.isMember (jss::key_type) ? params[jss::key_type]
: "secp256k1");
std::string seed = result[jss::master_seed].asString();
result = walletPropose (params);
// We asked for two random seeds, so they shouldn't match.
BEAST_EXPECT(result[jss::master_seed].asString() != seed);
}
void testSecretWallet (Json::Value const& params, key_strings const& s)
{
Json::Value result = walletPropose (params);
BEAST_EXPECT(! contains_error (result));
expectEquals (result[jss::account_id], s.account_id);
expectEquals (result[jss::master_key], s.master_key);
expectEquals (result[jss::master_seed], s.master_seed);
expectEquals (result[jss::master_seed_hex], s.master_seed_hex);
expectEquals (result[jss::public_key], s.public_key);
expectEquals (result[jss::public_key_hex], s.public_key_hex);
expectEquals (result[jss::key_type],
params.isMember (jss::key_type) ? params[jss::key_type]
: "secp256k1");
}
void testSeed (boost::optional<std::string> const& keyType,
key_strings const& strings)
{
testcase ("seed");
Json::Value params;
if (keyType)
params[jss::key_type] = *keyType;
params[jss::seed] = strings.master_seed;
testSecretWallet (params, strings);
}
void testSeedHex (boost::optional<std::string> const& keyType,
key_strings const& strings)
{
testcase ("seed_hex");
Json::Value params;
if (keyType)
params[jss::key_type] = *keyType;
params[jss::seed_hex] = strings.master_seed_hex;
testSecretWallet (params, strings);
}
void testLegacyPassphrase (char const* value,
boost::optional<std::string> const& keyType,
key_strings const& strings)
{
Json::Value params;
if (keyType)
params[jss::key_type] = *keyType;
params[jss::passphrase] = value;
testSecretWallet (params, strings);
}
void testLegacyPassphrase(boost::optional<std::string> const& keyType,
key_strings const& strings)
{
testcase ("passphrase");
testLegacyPassphrase (common::passphrase, keyType, strings);
testLegacyPassphrase (strings.master_key, keyType, strings);
testLegacyPassphrase (strings.master_seed, keyType, strings);
testLegacyPassphrase (strings.master_seed_hex, keyType, strings);
}
void testKeyType (boost::optional<std::string> const& keyType,
key_strings const& strings)
{
testcase (keyType ? *keyType : "no key_type");
testRandomWallet (keyType);
testSeed (keyType, strings);
testSeedHex (keyType, strings);
testLegacyPassphrase (keyType, strings);
Json::Value params;
if (keyType)
params[jss::key_type] = *keyType;
params[jss::seed] = strings.master_seed;
params[jss::seed_hex] = strings.master_seed_hex;
// Secret fields are mutually exclusive.
BEAST_EXPECT(contains_error (walletPropose (params)));
}
void testBadInput ()
{
testcase ("Bad inputs");
// Passing non-strings where strings are required
{
Json::Value params;
params[jss::key_type] = "secp256k1";
params[jss::passphrase] = 20160506;
auto result = walletPropose (params);
BEAST_EXPECT(contains_error (result));
BEAST_EXPECT(result[jss::error_message] ==
"Invalid field 'passphrase', not string.");
}
{
Json::Value params;
params[jss::key_type] = "secp256k1";
params[jss::seed] = Json::objectValue;
auto result = walletPropose (params);
BEAST_EXPECT(contains_error (result));
BEAST_EXPECT(result[jss::error_message] ==
"Invalid field 'seed', not string.");
}
{
Json::Value params;
params[jss::key_type] = "ed25519";
params[jss::seed_hex] = Json::arrayValue;
auto result = walletPropose (params);
BEAST_EXPECT(contains_error (result));
BEAST_EXPECT(result[jss::error_message] ==
"Invalid field 'seed_hex', not string.");
}
// Specifying multiple items at once
{
Json::Value params;
params[jss::key_type] = "secp256k1";
params[jss::passphrase] = common::master_key;
params[jss::seed_hex] = common::master_seed_hex;
params[jss::seed] = common::master_seed;
auto result = walletPropose (params);
BEAST_EXPECT(contains_error (result));
BEAST_EXPECT(result[jss::error_message] ==
"Exactly one of the following must be specified: passphrase, seed or seed_hex");
}
// Specifying bad key types:
{
Json::Value params;
params[jss::key_type] = "prime256v1";
params[jss::passphrase] = common::master_key;
auto result = walletPropose (params);
BEAST_EXPECT(contains_error (result));
BEAST_EXPECT(result[jss::error_message] ==
"Invalid parameters.");
}
{
Json::Value params;
params[jss::key_type] = Json::objectValue;
params[jss::seed_hex] = common::master_seed_hex;
auto result = walletPropose (params);
BEAST_EXPECT(contains_error (result));
BEAST_EXPECT(result[jss::error_message] ==
"Invalid field 'key_type', not string.");
}
{
Json::Value params;
params[jss::key_type] = Json::arrayValue;
params[jss::seed] = common::master_seed;
auto result = walletPropose (params);
BEAST_EXPECT(contains_error (result));
BEAST_EXPECT(result[jss::error_message] ==
"Invalid field 'key_type', not string.");
}
}
void testKeypairForSignature (
boost::optional<std::string> keyType,
key_strings const& strings)
{
testcase ("keypairForSignature - " +
(keyType ? *keyType : "no key_type"));
auto const publicKey = parseBase58<PublicKey>(
TokenType::TOKEN_ACCOUNT_PUBLIC, strings.public_key);
BEAST_EXPECT(publicKey);
if (!keyType)
{
{
Json::Value params;
Json::Value error;
params[jss::secret] = strings.master_seed;
auto ret = keypairForSignature (params, error);
BEAST_EXPECT(! contains_error (error));
BEAST_EXPECT(ret.first.size() != 0);
BEAST_EXPECT(ret.first == publicKey);
}
{
Json::Value params;
Json::Value error;
params[jss::secret] = strings.master_seed_hex;
auto ret = keypairForSignature (params, error);
BEAST_EXPECT(! contains_error (error));
BEAST_EXPECT(ret.first.size() != 0);
BEAST_EXPECT(ret.first == publicKey);
}
{
Json::Value params;
Json::Value error;
params[jss::secret] = strings.master_key;
auto ret = keypairForSignature (params, error);
BEAST_EXPECT(! contains_error (error));
BEAST_EXPECT(ret.first.size() != 0);
BEAST_EXPECT(ret.first == publicKey);
}
keyType.emplace ("secp256k1");
}
{
Json::Value params;
Json::Value error;
params[jss::key_type] = *keyType;
params[jss::seed] = strings.master_seed;
auto ret = keypairForSignature (params, error);
BEAST_EXPECT(! contains_error (error));
BEAST_EXPECT(ret.first.size() != 0);
BEAST_EXPECT(ret.first == publicKey);
}
{
Json::Value params;
Json::Value error;
params[jss::key_type] = *keyType;
params[jss::seed_hex] = strings.master_seed_hex;
auto ret = keypairForSignature (params, error);
BEAST_EXPECT(! contains_error (error));
BEAST_EXPECT(ret.first.size() != 0);
BEAST_EXPECT(ret.first == publicKey);
}
{
Json::Value params;
Json::Value error;
params[jss::key_type] = *keyType;
params[jss::passphrase] = strings.master_key;
auto ret = keypairForSignature (params, error);
BEAST_EXPECT(! contains_error (error));
BEAST_EXPECT(ret.first.size() != 0);
BEAST_EXPECT(ret.first == publicKey);
}
}
void testKeypairForSignatureErrors()
{
// Specify invalid "secret"
{
Json::Value params;
Json::Value error;
params[jss::secret] = 314159265;
auto ret = keypairForSignature (params, error);
BEAST_EXPECT(contains_error (error));
BEAST_EXPECT(error[jss::error_message] ==
"Invalid field 'secret', not string.");
BEAST_EXPECT(ret.first.size() == 0);
}
{
Json::Value params;
Json::Value error;
params[jss::secret] = Json::arrayValue;
params[jss::secret].append ("array:0");
auto ret = keypairForSignature (params, error);
BEAST_EXPECT(contains_error (error));
BEAST_EXPECT(error[jss::error_message] ==
"Invalid field 'secret', not string.");
BEAST_EXPECT(ret.first.size() == 0);
}
{
Json::Value params;
Json::Value error;
params[jss::secret] = Json::objectValue;
params[jss::secret]["string"] = "string";
params[jss::secret]["number"] = 702;
auto ret = keypairForSignature (params, error);
BEAST_EXPECT(contains_error (error));
BEAST_EXPECT(ret.first.size() == 0);
BEAST_EXPECT(error[jss::error_message] ==
"Invalid field 'secret', not string.");
}
// Specify "secret" and "key_type"
{
Json::Value params;
Json::Value error;
params[jss::key_type] = "ed25519";
params[jss::secret] = common::master_seed;
auto ret = keypairForSignature (params, error);
BEAST_EXPECT(contains_error (error));
BEAST_EXPECT(error[jss::error_message] ==
"The secret field is not allowed if key_type is used.");
BEAST_EXPECT(ret.first.size() == 0);
}
// Specify unknown or bad "key_type"
{
Json::Value params;
Json::Value error;
params[jss::key_type] = "prime256v1";
params[jss::passphrase] = common::master_key;
auto ret = keypairForSignature (params, error);
BEAST_EXPECT(contains_error (error));
BEAST_EXPECT(error[jss::error_message] ==
"Invalid field 'key_type'.");
BEAST_EXPECT(ret.first.size() == 0);
}
{
Json::Value params;
Json::Value error;
params[jss::key_type] = Json::objectValue;
params[jss::seed_hex] = common::master_seed_hex;
auto ret = keypairForSignature (params, error);
BEAST_EXPECT(contains_error (error));
BEAST_EXPECT(error[jss::error_message] ==
"Invalid field 'key_type', not string.");
BEAST_EXPECT(ret.first.size() == 0);
}
{
Json::Value params;
Json::Value error;
params[jss::key_type] = Json::arrayValue;
params[jss::seed] = common::master_seed;
auto ret = keypairForSignature (params, error);
BEAST_EXPECT(contains_error (error));
BEAST_EXPECT(error[jss::error_message] ==
"Invalid field 'key_type', not string.");
BEAST_EXPECT(ret.first.size() == 0);
}
// Specify non-string passphrase
{ // not a passphrase: number
Json::Value params;
Json::Value error;
params[jss::key_type] = "secp256k1";
params[jss::passphrase] = 1234567890;
auto ret = keypairForSignature (params, error);
BEAST_EXPECT(contains_error (error));
BEAST_EXPECT(error[jss::error_message] ==
"Invalid field 'passphrase', not string.");
BEAST_EXPECT(ret.first.size() == 0);
}
{ // not a passphrase: object
Json::Value params;
Json::Value error;
params[jss::key_type] = "secp256k1";
params[jss::passphrase] = Json::objectValue;
auto ret = keypairForSignature (params, error);
BEAST_EXPECT(contains_error (error));
BEAST_EXPECT(error[jss::error_message] ==
"Invalid field 'passphrase', not string.");
BEAST_EXPECT(ret.first.size() == 0);
}
{ // not a passphrase: array
Json::Value params;
Json::Value error;
params[jss::key_type] = "secp256k1";
params[jss::passphrase] = Json::arrayValue;
auto ret = keypairForSignature (params, error);
BEAST_EXPECT(contains_error (error));
BEAST_EXPECT(error[jss::error_message] ==
"Invalid field 'passphrase', not string.");
BEAST_EXPECT(ret.first.size() == 0);
}
{ // not a passphrase: empty string
Json::Value params;
Json::Value error;
params[jss::key_type] = "secp256k1";
params[jss::passphrase] = "";
auto ret = keypairForSignature (params, error);
BEAST_EXPECT(contains_error (error));
BEAST_EXPECT(error[jss::error_message] ==
"Disallowed seed.");
BEAST_EXPECT(ret.first.size() == 0);
}
// Specify non-string or invalid seed
{ // not a seed: number
Json::Value params;
Json::Value error;
params[jss::key_type] = "secp256k1";
params[jss::seed] = 443556;
auto ret = keypairForSignature (params, error);
BEAST_EXPECT(contains_error (error));
BEAST_EXPECT(error[jss::error_message] ==
"Invalid field 'seed', not string.");
BEAST_EXPECT(ret.first.size() == 0);
}
{ // not a string: object
Json::Value params;
Json::Value error;
params[jss::key_type] = "secp256k1";
params[jss::seed] = Json::objectValue;
auto ret = keypairForSignature (params, error);
BEAST_EXPECT(contains_error (error));
BEAST_EXPECT(error[jss::error_message] ==
"Invalid field 'seed', not string.");
BEAST_EXPECT(ret.first.size() == 0);
}
{ // not a string: array
Json::Value params;
Json::Value error;
params[jss::key_type] = "secp256k1";
params[jss::seed] = Json::arrayValue;
auto ret = keypairForSignature (params, error);
BEAST_EXPECT(contains_error (error));
BEAST_EXPECT(error[jss::error_message] ==
"Invalid field 'seed', not string.");
BEAST_EXPECT(ret.first.size() == 0);
}
{ // not a seed: empty
Json::Value params;
Json::Value error;
params[jss::key_type] = "secp256k1";
params[jss::seed] = "";
auto ret = keypairForSignature (params, error);
BEAST_EXPECT(contains_error (error));
BEAST_EXPECT(error[jss::error_message] ==
"Disallowed seed.");
BEAST_EXPECT(ret.first.size() == 0);
}
{ // not a seed: invalid characters
Json::Value params;
Json::Value error;
params[jss::key_type] = "secp256k1";
params[jss::seed] = "s M V s h z D F p t Z E m h s";
auto ret = keypairForSignature (params, error);
BEAST_EXPECT(contains_error (error));
BEAST_EXPECT(error[jss::error_message] ==
"Disallowed seed.");
BEAST_EXPECT(ret.first.size() == 0);
}
{ // not a seed: random string
Json::Value params;
Json::Value error;
params[jss::key_type] = "secp256k1";
params[jss::seed] = "pnnjkbnobnml43679nbvjdsklnbjs";
auto ret = keypairForSignature (params, error);
BEAST_EXPECT(contains_error (error));
BEAST_EXPECT(error[jss::error_message] ==
"Disallowed seed.");
BEAST_EXPECT(ret.first.size() == 0);
}
// Specify non-string or invalid seed_hex
{ // not a string: number
Json::Value params;
Json::Value error;
params[jss::key_type] = "secp256k1";
params[jss::seed_hex] = 443556;
auto ret = keypairForSignature (params, error);
BEAST_EXPECT(contains_error (error));
BEAST_EXPECT(error[jss::error_message] ==
"Invalid field 'seed_hex', not string.");
BEAST_EXPECT(ret.first.size() == 0);
}
{ // not a string: object
Json::Value params;
Json::Value error;
params[jss::key_type] = "secp256k1";
params[jss::seed_hex] = Json::objectValue;
auto ret = keypairForSignature (params, error);
BEAST_EXPECT(contains_error (error));
BEAST_EXPECT(error[jss::error_message] ==
"Invalid field 'seed_hex', not string.");
BEAST_EXPECT(ret.first.size() == 0);
}
{ // not a string: array
Json::Value params;
Json::Value error;
params[jss::key_type] = "secp256k1";
params[jss::seed_hex] = Json::arrayValue;
auto ret = keypairForSignature (params, error);
BEAST_EXPECT(contains_error (error));
BEAST_EXPECT(error[jss::error_message] ==
"Invalid field 'seed_hex', not string.");
BEAST_EXPECT(ret.first.size() == 0);
}
{ // empty
Json::Value params;
Json::Value error;
params[jss::key_type] = "secp256k1";
params[jss::seed_hex] = "";
auto ret = keypairForSignature (params, error);
BEAST_EXPECT(contains_error (error));
BEAST_EXPECT(error[jss::error_message] ==
"Disallowed seed.");
BEAST_EXPECT(ret.first.size() == 0);
}
{ // short
Json::Value params;
Json::Value error;
params[jss::key_type] = "secp256k1";
params[jss::seed_hex] = "A670A19B";
auto ret = keypairForSignature (params, error);
BEAST_EXPECT(contains_error (error));
BEAST_EXPECT(error[jss::error_message] ==
"Disallowed seed.");
BEAST_EXPECT(ret.first.size() == 0);
}
{ // not hex
Json::Value params;
Json::Value error;
params[jss::key_type] = "secp256k1";
params[jss::seed_hex] = common::passphrase;
auto ret = keypairForSignature (params, error);
BEAST_EXPECT(contains_error (error));
BEAST_EXPECT(error[jss::error_message] ==
"Disallowed seed.");
BEAST_EXPECT(ret.first.size() == 0);
}
{ // overlong
Json::Value params;
Json::Value error;
params[jss::key_type] = "secp256k1";
params[jss::seed_hex] = "BE6A670A19B209E112146D0A7ED2AAD72567D0FC913";
auto ret = keypairForSignature (params, error);
BEAST_EXPECT(contains_error (error));
BEAST_EXPECT(error[jss::error_message] ==
"Disallowed seed.");
BEAST_EXPECT(ret.first.size() == 0);
}
}
void run()
{
testKeyType (boost::none, secp256k1_strings);
testKeyType (std::string("secp256k1"), secp256k1_strings);
testKeyType (std::string("ed25519"), ed25519_strings);
testBadInput ();
testKeypairForSignature (boost::none, secp256k1_strings);
testKeypairForSignature (std::string("secp256k1"), secp256k1_strings);
testKeypairForSignature (std::string("ed25519"), ed25519_strings);
testKeypairForSignatureErrors ();
}
};
BEAST_DEFINE_TESTSUITE(WalletPropose,ripple_basics,ripple);
} // RPC
} // ripple

View File

@@ -0,0 +1,237 @@
//------------------------------------------------------------------------------
/*
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/protocol/JsonFields.h>
#include <ripple/test/jtx.h>
namespace ripple {
class LedgerData_test : public beast::unit_test::suite
{
public:
static
std::unique_ptr<Config>
makeConfig(bool setup_admin)
{
auto p = std::make_unique<Config>();
test::setupConfigForUnitTests(*p);
// the default config has admin active
// ...we remove them if setup_admin is false
if (! setup_admin)
{
(*p)["port_rpc"].set("admin","");
(*p)["port_ws"].set("admin","");
}
return p;
}
// test helper
static bool checkArraySize(Json::Value const& val, unsigned int size)
{
return val.isArray() &&
val.size() == size;
}
// test helper
static bool checkMarker(Json::Value const& val)
{
return val.isMember(jss::marker) &&
val[jss::marker].isString() &&
val[jss::marker].asString().size() > 0;
}
void testCurrentLedgerToLimits(bool as_admin)
{
using namespace test::jtx;
Env env {*this, makeConfig(as_admin)};
Account const gw {"gateway"};
auto const USD = gw["USD"];
env.fund(XRP(100000), gw);
int const max_limit = 256; //would be 2048 for binary requests, no need to test that here
for (auto i = 0; i < max_limit + 10; i++)
{
Account const bob {std::string("bob") + std::to_string(i)};
env.fund(XRP(1000), bob);
}
env.close();
// no limit specified
// with no limit specified, we get the max_limit if the total number of
// accounts is greater than max, which it is here
Json::Value jvParams;
jvParams[jss::ledger_index] = "current";
jvParams[jss::binary] = false;
auto const jrr = env.rpc ( "json", "ledger_data", jvParams.toStyledString() ) [jss::result];
BEAST_EXPECT(
jrr[jss::ledger_current_index].isIntegral() &&
jrr[jss::ledger_current_index].asInt() > 0 );
BEAST_EXPECT( checkMarker(jrr) );
BEAST_EXPECT( checkArraySize(jrr[jss::state], max_limit) );
// check limits values around the max_limit (+/- 1)
for (auto delta = -1; delta <= 1; delta++)
{
jvParams[jss::limit] = max_limit + delta;
auto const jrr = env.rpc ( "json", "ledger_data", jvParams.toStyledString() ) [jss::result];
BEAST_EXPECT(
checkArraySize( jrr[jss::state],
(delta > 0 && !as_admin) ? max_limit : max_limit + delta ));
}
}
void testCurrentLedgerBinary()
{
using namespace test::jtx;
Env env { *this, makeConfig(false) };
Account const gw { "gateway" };
auto const USD = gw["USD"];
env.fund(XRP(100000), gw);
int const num_accounts = 10;
for (auto i = 0; i < num_accounts; i++)
{
Account const bob { std::string("bob") + std::to_string(i) };
env.fund(XRP(1000), bob);
}
env.close();
// no limit specified
// with no limit specified, we should get all of our fund entries
// plus three more related to the gateway setup
Json::Value jvParams;
jvParams[jss::ledger_index] = "current";
jvParams[jss::binary] = true;
auto const jrr = env.rpc ( "json", "ledger_data", jvParams.toStyledString() ) [jss::result];
BEAST_EXPECT(
jrr[jss::ledger_current_index].isIntegral() &&
jrr[jss::ledger_current_index].asInt() > 0);
BEAST_EXPECT( ! jrr.isMember(jss::marker) );
BEAST_EXPECT( checkArraySize(jrr[jss::state], num_accounts + 3) );
}
void testBadInput()
{
using namespace test::jtx;
Env env { *this };
Account const gw { "gateway" };
auto const USD = gw["USD"];
Account const bob { "bob" };
env.fund(XRP(10000), gw, bob);
env.trust(USD(1000), bob);
{
// bad limit
Json::Value jvParams;
jvParams[jss::limit] = "0"; // NOT an integer
auto const jrr = env.rpc ( "json", "ledger_data", jvParams.toStyledString() ) [jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'limit', not integer.");
}
{
// invalid marker
Json::Value jvParams;
jvParams[jss::marker] = "NOT_A_MARKER";
auto const jrr = env.rpc ( "json", "ledger_data", jvParams.toStyledString() ) [jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'marker', not valid.");
}
{
// invalid marker - not a string
Json::Value jvParams;
jvParams[jss::marker] = 1;
auto const jrr = env.rpc ( "json", "ledger_data", jvParams.toStyledString() ) [jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'marker', not valid.");
}
{
// ask for a bad ledger index
Json::Value jvParams;
jvParams[jss::ledger_index] = 10u;
auto const jrr = env.rpc ( "json", "ledger_data", jvParams.toStyledString() ) [jss::result];
BEAST_EXPECT(jrr[jss::error] == "lgrNotFound");
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error_message] == "ledgerNotFound");
}
}
void testMarkerFollow()
{
using namespace test::jtx;
Env env { *this, makeConfig(false) };
Account const gw { "gateway" };
auto const USD = gw["USD"];
env.fund(XRP(100000), gw);
int const num_accounts = 20;
for (auto i = 0; i < num_accounts; i++)
{
Account const bob { std::string("bob") + std::to_string(i) };
env.fund(XRP(1000), bob);
}
env.close();
// no limit specified
// with no limit specified, we should get all of our fund entries
// plus three more related to the gateway setup
Json::Value jvParams;
jvParams[jss::ledger_index] = "current";
jvParams[jss::binary] = false;
auto jrr = env.rpc ( "json", "ledger_data", jvParams.toStyledString() ) [jss::result];
auto const total_count = jrr[jss::state].size();
// now make request with a limit and loop until we get all
jvParams[jss::limit] = 5;
jrr = env.rpc ( "json", "ledger_data", jvParams.toStyledString() ) [jss::result];
BEAST_EXPECT( checkMarker(jrr) );
auto running_total = jrr[jss::state].size();
while ( jrr.isMember(jss::marker) )
{
jvParams[jss::marker] = jrr[jss::marker];
jrr = env.rpc ( "json", "ledger_data", jvParams.toStyledString() ) [jss::result];
running_total += jrr[jss::state].size();
}
BEAST_EXPECT( running_total == total_count );
}
void run()
{
testCurrentLedgerToLimits(true);
testCurrentLedgerToLimits(false);
testCurrentLedgerBinary();
testBadInput();
testMarkerFollow();
}
};
BEAST_DEFINE_TESTSUITE(LedgerData,app,ripple);
}

View File

@@ -0,0 +1,317 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012-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 <BeastConfig.h>
#include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/JsonFields.h>
#include <ripple/test/jtx.h>
#include <ripple/beast/unit_test.h>
#include <ripple/app/ledger/LedgerMaster.h>
namespace ripple {
namespace RPC {
class LedgerRequestRPC_test : public beast::unit_test::suite
{
public:
static
std::unique_ptr<Config>
makeNonAdminConfig()
{
auto p = std::make_unique<Config>();
test::setupConfigForUnitTests(*p);
(*p)["port_rpc"].set("admin","");
(*p)["port_ws"].set("admin","");
return p;
}
void testLedgerRequest()
{
using namespace test::jtx;
Env env(*this);
env.close();
env.close();
BEAST_EXPECT(env.current()->info().seq == 5);
{
// arbitrary text is converted to 0.
auto const result = env.rpc("ledger_request", "arbitrary_text");
BEAST_EXPECT(RPC::contains_error(result[jss::result]) &&
result[jss::result][jss::error_message] ==
"Ledger index too small");
}
{
auto const result = env.rpc("ledger_request", "-1");
BEAST_EXPECT(RPC::contains_error(result[jss::result]) &&
result[jss::result][jss::error_message] ==
"Ledger index too small");
}
{
auto const result = env.rpc("ledger_request", "0");
BEAST_EXPECT(RPC::contains_error(result[jss::result]) &&
result[jss::result][jss::error_message] ==
"Ledger index too small");
}
{
auto const result = env.rpc("ledger_request", "1");
BEAST_EXPECT(!RPC::contains_error(result[jss::result]) &&
result[jss::result][jss::ledger_index] == 1 &&
result[jss::result].isMember(jss::ledger));
BEAST_EXPECT(result[jss::result][jss::ledger].
isMember(jss::ledger_hash) &&
result[jss::result][jss::ledger]
[jss::ledger_hash].isString());
}
{
auto const result = env.rpc("ledger_request", "2");
BEAST_EXPECT(!RPC::contains_error(result[jss::result]) &&
result[jss::result][jss::ledger_index] == 2 &&
result[jss::result].isMember(jss::ledger));
BEAST_EXPECT(result[jss::result][jss::ledger].
isMember(jss::ledger_hash) &&
result[jss::result][jss::ledger]
[jss::ledger_hash].isString());
}
{
auto const result = env.rpc("ledger_request", "3");
BEAST_EXPECT(!RPC::contains_error(result[jss::result]) &&
result[jss::result][jss::ledger_index] == 3 &&
result[jss::result].isMember(jss::ledger));
BEAST_EXPECT(result[jss::result][jss::ledger].
isMember(jss::ledger_hash) &&
result[jss::result][jss::ledger]
[jss::ledger_hash].isString());
auto const ledgerHash = result[jss::result]
[jss::ledger][jss::ledger_hash].asString();
{
// Intentionally shadow `result` here to avoid reuing it.
auto const result = env.rpc("ledger_request", ledgerHash);
BEAST_EXPECT(!RPC::contains_error(result[jss::result]) &&
result[jss::result][jss::ledger_index] == 3 &&
result[jss::result].isMember(jss::ledger));
BEAST_EXPECT(result[jss::result][jss::ledger].
isMember(jss::ledger_hash) &&
result[jss::result][jss::ledger]
[jss::ledger_hash] == ledgerHash);
}
}
{
std::string ledgerHash(64, 'q');
auto const result = env.rpc("ledger_request", ledgerHash);
BEAST_EXPECT(RPC::contains_error(result[jss::result]) &&
result[jss::result][jss::error_message] ==
"Invalid field 'ledger_hash'.");
}
{
std::string ledgerHash(64, '1');
auto const result = env.rpc("ledger_request", ledgerHash);
BEAST_EXPECT(!RPC::contains_error(result[jss::result]) &&
result[jss::result][jss::have_header] == false);
}
{
auto const result = env.rpc("ledger_request", "4");
BEAST_EXPECT(RPC::contains_error(result[jss::result]) &&
result[jss::result][jss::error_message] ==
"Ledger index too large");
}
{
auto const result = env.rpc("ledger_request", "5");
BEAST_EXPECT(RPC::contains_error(result[jss::result]) &&
result[jss::result][jss::error_message] ==
"Ledger index too large");
}
}
void testEvolution()
{
using namespace test::jtx;
Env env { *this };
Account const gw { "gateway" };
auto const USD = gw["USD"];
env.fund(XRP(100000), gw);
env.close();
env.memoize("bob");
env.fund(XRP(1000), "bob");
env.close();
env.memoize("alice");
env.fund(XRP(1000), "alice");
env.close();
env.memoize("carol");
env.fund(XRP(1000), "carol");
env.close();
auto result = env.rpc ( "ledger_request", "1" ) [jss::result];
BEAST_EXPECT(result[jss::ledger][jss::ledger_index] == "1");
BEAST_EXPECT(result[jss::ledger][jss::total_coins] == "100000000000000000");
BEAST_EXPECT(result[jss::ledger][jss::closed] == true);
BEAST_EXPECT(result[jss::ledger][jss::ledger_hash] == "AB868A6CFEEC779C2FF845C0AF00A642259986AF40C01976A7F842B6918936C7");
BEAST_EXPECT(result[jss::ledger][jss::parent_hash] == "0000000000000000000000000000000000000000000000000000000000000000");
BEAST_EXPECT(result[jss::ledger][jss::account_hash] == "A21ED30C04C88046FC61DB9DC19375EEDBD365FD8C17286F27127DF804E9CAA6");
BEAST_EXPECT(result[jss::ledger][jss::transaction_hash] == "0000000000000000000000000000000000000000000000000000000000000000");
result = env.rpc ( "ledger_request", "2" ) [jss::result];
BEAST_EXPECT(result[jss::ledger][jss::ledger_index] == "2");
BEAST_EXPECT(result[jss::ledger][jss::total_coins] == "100000000000000000");
BEAST_EXPECT(result[jss::ledger][jss::closed] == true);
BEAST_EXPECT(result[jss::ledger][jss::ledger_hash] == "8AEDBB96643962F1D40F01E25632ABB3C56C9F04B0231EE4B18248B90173D189");
BEAST_EXPECT(result[jss::ledger][jss::parent_hash] == "AB868A6CFEEC779C2FF845C0AF00A642259986AF40C01976A7F842B6918936C7");
BEAST_EXPECT(result[jss::ledger][jss::account_hash] == "183D5235C7C1FB5AE67AD2F6CC3B28F5FB86E8C4F89DB50DD85641A96470534E");
BEAST_EXPECT(result[jss::ledger][jss::transaction_hash] == "0000000000000000000000000000000000000000000000000000000000000000");
result = env.rpc ( "ledger_request", "3" ) [jss::result];
BEAST_EXPECT(result[jss::ledger][jss::ledger_index] == "3");
BEAST_EXPECT(result[jss::ledger][jss::total_coins] == "99999999999999980");
BEAST_EXPECT(result[jss::ledger][jss::closed] == true);
BEAST_EXPECT(result[jss::ledger][jss::ledger_hash] == "D2EE1E2A7288AAD43D6FA8AD8007FD1A95646F365EF3A1AD608A03258F11CF18");
BEAST_EXPECT(result[jss::ledger][jss::parent_hash] == "8AEDBB96643962F1D40F01E25632ABB3C56C9F04B0231EE4B18248B90173D189");
BEAST_EXPECT(result[jss::ledger][jss::account_hash] == "22565DC00D1A30F2C15871714E512976EF476281E5E87FF63D3E129C9069F4F4");
BEAST_EXPECT(result[jss::ledger][jss::transaction_hash] == "0213EC486C058B3942FBE3DAC6839949A5C5B02B8B4244C8998EFDF04DBD8222");
result = env.rpc ( "ledger_request", "4" ) [jss::result];
BEAST_EXPECT(result[jss::ledger][jss::ledger_index] == "4");
BEAST_EXPECT(result[jss::ledger][jss::total_coins] == "99999999999999960");
BEAST_EXPECT(result[jss::ledger][jss::closed] == true);
BEAST_EXPECT(result[jss::ledger][jss::ledger_hash] == "8F9032390CDD4C9D7A5B216AFDA3B525A3B39D7589C69D90D4C6BCA4619DD33C");
BEAST_EXPECT(result[jss::ledger][jss::parent_hash] == "D2EE1E2A7288AAD43D6FA8AD8007FD1A95646F365EF3A1AD608A03258F11CF18");
BEAST_EXPECT(result[jss::ledger][jss::account_hash] == "C3335CA14E712CB28F2A7C09BEB9A24BF30BBFA5528F156C19F6665D7A588FEA");
BEAST_EXPECT(result[jss::ledger][jss::transaction_hash] == "3CBDB8F42E04333E1642166BFB93AC9A7E1C6C067092CD5D881D6F3AB3D67E76");
result = env.rpc ( "ledger_request", "5" ) [jss::result];
BEAST_EXPECT(result[jss::ledger][jss::ledger_index] == "5");
BEAST_EXPECT(result[jss::ledger][jss::total_coins] == "99999999999999940");
BEAST_EXPECT(result[jss::ledger][jss::closed] == true);
BEAST_EXPECT(result[jss::ledger][jss::ledger_hash] == "3EDEB201735867A8EEECBC79A75902C05A7E3F192E4C12E02E67BFDDE5566CCE");
BEAST_EXPECT(result[jss::ledger][jss::parent_hash] == "8F9032390CDD4C9D7A5B216AFDA3B525A3B39D7589C69D90D4C6BCA4619DD33C");
BEAST_EXPECT(result[jss::ledger][jss::account_hash] == "7C77B1E9EB86410D84EE0CD50716AAA21192F19CF533194AD705798895248212");
BEAST_EXPECT(result[jss::ledger][jss::transaction_hash] == "C3D086CD6BDB9E97AD1D513B2C049EF2840BD21D0B3E22D84EBBB89B6D2EF59D");
result = env.rpc ( "ledger_request", "6" ) [jss::result];
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::status] == "error");
BEAST_EXPECT(result[jss::error_message] == "Ledger index too large");
}
void testBadInput()
{
using namespace test::jtx;
Env env { *this };
Account const gw { "gateway" };
auto const USD = gw["USD"];
env.fund(XRP(100000), gw);
env.close();
Json::Value jvParams;
jvParams[jss::ledger_hash] = "AB868A6CFEEC779C2FF845C0AF00A642259986AF40C01976A7F842B6918936C7";
jvParams[jss::ledger_index] = "1";
auto result = env.rpc ("json", "ledger_request", jvParams.toStyledString()) [jss::result];
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::status] == "error");
BEAST_EXPECT(result[jss::error_message] == "Exactly one of ledger_hash and ledger_index can be set.");
// the purpose in this test is to force the ledger expiration/out of
// date check to trigger
env.timeKeeper().adjustCloseTime(weeks{3});
result = env.rpc ( "ledger_request", "1" ) [jss::result];
BEAST_EXPECT(result[jss::error] == "noCurrent");
BEAST_EXPECT(result[jss::status] == "error");
BEAST_EXPECT(result[jss::error_message] == "Current ledger is unavailable.");
}
void testMoreThan256Closed()
{
using namespace test::jtx;
Env env {*this};
Account const gw {"gateway"};
env.app().getLedgerMaster().tune(0, 3600);
auto const USD = gw["USD"];
env.fund(XRP(100000), gw);
int const max_limit = 256;
for (auto i = 0; i < max_limit + 10; i++)
{
Account const bob {std::string("bob") + std::to_string(i)};
env.fund(XRP(1000), bob);
env.close();
}
auto result = env.rpc ( "ledger_request", "1" ) [jss::result];
BEAST_EXPECT(result[jss::ledger][jss::ledger_index] == "1");
BEAST_EXPECT(result[jss::ledger][jss::total_coins] == "100000000000000000");
BEAST_EXPECT(result[jss::ledger][jss::closed] == true);
BEAST_EXPECT(result[jss::ledger][jss::ledger_hash] == "AB868A6CFEEC779C2FF845C0AF00A642259986AF40C01976A7F842B6918936C7");
BEAST_EXPECT(result[jss::ledger][jss::parent_hash] == "0000000000000000000000000000000000000000000000000000000000000000");
BEAST_EXPECT(result[jss::ledger][jss::account_hash] == "A21ED30C04C88046FC61DB9DC19375EEDBD365FD8C17286F27127DF804E9CAA6");
BEAST_EXPECT(result[jss::ledger][jss::transaction_hash] == "0000000000000000000000000000000000000000000000000000000000000000");
}
void testNonAdmin()
{
using namespace test::jtx;
Env env { *this, makeNonAdminConfig() };
Account const gw { "gateway" };
auto const USD = gw["USD"];
env.fund(XRP(100000), gw);
env.close();
auto const result = env.rpc ( "ledger_request", "1" ) [jss::result];
BEAST_EXPECT(result[jss::error] == "noPermission");
BEAST_EXPECT(result[jss::status] == "error");
BEAST_EXPECT(result[jss::error_message] == "You don't have permission for this command.");
}
void run ()
{
testLedgerRequest();
testEvolution();
testBadInput();
testMoreThan256Closed();
testNonAdmin();
}
};
BEAST_DEFINE_TESTSUITE(LedgerRequestRPC,app,ripple);
} // RPC
} // ripple

View File

@@ -0,0 +1,321 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 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 <BeastConfig.h>
#include <ripple/core/JobQueue.h>
#include <ripple/protocol/JsonFields.h>
#include <ripple/test/jtx.h>
#include <ripple/test/WSClient.h>
#include <ripple/beast/unit_test.h>
namespace ripple {
namespace test {
class RobustTransaction_test : public beast::unit_test::suite
{
public:
void
testSequenceRealignment()
{
using namespace std::chrono_literals;
using namespace jtx;
Env env(*this);
env.fund(XRP(10000), "alice", "bob");
env.close();
auto wsc = makeWSClient(env.app().config());
{
// RPC subscribe to transactions stream
Json::Value jv;
jv[jss::streams] = Json::arrayValue;
jv[jss::streams].append("transactions");
jv = wsc->invoke("subscribe", jv);
BEAST_EXPECT(jv[jss::status] == "success");
}
{
// Submit past ledger sequence transaction
Json::Value payment;
payment[jss::secret] = toBase58(generateSeed("alice"));
payment[jss::tx_json] = pay("alice", "bob", XRP(1));
payment[jss::tx_json][sfLastLedgerSequence.fieldName] = 1;
auto jv = wsc->invoke("submit", payment);
BEAST_EXPECT(jv[jss::result][jss::engine_result] ==
"tefMAX_LEDGER");
// Submit past sequence transaction
payment[jss::tx_json] = pay("alice", "bob", XRP(1));
payment[jss::tx_json][sfSequence.fieldName] =
env.seq("alice") - 1;
jv = wsc->invoke("submit", payment);
BEAST_EXPECT(jv[jss::result][jss::engine_result] ==
"tefPAST_SEQ");
// Submit future sequence transaction
payment[jss::tx_json][sfSequence.fieldName] =
env.seq("alice") + 1;
jv = wsc->invoke("submit", payment);
BEAST_EXPECT(jv[jss::result][jss::engine_result] ==
"terPRE_SEQ");
// Submit transaction to bridge the sequence gap
payment[jss::tx_json][sfSequence.fieldName] =
env.seq("alice");
jv = wsc->invoke("submit", payment);
BEAST_EXPECT(jv[jss::result][jss::engine_result] ==
"tesSUCCESS");
// Wait for the jobqueue to process everything
env.app().getJobQueue().rendezvous();
// Finalize transactions
jv = wsc->invoke("ledger_accept");
BEAST_EXPECT(jv[jss::result].isMember(
jss::ledger_current_index));
}
{
// Check balances
BEAST_EXPECT(wsc->findMsg(5s,
[&](auto const& jv)
{
auto const& ff = jv[jss::meta]["AffectedNodes"]
[1u]["ModifiedNode"]["FinalFields"];
return ff[jss::Account] == Account("bob").human() &&
ff["Balance"] == "10001000000";
}));
BEAST_EXPECT(wsc->findMsg(5s,
[&](auto const& jv)
{
auto const& ff = jv[jss::meta]["AffectedNodes"]
[1u]["ModifiedNode"]["FinalFields"];
return ff[jss::Account] == Account("bob").human() &&
ff["Balance"] == "10002000000";
}));
}
}
/*
Submit a normal payment. Client disconnects after the proposed
transaction result is received.
Client reconnects in the future. During this time it is presumed that the
transaction should have succeeded.
Upon reconnection, recent account transaction history is loaded.
The submitted transaction should be detected, and the transaction should
ultimately succeed.
*/
void
testReconnect()
{
using namespace jtx;
Env env(*this);
env.fund(XRP(10000), "alice", "bob");
env.close();
auto wsc = makeWSClient(env.app().config());
{
// Submit normal payment
Json::Value jv;
jv[jss::secret] = toBase58(generateSeed("alice"));
jv[jss::tx_json] = pay("alice", "bob", XRP(1));
jv = wsc->invoke("submit", jv);
BEAST_EXPECT(jv[jss::result][jss::engine_result] ==
"tesSUCCESS");
// Disconnect
wsc.reset();
// Server finalizes transaction
env.close();
}
{
// RPC account_tx
Json::Value jv;
jv[jss::account] = Account("bob").human();
jv[jss::ledger_index_min] = -1;
jv[jss::ledger_index_max] = -1;
wsc = makeWSClient(env.app().config());
jv = wsc->invoke("account_tx", jv);
// Check balance
auto ff = jv[jss::result][jss::transactions][0u][jss::meta]
["AffectedNodes"][1u]["ModifiedNode"]["FinalFields"];
BEAST_EXPECT(ff[jss::Account] ==
Account("bob").human());
BEAST_EXPECT(ff["Balance"] == "10001000000");
}
}
void
testReconnectAfterWait()
{
using namespace std::chrono_literals;
using namespace jtx;
Env env(*this);
env.fund(XRP(10000), "alice", "bob");
env.close();
auto wsc = makeWSClient(env.app().config());
{
// Submit normal payment
Json::Value jv;
jv[jss::secret] = toBase58(generateSeed("alice"));
jv[jss::tx_json] = pay("alice", "bob", XRP(1));
jv = wsc->invoke("submit", jv);
BEAST_EXPECT(jv[jss::result][jss::engine_result] ==
"tesSUCCESS");
// Finalize transaction
jv = wsc->invoke("ledger_accept");
BEAST_EXPECT(jv[jss::result].isMember(
jss::ledger_current_index));
// Wait for the jobqueue to process everything
env.app().getJobQueue().rendezvous();
}
{
// RPC subscribe to ledger stream
Json::Value jv;
jv[jss::streams] = Json::arrayValue;
jv[jss::streams].append("ledger");
jv = wsc->invoke("subscribe", jv);
BEAST_EXPECT(jv[jss::status] == "success");
// Close ledgers
for(auto i = 0; i < 8; ++i)
{
BEAST_EXPECT(wsc->invoke("ledger_accept")[jss::result].
isMember(jss::ledger_current_index));
// Wait for the jobqueue to process everything
env.app().getJobQueue().rendezvous();
BEAST_EXPECT(wsc->findMsg(5s,
[&](auto const& jv)
{
return jv[jss::type] == "ledgerClosed";
}));
}
}
{
// Disconnect, reconnect
wsc = makeWSClient(env.app().config());
// RPC subscribe to ledger stream
Json::Value jv;
jv[jss::streams] = Json::arrayValue;
jv[jss::streams].append("ledger");
jv = wsc->invoke("subscribe", jv);
BEAST_EXPECT(jv[jss::status] == "success");
// Close ledgers
for (auto i = 0; i < 2; ++i)
{
BEAST_EXPECT(wsc->invoke("ledger_accept")[jss::result].
isMember(jss::ledger_current_index));
// Wait for the jobqueue to process everything
env.app().getJobQueue().rendezvous();
BEAST_EXPECT(wsc->findMsg(5s,
[&](auto const& jv)
{
return jv[jss::type] == "ledgerClosed";
}));
}
}
{
// RPC account_tx
Json::Value jv;
jv[jss::account] = Account("bob").human();
jv[jss::ledger_index_min] = -1;
jv[jss::ledger_index_max] = -1;
wsc = makeWSClient(env.app().config());
jv = wsc->invoke("account_tx", jv);
// Check balance
auto ff = jv[jss::result][jss::transactions][0u][jss::meta]
["AffectedNodes"][1u]["ModifiedNode"]["FinalFields"];
BEAST_EXPECT(ff[jss::Account] ==
Account("bob").human());
BEAST_EXPECT(ff["Balance"] == "10001000000");
}
}
void
testAccountsProposed()
{
using namespace std::chrono_literals;
using namespace jtx;
Env env(*this);
env.fund(XRP(10000), "alice");
env.close();
auto wsc = makeWSClient(env.app().config());
{
// RPC subscribe to accounts_proposed stream
Json::Value jv;
jv[jss::accounts_proposed] = Json::arrayValue;
jv[jss::accounts_proposed].append(
Account("alice").human());
jv = wsc->invoke("subscribe", jv);
BEAST_EXPECT(jv[jss::status] == "success");
}
{
// Submit account_set transaction
Json::Value jv;
jv[jss::secret] = toBase58(generateSeed("alice"));
jv[jss::tx_json] = fset("alice", 0);
jv[jss::tx_json][jss::Fee] = 10;
jv = wsc->invoke("submit", jv);
BEAST_EXPECT(jv[jss::result][jss::engine_result] ==
"tesSUCCESS");
}
{
// Check stream update
BEAST_EXPECT(wsc->findMsg(5s,
[&](auto const& jv)
{
return jv[jss::transaction][jss::TransactionType] ==
"AccountSet";
}));
}
}
void
run() override
{
testSequenceRealignment();
testReconnect();
testReconnectAfterWait();
testAccountsProposed();
}
};
BEAST_DEFINE_TESTSUITE(RobustTransaction,app,ripple);
} // test
} // ripple

View File

@@ -0,0 +1,108 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012-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 <BeastConfig.h>
#include <ripple/protocol/JsonFields.h>
#include <ripple/test/jtx.h>
#include <ripple/beast/unit_test.h>
namespace ripple {
namespace test {
namespace validator {
static auto const seed = "ss7t3J9dYentEFgKdPA3q6eyxtrLB";
static auto const master_key =
"nHU4LxxrSQsRTKy5uZbX95eYowoamUEPCcWraxoiCNbtDaUr1V34";
static auto const signing_key =
"n9LHPLA36SBky1YjbaVEApQQ3s9XcpazCgfAG7jsqBb1ugDAosbm";
// Format manifest string to test trim()
static auto const manifest =
" JAAAAAFxIe2FwblmJwz4pVYXHLJSzSBgIK7mpQuHNQ88CxW\n"
" \tjIN7q4nMhAuUTyasIhvj2KPfNRbmmIBnqNUzidgkKb244eP \n"
"\t794ZpMdkC+8l5n3R/CHP6SAwhYDOaqub0Cs2NjjewBnp1mf\n"
"\t 23rhAzdcjRuWzm0IT12eduZ0DwcF5Ng8rAelaYP1iT93ScE\t \t";
static auto sequence = 1;
}
class ServerInfo_test : public beast::unit_test::suite
{
public:
static
std::unique_ptr<Config>
makeValidatorConfig()
{
auto p = std::make_unique<Config>();
boost::format toLoad(R"rippleConfig(
[validation_manifest]
%1%
[validation_seed]
%2%
)rippleConfig");
p->loadFromString (boost::str (
toLoad % validator::manifest % validator::seed));
setupConfigForUnitTests(*p);
return p;
}
void testServerInfo()
{
using namespace test::jtx;
{
Env env(*this);
auto const result = env.rpc("server_info", "1");
BEAST_EXPECT(!result[jss::result].isMember (jss::error));
BEAST_EXPECT(result[jss::status] == "success");
BEAST_EXPECT(result[jss::result].isMember(jss::info));
}
{
Env env(*this, makeValidatorConfig());
auto const result = env.rpc("server_info", "1");
BEAST_EXPECT(!result[jss::result].isMember (jss::error));
BEAST_EXPECT(result[jss::status] == "success");
BEAST_EXPECT(result[jss::result].isMember(jss::info));
BEAST_EXPECT(result[jss::result][jss::info]
[jss::pubkey_validator] == validator::signing_key);
BEAST_EXPECT(result[jss::result][jss::info].isMember(
jss::validation_manifest));
BEAST_EXPECT(result[jss::result][jss::info][jss::validation_manifest]
[jss::master_key] == validator::master_key);
BEAST_EXPECT(result[jss::result][jss::info][jss::validation_manifest]
[jss::signing_key] == validator::signing_key);
BEAST_EXPECT(result[jss::result][jss::info][jss::validation_manifest]
[jss::seq] == validator::sequence);
}
}
void run ()
{
testServerInfo ();
}
};
BEAST_DEFINE_TESTSUITE(ServerInfo,app,ripple);
} // test
} // ripple

View File

@@ -0,0 +1,215 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 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 <BeastConfig.h>
#include <ripple/rpc/Status.h>
#include <ripple/basics/contract.h>
#include <ripple/beast/unit_test.h>
#include <algorithm>
namespace ripple {
namespace RPC {
class codeString_test : public beast::unit_test::suite
{
private:
template <typename Type>
std::string codeString (Type t)
{
return Status (t).codeString();
}
void test_OK ()
{
testcase ("OK");
{
auto s = codeString (Status ());
expect (s.empty(), "String for OK status");
}
{
auto s = codeString (Status::OK);
expect (s.empty(), "String for OK status");
}
{
auto s = codeString (0);
expect (s.empty(), "String for 0 status");
}
{
auto s = codeString (tesSUCCESS);
expect (s.empty(), "String for tesSUCCESS");
}
{
auto s = codeString (rpcSUCCESS);
expect (s.empty(), "String for rpcSUCCESS");
}
}
void test_error ()
{
testcase ("error");
{
auto s = codeString (23);
expect (s == "23", s);
}
{
auto s = codeString (temBAD_AMOUNT);
expect (s == "temBAD_AMOUNT: Can only send positive amounts.", s);
}
{
auto s = codeString (rpcBAD_SYNTAX);
expect (s == "badSyntax: Syntax error.", s);
}
}
public:
void run()
{
test_OK ();
test_error ();
}
};
BEAST_DEFINE_TESTSUITE (codeString, Status, RPC);
class fillJson_test : public beast::unit_test::suite
{
private:
Json::Value value_;
template <typename Type>
void fillJson (Type t)
{
value_.clear ();
Status (t).fillJson (value_);
}
void test_OK ()
{
testcase ("OK");
fillJson (Status ());
expect (! value_, "Value for empty status");
fillJson (0);
expect (! value_, "Value for 0 status");
fillJson (Status::OK);
expect (! value_, "Value for OK status");
fillJson (tesSUCCESS);
expect (! value_, "Value for tesSUCCESS");
fillJson (rpcSUCCESS);
expect (! value_, "Value for rpcSUCCESS");
}
template <typename Type>
void expectFill (
std::string const& label,
Type status,
Status::Strings messages,
std::string const& message)
{
value_.clear ();
fillJson (Status (status, messages));
auto prefix = label + ": ";
expect (bool (value_), prefix + "No value");
auto error = value_[jss::error];
expect (bool (error), prefix + "No error.");
auto code = error[jss::code].asInt();
expect (status == code, prefix + "Wrong status " +
std::to_string (code) + " != " + std::to_string (status));
auto m = error[jss::message].asString ();
expect (m == message, m + " != " + message);
auto d = error[jss::data];
size_t s1 = d.size(), s2 = messages.size();
expect (s1 == s2, prefix + "Data sizes differ " +
std::to_string (s1) + " != " + std::to_string (s2));
for (auto i = 0; i < std::min (s1, s2); ++i)
{
auto ds = d[i].asString();
expect (ds == messages[i], prefix + ds + " != " + messages[i]);
}
}
void test_error ()
{
testcase ("error");
expectFill (
"temBAD_AMOUNT",
temBAD_AMOUNT,
{},
"temBAD_AMOUNT: Can only send positive amounts.");
expectFill (
"rpcBAD_SYNTAX",
rpcBAD_SYNTAX,
{"An error.", "Another error."},
"badSyntax: Syntax error.");
expectFill (
"integer message",
23,
{"Stuff."},
"23");
}
void test_throw ()
{
testcase ("throw");
try
{
Throw<Status> (Status(temBAD_PATH, { "path=sdcdfd" }));
}
catch (Status const& s)
{
expect (s.toTER () == temBAD_PATH, "temBAD_PATH wasn't thrown");
auto msgs = s.messages ();
expect (msgs.size () == 1, "Wrong number of messages");
expect (msgs[0] == "path=sdcdfd", msgs[0]);
}
catch (std::exception const&)
{
expect (false, "Didn't catch a Status");
}
}
public:
void run()
{
test_OK ();
test_error ();
test_throw ();
}
};
BEAST_DEFINE_TESTSUITE (fillJson, Status, RPC);
} // namespace RPC
} // ripple

View File

@@ -0,0 +1,342 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 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 <BeastConfig.h>
#include <ripple/app/misc/LoadFeeTrack.h>
#include <ripple/app/misc/NetworkOPs.h>
#include <ripple/protocol/JsonFields.h>
#include <ripple/test/WSClient.h>
#include <ripple/test/jtx.h>
#include <ripple/beast/unit_test.h>
namespace ripple {
namespace test {
class Subscribe_test : public beast::unit_test::suite
{
public:
void testServer()
{
using namespace std::chrono_literals;
using namespace jtx;
Env env(*this);
auto wsc = makeWSClient(env.app().config());
Json::Value stream;
{
// RPC subscribe to server stream
stream[jss::streams] = Json::arrayValue;
stream[jss::streams].append("server");
auto jv = wsc->invoke("subscribe", stream);
BEAST_EXPECT(jv[jss::status] == "success");
}
{
// Raise fee to cause an update
auto& feeTrack = env.app().getFeeTrack();
for(int i = 0; i < 5; ++i)
feeTrack.raiseLocalFee();
env.app().getOPs().reportFeeChange();
// Check stream update
BEAST_EXPECT(wsc->findMsg(5s,
[&](auto const& jv)
{
return jv[jss::type] == "serverStatus";
}));
}
{
// RPC unsubscribe
auto jv = wsc->invoke("unsubscribe", stream);
BEAST_EXPECT(jv[jss::status] == "success");
}
{
// Raise fee to cause an update
auto& feeTrack = env.app().getFeeTrack();
for (int i = 0; i < 5; ++i)
feeTrack.raiseLocalFee();
env.app().getOPs().reportFeeChange();
// Check stream update
BEAST_EXPECT(! wsc->getMsg(10ms));
}
}
void testLedger()
{
using namespace std::chrono_literals;
using namespace jtx;
Env env(*this);
auto wsc = makeWSClient(env.app().config());
Json::Value stream;
{
// RPC subscribe to ledger stream
stream[jss::streams] = Json::arrayValue;
stream[jss::streams].append("ledger");
auto jv = wsc->invoke("subscribe", stream);
BEAST_EXPECT(jv[jss::result][jss::ledger_index] == 2);
}
{
// Accept a ledger
env.close();
// Check stream update
BEAST_EXPECT(wsc->findMsg(5s,
[&](auto const& jv)
{
return jv[jss::ledger_index] == 3;
}));
}
{
// Accept another ledger
env.close();
// Check stream update
BEAST_EXPECT(wsc->findMsg(5s,
[&](auto const& jv)
{
return jv[jss::ledger_index] == 4;
}));
}
// RPC unsubscribe
auto jv = wsc->invoke("unsubscribe", stream);
BEAST_EXPECT(jv[jss::status] == "success");
}
void testTransactions()
{
using namespace std::chrono_literals;
using namespace jtx;
Env env(*this);
auto wsc = makeWSClient(env.app().config());
Json::Value stream;
{
// RPC subscribe to transactions stream
stream[jss::streams] = Json::arrayValue;
stream[jss::streams].append("transactions");
auto jv = wsc->invoke("subscribe", stream);
BEAST_EXPECT(jv[jss::status] == "success");
}
{
env.fund(XRP(10000), "alice");
env.close();
// Check stream update for payment transaction
BEAST_EXPECT(wsc->findMsg(5s,
[&](auto const& jv)
{
return jv[jss::meta]["AffectedNodes"][1u]
["CreatedNode"]["NewFields"][jss::Account] ==
Account("alice").human();
}));
// Check stream update for accountset transaction
BEAST_EXPECT(wsc->findMsg(5s,
[&](auto const& jv)
{
return jv[jss::meta]["AffectedNodes"][0u]
["ModifiedNode"]["FinalFields"][jss::Account] ==
Account("alice").human();
}));
env.fund(XRP(10000), "bob");
env.close();
// Check stream update for payment transaction
BEAST_EXPECT(wsc->findMsg(5s,
[&](auto const& jv)
{
return jv[jss::meta]["AffectedNodes"][1u]
["CreatedNode"]["NewFields"][jss::Account] ==
Account("bob").human();
}));
// Check stream update for accountset transaction
BEAST_EXPECT(wsc->findMsg(5s,
[&](auto const& jv)
{
return jv[jss::meta]["AffectedNodes"][0u]
["ModifiedNode"]["FinalFields"][jss::Account] ==
Account("bob").human();
}));
}
{
// RPC unsubscribe
auto jv = wsc->invoke("unsubscribe", stream);
BEAST_EXPECT(jv[jss::status] == "success");
}
{
// RPC subscribe to accounts stream
stream = Json::objectValue;
stream[jss::accounts] = Json::arrayValue;
stream[jss::accounts].append(Account("alice").human());
auto jv = wsc->invoke("subscribe", stream);
BEAST_EXPECT(jv[jss::status] == "success");
}
{
// Transaction that does not affect stream
env.fund(XRP(10000), "carol");
env.close();
BEAST_EXPECT(! wsc->getMsg(10ms));
// Transactions concerning alice
env.trust(Account("bob")["USD"](100), "alice");
env.close();
// Check stream updates
BEAST_EXPECT(wsc->findMsg(5s,
[&](auto const& jv)
{
return jv[jss::meta]["AffectedNodes"][1u]
["ModifiedNode"]["FinalFields"][jss::Account] ==
Account("alice").human();
}));
BEAST_EXPECT(wsc->findMsg(5s,
[&](auto const& jv)
{
return jv[jss::meta]["AffectedNodes"][1u]
["CreatedNode"]["NewFields"]["LowLimit"]
[jss::issuer] == Account("alice").human();
}));
}
// RPC unsubscribe
auto jv = wsc->invoke("unsubscribe", stream);
BEAST_EXPECT(jv[jss::status] == "success");
}
void testManifests()
{
using namespace jtx;
Env env(*this);
auto wsc = makeWSClient(env.app().config());
Json::Value stream;
{
// RPC subscribe to manifests stream
stream[jss::streams] = Json::arrayValue;
stream[jss::streams].append("manifests");
auto jv = wsc->invoke("subscribe", stream);
BEAST_EXPECT(jv[jss::status] == "success");
}
// RPC unsubscribe
auto jv = wsc->invoke("unsubscribe", stream);
BEAST_EXPECT(jv[jss::status] == "success");
}
static
std::unique_ptr<Config>
makeValidatorConfig(
std::string const& valPrivateKey, std::string const& valPublicKey)
{
auto p = std::make_unique<Config>();
setupConfigForUnitTests(*p);
// If the config has valid validation keys then we run as a validator.
auto const sk = parseBase58<SecretKey>(
TOKEN_NODE_PRIVATE,
valPrivateKey);
if (!sk)
Throw<std::runtime_error> ("Invalid validation private key");
p->VALIDATION_PRIV = *sk;
auto const pk = parseBase58<PublicKey>(
TOKEN_NODE_PUBLIC,
valPublicKey);
if (!pk)
Throw<std::runtime_error> ("Invalid validation public key");
p->VALIDATION_PUB = *pk;
return p;
}
void testValidations()
{
using namespace jtx;
// Public key must be derived from the private key
const std::string valPrivateKey =
"paEdUCVVCNnv4aYBepid9Xh3NaAr9xWRw2vh351piFJrxQwvExd";
const std::string valPublicKey =
"n9MvFGjgv1kYkm7bLbb2QUwSqgzrQkYMYHXtrzN8W28Jfp2mVihq";
Env env(*this, makeValidatorConfig(valPrivateKey, valPublicKey));
auto wsc = makeWSClient(env.app().config());
Json::Value stream;
{
// RPC subscribe to validations stream
stream[jss::streams] = Json::arrayValue;
stream[jss::streams].append("validations");
auto jv = wsc->invoke("subscribe", stream);
BEAST_EXPECT(jv[jss::status] == "success");
}
{
// Accept a ledger
env.close();
// Check stream update
BEAST_EXPECT(wsc->findMsg(5s,
[&](auto const& jv)
{
return jv[jss::type] == "validationReceived" &&
jv[jss::validation_public_key] == valPublicKey &&
jv[jss::ledger_hash] ==
to_string(env.closed()->info().hash) &&
jv[jss::ledger_index] ==
to_string(env.closed()->info().seq) &&
jv[jss::flags] ==
(vfFullyCanonicalSig | STValidation::kFullFlag) &&
jv[jss::full] == true &&
!jv.isMember(jss::load_fee) &&
jv[jss::signature] &&
jv[jss::signing_time];
}));
}
// RPC unsubscribe
auto jv = wsc->invoke("unsubscribe", stream);
BEAST_EXPECT(jv[jss::status] == "success");
}
void run() override
{
testServer();
testLedger();
testTransactions();
testManifests();
testValidations();
}
};
BEAST_DEFINE_TESTSUITE(Subscribe,app,ripple);
} // test
} // ripple