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,258 @@
//------------------------------------------------------------------------------
/*
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 <ripple/core/DatabaseCon.h>
#include <ripple/app/misc/impl/AccountTxPaging.h>
#include <ripple/protocol/types.h>
#include <ripple/test/jtx.h>
#include <ripple/beast/unit_test.h>
#include <cstdlib>
#include <memory>
#include <vector>
namespace ripple {
struct AccountTxPaging_test : beast::unit_test::suite
{
std::unique_ptr<DatabaseCon> db_;
std::unique_ptr<AccountIDCache> idCache_;
NetworkOPs::AccountTxs txs_;
AccountID account_;
void
run() override
{
std::string data_path;
if (auto const fixtures = std::getenv("TEST_FIXTURES"))
data_path = fixtures;
if (data_path.empty ())
{
fail("The 'TEST_FIXTURES' environment variable is empty.");
return;
}
DatabaseCon::Setup dbConf;
dbConf.dataDir = data_path + "/";
db_ = std::make_unique <DatabaseCon> (
dbConf, "account-tx-transactions.db", nullptr, 0);
idCache_ = std::make_unique<AccountIDCache>(128000);
account_ = *parseBase58<AccountID>(
"rfu6L5p3azwPzQZsbTafuVk884N9YoKvVG");
testAccountTxPaging();
}
void
checkToken (Json::Value const& token, int ledger, int sequence)
{
BEAST_EXPECT(token.isMember ("ledger"));
BEAST_EXPECT(token["ledger"].asInt() == ledger);
BEAST_EXPECT(token.isMember ("seq"));
BEAST_EXPECT(token["seq"].asInt () == sequence);
}
void
checkTransaction (NetworkOPs::AccountTx const& tx, int ledger, int index)
{
BEAST_EXPECT(tx.second->getLgrSeq () == ledger);
BEAST_EXPECT(tx.second->getIndex () == index);
}
std::size_t
next (
int limit,
bool forward,
Json::Value& token,
std::int32_t minLedger,
std::int32_t maxLedger)
{
txs_.clear();
std::int32_t const page_length = 200;
bool const admin = true;
test::jtx::Env env(*this);
Application& app = env.app();
auto& txs = txs_;
auto bound = [&txs, &app](
std::uint32_t ledger_index,
std::string const& status,
Blob const& rawTxn,
Blob const& rawMeta)
{
convertBlobsToTxResult (
txs, ledger_index, status, rawTxn, rawMeta, app);
};
accountTxPage(*db_, *idCache_, [](std::uint32_t){}, bound, account_, minLedger,
maxLedger, forward, token, limit, admin, page_length);
return txs_.size();
}
void
testAccountTxPaging ()
{
using namespace std::placeholders;
bool const forward = true;
std::int32_t min_ledger;
std::int32_t max_ledger;
Json::Value token;
int limit;
// the supplied account-tx-transactions.db contains contains
// transactions with the following ledger/sequence pairs.
// 3|5
// 4|4
// 4|10
// 5|4
// 5|7
// 6|1
// 6|5
// 6|6
// 6|7
// 6|8
// 6|9
// 6|10
// 6|11
min_ledger = 2;
max_ledger = 5;
{
limit = 2;
BEAST_EXPECT(next(limit, forward, token, min_ledger, max_ledger) == 2);
checkTransaction (txs_[0], 3, 5);
checkTransaction (txs_[1], 4, 4);
checkToken (token, 4, 10);
BEAST_EXPECT(next(limit, forward, token, min_ledger, max_ledger) == 2);
checkTransaction (txs_[0], 4, 10);
checkTransaction (txs_[1], 5, 4);
checkToken (token, 5, 7);
BEAST_EXPECT(next(limit, forward, token, min_ledger, max_ledger) == 1);
checkTransaction (txs_[0], 5, 7);
BEAST_EXPECT(! token["ledger"]);
BEAST_EXPECT(! token["seq"]);
}
token = Json::nullValue;
min_ledger = 3;
max_ledger = 9;
{
limit = 1;
BEAST_EXPECT(next(limit, forward, token, min_ledger, max_ledger) == 1);
checkTransaction (txs_[0], 3, 5);
checkToken (token, 4, 4);
BEAST_EXPECT(next(limit, forward, token, min_ledger, max_ledger) == 1);
checkTransaction (txs_[0], 4, 4);
checkToken (token, 4, 10);
BEAST_EXPECT(next(limit, forward, token, min_ledger, max_ledger) == 1);
checkTransaction (txs_[0], 4, 10);
checkToken (token, 5, 4);
}
{
limit = 3;
BEAST_EXPECT(next(limit, forward, token, min_ledger, max_ledger) == 3);
checkTransaction (txs_[0], 5, 4);
checkTransaction (txs_[1], 5, 7);
checkTransaction (txs_[2], 6, 1);
checkToken (token, 6, 5);
BEAST_EXPECT(next(limit, forward, token, min_ledger, max_ledger) == 3);
checkTransaction (txs_[0], 6, 5);
checkTransaction (txs_[1], 6, 6);
checkTransaction (txs_[2], 6, 7);
checkToken (token, 6, 8);
BEAST_EXPECT(next(limit, forward, token, min_ledger, max_ledger) == 3);
checkTransaction (txs_[0], 6, 8);
checkTransaction (txs_[1], 6, 9);
checkTransaction (txs_[2], 6, 10);
checkToken (token, 6, 11);
BEAST_EXPECT(next(limit, forward, token, min_ledger, max_ledger) == 1);
checkTransaction (txs_[0], 6, 11);
BEAST_EXPECT(! token["ledger"]);
BEAST_EXPECT(! token["seq"]);
}
token = Json::nullValue;
{
limit = 2;
BEAST_EXPECT(next(limit, ! forward, token, min_ledger, max_ledger) == 2);
checkTransaction (txs_[0], 6, 11);
checkTransaction (txs_[1], 6, 10);
checkToken (token, 6, 9);
BEAST_EXPECT(next(limit, ! forward, token, min_ledger, max_ledger) == 2);
checkTransaction (txs_[0], 6, 9);
checkTransaction (txs_[1], 6, 8);
checkToken (token, 6, 7);
}
{
limit = 3;
BEAST_EXPECT(next(limit, ! forward, token, min_ledger, max_ledger) == 3);
checkTransaction (txs_[0], 6, 7);
checkTransaction (txs_[1], 6, 6);
checkTransaction (txs_[2], 6, 5);
checkToken (token, 6, 1);
BEAST_EXPECT(next(limit, ! forward, token, min_ledger, max_ledger) == 3);
checkTransaction (txs_[0], 6, 1);
checkTransaction (txs_[1], 5, 7);
checkTransaction (txs_[2], 5, 4);
checkToken (token, 4, 10);
BEAST_EXPECT(next(limit, ! forward, token, min_ledger, max_ledger) == 3);
checkTransaction (txs_[0], 4, 10);
checkTransaction (txs_[1], 4, 4);
checkTransaction (txs_[2], 3, 5);
}
BEAST_EXPECT(! token["ledger"]);
BEAST_EXPECT(! token["seq"]);
}
};
BEAST_DEFINE_TESTSUITE_MANUAL(AccountTxPaging,app,ripple);
}

View File

@@ -0,0 +1,763 @@
//------------------------------------------------------------------------------
/*
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/AmendmentTable.h>
#include <ripple/basics/BasicConfig.h>
#include <ripple/basics/chrono.h>
#include <ripple/basics/Log.h>
#include <ripple/core/ConfigSections.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/PublicKey.h>
#include <ripple/protocol/SecretKey.h>
#include <ripple/protocol/digest.h>
#include <ripple/protocol/TxFlags.h>
#include <ripple/beast/unit_test.h>
namespace ripple
{
namespace detail {
extern
std::vector<std::string>
supportedAmendments ();
}
class AmendmentTable_test final : public beast::unit_test::suite
{
private:
// 204/256 about 80% (we round down because the implementation rounds up)
static int const majorityFraction{204};
static
uint256
amendmentId (std::string in)
{
sha256_hasher h;
using beast::hash_append;
hash_append(h, in);
auto const d = static_cast<sha256_hasher::result_type>(h);
uint256 result;
std::memcpy(result.data(), d.data(), d.size());
return result;
}
static
std::vector<std::string>
createSet (int group, int count)
{
std::vector<std::string> amendments;
for (int i = 0; i < count; i++)
amendments.push_back (
"Amendment" + std::to_string ((1000000 * group) + i));
return amendments;
}
static
Section
makeSection (std::vector<std::string> const& amendments)
{
Section section ("Test");
for (auto const& a : amendments)
section.append (to_string(amendmentId (a)) + " " + a);
return section;
}
static
Section
makeSection (uint256 const& amendment)
{
Section section ("Test");
section.append (to_string (amendment) + " " + to_string(amendment));
return section;
}
std::vector<std::string> const m_set1;
std::vector<std::string> const m_set2;
std::vector<std::string> const m_set3;
std::vector<std::string> const m_set4;
Section const emptySection;
public:
AmendmentTable_test ()
: m_set1 (createSet (1, 12))
, m_set2 (createSet (2, 12))
, m_set3 (createSet (3, 12))
, m_set4 (createSet (4, 12))
{
}
std::unique_ptr<AmendmentTable>
makeTable(
int w,
Section const supported,
Section const enabled,
Section const vetoed)
{
return make_AmendmentTable (
weeks (w),
majorityFraction,
supported,
enabled,
vetoed,
beast::Journal{});
}
std::unique_ptr<AmendmentTable>
makeTable (int w)
{
return makeTable (
w,
makeSection (m_set1),
makeSection (m_set2),
makeSection (m_set3));
};
void testConstruct ()
{
testcase ("Construction");
auto table = makeTable(1);
for (auto const& a : m_set1)
{
BEAST_EXPECT(table->isSupported (amendmentId (a)));
BEAST_EXPECT(!table->isEnabled (amendmentId (a)));
}
for (auto const& a : m_set2)
{
BEAST_EXPECT(table->isSupported (amendmentId (a)));
BEAST_EXPECT(table->isEnabled (amendmentId (a)));
}
for (auto const& a : m_set3)
{
BEAST_EXPECT(!table->isSupported (amendmentId (a)));
BEAST_EXPECT(!table->isEnabled (amendmentId (a)));
}
}
void testGet ()
{
testcase ("Name to ID mapping");
auto table = makeTable (1);
for (auto const& a : m_set1)
BEAST_EXPECT(table->find (a) == amendmentId (a));
for (auto const& a : m_set2)
BEAST_EXPECT(table->find (a) == amendmentId (a));
for (auto const& a : m_set3)
BEAST_EXPECT(!table->find (a));
for (auto const& a : m_set4)
BEAST_EXPECT(!table->find (a));
}
void testBadConfig ()
{
auto const section = makeSection (m_set1);
auto const id = to_string (amendmentId (m_set2[0]));
testcase ("Bad Config");
{ // Two arguments are required - we pass one
Section test = section;
test.append (id);
try
{
if (makeTable (2, test, emptySection, emptySection))
fail ("Accepted only amendment ID");
}
catch (...)
{
pass();
}
}
{ // Two arguments are required - we pass three
Section test = section;
test.append (id + " Test Name");
try
{
if (makeTable (2, test, emptySection, emptySection))
fail ("Accepted extra arguments");
}
catch (...)
{
pass();
}
}
{
auto sid = id;
sid.resize (sid.length() - 1);
Section test = section;
test.append (sid + " Name");
try
{
if (makeTable (2, test, emptySection, emptySection))
fail ("Accepted short amendment ID");
}
catch (...)
{
pass();
}
}
{
auto sid = id;
sid.resize (sid.length() + 1, '0');
Section test = section;
test.append (sid + " Name");
try
{
if (makeTable (2, test, emptySection, emptySection))
fail ("Accepted long amendment ID");
}
catch (...)
{
pass();
}
}
{
auto sid = id;
sid.resize (sid.length() - 1);
sid.push_back ('Q');
Section test = section;
test.append (sid + " Name");
try
{
if (makeTable (2, test, emptySection, emptySection))
fail ("Accepted non-hex amendment ID");
}
catch (...)
{
pass();
}
}
}
std::map<uint256, bool>
getState (
AmendmentTable *table,
std::set<uint256> const& exclude)
{
std::map<uint256, bool> state;
auto track = [&state,table](std::vector<std::string> const& v)
{
for (auto const& a : v)
{
auto const id = amendmentId(a);
state[id] = table->isEnabled (id);
}
};
track (m_set1);
track (m_set2);
track (m_set3);
track (m_set4);
for (auto const& a : exclude)
state.erase(a);
return state;
}
void testEnableDisable ()
{
testcase ("enable & disable");
auto const testAmendment = amendmentId("TestAmendment");
auto table = makeTable (2);
// Subset of amendments to enable
std::set<uint256> enabled;
enabled.insert (testAmendment);
enabled.insert (amendmentId(m_set1[0]));
enabled.insert (amendmentId(m_set2[0]));
enabled.insert (amendmentId(m_set3[0]));
enabled.insert (amendmentId(m_set4[0]));
// Get the state before, excluding the items we'll change:
auto const pre_state = getState (table.get(), enabled);
// Enable the subset and verify
for (auto const& a : enabled)
table->enable (a);
for (auto const& a : enabled)
BEAST_EXPECT(table->isEnabled (a));
// Disable the subset and verify
for (auto const& a : enabled)
table->disable (a);
for (auto const& a : enabled)
BEAST_EXPECT(!table->isEnabled (a));
// Get the state after, excluding the items we changed:
auto const post_state = getState (table.get(), enabled);
// Ensure the states are identical
auto ret = std::mismatch(
pre_state.begin(), pre_state.end(),
post_state.begin(), post_state.end());
BEAST_EXPECT(ret.first == pre_state.end());
BEAST_EXPECT(ret.second == post_state.end());
}
std::vector <PublicKey> makeValidators (int num)
{
std::vector <PublicKey> ret;
ret.reserve (num);
for (int i = 0; i < num; ++i)
{
ret.push_back (
randomKeyPair(KeyType::secp256k1).first);
}
return ret;
}
static NetClock::time_point weekTime (weeks w)
{
return NetClock::time_point{w};
}
// Execute a pretend consensus round for a flag ledger
void doRound(
AmendmentTable& table,
weeks week,
std::vector <PublicKey> const& validators,
std::vector <std::pair <uint256, int>> const& votes,
std::vector <uint256>& ourVotes,
std::set <uint256>& enabled,
majorityAmendments_t& majority)
{
// Do a round at the specified time
// Returns the amendments we voted for
// Parameters:
// table: Our table of known and vetoed amendments
// validators: The addreses of validators we trust
// votes: Amendments and the number of validators who vote for them
// ourVotes: The amendments we vote for in our validation
// enabled: In/out enabled amendments
// majority: In/our majority amendments (and when they got a majority)
auto const roundTime = weekTime (week);
// Build validations
ValidationSet validations;
validations.reserve (validators.size ());
int i = 0;
for (auto const& val : validators)
{
auto v = std::make_shared <STValidation> (
uint256(), roundTime, val, true);
++i;
STVector256 field (sfAmendments);
for (auto const& amendment : votes)
{
if ((256 * i) < (validators.size() * amendment.second))
{
// We vote yes on this amendment
field.push_back (amendment.first);
}
}
if (!field.empty ())
v->setFieldV256 (sfAmendments, field);
v->setTrusted();
validations [calcNodeID(val)] = v;
}
ourVotes = table.doValidation (enabled);
auto actions = table.doVoting (
roundTime, enabled, majority, validations);
for (auto const& action : actions)
{
// This code assumes other validators do as we do
auto const& hash = action.first;
switch (action.second)
{
case 0:
// amendment goes from majority to enabled
if (enabled.find (hash) != enabled.end ())
Throw<std::runtime_error> ("enabling already enabled");
if (majority.find (hash) == majority.end ())
Throw<std::runtime_error> ("enabling without majority");
enabled.insert (hash);
majority.erase (hash);
break;
case tfGotMajority:
if (majority.find (hash) != majority.end ())
Throw<std::runtime_error> ("got majority while having majority");
majority[hash] = roundTime;
break;
case tfLostMajority:
if (majority.find (hash) == majority.end ())
Throw<std::runtime_error> ("lost majority without majority");
majority.erase (hash);
break;
default:
Throw<std::runtime_error> ("unknown action");
}
}
}
// No vote on unknown amendment
void testNoOnUnknown ()
{
testcase ("Vote NO on unknown");
auto const testAmendment = amendmentId("TestAmendment");
auto const validators = makeValidators (10);
auto table = makeTable (2,
emptySection,
emptySection,
emptySection);
std::vector <std::pair <uint256, int>> votes;
std::vector <uint256> ourVotes;
std::set <uint256> enabled;
majorityAmendments_t majority;
doRound (*table, weeks{1},
validators,
votes,
ourVotes,
enabled,
majority);
BEAST_EXPECT(ourVotes.empty());
BEAST_EXPECT(enabled.empty());
BEAST_EXPECT(majority.empty());
votes.emplace_back (testAmendment, 256);
doRound (*table, weeks{2},
validators,
votes,
ourVotes,
enabled,
majority);
BEAST_EXPECT(ourVotes.empty());
BEAST_EXPECT(enabled.empty());
majority[testAmendment] = weekTime(weeks{1});
// Note that the simulation code assumes others behave as we do,
// so the amendment won't get enabled
doRound (*table, weeks{5},
validators,
votes,
ourVotes,
enabled,
majority);
BEAST_EXPECT(ourVotes.empty());
BEAST_EXPECT(enabled.empty());
}
// No vote on vetoed amendment
void testNoOnVetoed ()
{
testcase ("Vote NO on vetoed");
auto const testAmendment = amendmentId ("vetoedAmendment");
auto table = makeTable (2,
emptySection,
emptySection,
makeSection (testAmendment));
auto const validators = makeValidators (10);
std::vector <std::pair <uint256, int>> votes;
std::vector <uint256> ourVotes;
std::set <uint256> enabled;
majorityAmendments_t majority;
doRound (*table, weeks{1},
validators,
votes,
ourVotes,
enabled,
majority);
BEAST_EXPECT(ourVotes.empty());
BEAST_EXPECT(enabled.empty());
BEAST_EXPECT(majority.empty());
votes.emplace_back (testAmendment, 256);
doRound (*table, weeks{2},
validators,
votes,
ourVotes,
enabled,
majority);
BEAST_EXPECT(ourVotes.empty());
BEAST_EXPECT(enabled.empty());
majority[testAmendment] = weekTime(weeks{1});
doRound (*table, weeks{5},
validators,
votes,
ourVotes,
enabled,
majority);
BEAST_EXPECT(ourVotes.empty());
BEAST_EXPECT(enabled.empty());
}
// Vote on and enable known, not-enabled amendment
void testVoteEnable ()
{
testcase ("voteEnable");
auto table = makeTable (
2,
makeSection (m_set1),
emptySection,
emptySection);
auto const validators = makeValidators (10);
std::vector <std::pair <uint256, int>> votes;
std::vector <uint256> ourVotes;
std::set <uint256> enabled;
majorityAmendments_t majority;
// Week 1: We should vote for all known amendments not enabled
doRound (*table, weeks{1},
validators,
votes,
ourVotes,
enabled,
majority);
BEAST_EXPECT(ourVotes.size() == m_set1.size());
BEAST_EXPECT(enabled.empty());
for (auto const& i : m_set1)
BEAST_EXPECT(majority.find(amendmentId (i)) == majority.end());
// Now, everyone votes for this feature
for (auto const& i : m_set1)
votes.emplace_back (amendmentId(i), 256);
// Week 2: We should recognize a majority
doRound (*table, weeks{2},
validators,
votes,
ourVotes,
enabled,
majority);
BEAST_EXPECT(ourVotes.size() == m_set1.size());
BEAST_EXPECT(enabled.empty());
for (auto const& i : m_set1)
BEAST_EXPECT(majority[amendmentId (i)] == weekTime(weeks{2}));
// Week 5: We should enable the amendment
doRound (*table, weeks{5},
validators,
votes,
ourVotes,
enabled,
majority);
BEAST_EXPECT(enabled.size() == m_set1.size());
// Week 6: We should remove it from our votes and from having a majority
doRound (*table, weeks{6},
validators,
votes,
ourVotes,
enabled,
majority);
BEAST_EXPECT(enabled.size() == m_set1.size());
BEAST_EXPECT(ourVotes.empty());
for (auto const& i : m_set1)
BEAST_EXPECT(majority.find(amendmentId (i)) == majority.end());
}
// Detect majority at 80%, enable later
void testDetectMajority ()
{
testcase ("detectMajority");
auto const testAmendment = amendmentId ("detectMajority");
auto table = makeTable (
2,
makeSection (testAmendment),
emptySection,
emptySection);
auto const validators = makeValidators (16);
std::set <uint256> enabled;
majorityAmendments_t majority;
for (int i = 0; i <= 17; ++i)
{
std::vector <std::pair <uint256, int>> votes;
std::vector <uint256> ourVotes;
if ((i > 0) && (i < 17))
votes.emplace_back (testAmendment, i * 16);
doRound (*table, weeks{i},
validators, votes, ourVotes, enabled, majority);
if (i < 13)
{
// We are voting yes, not enabled, no majority
BEAST_EXPECT(!ourVotes.empty());
BEAST_EXPECT(enabled.empty());
BEAST_EXPECT(majority.empty());
}
else if (i < 15)
{
// We have a majority, not enabled, keep voting
BEAST_EXPECT(!ourVotes.empty());
BEAST_EXPECT(!majority.empty());
BEAST_EXPECT(enabled.empty());
}
else if (i == 15)
{
// enable, keep voting, remove from majority
BEAST_EXPECT(!ourVotes.empty());
BEAST_EXPECT(majority.empty());
BEAST_EXPECT(!enabled.empty());
}
else
{
// Done, we should be enabled and not voting
BEAST_EXPECT(ourVotes.empty());
BEAST_EXPECT(majority.empty());
BEAST_EXPECT(!enabled.empty());
}
}
}
// Detect loss of majority
void testLostMajority ()
{
testcase ("lostMajority");
auto const testAmendment = amendmentId ("lostMajority");
auto const validators = makeValidators (16);
auto table = makeTable (
8,
makeSection (testAmendment),
emptySection,
emptySection);
std::set <uint256> enabled;
majorityAmendments_t majority;
{
// establish majority
std::vector <std::pair <uint256, int>> votes;
std::vector <uint256> ourVotes;
votes.emplace_back (testAmendment, 250);
doRound (*table, weeks{1},
validators, votes, ourVotes, enabled, majority);
BEAST_EXPECT(enabled.empty());
BEAST_EXPECT(!majority.empty());
}
for (int i = 1; i < 16; ++i)
{
std::vector <std::pair <uint256, int>> votes;
std::vector <uint256> ourVotes;
// Gradually reduce support
votes.emplace_back (testAmendment, 256 - i * 8);
doRound (*table, weeks{i + 1},
validators, votes, ourVotes, enabled, majority);
if (i < 8)
{
// We are voting yes, not enabled, majority
BEAST_EXPECT(!ourVotes.empty());
BEAST_EXPECT(enabled.empty());
BEAST_EXPECT(!majority.empty());
}
else
{
// No majority, not enabled, keep voting
BEAST_EXPECT(!ourVotes.empty());
BEAST_EXPECT(majority.empty());
BEAST_EXPECT(enabled.empty());
}
}
}
void
testSupportedAmendments ()
{
for (auto const& amend : detail::supportedAmendments ())
BEAST_EXPECT(amend.substr (0, 64) ==
to_string (feature (amend.substr (65))));
}
void run ()
{
testConstruct();
testGet ();
testBadConfig ();
testEnableDisable ();
testNoOnUnknown ();
testNoOnVetoed ();
testVoteEnable ();
testDetectMajority ();
testLostMajority ();
testSupportedAmendments ();
}
};
BEAST_DEFINE_TESTSUITE (AmendmentTable, app, ripple);
} // ripple

View File

@@ -0,0 +1,162 @@
//------------------------------------------------------------------------------
/*
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/test/jtx.h>
#include <ripple/beast/unit_test.h>
namespace ripple {
namespace test {
class CrossingLimits_test : public beast::unit_test::suite
{
private:
void
n_offers (
jtx::Env& env,
std::size_t n,
jtx::Account const& account,
STAmount const& in,
STAmount const& out)
{
using namespace jtx;
auto const ownerCount = env.le(account)->getFieldU32(sfOwnerCount);
for (std::size_t i = 0; i < n; i++)
env(offer(account, in, out));
env.require (owners (account, ownerCount + n));
}
public:
void
testStepLimit()
{
using namespace jtx;
Env env(*this);
auto const xrpMax = XRP(100000000000);
auto const gw = Account("gateway");
auto const USD = gw["USD"];
env.fund(XRP(100000000), gw, "alice", "bob", "carol", "dan");
env.trust(USD(1), "bob");
env(pay(gw, "bob", USD(1)));
env.trust(USD(1), "dan");
env(pay(gw, "dan", USD(1)));
n_offers (env, 2000, "bob", XRP(1), USD(1));
n_offers (env, 1, "dan", XRP(1), USD(1));
// Alice offers to buy 1000 XRP for 1000 USD. She takes Bob's first
// offer, and removes 999 more as unfunded and hits the step limit.
env(offer("alice", USD(1000), XRP(1000)),
require (
balance("alice", USD(1)), owners("alice", 2),
balance("bob", USD(0)), owners("bob", 1001),
balance("dan", USD(1)), owners("dan", 2)));
// Carol offers to buy 1000 XRP for 1000 USD. She removes Bob's next
// 1000 offers as unfunded and hits the step limit.
env(offer("carol", USD(1000), XRP(1000)),
require (
balance("carol", USD(none)), owners("carol", 1),
balance("bob", USD(0)), owners("bob", 1),
balance("dan", USD(1)), owners("dan", 2)));
}
void
testCrossingLimit()
{
using namespace jtx;
Env env(*this);
auto const xrpMax = XRP(100000000000);
auto const gw = Account("gateway");
auto const USD = gw["USD"];
env.fund(XRP(100000000), gw, "alice", "bob", "carol");
env.trust(USD(1000), "bob");
env(pay(gw, "bob", USD(1000)));
n_offers (env, 1000, "bob", XRP(1), USD(1));
// Alice offers to buy 1000 XRP for 1000 USD. She takes the first
// 850 offers, hitting the crossing limit.
env(offer("alice", USD(1000), XRP(1000)),
require (
balance("alice", USD(850)),
balance("bob", USD(150)), owners ("bob", 151)));
// Carol offers to buy 1000 XRP for 1000 USD. She takes the remaining
// 150 offers without hitting a limit.
env(offer("carol", USD(1000), XRP(1000)),
require (
balance("carol", USD(150)),
balance("bob", USD(0)), owners ("bob", 1)));
}
void
testStepAndCrossingLimit()
{
using namespace jtx;
Env env(*this);
auto const xrpMax = XRP(100000000000);
auto const gw = Account("gateway");
auto const USD = gw["USD"];
env.fund(XRP(100000000), gw, "alice", "bob", "carol", "dan", "evita");
env.trust(USD(1000), "alice");
env(pay(gw, "alice", USD(1000)));
env.trust(USD(1000), "carol");
env(pay(gw, "carol", USD(1)));
env.trust(USD(1000), "evita");
env(pay(gw, "evita", USD(1000)));
n_offers (env, 400, "alice", XRP(1), USD(1));
n_offers (env, 700, "carol", XRP(1), USD(1));
n_offers (env, 999, "evita", XRP(1), USD(1));
// Bob offers to buy 1000 XRP for 1000 USD. He takes all 400 USD from
// Alice's offers, 1 USD from Carol's and then removes 599 of Carol's
// offers as unfunded, before hitting the step limit.
env(offer("bob", USD(1000), XRP(1000)),
require (
balance("bob", USD(401)),
balance("alice", USD(600)), owners("alice", 1),
balance("carol", USD(0)), owners("carol", 101),
balance("evita", USD(1000)), owners("evita", 1000)));
// Dan offers to buy 900 XRP for 900 USD. He removes all 100 of Carol's
// offers as unfunded, then takes 850 USD from Evita's, hitting the
// crossing limit.
env(offer("dan", USD(900), XRP(900)),
require (
balance("dan", USD(850)),
balance("alice", USD(600)), owners("alice", 1),
balance("carol", USD(0)), owners("carol", 1),
balance("evita", USD(150)), owners("evita", 150)));
}
void
run()
{
testStepLimit();
testCrossingLimit();
testStepAndCrossingLimit();
}
};
BEAST_DEFINE_TESTSUITE_MANUAL(CrossingLimits,tx,ripple);
} // test
} // ripple

View File

@@ -0,0 +1,120 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012-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/test/jtx.h>
#include <ripple/beast/unit_test.h>
namespace ripple {
namespace test {
class DeliverMin_test : public beast::unit_test::suite
{
public:
void
test_convert_all_of_an_asset()
{
testcase("Convert all of an asset using DeliverMin");
using namespace jtx;
auto const gw = Account("gateway");
auto const USD = gw["USD"];
{
Env env(*this);
env.fund(XRP(10000), "alice", "bob", "carol", gw);
env.trust(USD(100), "alice", "bob", "carol");
env(pay("alice", "bob", USD(10)), delivermin(USD(10)), ter(temBAD_AMOUNT));
env(pay("alice", "bob", USD(10)), delivermin(USD(-5)),
txflags(tfPartialPayment), ter(temBAD_AMOUNT));
env(pay("alice", "bob", USD(10)), delivermin(XRP(5)),
txflags(tfPartialPayment), ter(temBAD_AMOUNT));
env(pay("alice", "bob", USD(10)),
delivermin(Account("carol")["USD"](5)),
txflags(tfPartialPayment), ter(temBAD_AMOUNT));
env(pay("alice", "bob", USD(10)), delivermin(USD(15)),
txflags(tfPartialPayment), ter(temBAD_AMOUNT));
env(pay(gw, "carol", USD(50)));
env(offer("carol", XRP(5), USD(5)));
env(pay("alice", "bob", USD(10)), paths(XRP),
delivermin(USD(7)), txflags(tfPartialPayment),
sendmax(XRP(5)), ter(tecPATH_PARTIAL));
env.require(balance("alice", XRP(9999.99999)));
env.require(balance("bob", XRP(10000)));
}
{
Env env(*this);
env.fund(XRP(10000), "alice", "bob", gw);
env.trust(USD(1000), "alice", "bob");
env(pay(gw, "bob", USD(100)));
env(offer("bob", XRP(100), USD(100)));
env(pay("alice", "alice", USD(10000)), paths(XRP),
delivermin(USD(100)), txflags(tfPartialPayment),
sendmax(XRP(100)));
env.require(balance("alice", USD(100)));
}
{
Env env(*this);
env.fund(XRP(10000), "alice", "bob", "carol", gw);
env.trust(USD(1000), "bob", "carol");
env(pay(gw, "bob", USD(200)));
env(offer("bob", XRP(100), USD(100)));
env(offer("bob", XRP(1000), USD(100)));
env(offer("bob", XRP(10000), USD(100)));
env(pay("alice", "carol", USD(10000)), paths(XRP),
delivermin(USD(200)), txflags(tfPartialPayment),
sendmax(XRP(1000)), ter(tecPATH_PARTIAL));
env(pay("alice", "carol", USD(10000)), paths(XRP),
delivermin(USD(200)), txflags(tfPartialPayment),
sendmax(XRP(1100)));
env.require(balance("bob", USD(0)));
env.require(balance("carol", USD(200)));
}
{
Env env(*this);
env.fund(XRP(10000), "alice", "bob", "carol", "dan", gw);
env.trust(USD(1000), "bob", "carol", "dan");
env(pay(gw, "bob", USD(100)));
env(pay(gw, "dan", USD(100)));
env(offer("bob", XRP(100), USD(100)));
env(offer("bob", XRP(1000), USD(100)));
env(offer("dan", XRP(100), USD(100)));
env(pay("alice", "carol", USD(10000)), paths(XRP),
delivermin(USD(200)), txflags(tfPartialPayment),
sendmax(XRP(200)));
env.require(balance("bob", USD(0)));
env.require(balance("carol", USD(200)));
env.require(balance("dan", USD(0)));
}
}
void
run()
{
test_convert_all_of_an_asset();
}
};
BEAST_DEFINE_TESTSUITE(DeliverMin,app,ripple);
} // test
} // ripple

1327
src/test/app/Flow_test.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,248 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012-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/app/misc/HashRouter.h>
#include <ripple/basics/chrono.h>
#include <ripple/beast/unit_test.h>
namespace ripple {
namespace test {
class HashRouter_test : public beast::unit_test::suite
{
void
testNonExpiration()
{
using namespace std::chrono_literals;
TestStopwatch stopwatch;
HashRouter router(stopwatch, 2s);
uint256 const key1(1);
uint256 const key2(2);
uint256 const key3(3);
// t=0
router.setFlags(key1, 11111);
BEAST_EXPECT(router.getFlags(key1) == 11111);
router.setFlags(key2, 22222);
BEAST_EXPECT(router.getFlags(key2) == 22222);
// key1 : 0
// key2 : 0
// key3: null
++stopwatch;
// Because we are accessing key1 here, it
// will NOT be expired for another two ticks
BEAST_EXPECT(router.getFlags(key1) == 11111);
// key1 : 1
// key2 : 0
// key3 null
++stopwatch;
// t=3
router.setFlags(key3,33333); // force expiration
BEAST_EXPECT(router.getFlags(key1) == 11111);
BEAST_EXPECT(router.getFlags(key2) == 0);
}
void
testExpiration()
{
using namespace std::chrono_literals;
TestStopwatch stopwatch;
HashRouter router(stopwatch, 2s);
uint256 const key1(1);
uint256 const key2(2);
uint256 const key3(3);
uint256 const key4(4);
BEAST_EXPECT(key1 != key2 &&
key2 != key3 &&
key3 != key4);
// t=0
router.setFlags(key1, 12345);
BEAST_EXPECT(router.getFlags(key1) == 12345);
// key1 : 0
// key2 : null
// key3 : null
++stopwatch;
// Expiration is triggered by insertion,
// and timestamps are updated on access,
// so key1 will be expired after the second
// call to setFlags.
// t=1
router.setFlags(key2, 9999);
BEAST_EXPECT(router.getFlags(key1) == 12345);
BEAST_EXPECT(router.getFlags(key2) == 9999);
// key1 : 1
// key2 : 1
// key3 : null
++stopwatch;
// t=2
BEAST_EXPECT(router.getFlags(key2) == 9999);
// key1 : 1
// key2 : 2
// key3 : null
++stopwatch;
// t=3
router.setFlags(key3, 2222);
BEAST_EXPECT(router.getFlags(key1) == 0);
BEAST_EXPECT(router.getFlags(key2) == 9999);
BEAST_EXPECT(router.getFlags(key3) == 2222);
// key1 : 3
// key2 : 3
// key3 : 3
++stopwatch;
// t=4
// No insertion, no expiration
router.setFlags(key1, 7654);
BEAST_EXPECT(router.getFlags(key1) == 7654);
BEAST_EXPECT(router.getFlags(key2) == 9999);
BEAST_EXPECT(router.getFlags(key3) == 2222);
// key1 : 4
// key2 : 4
// key3 : 4
++stopwatch;
++stopwatch;
// t=6
router.setFlags(key4, 7890);
BEAST_EXPECT(router.getFlags(key1) == 0);
BEAST_EXPECT(router.getFlags(key2) == 0);
BEAST_EXPECT(router.getFlags(key3) == 0);
BEAST_EXPECT(router.getFlags(key4) == 7890);
// key1 : 6
// key2 : 6
// key3 : 6
// key4 : 6
}
void testSuppression()
{
// Normal HashRouter
using namespace std::chrono_literals;
TestStopwatch stopwatch;
HashRouter router(stopwatch, 2s);
uint256 const key1(1);
uint256 const key2(2);
uint256 const key3(3);
uint256 const key4(4);
BEAST_EXPECT(key1 != key2 &&
key2 != key3 &&
key3 != key4);
int flags = 12345; // This value is ignored
router.addSuppression(key1);
BEAST_EXPECT(router.addSuppressionPeer(key2, 15));
BEAST_EXPECT(router.addSuppressionPeer(key3, 20, flags));
BEAST_EXPECT(flags == 0);
++stopwatch;
BEAST_EXPECT(!router.addSuppressionPeer(key1, 2));
BEAST_EXPECT(!router.addSuppressionPeer(key2, 3));
BEAST_EXPECT(!router.addSuppressionPeer(key3, 4, flags));
BEAST_EXPECT(flags == 0);
BEAST_EXPECT(router.addSuppressionPeer(key4, 5));
}
void
testSetFlags()
{
using namespace std::chrono_literals;
TestStopwatch stopwatch;
HashRouter router(stopwatch, 2s);
uint256 const key1(1);
BEAST_EXPECT(router.setFlags(key1, 10));
BEAST_EXPECT(!router.setFlags(key1, 10));
BEAST_EXPECT(router.setFlags(key1, 20));
}
void
testRelay()
{
using namespace std::chrono_literals;
TestStopwatch stopwatch;
HashRouter router(stopwatch, 1s);
uint256 const key1(1);
boost::optional<std::set<HashRouter::PeerShortID>> peers;
peers = router.shouldRelay(key1);
BEAST_EXPECT(peers && peers->empty());
router.addSuppressionPeer(key1, 1);
router.addSuppressionPeer(key1, 3);
router.addSuppressionPeer(key1, 5);
// No action, because relayed
BEAST_EXPECT(!router.shouldRelay(key1));
// Expire, but since the next search will
// be for this entry, it will get refreshed
// instead. However, the relay won't.
++stopwatch;
// Get those peers we added earlier
peers = router.shouldRelay(key1);
BEAST_EXPECT(peers && peers->size() == 3);
router.addSuppressionPeer(key1, 2);
router.addSuppressionPeer(key1, 4);
// No action, because relayed
BEAST_EXPECT(!router.shouldRelay(key1));
// Expire, but since the next search will
// be for this entry, it will get refreshed
// instead. However, the relay won't.
++stopwatch;
// Relay again
peers = router.shouldRelay(key1);
BEAST_EXPECT(peers && peers->size() == 2);
// Expire again
++stopwatch;
// Confirm that peers list is empty.
peers = router.shouldRelay(key1);
BEAST_EXPECT(peers && peers->size() == 0);
}
public:
void
run()
{
testNonExpiration();
testExpiration();
testSuppression();
testSetFlags();
testRelay();
}
};
BEAST_DEFINE_TESTSUITE(HashRouter, app, ripple);
}
}

View File

@@ -0,0 +1,60 @@
//------------------------------------------------------------------------------
/*
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/core/Config.h>
#include <ripple/beast/unit_test.h>
#include <ripple/ledger/ReadView.h>
namespace ripple {
class LoadFeeTrack_test : public beast::unit_test::suite
{
public:
void run ()
{
Config d; // get a default configuration object
LoadFeeTrack l;
Fees const fees = [&]()
{
Fees f;
f.base = d.FEE_DEFAULT;
f.units = d.TRANSACTION_FEE_BASE;
f.reserve = 200 * SYSTEM_CURRENCY_PARTS;
f.increment = 50 * SYSTEM_CURRENCY_PARTS;
return f;
}();
BEAST_EXPECT (scaleFeeBase (10000, fees) == 10000);
BEAST_EXPECT (scaleFeeLoad (10000, l, fees, false) == 10000);
BEAST_EXPECT (scaleFeeBase (1, fees) == 1);
BEAST_EXPECT (scaleFeeLoad (1, l, fees, false) == 1);
// Check new default fee values give same fees as old defaults
BEAST_EXPECT (scaleFeeBase (d.FEE_DEFAULT, fees) == 10);
BEAST_EXPECT (scaleFeeBase (d.FEE_ACCOUNT_RESERVE, fees) == 200 * SYSTEM_CURRENCY_PARTS);
BEAST_EXPECT (scaleFeeBase (d.FEE_OWNER_RESERVE, fees) == 50 * SYSTEM_CURRENCY_PARTS);
BEAST_EXPECT (scaleFeeBase (d.FEE_OFFER, fees) == 10);
}
};
BEAST_DEFINE_TESTSUITE(LoadFeeTrack,ripple_core,ripple);
} // ripple

View File

@@ -0,0 +1,846 @@
//------------------------------------------------------------------------------
/*
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> // jss:: definitions
#include <ripple/protocol/Feature.h>
#include <ripple/test/jtx.h>
namespace ripple {
namespace test {
class MultiSign_test : public beast::unit_test::suite
{
// Unfunded accounts to use for phantom signing.
jtx::Account const bogie {"bogie", KeyType::secp256k1};
jtx::Account const demon {"demon", KeyType::ed25519};
jtx::Account const ghost {"ghost", KeyType::secp256k1};
jtx::Account const haunt {"haunt", KeyType::ed25519};
jtx::Account const jinni {"jinni", KeyType::secp256k1};
jtx::Account const phase {"phase", KeyType::ed25519};
jtx::Account const shade {"shade", KeyType::secp256k1};
jtx::Account const spook {"spook", KeyType::ed25519};
public:
void test_noReserve()
{
using namespace jtx;
Env env(*this, features(featureMultiSign));
Account const alice {"alice", KeyType::secp256k1};
// Pay alice enough to meet the initial reserve, but not enough to
// meet the reserve for a SignerListSet.
env.fund(XRP(200), alice);
env.close();
env.require (owners (alice, 0));
{
// Attach a signer list to alice. Should fail.
Json::Value smallSigners = signers(alice, 1, { { bogie, 1 } });
env(smallSigners, ter(tecINSUFFICIENT_RESERVE));
env.close();
env.require (owners (alice, 0));
// Fund alice enough to set the signer list, then attach signers.
env(pay(env.master, alice, XRP(151)));
env.close();
env(smallSigners);
env.close();
env.require (owners (alice, 3));
}
{
// Replace with the biggest possible signer list. Should fail.
Json::Value bigSigners = signers(alice, 1, {
{ bogie, 1 }, { demon, 1 }, { ghost, 1 }, { haunt, 1 },
{ jinni, 1 }, { phase, 1 }, { shade, 1 }, { spook, 1 }});
env(bigSigners, ter(tecINSUFFICIENT_RESERVE));
env.close();
env.require (owners (alice, 3));
// Fund alice and succeed.
env(pay(env.master, alice, XRP(350)));
env.close();
env(bigSigners);
env.close();
env.require (owners (alice, 10));
}
// Remove alice's signer list and get the owner count back.
env(signers(alice, jtx::none));
env.close();
env.require (owners (alice, 0));
}
void test_signerListSet()
{
using namespace jtx;
Env env(*this, features(featureMultiSign));
Account const alice {"alice", KeyType::ed25519};
env.fund(XRP(1000), alice);
// Add alice as a multisigner for herself. Should fail.
env(signers(alice, 1, { { alice, 1} }), ter (temBAD_SIGNER));
// Add a signer with a weight of zero. Should fail.
env(signers(alice, 1, { { bogie, 0} }), ter (temBAD_WEIGHT));
// Add a signer where the weight is too big. Should fail since
// the weight field is only 16 bits. The jtx framework can't do
// this kind of test, so it's commented out.
// env(signers(alice, 1, { { bogie, 0x10000} }), ter (temBAD_WEIGHT));
// Add the same signer twice. Should fail.
env(signers(alice, 1, {
{ bogie, 1 }, { demon, 1 }, { ghost, 1 }, { haunt, 1 },
{ jinni, 1 }, { phase, 1 }, { demon, 1 }, { spook, 1 }}),
ter(temBAD_SIGNER));
// Set a quorum of zero. Should fail.
env(signers(alice, 0, { { bogie, 1} }), ter (temMALFORMED));
// Make a signer list where the quorum can't be met. Should fail.
env(signers(alice, 9, {
{ bogie, 1 }, { demon, 1 }, { ghost, 1 }, { haunt, 1 },
{ jinni, 1 }, { phase, 1 }, { shade, 1 }, { spook, 1 }}),
ter(temBAD_QUORUM));
// Make a signer list that's too big. Should fail.
Account const spare ("spare", KeyType::secp256k1);
env(signers(alice, 1, {
{ bogie, 1 }, { demon, 1 }, { ghost, 1 }, { haunt, 1 },
{ jinni, 1 }, { phase, 1 }, { shade, 1 }, { spook, 1 },
{ spare, 1 }}),
ter(temMALFORMED));
env.close();
env.require (owners (alice, 0));
}
void test_phantomSigners()
{
using namespace jtx;
Env env(*this, features(featureMultiSign));
Account const alice {"alice", KeyType::ed25519};
env.fund(XRP(1000), alice);
env.close();
// Attach phantom signers to alice and use them for a transaction.
env(signers(alice, 1, {{bogie, 1}, {demon, 1}}));
env.close();
env.require (owners (alice, 4));
// This should work.
auto const baseFee = env.current()->fees().base;
std::uint32_t aliceSeq = env.seq (alice);
env(noop(alice), msig(bogie, demon), fee(3 * baseFee));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
// Either signer alone should work.
aliceSeq = env.seq (alice);
env(noop(alice), msig(bogie), fee(2 * baseFee));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
aliceSeq = env.seq (alice);
env(noop(alice), msig(demon), fee(2 * baseFee));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
// Duplicate signers should fail.
aliceSeq = env.seq (alice);
env(noop(alice), msig(demon, demon), fee(3 * baseFee), ter(temINVALID));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq);
// A non-signer should fail.
aliceSeq = env.seq (alice);
env(noop(alice),
msig(bogie, spook), fee(3 * baseFee), ter(tefBAD_SIGNATURE));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq);
// Don't meet the quorum. Should fail.
env(signers(alice, 2, {{bogie, 1}, {demon, 1}}));
aliceSeq = env.seq (alice);
env(noop(alice), msig(bogie), fee(2 * baseFee), ter(tefBAD_QUORUM));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq);
// Meet the quorum. Should succeed.
aliceSeq = env.seq (alice);
env(noop(alice), msig(bogie, demon), fee(3 * baseFee));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
}
void
test_enablement()
{
using namespace jtx;
Env env(*this);
Account const alice {"alice", KeyType::ed25519};
env.fund(XRP(1000), alice);
env.close();
// NOTE: These six tests will fail when multisign is default enabled.
env(signers(alice, 1, {{bogie, 1}}), ter(temDISABLED));
env.close();
env.require (owners (alice, 0));
std::uint32_t aliceSeq = env.seq (alice);
auto const baseFee = env.current()->fees().base;
env(noop(alice), msig(bogie), fee(2 * baseFee), ter(temINVALID));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq);
env(signers(alice, 1, {{bogie, 1}, {demon,1}}), ter(temDISABLED));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq);
}
void test_fee ()
{
using namespace jtx;
Env env(*this, features(featureMultiSign));
Account const alice {"alice", KeyType::ed25519};
env.fund(XRP(1000), alice);
env.close();
// Attach maximum possible number of signers to alice.
env(signers(alice, 1, {{bogie, 1}, {demon, 1}, {ghost, 1}, {haunt, 1},
{jinni, 1}, {phase, 1}, {shade, 1}, {spook, 1}}));
env.close();
env.require (owners (alice, 10));
// This should work.
auto const baseFee = env.current()->fees().base;
std::uint32_t aliceSeq = env.seq (alice);
env(noop(alice), msig(bogie), fee(2 * baseFee));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
// This should fail because the fee is too small.
aliceSeq = env.seq (alice);
env(noop(alice),
msig(bogie), fee((2 * baseFee) - 1), ter(telINSUF_FEE_P));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq);
// This should work.
aliceSeq = env.seq (alice);
env(noop(alice),
msig(bogie, demon, ghost, haunt, jinni, phase, shade, spook),
fee(9 * baseFee));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
// This should fail because the fee is too small.
aliceSeq = env.seq (alice);
env(noop(alice),
msig(bogie, demon, ghost, haunt, jinni, phase, shade, spook),
fee((9 * baseFee) - 1),
ter(telINSUF_FEE_P));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq);
}
void test_misorderedSigners()
{
using namespace jtx;
Env env(*this, features(featureMultiSign));
Account const alice {"alice", KeyType::ed25519};
env.fund(XRP(1000), alice);
env.close();
// The signatures in a transaction must be submitted in sorted order.
// Make sure the transaction fails if they are not.
env(signers(alice, 1, {{bogie, 1}, {demon, 1}}));
env.close();
env.require (owners(alice, 4));
msig phantoms {bogie, demon};
std::reverse (phantoms.signers.begin(), phantoms.signers.end());
std::uint32_t const aliceSeq = env.seq (alice);
env(noop(alice), phantoms, ter(temINVALID));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq);
}
void test_masterSigners()
{
using namespace jtx;
Env env(*this, features(featureMultiSign));
Account const alice {"alice", KeyType::ed25519};
Account const becky {"becky", KeyType::secp256k1};
Account const cheri {"cheri", KeyType::ed25519};
env.fund(XRP(1000), alice, becky, cheri);
env.close();
// For a different situation, give alice a regular key but don't use it.
Account const alie {"alie", KeyType::secp256k1};
env(regkey (alice, alie));
env.close();
std::uint32_t aliceSeq = env.seq (alice);
env(noop(alice), sig(alice));
env(noop(alice), sig(alie));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq + 2);
//Attach signers to alice
env(signers(alice, 4, {{becky, 3}, {cheri, 4}}), sig (alice));
env.close();
env.require (owners (alice, 4));
// Attempt a multisigned transaction that meets the quorum.
auto const baseFee = env.current()->fees().base;
aliceSeq = env.seq (alice);
env(noop(alice), msig(cheri), fee(2 * baseFee));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
// If we don't meet the quorum the transaction should fail.
aliceSeq = env.seq (alice);
env(noop(alice), msig(becky), fee(2 * baseFee), ter(tefBAD_QUORUM));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq);
// Give becky and cheri regular keys.
Account const beck {"beck", KeyType::ed25519};
env(regkey (becky, beck));
Account const cher {"cher", KeyType::ed25519};
env(regkey (cheri, cher));
env.close();
// becky's and cheri's master keys should still work.
aliceSeq = env.seq (alice);
env(noop(alice), msig(becky, cheri), fee(3 * baseFee));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
}
void test_regularSigners()
{
using namespace jtx;
Env env(*this, features(featureMultiSign));
Account const alice {"alice", KeyType::secp256k1};
Account const becky {"becky", KeyType::ed25519};
Account const cheri {"cheri", KeyType::secp256k1};
env.fund(XRP(1000), alice, becky, cheri);
env.close();
// Attach signers to alice.
env(signers(alice, 1, {{becky, 1}, {cheri, 1}}), sig (alice));
// Give everyone regular keys.
Account const alie {"alie", KeyType::ed25519};
env(regkey (alice, alie));
Account const beck {"beck", KeyType::secp256k1};
env(regkey (becky, beck));
Account const cher {"cher", KeyType::ed25519};
env(regkey (cheri, cher));
env.close();
// Disable cheri's master key to mix things up.
env(fset (cheri, asfDisableMaster), sig(cheri));
env.close();
// Attempt a multisigned transaction that meets the quorum.
auto const baseFee = env.current()->fees().base;
std::uint32_t aliceSeq = env.seq (alice);
env(noop(alice), msig(msig::Reg{cheri, cher}), fee(2 * baseFee));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
// cheri should not be able to multisign using her master key.
aliceSeq = env.seq (alice);
env(noop(alice), msig(cheri), fee(2 * baseFee), ter(tefMASTER_DISABLED));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq);
// becky should be able to multisign using either of her keys.
aliceSeq = env.seq (alice);
env(noop(alice), msig(becky), fee(2 * baseFee));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
aliceSeq = env.seq (alice);
env(noop(alice), msig(msig::Reg{becky, beck}), fee(2 * baseFee));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
// Both becky and cheri should be able to sign using regular keys.
aliceSeq = env.seq (alice);
env(noop(alice), fee(3 * baseFee),
msig(msig::Reg{becky, beck}, msig::Reg{cheri, cher}));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
}
void test_heterogeneousSigners()
{
using namespace jtx;
Env env(*this, features(featureMultiSign));
Account const alice {"alice", KeyType::secp256k1};
Account const becky {"becky", KeyType::ed25519};
Account const cheri {"cheri", KeyType::secp256k1};
Account const daria {"daria", KeyType::ed25519};
env.fund(XRP(1000), alice, becky, cheri, daria);
env.close();
// alice uses a regular key with the master disabled.
Account const alie {"alie", KeyType::secp256k1};
env(regkey (alice, alie));
env(fset (alice, asfDisableMaster), sig(alice));
// becky is master only without a regular key.
// cheri has a regular key, but leaves the master key enabled.
Account const cher {"cher", KeyType::secp256k1};
env(regkey (cheri, cher));
// daria has a regular key and disables her master key.
Account const dari {"dari", KeyType::ed25519};
env(regkey (daria, dari));
env(fset (daria, asfDisableMaster), sig(daria));
env.close();
// Attach signers to alice.
env(signers(alice, 1,
{{becky, 1}, {cheri, 1}, {daria, 1}, {jinni, 1}}), sig (alie));
env.close();
env.require (owners (alice, 6));
// Each type of signer should succeed individually.
auto const baseFee = env.current()->fees().base;
std::uint32_t aliceSeq = env.seq (alice);
env(noop(alice), msig(becky), fee(2 * baseFee));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
aliceSeq = env.seq (alice);
env(noop(alice), msig(cheri), fee(2 * baseFee));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
aliceSeq = env.seq (alice);
env(noop(alice), msig(msig::Reg{cheri, cher}), fee(2 * baseFee));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
aliceSeq = env.seq (alice);
env(noop(alice), msig(msig::Reg{daria, dari}), fee(2 * baseFee));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
aliceSeq = env.seq (alice);
env(noop(alice), msig(jinni), fee(2 * baseFee));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
// Should also work if all signers sign.
aliceSeq = env.seq (alice);
env(noop(alice), fee(5 * baseFee),
msig(becky, msig::Reg{cheri, cher}, msig::Reg{daria, dari}, jinni));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
// Require all signers to sign.
env(signers(alice, 0x3FFFC, {{becky, 0xFFFF},
{cheri, 0xFFFF}, {daria, 0xFFFF}, {jinni, 0xFFFF}}), sig (alie));
env.close();
env.require (owners (alice, 6));
aliceSeq = env.seq (alice);
env(noop(alice), fee(9 * baseFee),
msig(becky, msig::Reg{cheri, cher}, msig::Reg{daria, dari}, jinni));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
// Try cheri with both key types.
aliceSeq = env.seq (alice);
env(noop(alice), fee(5 * baseFee),
msig(becky, cheri, msig::Reg{daria, dari}, jinni));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
// Makes sure the maximum allowed number of signers works.
env(signers(alice, 0x7FFF8, {{becky, 0xFFFF}, {cheri, 0xFFFF},
{daria, 0xFFFF}, {haunt, 0xFFFF}, {jinni, 0xFFFF},
{phase, 0xFFFF}, {shade, 0xFFFF}, {spook, 0xFFFF}}), sig (alie));
env.close();
env.require (owners (alice, 10));
aliceSeq = env.seq (alice);
env(noop(alice), fee(9 * baseFee), msig(becky, msig::Reg{cheri, cher},
msig::Reg{daria, dari}, haunt, jinni, phase, shade, spook));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
// One signer short should fail.
aliceSeq = env.seq (alice);
env(noop(alice), msig(becky, cheri, haunt, jinni, phase, shade, spook),
fee(8 * baseFee), ter (tefBAD_QUORUM));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq);
// Remove alice's signer list and get the owner count back.
env(signers(alice, jtx::none), sig(alie));
env.close();
env.require (owners (alice, 0));
}
// We want to always leave an account signable. Make sure the that we
// disallow removing the last way a transaction may be signed.
void test_keyDisable()
{
using namespace jtx;
Env env(*this, features(featureMultiSign));
Account const alice {"alice", KeyType::ed25519};
env.fund(XRP(1000), alice);
// There are three negative tests we need to make:
// M0. A lone master key cannot be disabled.
// R0. A lone regular key cannot be removed.
// L0. A lone signer list cannot be removed.
//
// Additionally, there are 6 positive tests we need to make:
// M1. The master key can be disabled if there's a regular key.
// M2. The master key can be disabled if there's a signer list.
//
// R1. The regular key can be removed if there's a signer list.
// R2. The regular key can be removed if the master key is enabled.
//
// L1. The signer list can be removed if the master key is enabled.
// L2. The signer list can be removed if there's a regular key.
// Master key tests.
// M0: A lone master key cannot be disabled.
env(fset (alice, asfDisableMaster),
sig(alice), ter(tecNO_ALTERNATIVE_KEY));
// Add a regular key.
Account const alie {"alie", KeyType::ed25519};
env(regkey (alice, alie));
// M1: The master key can be disabled if there's a regular key.
env(fset (alice, asfDisableMaster), sig(alice));
// R0: A lone regular key cannot be removed.
env(regkey (alice, disabled), sig(alie), ter(tecNO_ALTERNATIVE_KEY));
// Add a signer list.
env(signers(alice, 1, {{bogie, 1}}), sig (alie));
// R1: The regular key can be removed if there's a signer list.
env(regkey (alice, disabled), sig(alie));
// L0; A lone signer list cannot be removed.
auto const baseFee = env.current()->fees().base;
env(signers(alice, jtx::none), msig(bogie),
fee(2 * baseFee), ter(tecNO_ALTERNATIVE_KEY));
// Enable the master key.
env(fclear (alice, asfDisableMaster), msig(bogie), fee(2 * baseFee));
// L1: The signer list can be removed if the master key is enabled.
env(signers(alice, jtx::none), msig(bogie), fee(2 * baseFee));
// Add a signer list.
env(signers(alice, 1, {{bogie, 1}}), sig (alice));
// M2: The master key can be disabled if there's a signer list.
env(fset (alice, asfDisableMaster), sig(alice));
// Add a regular key.
env(regkey (alice, alie), msig(bogie), fee(2 * baseFee));
// L2: The signer list can be removed if there's a regular key.
env(signers(alice, jtx::none), sig(alie));
// Enable the master key.
env(fclear (alice, asfDisableMaster), sig(alie));
// R2: The regular key can be removed if the master key is enabled.
env(regkey (alice, disabled), sig(alie));
}
// Verify that the first regular key can be made for free using the
// master key, but not when multisigning.
void test_regKey()
{
using namespace jtx;
Env env(*this, features(featureMultiSign));
Account const alice {"alice", KeyType::secp256k1};
env.fund(XRP(1000), alice);
// Give alice a regular key with a zero fee. Should succeed. Once.
Account const alie {"alie", KeyType::ed25519};
env(regkey (alice, alie), sig (alice), fee (0));
// Try it again and creating the regular key for free should fail.
Account const liss {"liss", KeyType::secp256k1};
env(regkey (alice, liss), sig (alice), fee (0), ter(telINSUF_FEE_P));
// But paying to create a regular key should succeed.
env(regkey (alice, liss), sig (alice));
// In contrast, trying to multisign for a regular key with a zero
// fee should always fail. Even the first time.
Account const becky {"becky", KeyType::ed25519};
env.fund(XRP(1000), becky);
env(signers(becky, 1, {{alice, 1}}), sig (becky));
env(regkey (becky, alie), msig (alice), fee (0), ter(telINSUF_FEE_P));
// Using the master key to sign for a regular key for free should
// still work.
env(regkey (becky, alie), sig (becky), fee (0));
}
// See if every kind of transaction can be successfully multi-signed.
void test_txTypes()
{
using namespace jtx;
Env env(*this, features(featureMultiSign));
Account const alice {"alice", KeyType::secp256k1};
Account const becky {"becky", KeyType::ed25519};
Account const zelda {"zelda", KeyType::secp256k1};
Account const gw {"gw"};
auto const USD = gw["USD"];
env.fund(XRP(1000), alice, becky, zelda, gw);
env.close();
// alice uses a regular key with the master disabled.
Account const alie {"alie", KeyType::secp256k1};
env(regkey (alice, alie));
env(fset (alice, asfDisableMaster), sig(alice));
// Attach signers to alice.
env(signers(alice, 2, {{becky, 1}, {bogie, 1}}), sig (alie));
env.close();
env.require (owners (alice, 4));
// Multisign a ttPAYMENT.
auto const baseFee = env.current()->fees().base;
std::uint32_t aliceSeq = env.seq (alice);
env(pay(alice, env.master, XRP(1)),
msig(becky, bogie), fee(3 * baseFee));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
// Multisign a ttACCOUNT_SET.
aliceSeq = env.seq (alice);
env(noop(alice), msig(becky, bogie), fee(3 * baseFee));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
// Multisign a ttREGULAR_KEY_SET.
aliceSeq = env.seq (alice);
Account const ace {"ace", KeyType::secp256k1};
env(regkey (alice, ace), msig(becky, bogie), fee(3 * baseFee));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
// Multisign a ttTRUST_SET
env(trust("alice", USD(100)),
msig(becky, bogie), fee(3 * baseFee), require (lines("alice", 1)));
env.close();
env.require (owners (alice, 5));
// Multisign a ttOFFER_CREATE transaction.
env(pay(gw, alice, USD(50)));
env.close();
env.require(balance(alice, USD(50)));
env.require(balance(gw, alice["USD"](-50)));
std::uint32_t const offerSeq = env.seq (alice);
env(offer(alice, XRP(50), USD(50)),
msig (becky, bogie), fee(3 * baseFee));
env.close();
env.require(owners(alice, 6));
// Now multisign a ttOFFER_CANCEL canceling the offer we just created.
{
aliceSeq = env.seq (alice);
Json::Value cancelOffer;
cancelOffer[jss::Account] = alice.human();
cancelOffer[jss::OfferSequence] = offerSeq;
cancelOffer[jss::TransactionType] = "OfferCancel";
env (cancelOffer, seq (aliceSeq),
msig (becky, bogie), fee(3 * baseFee));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
env.require(owners(alice, 5));
}
// Multisign a ttSIGNER_LIST_SET.
env(signers(alice, 3, {{becky, 1}, {bogie, 1}, {demon, 1}}),
msig (becky, bogie), fee(3 * baseFee));
env.close();
env.require (owners (alice, 6));
}
void test_badSignatureText()
{
// Verify that the text returned for signature failures is correct.
using namespace jtx;
Env env(*this, features(featureMultiSign));
// lambda that submits an STTx and returns the resulting JSON.
auto submitSTTx = [&env] (STTx const& stx)
{
Json::Value jvResult;
jvResult[jss::tx_blob] = strHex (stx.getSerializer().slice());
return env.rpc ("json", "submit", jvResult.toStyledString());
};
Account const alice {"alice"};
env.fund(XRP(1000), alice);
env(signers(alice, 1, {{bogie, 1}, {demon, 1}}), sig (alice));
auto const baseFee = env.current()->fees().base;
{
// Single-sign, but leave an empty SigningPubKey.
JTx tx = env.jt (noop (alice), sig(alice));
STTx local = *(tx.stx);
local.setFieldVL (sfSigningPubKey, Blob()); // Empty SigningPubKey
auto const info = submitSTTx (local);
BEAST_EXPECT(info[jss::result][jss::error_exception] ==
"fails local checks: Empty SigningPubKey.");
}
{
// Single-sign, but invalidate the signature.
JTx tx = env.jt (noop (alice), sig(alice));
STTx local = *(tx.stx);
// Flip some bits in the signature.
auto badSig = local.getFieldVL (sfTxnSignature);
badSig[20] ^= 0xAA;
local.setFieldVL (sfTxnSignature, badSig);
// Signature should fail.
auto const info = submitSTTx (local);
BEAST_EXPECT(info[jss::result][jss::error_exception] ==
"fails local checks: Invalid signature.");
}
{
// Multisign, but leave a nonempty sfSigningPubKey.
JTx tx = env.jt (noop (alice), fee(2 * baseFee), msig(bogie));
STTx local = *(tx.stx);
local[sfSigningPubKey] = alice.pk(); // Insert sfSigningPubKey
auto const info = submitSTTx (local);
BEAST_EXPECT(info[jss::result][jss::error_exception] ==
"fails local checks: Cannot both single- and multi-sign.");
}
{
// Both multi- and single-sign with an empty SigningPubKey.
JTx tx = env.jt (noop(alice), fee(2 * baseFee), msig(bogie));
STTx local = *(tx.stx);
local.sign (alice.pk(), alice.sk());
local.setFieldVL (sfSigningPubKey, Blob()); // Empty SigningPubKey
auto const info = submitSTTx (local);
BEAST_EXPECT(info[jss::result][jss::error_exception] ==
"fails local checks: Cannot both single- and multi-sign.");
}
{
// Multisign but invalidate one of the signatures.
JTx tx = env.jt (noop(alice), fee(2 * baseFee), msig(bogie));
STTx local = *(tx.stx);
// Flip some bits in the signature.
auto& signer = local.peekFieldArray (sfSigners).back();
auto badSig = signer.getFieldVL (sfTxnSignature);
badSig[20] ^= 0xAA;
signer.setFieldVL (sfTxnSignature, badSig);
// Signature should fail.
auto const info = submitSTTx (local);
BEAST_EXPECT(info[jss::result][jss::error_exception].asString().
find ("Invalid signature on account r") != std::string::npos);
}
{
// Multisign with an empty signers array should fail.
JTx tx = env.jt (noop(alice), fee(2 * baseFee), msig(bogie));
STTx local = *(tx.stx);
local.peekFieldArray (sfSigners).clear(); // Empty Signers array.
auto const info = submitSTTx (local);
BEAST_EXPECT(info[jss::result][jss::error_exception] ==
"fails local checks: Invalid Signers array size.");
}
{
// Multisign 9 times should fail.
JTx tx = env.jt (noop(alice), fee(2 * baseFee),
msig(bogie, bogie, bogie,
bogie, bogie, bogie, bogie, bogie, bogie));
STTx local = *(tx.stx);
auto const info = submitSTTx (local);
BEAST_EXPECT(info[jss::result][jss::error_exception] ==
"fails local checks: Invalid Signers array size.");
}
{
// The account owner may not multisign for themselves.
JTx tx = env.jt (noop(alice), fee(2 * baseFee), msig(alice));
STTx local = *(tx.stx);
auto const info = submitSTTx (local);
BEAST_EXPECT(info[jss::result][jss::error_exception] ==
"fails local checks: Invalid multisigner.");
}
{
// No duplicate multisignatures allowed.
JTx tx = env.jt (noop(alice), fee(2 * baseFee), msig(bogie, bogie));
STTx local = *(tx.stx);
auto const info = submitSTTx (local);
BEAST_EXPECT(info[jss::result][jss::error_exception] ==
"fails local checks: Duplicate Signers not allowed.");
}
{
// Multisignatures must be submitted in sorted order.
JTx tx = env.jt (noop(alice), fee(2 * baseFee), msig(bogie, demon));
STTx local = *(tx.stx);
// Unsort the Signers array.
auto& signers = local.peekFieldArray (sfSigners);
std::reverse (signers.begin(), signers.end());
// Signature should fail.
auto const info = submitSTTx (local);
BEAST_EXPECT(info[jss::result][jss::error_exception] ==
"fails local checks: Unsorted Signers array.");
}
}
void run() override
{
test_noReserve();
test_signerListSet();
test_phantomSigners();
test_enablement();
test_fee();
test_misorderedSigners();
test_masterSigners();
test_regularSigners();
test_heterogeneousSigners();
test_keyDisable();
test_regKey();
test_txTypes();
test_badSignatureText();
}
};
BEAST_DEFINE_TESTSUITE(MultiSign, app, ripple);
} // test
} // ripple

View File

@@ -0,0 +1,44 @@
//------------------------------------------------------------------------------
/*
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/tx/impl/OfferStream.h>
#include <ripple/beast/unit_test.h>
namespace ripple {
class OfferStream_test : public beast::unit_test::suite
{
public:
void
test()
{
pass();
}
void
run()
{
test();
}
};
BEAST_DEFINE_TESTSUITE(OfferStream,tx,ripple);
}

816
src/test/app/Offer_test.cpp Normal file
View File

@@ -0,0 +1,816 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012-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/app/main/Application.h>
#include <ripple/app/ledger/Ledger.h>
#include <ripple/basics/Log.h>
#include <ripple/test/jtx.h>
#include <ripple/test/jtx/Account.h>
#include <ripple/ledger/tests/PathSet.h>
#include <ripple/protocol/SystemParameters.h>
namespace ripple {
namespace test {
class Offer_test : public beast::unit_test::suite
{
XRPAmount reserve(jtx::Env& env, std::uint32_t count)
{
return env.current()->fees().accountReserve (count);
}
std::uint32_t lastClose (jtx::Env& env)
{
return env.current()->info().parentCloseTime.time_since_epoch().count();
}
static auto
xrpMinusFee (jtx::Env const& env, std::int64_t xrpAmount)
-> jtx::PrettyAmount
{
using namespace jtx;
auto feeDrops = env.current ()->fees ().base;
return drops (dropsPerXRP<std::int64_t>::value * xrpAmount - feeDrops);
};
public:
void testRmFundedOffer ()
{
testcase ("Incorrect Removal of Funded Offers");
// We need at least two paths. One at good quality and one at bad quality.
// The bad quality path needs two offer books in a row. Each offer book
// should have two offers at the same quality, the offers should be
// completely consumed, and the payment should should require both offers to
// be satisified. The first offer must be "taker gets" XRP. Old, broken
// would remove the first "taker gets" xrp offer, even though the offer is
// still funded and not used for the payment.
using namespace jtx;
Env env (*this);
// ledger close times have a dynamic resolution depending on network
// conditions it appears the resolution in test is 10 seconds
env.close ();
auto const gw = Account ("gateway");
auto const USD = gw["USD"];
auto const BTC = gw["BTC"];
Account const alice ("alice");
Account const bob ("bob");
Account const carol ("carol");
env.fund (XRP (10000), alice, bob, carol, gw);
env.trust (USD (1000), alice, bob, carol);
env.trust (BTC (1000), alice, bob, carol);
env (pay (gw, alice, BTC (1000)));
env (pay (gw, carol, USD (1000)));
env (pay (gw, carol, BTC (1000)));
// Must be two offers at the same quality
// "taker gets" must be XRP
// (Different amounts so I can distinguish the offers)
env (offer (carol, BTC (49), XRP (49)));
env (offer (carol, BTC (51), XRP (51)));
// Offers for the poor quality path
// Must be two offers at the same quality
env (offer (carol, XRP (50), USD (50)));
env (offer (carol, XRP (50), USD (50)));
// Offers for the good quality path
env (offer (carol, BTC (1), USD (100)));
PathSet paths (Path (XRP, USD), Path (USD));
env (pay ("alice", "bob", USD (100)), json (paths.json ()),
sendmax (BTC (1000)), txflags (tfPartialPayment));
env.require (balance ("bob", USD (100)));
BEAST_EXPECT(!isOffer (env, "carol", BTC (1), USD (100)) &&
isOffer (env, "carol", BTC (49), XRP (49)));
}
void testCanceledOffer ()
{
testcase ("Removing Canceled Offers");
using namespace jtx;
Env env (*this);
auto const gw = Account ("gateway");
auto const USD = gw["USD"];
env.fund (XRP (10000), "alice", gw);
env.trust (USD (100), "alice");
env (pay (gw, "alice", USD (50)));
auto const firstOfferSeq = env.seq ("alice");
Json::StaticString const osKey ("OfferSequence");
env (offer ("alice", XRP (500), USD (100)),
require (offers ("alice", 1)));
BEAST_EXPECT(isOffer (env, "alice", XRP (500), USD (100)));
// cancel the offer above and replace it with a new offer
env (offer ("alice", XRP (300), USD (100)), json (osKey, firstOfferSeq),
require (offers ("alice", 1)));
BEAST_EXPECT(isOffer (env, "alice", XRP (300), USD (100)) &&
!isOffer (env, "alice", XRP (500), USD (100)));
// Test canceling non-existant offer.
env (offer ("alice", XRP (400), USD (200)), json (osKey, firstOfferSeq),
require (offers ("alice", 2)));
BEAST_EXPECT(isOffer (env, "alice", XRP (300), USD (100)) &&
isOffer (env, "alice", XRP (400), USD (200)));
}
void testTinyPayment ()
{
testcase ("Tiny payments");
// Regression test for tiny payments
using namespace jtx;
using namespace std::chrono_literals;
auto const alice = Account ("alice");
auto const bob = Account ("bob");
auto const carol = Account ("carol");
auto const gw = Account ("gw");
auto const USD = gw["USD"];
auto const EUR = gw["EUR"];
Env env (*this);
env.fund (XRP (10000), alice, bob, carol, gw);
env.trust (USD (1000), alice, bob, carol);
env.trust (EUR (1000), alice, bob, carol);
env (pay (gw, alice, USD (100)));
env (pay (gw, carol, EUR (100)));
// Create more offers than the loop max count in DeliverNodeReverse
for (int i=0;i<101;++i)
env (offer (carol, USD (1), EUR (2)));
for (auto timeDelta : {
- env.closed()->info().closeTimeResolution,
env.closed()->info().closeTimeResolution} )
{
auto const closeTime = STAmountSO::soTime + timeDelta;
env.close (closeTime);
*stAmountCalcSwitchover = closeTime > STAmountSO::soTime;
// Will fail without the underflow fix
auto expectedResult = *stAmountCalcSwitchover ?
tesSUCCESS : tecPATH_PARTIAL;
env (pay ("alice", "bob", EUR (epsilon)), path (~EUR),
sendmax (USD (100)), ter (expectedResult));
}
}
void testXRPTinyPayment ()
{
testcase ("XRP Tiny payments");
// Regression test for tiny xrp payments
// In some cases, when the payment code calculates
// the amount of xrp needed as input to an xrp->iou offer
// it would incorrectly round the amount to zero (even when
// round-up was set to true).
// The bug would cause funded offers to be incorrectly removed
// because the code thought they were unfunded.
// The conditions to trigger the bug are:
// 1) When we calculate the amount of input xrp needed for an offer from
// xrp->iou, the amount is less than 1 drop (after rounding up the float
// representation).
// 2) There is another offer in the same book with a quality sufficiently bad that
// when calculating the input amount needed the amount is not set to zero.
using namespace jtx;
using namespace std::chrono_literals;
auto const alice = Account ("alice");
auto const bob = Account ("bob");
auto const carol = Account ("carol");
auto const dan = Account ("dan");
auto const erin = Account ("erin");
auto const gw = Account ("gw");
auto const USD = gw["USD"];
for (auto withFix : {false, true})
{
Env env (*this);
auto closeTime = [&]
{
auto const delta =
100 * env.closed ()->info ().closeTimeResolution;
if (withFix)
return STAmountSO::soTime2 + delta;
else
return STAmountSO::soTime2 - delta;
}();
auto offerCount = [&env](jtx::Account const& account)
{
auto count = 0;
forEachItem (*env.current (), account,
[&](std::shared_ptr<SLE const> const& sle)
{
if (sle->getType () == ltOFFER)
++count;
});
return count;
};
env.fund (XRP (10000), alice, bob, carol, dan, erin, gw);
env.trust (USD (1000), alice, bob, carol, dan, erin);
env (pay (gw, carol, USD (0.99999)));
env (pay (gw, dan, USD (1)));
env (pay (gw, erin, USD (1)));
// Carol doen't quite have enough funds for this offer
// The amount left after this offer is taken will cause
// STAmount to incorrectly round to zero when the next offer
// (at a good quality) is considered. (when the
// stAmountCalcSwitchover2 patch is inactive)
env (offer (carol, drops (1), USD (1)));
// Offer at a quality poor enough so when the input xrp is calculated
// in the reverse pass, the amount is not zero.
env (offer (dan, XRP (100), USD (1)));
env.close (closeTime);
// This is the funded offer that will be incorrectly removed.
// It is considered after the offer from carol, which leaves a
// tiny amount left to pay. When calculating the amount of xrp
// needed for this offer, it will incorrectly compute zero in both
// the forward and reverse passes (when the stAmountCalcSwitchover2 is
// inactive.)
env (offer (erin, drops (1), USD (1)));
{
env (pay (alice, bob, USD (1)), path (~USD),
sendmax (XRP (102)),
txflags (tfNoRippleDirect | tfPartialPayment));
BEAST_EXPECT(offerCount (carol) == 0);
BEAST_EXPECT(offerCount (dan) == 1);
if (!withFix)
{
// funded offer was removed
BEAST_EXPECT(offerCount (erin) == 0);
env.require (balance ("erin", USD (1)));
}
else
{
// offer was correctly consumed. There is stil some
// liquidity left on that offer.
BEAST_EXPECT(offerCount (erin) == 1);
env.require (balance ("erin", USD (0.99999)));
}
}
}
}
void testEnforceNoRipple ()
{
testcase ("Enforce No Ripple");
using namespace jtx;
auto const gw = Account ("gateway");
auto const USD = gw["USD"];
auto const BTC = gw["BTC"];
auto const EUR = gw["EUR"];
Account const alice ("alice");
Account const bob ("bob");
Account const carol ("carol");
Account const dan ("dan");
{
// No ripple with an implied account step after an offer
Env env (*this);
auto const gw1 = Account ("gw1");
auto const USD1 = gw1["USD"];
auto const gw2 = Account ("gw2");
auto const USD2 = gw2["USD"];
env.fund (XRP (10000), alice, noripple (bob), carol, dan, gw1, gw2);
env.trust (USD1 (1000), alice, carol, dan);
env(trust (bob, USD1 (1000), tfSetNoRipple));
env.trust (USD2 (1000), alice, carol, dan);
env(trust (bob, USD2 (1000), tfSetNoRipple));
env (pay (gw1, dan, USD1 (50)));
env (pay (gw1, bob, USD1 (50)));
env (pay (gw2, bob, USD2 (50)));
env (offer (dan, XRP (50), USD1 (50)));
env (pay (alice, carol, USD2 (50)), path (~USD1, bob), ter(tecPATH_DRY),
sendmax (XRP (50)), txflags (tfNoRippleDirect));
}
{
// Make sure payment works with default flags
Env env (*this);
auto const gw1 = Account ("gw1");
auto const USD1 = gw1["USD"];
auto const gw2 = Account ("gw2");
auto const USD2 = gw2["USD"];
env.fund (XRP (10000), alice, bob, carol, dan, gw1, gw2);
env.trust (USD1 (1000), alice, bob, carol, dan);
env.trust (USD2 (1000), alice, bob, carol, dan);
env (pay (gw1, dan, USD1 (50)));
env (pay (gw1, bob, USD1 (50)));
env (pay (gw2, bob, USD2 (50)));
env (offer (dan, XRP (50), USD1 (50)));
env (pay (alice, carol, USD2 (50)), path (~USD1, bob),
sendmax (XRP (50)), txflags (tfNoRippleDirect));
env.require (balance (alice, xrpMinusFee (env, 10000 - 50)));
env.require (balance (bob, USD1 (100)));
env.require (balance (bob, USD2 (0)));
env.require (balance (carol, USD2 (50)));
}
}
void
testInsufficientReserve ()
{
testcase ("Insufficient Reserve");
// If an account places an offer and its balance
// *before* the transaction began isn't high enough
// to meet the reserve *after* the transaction runs,
// then no offer should go on the books but if the
// offer partially or fully crossed the tx succeeds.
using namespace jtx;
auto const gw = Account("gateway");
auto const USD = gw["USD"];
auto const usdOffer = USD(1000);
auto const xrpOffer = XRP(1000);
// No crossing:
{
Env env(*this);
env.fund (XRP(1000000), gw);
auto const f = env.current ()->fees ().base;
auto const r = reserve (env, 0);
env.fund (r + f, "alice");
env (trust ("alice", usdOffer), ter(tesSUCCESS));
env (pay (gw, "alice", usdOffer), ter(tesSUCCESS));
env (offer ("alice", xrpOffer, usdOffer), ter(tecINSUF_RESERVE_OFFER));
env.require (
balance ("alice", r - f),
owners ("alice", 1));
}
// Partial cross:
{
Env env(*this);
env.fund (XRP(1000000), gw);
auto const f = env.current ()->fees ().base;
auto const r = reserve (env, 0);
auto const usdOffer2 = USD(500);
auto const xrpOffer2 = XRP(500);
env.fund (r + f + xrpOffer, "bob");
env (offer ("bob", usdOffer2, xrpOffer2), ter(tesSUCCESS));
env.fund (r + f, "alice");
env (trust ("alice", usdOffer), ter(tesSUCCESS));
env (pay (gw, "alice", usdOffer), ter(tesSUCCESS));
env (offer ("alice", xrpOffer, usdOffer), ter(tesSUCCESS));
env.require (
balance ("alice", r - f + xrpOffer2),
balance ("alice", usdOffer2),
owners ("alice", 1),
balance ("bob", r + xrpOffer2),
balance ("bob", usdOffer2),
owners ("bob", 1));
}
// Account has enough reserve as is, but not enough
// if an offer were added. Attempt to sell IOUs to
// buy XRP. If it fully crosses, we succeed.
{
Env env(*this);
env.fund (XRP(1000000), gw);
auto const f = env.current ()->fees ().base;
auto const r = reserve (env, 0);
auto const usdOffer2 = USD(500);
auto const xrpOffer2 = XRP(500);
env.fund (r + f + xrpOffer, "bob", "carol");
env (offer ("bob", usdOffer2, xrpOffer2), ter(tesSUCCESS));
env (offer ("carol", usdOffer, xrpOffer), ter(tesSUCCESS));
env.fund (r + f, "alice");
env (trust ("alice", usdOffer), ter(tesSUCCESS));
env (pay (gw, "alice", usdOffer), ter(tesSUCCESS));
env (offer ("alice", xrpOffer, usdOffer), ter(tesSUCCESS));
env.require (
balance ("alice", r - f + xrpOffer),
balance ("alice", USD(0)),
owners ("alice", 1),
balance ("bob", r + xrpOffer2),
balance ("bob", usdOffer2),
owners ("bob", 1),
balance ("carol", r + xrpOffer2),
balance ("carol", usdOffer2),
owners ("carol", 2));
}
}
void
testFillModes ()
{
testcase ("Fill Modes");
using namespace jtx;
auto const startBalance = XRP(1000000);
auto const gw = Account("gateway");
auto const USD = gw["USD"];
// Fill or Kill - unless we fully cross, just charge
// a fee and not place the offer on the books:
{
Env env(*this);
env.fund (startBalance, gw);
auto const f = env.current ()->fees ().base;
env.fund (startBalance, "alice", "bob");
env (offer ("bob", USD(500), XRP(500)), ter(tesSUCCESS));
env (trust ("alice", USD(1000)), ter(tesSUCCESS));
env (pay (gw, "alice", USD(1000)), ter(tesSUCCESS));
// Order that can't be filled:
env (offer ("alice", XRP(1000), USD(1000)),
txflags (tfFillOrKill), ter(tesSUCCESS));
env.require (
balance ("alice", startBalance - f - f),
balance ("alice", USD(1000)),
owners ("alice", 1),
offers ("alice", 0),
balance ("bob", startBalance - f),
balance ("bob", USD(none)),
owners ("bob", 1),
offers ("bob", 1));
// Order that can be filled
env (offer ("alice", XRP(500), USD(500)),
txflags (tfFillOrKill), ter(tesSUCCESS));
env.require (
balance ("alice", startBalance - f - f - f + XRP(500)),
balance ("alice", USD(500)),
owners ("alice", 1),
offers ("alice", 0),
balance ("bob", startBalance - f - XRP(500)),
balance ("bob", USD(500)),
owners ("bob", 1),
offers ("bob", 0));
}
// Immediate or Cancel - cross as much as possible
// and add nothing on the books:
{
Env env(*this);
env.fund (startBalance, gw);
auto const f = env.current ()->fees ().base;
env.fund (startBalance, "alice", "bob");
env (trust ("alice", USD(1000)), ter(tesSUCCESS));
env (pay (gw, "alice", USD(1000)), ter(tesSUCCESS));
// No cross:
env (offer ("alice", XRP(1000), USD(1000)),
txflags (tfImmediateOrCancel), ter(tesSUCCESS));
env.require (
balance ("alice", startBalance - f - f),
balance ("alice", USD(1000)),
owners ("alice", 1),
offers ("alice", 0));
// Partially cross:
env (offer ("bob", USD(50), XRP(50)), ter(tesSUCCESS));
env (offer ("alice", XRP(1000), USD(1000)),
txflags (tfImmediateOrCancel), ter(tesSUCCESS));
env.require (
balance ("alice", startBalance - f - f - f + XRP(50)),
balance ("alice", USD(950)),
owners ("alice", 1),
offers ("alice", 0),
balance ("bob", startBalance - f - XRP(50)),
balance ("bob", USD(50)),
owners ("bob", 1),
offers ("bob", 0));
// Fully cross:
env (offer ("bob", USD(50), XRP(50)), ter(tesSUCCESS));
env (offer ("alice", XRP(50), USD(50)),
txflags (tfImmediateOrCancel), ter(tesSUCCESS));
env.require (
balance ("alice", startBalance - f - f - f - f + XRP(100)),
balance ("alice", USD(900)),
owners ("alice", 1),
offers ("alice", 0),
balance ("bob", startBalance - f - f - XRP(100)),
balance ("bob", USD(100)),
owners ("bob", 1),
offers ("bob", 0));
}
}
void
testMalformed()
{
testcase ("Malformed Detection");
using namespace jtx;
auto const startBalance = XRP(1000000);
auto const gw = Account("gateway");
auto const USD = gw["USD"];
Env env(*this);
env.fund (startBalance, gw);
env.fund (startBalance, "alice");
// Order that has invalid flags
env (offer ("alice", USD(1000), XRP(1000)),
txflags (tfImmediateOrCancel + 1), ter(temINVALID_FLAG));
env.require (
balance ("alice", startBalance),
owners ("alice", 0),
offers ("alice", 0));
// Order with incompatible flags
env (offer ("alice", USD(1000), XRP(1000)),
txflags (tfImmediateOrCancel | tfFillOrKill), ter(temINVALID_FLAG));
env.require (
balance ("alice", startBalance),
owners ("alice", 0),
offers ("alice", 0));
// Sell and buy the same asset
{
// Alice tries an XRP to XRP order:
env (offer ("alice", XRP(1000), XRP(1000)), ter(temBAD_OFFER));
env.require (
owners ("alice", 0),
offers ("alice", 0));
// Alice tries an IOU to IOU order:
env (trust ("alice", USD(1000)), ter(tesSUCCESS));
env (pay (gw, "alice", USD(1000)), ter(tesSUCCESS));
env (offer ("alice", USD(1000), USD(1000)), ter(temREDUNDANT));
env.require (
owners ("alice", 1),
offers ("alice", 0));
}
// Offers with negative amounts
{
env (offer ("alice", -USD(1000), XRP(1000)), ter(temBAD_OFFER));
env.require (
owners ("alice", 1),
offers ("alice", 0));
env (offer ("alice", USD(1000), -XRP(1000)), ter(temBAD_OFFER));
env.require (
owners ("alice", 1),
offers ("alice", 0));
}
// Offer with a bad expiration
{
Json::StaticString const key ("Expiration");
env (offer ("alice", USD(1000), XRP(1000)),
json (key, std::uint32_t (0)), ter(temBAD_EXPIRATION));
env.require (
owners ("alice", 1),
offers ("alice", 0));
}
// Offer with a bad offer sequence
{
Json::StaticString const key ("OfferSequence");
env (offer ("alice", USD(1000), XRP(1000)),
json (key, std::uint32_t (0)), ter(temBAD_SEQUENCE));
env.require (
owners ("alice", 1),
offers ("alice", 0));
}
// Use XRP as a currency code
{
auto const BAD = IOU(gw, badCurrency());
env (offer ("alice", XRP(1000), BAD(1000)), ter(temBAD_CURRENCY));
env.require (
owners ("alice", 1),
offers ("alice", 0));
}
}
void
testExpiration ()
{
testcase ("Offer Expiration");
using namespace jtx;
auto const gw = Account("gateway");
auto const USD = gw["USD"];
auto const startBalance = XRP(1000000);
auto const usdOffer = USD(1000);
auto const xrpOffer = XRP(1000);
Json::StaticString const key ("Expiration");
Env env(*this);
env.fund (startBalance, gw, "alice", "bob");
env.close();
auto const f = env.current ()->fees ().base;
// Place an offer that should have already expired
env (trust ("alice", usdOffer), ter(tesSUCCESS));
env (pay (gw, "alice", usdOffer), ter(tesSUCCESS));
env.close();
env.require (
balance ("alice", startBalance - f),
balance ("alice", usdOffer),
offers ("alice", 0),
owners ("alice", 1));
env (offer ("alice", xrpOffer, usdOffer),
json (key, lastClose(env)), ter(tesSUCCESS));
env.require (
balance ("alice", startBalance - f - f),
balance ("alice", usdOffer),
offers ("alice", 0),
owners ("alice", 1));
env.close();
// Add an offer that's expires before the next ledger close
env (offer ("alice", xrpOffer, usdOffer),
json (key, lastClose(env) + 1), ter(tesSUCCESS));
env.require (
balance ("alice", startBalance - f - f - f),
balance ("alice", usdOffer),
offers ("alice", 1),
owners ("alice", 2));
// The offer expires (it's not removed yet)
env.close ();
env.require (
balance ("alice", startBalance - f - f - f),
balance ("alice", usdOffer),
offers ("alice", 1),
owners ("alice", 2));
// Add offer - the expired offer is removed
env (offer ("bob", usdOffer, xrpOffer), ter(tesSUCCESS));
env.require (
balance ("alice", startBalance - f - f - f),
balance ("alice", usdOffer),
offers ("alice", 0),
owners ("alice", 1),
balance ("bob", startBalance - f),
balance ("bob", USD(none)),
offers ("bob", 1),
owners ("bob", 1));
}
void
testUnfundedCross()
{
testcase ("Unfunded Crossing");
using namespace jtx;
auto const gw = Account("gateway");
auto const USD = gw["USD"];
auto const usdOffer = USD(1000);
auto const xrpOffer = XRP(1000);
Env env(*this);
env.fund (XRP(1000000), gw);
// The fee that's charged for transactions
auto const f = env.current ()->fees ().base;
// Account is at the reserve, and will dip below once
// fees are subtracted.
env.fund (reserve (env, 0), "alice");
env (offer ("alice", usdOffer, xrpOffer), ter(tecUNFUNDED_OFFER));
env.require (
balance ("alice", reserve (env, 0) - f),
owners ("alice", 0));
// Account has just enough for the reserve and the
// fee.
env.fund (reserve (env, 0) + f, "bob");
env (offer ("bob", usdOffer, xrpOffer), ter(tecUNFUNDED_OFFER));
env.require (
balance ("bob", reserve (env, 0)),
owners ("bob", 0));
// Account has enough for the reserve, the fee and
// the offer, and a bit more, but not enough for the
// reserve after the offer is placed.
env.fund (reserve (env, 0) + f + XRP(1), "carol");
env (offer ("carol", usdOffer, xrpOffer), ter(tecINSUF_RESERVE_OFFER));
env.require (
balance ("carol", reserve (env, 0) + XRP(1)),
owners ("carol", 0));
// Account has enough for the reserve plus one
// offer, and the fee.
env.fund (reserve (env, 1) + f, "dan");
env (offer ("dan", usdOffer, xrpOffer), ter(tesSUCCESS));
env.require (
balance ("dan", reserve (env, 1)),
owners ("dan", 1));
// Account has enough for the reserve plus one
// offer, the fee and the entire offer amount.
env.fund (reserve (env, 1) + f + xrpOffer, "eve");
env (offer ("eve", usdOffer, xrpOffer), ter(tesSUCCESS));
env.require (
balance ("eve", reserve (env, 1) + xrpOffer),
owners ("eve", 1));
}
void run ()
{
testCanceledOffer ();
testRmFundedOffer ();
testTinyPayment ();
testXRPTinyPayment ();
testEnforceNoRipple ();
testInsufficientReserve ();
testFillModes ();
testMalformed ();
testExpiration ();
testUnfundedCross ();
}
};
BEAST_DEFINE_TESTSUITE (Offer, tx, ripple);
} // test
} // ripple

View File

@@ -0,0 +1,188 @@
//------------------------------------------------------------------------------
/*
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/test/jtx.h>
#include <ripple/beast/unit_test.h>
#include <algorithm>
namespace ripple {
namespace test {
// Make sure "plump" order books don't have problems
class PlumpBook_test : public beast::unit_test::suite
{
public:
void
createOffers (jtx::Env& env,
jtx::IOU const& iou, std::size_t n)
{
using namespace jtx;
for (std::size_t i = 1; i <= n; ++i)
env(offer("alice", XRP(i), iou(1)));
}
void
test (std::size_t n)
{
using namespace jtx;
auto const billion = 1000000000ul;
Env env(*this);
env.disable_sigs();
auto const gw = Account("gateway");
auto const USD = gw["USD"];
env.fund(XRP(billion), gw, "alice", "bob", "carol");
env.trust(USD(billion), "alice", "bob", "carol");
env(pay(gw, "alice", USD(billion)));
createOffers(env, USD, n);
}
void
run() override
{
test(10000);
}
};
BEAST_DEFINE_TESTSUITE_MANUAL(PlumpBook,tx,ripple);
//------------------------------------------------------------------------------
// Ensure that unsigned transactions succeed during automatic test runs.
class ThinBook_test : public PlumpBook_test
{
public:
void
run() override
{
test(1);
}
};
BEAST_DEFINE_TESTSUITE(ThinBook, tx, ripple);
//------------------------------------------------------------------------------
class OversizeMeta_test : public beast::unit_test::suite
{
public:
void
createOffers (jtx::Env& env, jtx::IOU const& iou,
std::size_t n)
{
using namespace jtx;
for (std::size_t i = 1; i <= n; ++i)
env(offer("alice", XRP(1), iou(1)));
}
void
test()
{
std::size_t const n = 9000;
using namespace jtx;
auto const billion = 1000000000ul;
Env env(*this);
env.disable_sigs();
auto const gw = Account("gateway");
auto const USD = gw["USD"];
env.fund(XRP(billion), gw, "alice", "bob", "carol");
env.trust(USD(billion), "alice", "bob", "carol");
env(pay(gw, "alice", USD(billion)));
createOffers(env, USD, n);
env(pay("alice", gw, USD(billion)));
env(offer("alice", USD(1), XRP(1)));
}
void
run()
{
test();
}
};
BEAST_DEFINE_TESTSUITE_MANUAL(OversizeMeta,tx,ripple);
//------------------------------------------------------------------------------
class FindOversizeCross_test : public beast::unit_test::suite
{
public:
// Return lowest x in [lo, hi] for which f(x)==true
template <class Function>
static
std::size_t
bfind(std::size_t lo, std::size_t hi, Function&& f)
{
auto len = hi - lo;
while (len != 0)
{
auto l2 = len / 2;
auto m = lo + l2;
if (! f(m))
{
lo = ++m;
len -= l2 + 1;
}
else
len = l2;
}
return lo;
}
void
createOffers (jtx::Env& env, jtx::IOU const& iou,
std::size_t n)
{
using namespace jtx;
for (std::size_t i = 1; i <= n; ++i)
env(offer("alice", XRP(i), iou(1)));
}
bool
oversize(std::size_t n)
{
using namespace jtx;
auto const billion = 1000000000ul;
Env env(*this);
env.disable_sigs();
auto const gw = Account("gateway");
auto const USD = gw["USD"];
env.fund(XRP(billion), gw, "alice", "bob", "carol");
env.trust(USD(billion), "alice", "bob", "carol");
env(pay(gw, "alice", USD(billion)));
createOffers(env, USD, n);
env(pay("alice", gw, USD(billion)));
env(offer("alice", USD(1), XRP(1)), ter(std::ignore));
return env.ter() == tecOVERSIZE;
}
void
run()
{
auto const result = bfind(100, 9000,
[&](std::size_t n) { return oversize(n); });
log << "Min oversize offers = " << result << '\n';
}
};
BEAST_DEFINE_TESTSUITE_MANUAL(FindOversizeCross,tx,ripple);
} // test
} // ripple

895
src/test/app/Path_test.cpp Normal file
View File

@@ -0,0 +1,895 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012-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/app/paths/AccountCurrencies.h>
#include <ripple/basics/contract.h>
#include <ripple/core/JobQueue.h>
#include <ripple/json/json_reader.h>
#include <ripple/json/to_string.h>
#include <ripple/protocol/JsonFields.h>
#include <ripple/protocol/STParsedJSON.h>
#include <ripple/protocol/TxFlags.h>
#include <ripple/resource/Fees.h>
#include <ripple/rpc/Context.h>
#include <ripple/rpc/impl/Tuning.h>
#include <ripple/rpc/RPCHandler.h>
#include <ripple/test/jtx.h>
#include <ripple/beast/unit_test.h>
#include <chrono>
#include <condition_variable>
#include <mutex>
#include <thread>
namespace ripple {
namespace test {
//------------------------------------------------------------------------------
namespace detail {
void
stpath_append_one (STPath& st,
jtx::Account const& account)
{
st.push_back(STPathElement({
account.id(), boost::none, boost::none }));
}
template <class T>
std::enable_if_t<
std::is_constructible<jtx::Account, T>::value>
stpath_append_one (STPath& st,
T const& t)
{
stpath_append_one(st, jtx::Account{ t });
}
void
stpath_append_one (STPath& st,
jtx::IOU const& iou)
{
st.push_back(STPathElement({
iou.account.id(), iou.currency, boost::none }));
}
void
stpath_append_one (STPath& st,
jtx::BookSpec const& book)
{
st.push_back(STPathElement({
boost::none, book.currency, book.account }));
}
inline
void
stpath_append (STPath& st)
{
}
template <class T, class... Args>
void
stpath_append (STPath& st,
T const& t, Args const&... args)
{
stpath_append_one(st, t);
stpath_append(st, args...);
}
inline
void
stpathset_append (STPathSet& st)
{
}
template <class... Args>
void
stpathset_append(STPathSet& st,
STPath const& p, Args const&... args)
{
st.push_back(p);
stpathset_append(st, args...);
}
} // detail
template <class... Args>
STPath
stpath (Args const&... args)
{
STPath st;
detail::stpath_append(st, args...);
return st;
}
template <class... Args>
bool
same (STPathSet const& st1, Args const&... args)
{
STPathSet st2;
detail::stpathset_append(st2, args...);
if (st1.size() != st2.size())
return false;
for (auto const& p : st2)
{
if (std::find(st1.begin(), st1.end(), p) == st1.end())
return false;
}
return true;
}
bool
equal(STAmount const& sa1, STAmount const& sa2)
{
return sa1 == sa2 &&
sa1.issue().account == sa2.issue().account;
}
Json::Value
rpf(jtx::Account const& src, jtx::Account const& dst, std::uint32_t num_src)
{
Json::Value jv = Json::objectValue;
jv[jss::command] = "ripple_path_find";
jv[jss::source_account] = toBase58(src);
if (num_src > 0)
{
auto& sc = (jv[jss::source_currencies] = Json::arrayValue);
Json::Value j = Json::objectValue;
while (num_src--)
{
j[jss::currency] = std::to_string(num_src + 100);
sc.append(j);
}
}
auto const d = toBase58(dst);
jv[jss::destination_account] = d;
Json::Value& j = (jv[jss::destination_amount] = Json::objectValue);
j[jss::currency] = "USD";
j[jss::value] = "0.01";
j[jss::issuer] = d;
return jv;
}
//------------------------------------------------------------------------------
class Path_test : public beast::unit_test::suite
{
public:
class gate
{
private:
std::condition_variable cv_;
std::mutex mutex_;
bool signaled_ = false;
public:
// Thread safe, blocks until signaled or period expires.
// Returns `true` if signaled.
template <class Rep, class Period>
bool
wait_for(std::chrono::duration<Rep, Period> const& rel_time)
{
std::unique_lock<std::mutex> lk(mutex_);
auto b = cv_.wait_for(lk, rel_time, [=]{ return signaled_; });
signaled_ = false;
return b;
}
void
signal()
{
std::lock_guard<std::mutex> lk(mutex_);
signaled_ = true;
cv_.notify_all();
}
};
std::tuple <STPathSet, STAmount, STAmount>
find_paths(jtx::Env& env,
jtx::Account const& src, jtx::Account const& dst,
STAmount const& saDstAmount,
boost::optional<STAmount> const& saSendMax = boost::none)
{
using namespace jtx;
auto& app = env.app();
Resource::Charge loadType = Resource::feeReferenceRPC;
Resource::Consumer c;
RPC::Context context {beast::Journal(), {}, app, loadType,
app.getOPs(), app.getLedgerMaster(), c, Role::USER, {}};
Json::Value params = Json::objectValue;
params[jss::command] = "ripple_path_find";
params[jss::source_account] = toBase58 (src);
params[jss::destination_account] = toBase58 (dst);
params[jss::destination_amount] = saDstAmount.getJson(0);
if (saSendMax)
params[jss::send_max] = saSendMax->getJson(0);
Json::Value result;
gate g;
app.getJobQueue().postCoro(jtCLIENT, "RPC-Client",
[&](auto const& coro)
{
context.params = std::move (params);
context.jobCoro = coro;
RPC::doCommand (context, result);
g.signal();
});
BEAST_EXPECT(g.wait_for(5s));
BEAST_EXPECT(! result.isMember(jss::error));
STAmount da;
if (result.isMember(jss::destination_amount))
da = amountFromJson(sfGeneric,
result[jss::destination_amount]);
STAmount sa;
STPathSet paths;
if (result.isMember(jss::alternatives))
{
auto const& alts = result[jss::alternatives];
if (alts.size() > 0)
{
auto const& path = alts[0u];
if (path.isMember(jss::source_amount))
sa = amountFromJson(sfGeneric,
path[jss::source_amount]);
if (path.isMember(jss::destination_amount))
da = amountFromJson(sfGeneric,
path[jss::destination_amount]);
if (path.isMember(jss::paths_computed))
{
Json::Value p;
p["Paths"] = path[jss::paths_computed];
STParsedJSONObject po("generic", p);
paths = po.object->getFieldPathSet (sfPaths);
}
}
}
return std::make_tuple(
std::move(paths), std::move(sa), std::move(da));
}
void
source_currencies_limit()
{
testcase("source currency limits");
using namespace std::chrono_literals;
using namespace jtx;
Env env(*this);
auto const gw = Account("gateway");
env.fund(XRP(10000), "alice", "bob", gw);
env.trust(gw["USD"](100), "alice", "bob");
env.close();
auto& app = env.app();
Resource::Charge loadType = Resource::feeReferenceRPC;
Resource::Consumer c;
RPC::Context context {beast::Journal(), {}, app, loadType,
app.getOPs(), app.getLedgerMaster(), c, Role::USER, {}};
Json::Value result;
gate g;
// Test RPC::Tuning::max_src_cur source currencies.
app.getJobQueue().postCoro(jtCLIENT, "RPC-Client",
[&](auto const& coro)
{
context.params = rpf(Account("alice"), Account("bob"),
RPC::Tuning::max_src_cur);
context.jobCoro = coro;
RPC::doCommand(context, result);
g.signal();
});
BEAST_EXPECT(g.wait_for(5s));
BEAST_EXPECT(! result.isMember(jss::error));
// Test more than RPC::Tuning::max_src_cur source currencies.
app.getJobQueue().postCoro(jtCLIENT, "RPC-Client",
[&](auto const& coro)
{
context.params = rpf(Account("alice"), Account("bob"),
RPC::Tuning::max_src_cur + 1);
context.jobCoro = coro;
RPC::doCommand(context, result);
g.signal();
});
BEAST_EXPECT(g.wait_for(5s));
BEAST_EXPECT(result.isMember(jss::error));
// Test RPC::Tuning::max_auto_src_cur source currencies.
for (auto i = 0; i < (RPC::Tuning::max_auto_src_cur - 1); ++i)
env.trust(Account("alice")[std::to_string(i + 100)](100), "bob");
app.getJobQueue().postCoro(jtCLIENT, "RPC-Client",
[&](auto const& coro)
{
context.params = rpf(Account("alice"), Account("bob"), 0);
context.jobCoro = coro;
RPC::doCommand(context, result);
g.signal();
});
BEAST_EXPECT(g.wait_for(5s));
BEAST_EXPECT(! result.isMember(jss::error));
// Test more than RPC::Tuning::max_auto_src_cur source currencies.
env.trust(Account("alice")["AUD"](100), "bob");
app.getJobQueue().postCoro(jtCLIENT, "RPC-Client",
[&](auto const& coro)
{
context.params = rpf(Account("alice"), Account("bob"), 0);
context.jobCoro = coro;
RPC::doCommand(context, result);
g.signal();
});
BEAST_EXPECT(g.wait_for(5s));
BEAST_EXPECT(result.isMember(jss::error));
}
void
no_direct_path_no_intermediary_no_alternatives()
{
testcase("no direct path no intermediary no alternatives");
using namespace jtx;
Env env(*this);
env.fund(XRP(10000), "alice", "bob");
auto const result = find_paths(env,
"alice", "bob", Account("bob")["USD"](5));
BEAST_EXPECT(std::get<0>(result).empty());
}
void
direct_path_no_intermediary()
{
testcase("direct path no intermediary");
using namespace jtx;
Env env(*this);
env.fund(XRP(10000), "alice", "bob");
env.trust(Account("alice")["USD"](700), "bob");
STPathSet st;
STAmount sa;
std::tie(st, sa, std::ignore) = find_paths(env,
"alice", "bob", Account("bob")["USD"](5));
BEAST_EXPECT(st.empty());
BEAST_EXPECT(equal(sa, Account("alice")["USD"](5)));
}
void
payment_auto_path_find()
{
testcase("payment auto path find");
using namespace jtx;
Env env(*this);
auto const gw = Account("gateway");
auto const USD = gw["USD"];
env.fund(XRP(10000), "alice", "bob", gw);
env.trust(USD(600), "alice");
env.trust(USD(700), "bob");
env(pay(gw, "alice", USD(70)));
env(pay("alice", "bob", USD(24)));
env.require(balance("alice", USD(46)));
env.require(balance(gw, Account("alice")["USD"](-46)));
env.require(balance("bob", USD(24)));
env.require(balance(gw, Account("bob")["USD"](-24)));
}
void
path_find()
{
testcase("path find");
using namespace jtx;
Env env(*this);
auto const gw = Account("gateway");
auto const USD = gw["USD"];
env.fund(XRP(10000), "alice", "bob", gw);
env.trust(USD(600), "alice");
env.trust(USD(700), "bob");
env(pay(gw, "alice", USD(70)));
env(pay(gw, "bob", USD(50)));
STPathSet st;
STAmount sa;
std::tie(st, sa, std::ignore) = find_paths(env,
"alice", "bob", Account("bob")["USD"](5));
BEAST_EXPECT(same(st, stpath("gateway")));
BEAST_EXPECT(equal(sa, Account("alice")["USD"](5)));
}
void
xrp_to_xrp()
{
using namespace jtx;
testcase("XRP to XRP");
Env env(*this);
env.fund(XRP(10000), "alice", "bob");
auto const result = find_paths(env,
"alice", "bob", XRP(5));
BEAST_EXPECT(std::get<0>(result).empty());
}
void
path_find_consume_all()
{
testcase("path find consume all");
using namespace jtx;
{
Env env(*this);
env.fund(XRP(10000), "alice", "bob", "carol",
"dan", "edward");
env.trust(Account("alice")["USD"](10), "bob");
env.trust(Account("bob")["USD"](10), "carol");
env.trust(Account("carol")["USD"](10), "edward");
env.trust(Account("alice")["USD"](100), "dan");
env.trust(Account("dan")["USD"](100), "edward");
STPathSet st;
STAmount sa;
STAmount da;
std::tie(st, sa, da) = find_paths(env,
"alice", "edward", Account("edward")["USD"](-1));
BEAST_EXPECT(same(st, stpath("dan"), stpath("bob", "carol")));
BEAST_EXPECT(equal(sa, Account("alice")["USD"](110)));
BEAST_EXPECT(equal(da, Account("edward")["USD"](110)));
}
{
Env env(*this);
auto const gw = Account("gateway");
auto const USD = gw["USD"];
env.fund(XRP(10000), "alice", "bob", "carol", gw);
env.trust(USD(100), "bob", "carol");
env(pay(gw, "carol", USD(100)));
env(offer("carol", XRP(100), USD(100)));
STPathSet st;
STAmount sa;
STAmount da;
std::tie(st, sa, da) = find_paths(env,
"alice", "bob", Account("bob")["AUD"](-1),
boost::optional<STAmount>(XRP(100000000)));
BEAST_EXPECT(st.empty());
std::tie(st, sa, da) = find_paths(env,
"alice", "bob", Account("bob")["USD"](-1),
boost::optional<STAmount>(XRP(100000000)));
BEAST_EXPECT(sa == XRP(100));
BEAST_EXPECT(equal(da, Account("bob")["USD"](100)));
}
}
void
alternative_path_consume_both()
{
testcase("alternative path consume both");
using namespace jtx;
Env env(*this);
auto const gw = Account("gateway");
auto const USD = gw["USD"];
auto const gw2 = Account("gateway2");
auto const gw2_USD = gw2["USD"];
env.fund(XRP(10000), "alice", "bob", gw, gw2);
env.trust(USD(600), "alice");
env.trust(gw2_USD(800), "alice");
env.trust(USD(700), "bob");
env.trust(gw2_USD(900), "bob");
env(pay(gw, "alice", USD(70)));
env(pay(gw2, "alice", gw2_USD(70)));
env(pay("alice", "bob", Account("bob")["USD"](140)),
paths(Account("alice")["USD"]));
env.require(balance("alice", USD(0)));
env.require(balance("alice", gw2_USD(0)));
env.require(balance("bob", USD(70)));
env.require(balance("bob", gw2_USD(70)));
env.require(balance(gw, Account("alice")["USD"](0)));
env.require(balance(gw, Account("bob")["USD"](-70)));
env.require(balance(gw2, Account("alice")["USD"](0)));
env.require(balance(gw2, Account("bob")["USD"](-70)));
}
void
alternative_paths_consume_best_transfer()
{
testcase("alternative paths consume best transfer");
using namespace jtx;
Env env(*this);
auto const gw = Account("gateway");
auto const USD = gw["USD"];
auto const gw2 = Account("gateway2");
auto const gw2_USD = gw2["USD"];
env.fund(XRP(10000), "alice", "bob", gw, gw2);
env(rate(gw2, 1.1));
env.trust(USD(600), "alice");
env.trust(gw2_USD(800), "alice");
env.trust(USD(700), "bob");
env.trust(gw2_USD(900), "bob");
env(pay(gw, "alice", USD(70)));
env(pay(gw2, "alice", gw2_USD(70)));
env(pay("alice", "bob", USD(70)));
env.require(balance("alice", USD(0)));
env.require(balance("alice", gw2_USD(70)));
env.require(balance("bob", USD(70)));
env.require(balance("bob", gw2_USD(0)));
env.require(balance(gw, Account("alice")["USD"](0)));
env.require(balance(gw, Account("bob")["USD"](-70)));
env.require(balance(gw2, Account("alice")["USD"](-70)));
env.require(balance(gw2, Account("bob")["USD"](0)));
}
void
alternative_paths_consume_best_transfer_first()
{
testcase("alternative paths - consume best transfer first");
using namespace jtx;
Env env(*this);
auto const gw = Account("gateway");
auto const USD = gw["USD"];
auto const gw2 = Account("gateway2");
auto const gw2_USD = gw2["USD"];
env.fund(XRP(10000), "alice", "bob", gw, gw2);
env(rate(gw2, 1.1));
env.trust(USD(600), "alice");
env.trust(gw2_USD(800), "alice");
env.trust(USD(700), "bob");
env.trust(gw2_USD(900), "bob");
env(pay(gw, "alice", USD(70)));
env(pay(gw2, "alice", gw2_USD(70)));
env(pay("alice", "bob", Account("bob")["USD"](77)),
sendmax(Account("alice")["USD"](100)),
paths(Account("alice")["USD"]));
env.require(balance("alice", USD(0)));
env.require(balance("alice", gw2_USD(62.3)));
env.require(balance("bob", USD(70)));
env.require(balance("bob", gw2_USD(7)));
env.require(balance(gw, Account("alice")["USD"](0)));
env.require(balance(gw, Account("bob")["USD"](-70)));
env.require(balance(gw2, Account("alice")["USD"](-62.3)));
env.require(balance(gw2, Account("bob")["USD"](-7)));
}
void
alternative_paths_limit_returned_paths_to_best_quality()
{
testcase("alternative paths - limit returned paths to best quality");
using namespace jtx;
Env env(*this);
auto const gw = Account("gateway");
auto const USD = gw["USD"];
auto const gw2 = Account("gateway2");
auto const gw2_USD = gw2["USD"];
env.fund(XRP(10000), "alice", "bob", "carol", "dan", gw, gw2);
env(rate("carol", 1.1));
env.trust(Account("carol")["USD"](800), "alice", "bob");
env.trust(Account("dan")["USD"](800), "alice", "bob");
env.trust(USD(800), "alice", "bob");
env.trust(gw2_USD(800), "alice", "bob");
env.trust(Account("alice")["USD"](800), "dan");
env.trust(Account("bob")["USD"](800), "dan");
env(pay(gw2, "alice", gw2_USD(100)));
env(pay("carol", "alice", Account("carol")["USD"](100)));
env(pay(gw, "alice", USD(100)));
STPathSet st;
STAmount sa;
std::tie(st, sa, std::ignore) = find_paths(env,
"alice", "bob", Account("bob")["USD"](5));
BEAST_EXPECT(same(st, stpath("gateway"), stpath("gateway2"),
stpath("dan"), stpath("carol")));
BEAST_EXPECT(equal(sa, Account("alice")["USD"](5)));
}
void
issues_path_negative_issue()
{
testcase("path negative: Issue #5");
using namespace jtx;
Env env(*this);
env.fund(XRP(10000), "alice", "bob", "carol", "dan");
env.trust(Account("bob")["USD"](100), "alice", "carol", "dan");
env.trust(Account("alice")["USD"](100), "dan");
env.trust(Account("carol")["USD"](100), "dan");
env(pay("bob", "carol", Account("bob")["USD"](75)));
env.require(balance("bob", Account("carol")["USD"](-75)));
env.require(balance("carol", Account("bob")["USD"](75)));
auto result = find_paths(env,
"alice", "bob", Account("bob")["USD"](25));
BEAST_EXPECT(std::get<0>(result).empty());
env(pay("alice", "bob", Account("alice")["USD"](25)),
ter(tecPATH_DRY));
result = find_paths(env,
"alice", "bob", Account("alice")["USD"](25));
BEAST_EXPECT(std::get<0>(result).empty());
env.require(balance("alice", Account("bob")["USD"](0)));
env.require(balance("alice", Account("dan")["USD"](0)));
env.require(balance("bob", Account("alice")["USD"](0)));
env.require(balance("bob", Account("carol")["USD"](-75)));
env.require(balance("bob", Account("dan")["USD"](0)));
env.require(balance("carol", Account("bob")["USD"](75)));
env.require(balance("carol", Account("dan")["USD"](0)));
env.require(balance("dan", Account("alice")["USD"](0)));
env.require(balance("dan", Account("bob")["USD"](0)));
env.require(balance("dan", Account("carol")["USD"](0)));
}
// alice -- limit 40 --> bob
// alice --> carol --> dan --> bob
// Balance of 100 USD Bob - Balance of 37 USD -> Rod
void
issues_path_negative_ripple_client_issue_23_smaller()
{
testcase("path negative: ripple-client issue #23: smaller");
using namespace jtx;
Env env(*this);
env.fund(XRP(10000), "alice", "bob", "carol", "dan");
env.trust(Account("alice")["USD"](40), "bob");
env.trust(Account("dan")["USD"](20), "bob");
env.trust(Account("alice")["USD"](20), "carol");
env.trust(Account("carol")["USD"](20), "dan");
env(pay("alice", "bob", Account("bob")["USD"](55)),
paths(Account("alice")["USD"]));
env.require(balance("bob", Account("alice")["USD"](40)));
env.require(balance("bob", Account("dan")["USD"](15)));
}
// alice -120 USD-> edward -25 USD-> bob
// alice -25 USD-> carol -75 USD -> dan -100 USD-> bob
void
issues_path_negative_ripple_client_issue_23_larger()
{
testcase("path negative: ripple-client issue #23: larger");
using namespace jtx;
Env env(*this);
env.fund(XRP(10000), "alice", "bob", "carol", "dan", "edward");
env.trust(Account("alice")["USD"](120), "edward");
env.trust(Account("edward")["USD"](25), "bob");
env.trust(Account("dan")["USD"](100), "bob");
env.trust(Account("alice")["USD"](25), "carol");
env.trust(Account("carol")["USD"](75), "dan");
env(pay("alice", "bob", Account("bob")["USD"](50)),
paths(Account("alice")["USD"]));
env.require(balance("alice", Account("edward")["USD"](-25)));
env.require(balance("alice", Account("carol")["USD"](-25)));
env.require(balance("bob", Account("edward")["USD"](25)));
env.require(balance("bob", Account("dan")["USD"](25)));
env.require(balance("carol", Account("alice")["USD"](25)));
env.require(balance("carol", Account("dan")["USD"](-25)));
env.require(balance("dan", Account("carol")["USD"](25)));
env.require(balance("dan", Account("bob")["USD"](-25)));
}
// carol holds gateway AUD, sells gateway AUD for XRP
// bob will hold gateway AUD
// alice pays bob gateway AUD using XRP
void
via_offers_via_gateway()
{
testcase("via gateway");
using namespace jtx;
Env env(*this);
auto const gw = Account("gateway");
auto const AUD = gw["AUD"];
env.fund(XRP(10000), "alice", "bob", "carol", gw);
env(rate(gw, 1.1));
env.trust(AUD(100), "bob", "carol");
env(pay(gw, "carol", AUD(50)));
env(offer("carol", XRP(50), AUD(50)));
env(pay("alice", "bob", AUD(10)), sendmax(XRP(100)), paths(XRP));
env.require(balance("bob", AUD(10)));
env.require(balance("carol", AUD(39)));
auto const result = find_paths(env,
"alice", "bob", Account("bob")["USD"](25));
BEAST_EXPECT(std::get<0>(result).empty());
}
void
indirect_paths_path_find()
{
testcase("path find");
using namespace jtx;
Env env(*this);
env.fund(XRP(10000), "alice", "bob", "carol");
env.trust(Account("alice")["USD"](1000), "bob");
env.trust(Account("bob")["USD"](1000), "carol");
STPathSet st;
STAmount sa;
std::tie(st, sa, std::ignore) = find_paths(env,
"alice", "carol", Account("carol")["USD"](5));
BEAST_EXPECT(same(st, stpath("bob")));
BEAST_EXPECT(equal(sa, Account("alice")["USD"](5)));
}
void
quality_paths_quality_set_and_test()
{
testcase("quality set and test");
using namespace jtx;
Env env(*this);
env.fund(XRP(10000), "alice", "bob");
env(trust("bob", Account("alice")["USD"](1000)),
json("{\"" + sfQualityIn.fieldName + "\": 2000}"),
json("{\"" + sfQualityOut.fieldName + "\": 1400000000}"));
Json::Value jv;
Json::Reader().parse(R"({
"Balance" : {
"currency" : "USD",
"issuer" : "rrrrrrrrrrrrrrrrrrrrBZbvji",
"value" : "0"
},
"Flags" : 131072,
"HighLimit" : {
"currency" : "USD",
"issuer" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
"value" : "1000"
},
"HighNode" : "0000000000000000",
"HighQualityIn" : 2000,
"HighQualityOut" : 1400000000,
"LedgerEntryType" : "RippleState",
"LowLimit" : {
"currency" : "USD",
"issuer" : "rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn",
"value" : "0"
},
"LowNode" : "0000000000000000"
})", jv);
auto const jv_l = env.le(keylet::line(Account("bob").id(),
Account("alice")["USD"].issue()))->getJson(0);
for (auto it = jv.begin(); it != jv.end(); ++it)
BEAST_EXPECT(*it == jv_l[it.memberName()]);
}
void
trust_auto_clear_trust_normal_clear()
{
testcase("trust normal clear");
using namespace jtx;
Env env(*this);
env.fund(XRP(10000), "alice", "bob");
env.trust(Account("bob")["USD"](1000), "alice");
env.trust(Account("alice")["USD"](1000), "bob");
Json::Value jv;
Json::Reader().parse(R"({
"Balance" : {
"currency" : "USD",
"issuer" : "rrrrrrrrrrrrrrrrrrrrBZbvji",
"value" : "0"
},
"Flags" : 196608,
"HighLimit" : {
"currency" : "USD",
"issuer" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
"value" : "1000"
},
"HighNode" : "0000000000000000",
"LedgerEntryType" : "RippleState",
"LowLimit" : {
"currency" : "USD",
"issuer" : "rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn",
"value" : "1000"
},
"LowNode" : "0000000000000000"
})", jv);
auto const jv_l = env.le(keylet::line(Account("bob").id(),
Account("alice")["USD"].issue()))->getJson(0);
for (auto it = jv.begin(); it != jv.end(); ++it)
BEAST_EXPECT(*it == jv_l[it.memberName()]);
env.trust(Account("bob")["USD"](0), "alice");
env.trust(Account("alice")["USD"](0), "bob");
BEAST_EXPECT(env.le(keylet::line(Account("bob").id(),
Account("alice")["USD"].issue())) == nullptr);
}
void
trust_auto_clear_trust_auto_clear()
{
testcase("trust auto clear");
using namespace jtx;
Env env(*this);
env.fund(XRP(10000), "alice", "bob");
env.trust(Account("bob")["USD"](1000), "alice");
env(pay("bob", "alice", Account("bob")["USD"](50)));
env.trust(Account("bob")["USD"](0), "alice");
Json::Value jv;
Json::Reader().parse(R"({
"Balance" :
{
"currency" : "USD",
"issuer" : "rrrrrrrrrrrrrrrrrrrrBZbvji",
"value" : "50"
},
"Flags" : 65536,
"HighLimit" :
{
"currency" : "USD",
"issuer" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
"value" : "0"
},
"HighNode" : "0000000000000000",
"LedgerEntryType" : "RippleState",
"LowLimit" :
{
"currency" : "USD",
"issuer" : "rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn",
"value" : "0"
},
"LowNode" : "0000000000000000"
})", jv);
auto const jv_l = env.le(keylet::line(Account("alice").id(),
Account("bob")["USD"].issue()))->getJson(0);
for (auto it = jv.begin(); it != jv.end(); ++it)
BEAST_EXPECT(*it == jv_l[it.memberName()]);
env(pay("alice", "bob", Account("alice")["USD"](50)));
BEAST_EXPECT(env.le(keylet::line(Account("alice").id(),
Account("bob")["USD"].issue())) == nullptr);
}
void
run()
{
source_currencies_limit();
no_direct_path_no_intermediary_no_alternatives();
direct_path_no_intermediary();
payment_auto_path_find();
path_find();
path_find_consume_all();
alternative_path_consume_both();
alternative_paths_consume_best_transfer();
alternative_paths_consume_best_transfer_first();
alternative_paths_limit_returned_paths_to_best_quality();
issues_path_negative_issue();
issues_path_negative_ripple_client_issue_23_smaller();
issues_path_negative_ripple_client_issue_23_larger();
via_offers_via_gateway();
indirect_paths_path_find();
quality_paths_quality_set_and_test();
trust_auto_clear_trust_normal_clear();
trust_auto_clear_trust_auto_clear();
xrp_to_xrp();
}
};
BEAST_DEFINE_TESTSUITE(Path,app,ripple);
} // test
} // ripple

View File

@@ -0,0 +1,840 @@
//------------------------------------------------------------------------------
/*
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/basics/chrono.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/JsonFields.h>
#include <ripple/protocol/PayChan.h>
#include <ripple/protocol/TxFlags.h>
#include <ripple/test/jtx.h>
#include <chrono>
namespace ripple
{
namespace test
{
struct PayChan_test : public beast::unit_test::suite
{
static
uint256
channel (ReadView const& view,
jtx::Account const& account,
jtx::Account const& dst)
{
auto const sle = view.read (keylet::account (account));
if (!sle)
return beast::zero;
auto const k = keylet::payChan (account, dst, (*sle)[sfSequence] - 1);
return k.key;
}
static Buffer
signClaimAuth (PublicKey const& pk,
SecretKey const& sk,
uint256 const& channel,
STAmount const& authAmt)
{
Serializer msg;
serializePayChanAuthorization (msg, channel, authAmt.xrp ());
return sign (pk, sk, msg.slice ());
}
static
STAmount
channelBalance (ReadView const& view, uint256 const& chan)
{
auto const slep = view.read ({ltPAYCHAN, chan});
if (!slep)
return XRPAmount{-1};
return (*slep)[sfBalance];
}
static
bool
channelExists (ReadView const& view, uint256 const& chan)
{
auto const slep = view.read ({ltPAYCHAN, chan});
return bool(slep);
}
static
STAmount
channelAmount (ReadView const& view, uint256 const& chan)
{
auto const slep = view.read ({ltPAYCHAN, chan});
if (!slep)
return XRPAmount{-1};
return (*slep)[sfAmount];
}
static
boost::optional<std::int64_t>
channelExpiration (ReadView const& view, uint256 const& chan)
{
auto const slep = view.read ({ltPAYCHAN, chan});
if (!slep)
return boost::none;
if (auto const r = (*slep)[~sfExpiration])
return r.value();
return boost::none;
}
static Json::Value
create (jtx::Account const& account,
jtx::Account const& to,
STAmount const& amount,
NetClock::duration const& settleDelay,
PublicKey const& pk,
boost::optional<NetClock::time_point> const& cancelAfter = boost::none,
boost::optional<std::uint32_t> const& dstTag = boost::none)
{
using namespace jtx;
Json::Value jv;
jv[jss::TransactionType] = "PaymentChannelCreate";
jv[jss::Flags] = tfUniversal;
jv[jss::Account] = account.human ();
jv[jss::Destination] = to.human ();
jv[jss::Amount] = amount.getJson (0);
jv["SettleDelay"] = settleDelay.count ();
jv["PublicKey"] = strHex (pk.slice ());
if (cancelAfter)
jv["CancelAfter"] = cancelAfter->time_since_epoch ().count ();
if (dstTag)
jv["DestinationTag"] = *dstTag;
return jv;
}
static
Json::Value
fund (jtx::Account const& account,
uint256 const& channel,
STAmount const& amount,
boost::optional<NetClock::time_point> const& expiration = boost::none)
{
using namespace jtx;
Json::Value jv;
jv[jss::TransactionType] = "PaymentChannelFund";
jv[jss::Flags] = tfUniversal;
jv[jss::Account] = account.human ();
jv["Channel"] = to_string (channel);
jv[jss::Amount] = amount.getJson (0);
if (expiration)
jv["Expiration"] = expiration->time_since_epoch ().count ();
return jv;
}
static
Json::Value
claim (jtx::Account const& account,
uint256 const& channel,
boost::optional<STAmount> const& balance = boost::none,
boost::optional<STAmount> const& amount = boost::none,
boost::optional<Slice> const& signature = boost::none,
boost::optional<PublicKey> const& pk = boost::none)
{
using namespace jtx;
Json::Value jv;
jv[jss::TransactionType] = "PaymentChannelClaim";
jv[jss::Flags] = tfUniversal;
jv[jss::Account] = account.human ();
jv["Channel"] = to_string (channel);
if (amount)
jv[jss::Amount] = amount->getJson (0);
if (balance)
jv["Balance"] = balance->getJson (0);
if (signature)
jv["Signature"] = strHex (*signature);
if (pk)
jv["PublicKey"] = strHex (pk->slice ());
return jv;
}
void
testSimple ()
{
testcase ("simple");
using namespace jtx;
using namespace std::literals::chrono_literals;
Env env (*this, features (featurePayChan));
auto const alice = Account ("alice");
auto const bob = Account ("bob");
auto USDA = alice["USD"];
env.fund (XRP (10000), alice, bob);
auto const pk = alice.pk ();
auto const settleDelay = 100s;
env (create (alice, bob, XRP (1000), settleDelay, pk));
auto const chan = channel (*env.current (), alice, bob);
BEAST_EXPECT (channelBalance (*env.current (), chan) == XRP (0));
BEAST_EXPECT (channelAmount (*env.current (), chan) == XRP (1000));
{
auto const preAlice = env.balance (alice);
env (fund (alice, chan, XRP (1000)));
auto const feeDrops = env.current ()->fees ().base;
BEAST_EXPECT (env.balance (alice) == preAlice - XRP (1000) - feeDrops);
}
auto chanBal = channelBalance (*env.current (), chan);
auto chanAmt = channelAmount (*env.current (), chan);
BEAST_EXPECT (chanBal == XRP (0));
BEAST_EXPECT (chanAmt == XRP (2000));
{
// bad amounts (non-xrp, negative amounts)
env (create (alice, bob, USDA (1000), settleDelay, pk),
ter (temBAD_AMOUNT));
env (fund (alice, chan, USDA (1000)),
ter (temBAD_AMOUNT));
env (create (alice, bob, XRP (-1000), settleDelay, pk),
ter (temBAD_AMOUNT));
env (fund (alice, chan, XRP (-1000)),
ter (temBAD_AMOUNT));
}
// invalid account
env (create (alice, "noAccount", XRP (1000), settleDelay, pk),
ter (tecNO_DST));
// can't create channel to the same account
env (create (alice, alice, XRP (1000), settleDelay, pk),
ter (temDST_IS_SRC));
// invalid channel
env (fund (alice, channel (*env.current (), alice, "noAccount"), XRP (1000)),
ter (tecNO_ENTRY));
// not enough funds
env (create (alice, bob, XRP (10000), settleDelay, pk),
ter (tecUNFUNDED));
{
// No signature claim with bad amounts (negative and non-xrp)
auto const iou = USDA (100).value ();
auto const negXRP = XRP (-100).value ();
auto const posXRP = XRP (100).value ();
env (claim (alice, chan, iou, iou), ter (temBAD_AMOUNT));
env (claim (alice, chan, posXRP, iou), ter (temBAD_AMOUNT));
env (claim (alice, chan, iou, posXRP), ter (temBAD_AMOUNT));
env (claim (alice, chan, negXRP, negXRP), ter (temBAD_AMOUNT));
env (claim (alice, chan, posXRP, negXRP), ter (temBAD_AMOUNT));
env (claim (alice, chan, negXRP, posXRP), ter (temBAD_AMOUNT));
}
{
// No signature claim more than authorized
auto const delta = XRP (500);
auto const reqBal = chanBal + delta;
auto const authAmt = reqBal + XRP (-100);
assert (reqBal <= chanAmt);
env (claim (alice, chan, reqBal, authAmt), ter (tecNO_PERMISSION));
}
{
// No signature needed since the owner is claiming
auto const preBob = env.balance (bob);
auto const delta = XRP (500);
auto const reqBal = chanBal + delta;
auto const authAmt = reqBal + XRP (100);
assert (reqBal <= chanAmt);
env (claim (alice, chan, reqBal, authAmt));
BEAST_EXPECT (channelBalance (*env.current (), chan) == reqBal);
BEAST_EXPECT (channelAmount (*env.current (), chan) == chanAmt);
BEAST_EXPECT (env.balance (bob) == preBob + delta);
chanBal = reqBal;
}
{
// Claim with signature
auto preBob = env.balance (bob);
auto const delta = XRP (500);
auto const reqBal = chanBal + delta;
auto const authAmt = reqBal + XRP (100);
assert (reqBal <= chanAmt);
auto const sig =
signClaimAuth (alice.pk (), alice.sk (), chan, authAmt);
env (claim (bob, chan, reqBal, authAmt, Slice (sig), alice.pk ()));
BEAST_EXPECT (channelBalance (*env.current (), chan) == reqBal);
BEAST_EXPECT (channelAmount (*env.current (), chan) == chanAmt);
auto const feeDrops = env.current ()->fees ().base;
BEAST_EXPECT (env.balance (bob) == preBob + delta - feeDrops);
chanBal = reqBal;
// claim again
preBob = env.balance (bob);
env (claim (bob, chan, reqBal, authAmt, Slice (sig), alice.pk ()),
ter (tecUNFUNDED_PAYMENT));
BEAST_EXPECT (channelBalance (*env.current (), chan) == chanBal);
BEAST_EXPECT (channelAmount (*env.current (), chan) == chanAmt);
BEAST_EXPECT (env.balance (bob) == preBob - feeDrops);
}
{
// Try to claim more than authorized
auto const preBob = env.balance (bob);
STAmount const authAmt = chanBal + XRP (500);
STAmount const reqAmt = authAmt + 1;
assert (reqAmt <= chanAmt);
auto const sig =
signClaimAuth (alice.pk (), alice.sk (), chan, authAmt);
env (claim (bob, chan, reqAmt, authAmt, Slice (sig), alice.pk ()),
ter (tecNO_PERMISSION));
BEAST_EXPECT (channelBalance (*env.current (), chan) == chanBal);
BEAST_EXPECT (channelAmount (*env.current (), chan) == chanAmt);
auto const feeDrops = env.current ()->fees ().base;
BEAST_EXPECT (env.balance (bob) == preBob - feeDrops);
}
// Dst tries to fund the channel
env (fund (bob, chan, XRP (1000)), ter (tecNO_PERMISSION));
BEAST_EXPECT (channelBalance (*env.current (), chan) == chanBal);
BEAST_EXPECT (channelAmount (*env.current (), chan) == chanAmt);
{
// Wrong signing key
auto const sig =
signClaimAuth (bob.pk (), bob.sk (), chan, XRP (1500));
env (claim (bob, chan, XRP (1500).value (), XRP (1500).value (),
Slice (sig), bob.pk ()),
ter (temBAD_SIGNER));
BEAST_EXPECT (channelBalance (*env.current (), chan) == chanBal);
BEAST_EXPECT (channelAmount (*env.current (), chan) == chanAmt);
}
{
// Bad signature
auto const sig =
signClaimAuth (bob.pk (), bob.sk (), chan, XRP (1500));
env (claim (bob, chan, XRP (1500).value (), XRP (1500).value (),
Slice (sig), alice.pk ()),
ter (temBAD_SIGNATURE));
BEAST_EXPECT (channelBalance (*env.current (), chan) == chanBal);
BEAST_EXPECT (channelAmount (*env.current (), chan) == chanAmt);
}
{
// Dst closes channel
auto const preAlice = env.balance (alice);
auto const preBob = env.balance (bob);
env (claim (bob, chan), txflags (tfClose));
BEAST_EXPECT (!channelExists (*env.current (), chan));
auto const feeDrops = env.current ()->fees ().base;
auto const delta = chanAmt - chanBal;
assert (delta > beast::zero);
BEAST_EXPECT (env.balance (alice) == preAlice + delta);
BEAST_EXPECT (env.balance (bob) == preBob - feeDrops);
}
}
void
testCancelAfter ()
{
testcase ("cancel after");
using namespace jtx;
using namespace std::literals::chrono_literals;
auto const alice = Account ("alice");
auto const bob = Account ("bob");
auto const carol = Account ("carol");
{
// If dst claims after cancel after, channel closes
Env env (*this, features (featurePayChan));
env.fund (XRP (10000), alice, bob);
auto const pk = alice.pk ();
auto const settleDelay = 100s;
NetClock::time_point const cancelAfter =
env.current ()->info ().parentCloseTime + 3600s;
auto const channelFunds = XRP (1000);
env (create (
alice, bob, channelFunds, settleDelay, pk, cancelAfter));
auto const chan = channel (*env.current (), alice, bob);
if (!chan)
{
fail ();
return;
}
BEAST_EXPECT (channelExists (*env.current (), chan));
env.close (cancelAfter);
{
// dst cannot claim after cancelAfter
auto const chanBal = channelBalance (*env.current (), chan);
auto const chanAmt = channelAmount (*env.current (), chan);
auto preAlice = env.balance (alice);
auto preBob = env.balance (bob);
auto const delta = XRP (500);
auto const reqBal = chanBal + delta;
auto const authAmt = reqBal + XRP (100);
assert (reqBal <= chanAmt);
auto const sig =
signClaimAuth (alice.pk (), alice.sk (), chan, authAmt);
env (claim (
bob, chan, reqBal, authAmt, Slice (sig), alice.pk ()));
auto const feeDrops = env.current ()->fees ().base;
BEAST_EXPECT (!channelExists (*env.current (), chan));
BEAST_EXPECT (env.balance (bob) == preBob - feeDrops);
BEAST_EXPECT (env.balance (alice) == preAlice + channelFunds);
}
}
{
// Third party can close after cancel after
Env env (*this, features (featurePayChan));
env.fund (XRP (10000), alice, bob, carol);
auto const pk = alice.pk ();
auto const settleDelay = 100s;
NetClock::time_point const cancelAfter =
env.current ()->info ().parentCloseTime + 3600s;
auto const channelFunds = XRP (1000);
env (create (
alice, bob, channelFunds, settleDelay, pk, cancelAfter));
auto const chan = channel (*env.current (), alice, bob);
BEAST_EXPECT (channelExists (*env.current (), chan));
// third party close before cancelAfter
env (claim (carol, chan), txflags (tfClose),
ter (tecNO_PERMISSION));
BEAST_EXPECT (channelExists (*env.current (), chan));
env.close (cancelAfter);
// third party close after cancelAfter
auto const preAlice = env.balance (alice);
env (claim (carol, chan), txflags (tfClose));
BEAST_EXPECT (!channelExists (*env.current (), chan));
BEAST_EXPECT (env.balance (alice) == preAlice + channelFunds);
}
}
void
testExpiration ()
{
testcase ("expiration");
using namespace jtx;
using namespace std::literals::chrono_literals;
Env env (*this, features (featurePayChan));
auto const alice = Account ("alice");
auto const bob = Account ("bob");
auto const carol = Account ("carol");
env.fund (XRP (10000), alice, bob, carol);
auto const pk = alice.pk ();
auto const settleDelay = 3600s;
auto const closeTime = env.current ()->info ().parentCloseTime;
auto const minExpiration = closeTime + settleDelay;
NetClock::time_point const cancelAfter = closeTime + 7200s;
auto const channelFunds = XRP (1000);
env (create (alice, bob, channelFunds, settleDelay, pk, cancelAfter));
auto const chan = channel (*env.current (), alice, bob);
BEAST_EXPECT (channelExists (*env.current (), chan));
BEAST_EXPECT (!channelExpiration (*env.current (), chan));
// Owner closes, will close after settleDelay
env (claim (alice, chan), txflags (tfClose));
auto counts = [](
auto const& t) { return t.time_since_epoch ().count (); };
BEAST_EXPECT (*channelExpiration (*env.current (), chan) ==
counts (minExpiration));
// increase the expiration time
env (fund (
alice, chan, XRP (1), NetClock::time_point{minExpiration + 100s}));
BEAST_EXPECT (*channelExpiration (*env.current (), chan) ==
counts (minExpiration) + 100);
// decrease the expiration, but still above minExpiration
env (fund (
alice, chan, XRP (1), NetClock::time_point{minExpiration + 50s}));
BEAST_EXPECT (*channelExpiration (*env.current (), chan) ==
counts (minExpiration) + 50);
// decrease the expiration below minExpiration
env (fund (alice, chan, XRP (1),
NetClock::time_point{minExpiration - 50s}),
ter (temBAD_EXPIRATION));
BEAST_EXPECT (*channelExpiration (*env.current (), chan) ==
counts (minExpiration) + 50);
env (claim (bob, chan), txflags (tfRenew), ter (tecNO_PERMISSION));
BEAST_EXPECT (*channelExpiration (*env.current (), chan) ==
counts (minExpiration) + 50);
env (claim (alice, chan), txflags (tfRenew));
BEAST_EXPECT (!channelExpiration (*env.current (), chan));
// decrease the expiration below minExpiration
env (fund (alice, chan, XRP (1),
NetClock::time_point{minExpiration - 50s}),
ter (temBAD_EXPIRATION));
BEAST_EXPECT (!channelExpiration (*env.current (), chan));
env (fund (alice, chan, XRP (1), NetClock::time_point{minExpiration}));
env.close (minExpiration);
// Try to extend the expiration after the expiration has already passed
env (fund (
alice, chan, XRP (1), NetClock::time_point{minExpiration + 1000s}));
BEAST_EXPECT (!channelExists (*env.current (), chan));
}
void
testSettleDelay ()
{
testcase ("settle delay");
using namespace jtx;
using namespace std::literals::chrono_literals;
Env env (*this, features (featurePayChan));
auto const alice = Account ("alice");
auto const bob = Account ("bob");
env.fund (XRP (10000), alice, bob);
auto const pk = alice.pk ();
auto const settleDelay = 3600s;
NetClock::time_point const settleTimepoint =
env.current ()->info ().parentCloseTime + settleDelay;
auto const channelFunds = XRP (1000);
env (create (alice, bob, channelFunds, settleDelay, pk));
auto const chan = channel (*env.current (), alice, bob);
BEAST_EXPECT (channelExists (*env.current (), chan));
// Owner closes, will close after settleDelay
env (claim (alice, chan), txflags (tfClose));
BEAST_EXPECT (channelExists (*env.current (), chan));
env.close (settleTimepoint-settleDelay/2);
{
// receiver can still claim
auto const chanBal = channelBalance (*env.current (), chan);
auto const chanAmt = channelAmount (*env.current (), chan);
auto preBob = env.balance (bob);
auto const delta = XRP (500);
auto const reqBal = chanBal + delta;
auto const authAmt = reqBal + XRP (100);
assert (reqBal <= chanAmt);
auto const sig =
signClaimAuth (alice.pk (), alice.sk (), chan, authAmt);
env (claim (bob, chan, reqBal, authAmt, Slice (sig), alice.pk ()));
BEAST_EXPECT (channelBalance (*env.current (), chan) == reqBal);
BEAST_EXPECT (channelAmount (*env.current (), chan) == chanAmt);
auto const feeDrops = env.current ()->fees ().base;
BEAST_EXPECT (env.balance (bob) == preBob + delta - feeDrops);
}
env.close (settleTimepoint);
{
// past settleTime, channel will close
auto const chanBal = channelBalance (*env.current (), chan);
auto const chanAmt = channelAmount (*env.current (), chan);
auto const preAlice = env.balance (alice);
auto preBob = env.balance (bob);
auto const delta = XRP (500);
auto const reqBal = chanBal + delta;
auto const authAmt = reqBal + XRP (100);
assert (reqBal <= chanAmt);
auto const sig =
signClaimAuth (alice.pk (), alice.sk (), chan, authAmt);
env (claim (bob, chan, reqBal, authAmt, Slice (sig), alice.pk ()));
BEAST_EXPECT (!channelExists (*env.current (), chan));
auto const feeDrops = env.current ()->fees ().base;
BEAST_EXPECT (env.balance (alice) == preAlice + chanAmt - chanBal);
BEAST_EXPECT (env.balance (bob) == preBob - feeDrops);
}
}
void
testCloseDry ()
{
testcase ("close dry");
using namespace jtx;
using namespace std::literals::chrono_literals;
Env env (*this, features (featurePayChan));
auto const alice = Account ("alice");
auto const bob = Account ("bob");
env.fund (XRP (10000), alice, bob);
auto const pk = alice.pk ();
auto const settleDelay = 3600s;
auto const channelFunds = XRP (1000);
env (create (alice, bob, channelFunds, settleDelay, pk));
auto const chan = channel (*env.current (), alice, bob);
BEAST_EXPECT (channelExists (*env.current (), chan));
// Owner tries to close channel, but it will remain open (settle delay)
env (claim (alice, chan), txflags (tfClose));
BEAST_EXPECT (channelExists (*env.current (), chan));
{
// claim the entire amount
auto const preBob = env.balance (bob);
env (claim (
alice, chan, channelFunds.value (), channelFunds.value ()));
BEAST_EXPECT (channelBalance (*env.current (), chan) == channelFunds);
BEAST_EXPECT (env.balance (bob) == preBob + channelFunds);
}
auto const preAlice = env.balance (alice);
// Channel is now dry, can close before expiration date
env (claim (alice, chan), txflags (tfClose));
BEAST_EXPECT (!channelExists (*env.current (), chan));
auto const feeDrops = env.current ()->fees ().base;
BEAST_EXPECT (env.balance (alice) == preAlice - feeDrops);
}
void
testDefaultAmount ()
{
// auth amount defaults to balance if not present
testcase ("default amount");
using namespace jtx;
using namespace std::literals::chrono_literals;
Env env (*this, features (featurePayChan));
auto const alice = Account ("alice");
auto const bob = Account ("bob");
env.fund (XRP (10000), alice, bob);
auto const pk = alice.pk ();
auto const settleDelay = 3600s;
auto const channelFunds = XRP (1000);
env (create (alice, bob, channelFunds, settleDelay, pk));
auto const chan = channel (*env.current (), alice, bob);
BEAST_EXPECT (channelExists (*env.current (), chan));
// Owner tries to close channel, but it will remain open (settle delay)
env (claim (alice, chan), txflags (tfClose));
BEAST_EXPECT (channelExists (*env.current (), chan));
{
auto chanBal = channelBalance (*env.current (), chan);
auto chanAmt = channelAmount (*env.current (), chan);
auto const preBob = env.balance (bob);
auto const delta = XRP (500);
auto const reqBal = chanBal + delta;
assert (reqBal <= chanAmt);
auto const sig =
signClaimAuth (alice.pk (), alice.sk (), chan, reqBal);
env (claim (
bob, chan, reqBal, boost::none, Slice (sig), alice.pk ()));
BEAST_EXPECT (channelBalance (*env.current (), chan) == reqBal);
auto const feeDrops = env.current ()->fees ().base;
BEAST_EXPECT (env.balance (bob) == preBob + delta - feeDrops);
chanBal = reqBal;
}
{
// Claim again
auto chanBal = channelBalance (*env.current (), chan);
auto chanAmt = channelAmount (*env.current (), chan);
auto const preBob = env.balance (bob);
auto const delta = XRP (500);
auto const reqBal = chanBal + delta;
assert (reqBal <= chanAmt);
auto const sig =
signClaimAuth (alice.pk (), alice.sk (), chan, reqBal);
env (claim (
bob, chan, reqBal, boost::none, Slice (sig), alice.pk ()));
BEAST_EXPECT (channelBalance (*env.current (), chan) == reqBal);
auto const feeDrops = env.current ()->fees ().base;
BEAST_EXPECT (env.balance (bob) == preBob + delta - feeDrops);
chanBal = reqBal;
}
}
void
testDisallowXRP ()
{
// auth amount defaults to balance if not present
testcase ("Disallow XRP");
using namespace jtx;
using namespace std::literals::chrono_literals;
{
// Create a channel where dst disallows XRP
Env env (*this, features (featurePayChan));
auto const alice = Account ("alice");
auto const bob = Account ("bob");
env.fund (XRP (10000), alice, bob);
env (fset (bob, asfDisallowXRP));
auto const pk = alice.pk ();
auto const settleDelay = 3600s;
auto const channelFunds = XRP (1000);
env (create (alice, bob, channelFunds, settleDelay, pk),
ter (tecNO_TARGET));
auto const chan = channel (*env.current (), alice, bob);
BEAST_EXPECT (!channelExists (*env.current (), chan));
}
{
// Claim to a channel where dst disallows XRP
// (channel is created before disallow xrp is set)
Env env (*this, features (featurePayChan));
auto const alice = Account ("alice");
auto const bob = Account ("bob");
env.fund (XRP (10000), alice, bob);
auto const pk = alice.pk ();
auto const settleDelay = 3600s;
auto const channelFunds = XRP (1000);
env (create (alice, bob, channelFunds, settleDelay, pk));
auto const chan = channel (*env.current (), alice, bob);
BEAST_EXPECT (channelExists (*env.current (), chan));
env (fset (bob, asfDisallowXRP));
auto const preBob = env.balance (bob);
auto const reqBal = XRP (500).value();
env (claim (alice, chan, reqBal, reqBal), ter(tecNO_TARGET));
}
}
void
testDstTag ()
{
// auth amount defaults to balance if not present
testcase ("Dst Tag");
using namespace jtx;
using namespace std::literals::chrono_literals;
// Create a channel where dst disallows XRP
Env env (*this, features (featurePayChan));
auto const alice = Account ("alice");
auto const bob = Account ("bob");
env.fund (XRP (10000), alice, bob);
env (fset (bob, asfRequireDest));
auto const pk = alice.pk ();
auto const settleDelay = 3600s;
auto const channelFunds = XRP (1000);
env (create (alice, bob, channelFunds, settleDelay, pk),
ter (tecDST_TAG_NEEDED));
BEAST_EXPECT (!channelExists (
*env.current (), channel (*env.current (), alice, bob)));
env (
create (alice, bob, channelFunds, settleDelay, pk, boost::none, 1));
BEAST_EXPECT (channelExists (
*env.current (), channel (*env.current (), alice, bob)));
}
void
testMultiple ()
{
// auth amount defaults to balance if not present
testcase ("Multiple channels to the same account");
using namespace jtx;
using namespace std::literals::chrono_literals;
Env env (*this, features (featurePayChan));
auto const alice = Account ("alice");
auto const bob = Account ("bob");
env.fund (XRP (10000), alice, bob);
auto const pk = alice.pk ();
auto const settleDelay = 3600s;
auto const channelFunds = XRP (1000);
env (create (alice, bob, channelFunds, settleDelay, pk));
auto const chan1 = channel (*env.current (), alice, bob);
BEAST_EXPECT (channelExists (*env.current (), chan1));
env (create (alice, bob, channelFunds, settleDelay, pk));
auto const chan2 = channel (*env.current (), alice, bob);
BEAST_EXPECT (channelExists (*env.current (), chan2));
BEAST_EXPECT (chan1 != chan2);
}
void
testRPC ()
{
testcase ("RPC");
using namespace jtx;
using namespace std::literals::chrono_literals;
Env env (*this, features (featurePayChan));
auto const alice = Account ("alice");
auto const bob = Account ("bob");
env.fund (XRP (10000), alice, bob);
auto const pk = alice.pk ();
auto const settleDelay = 3600s;
auto const channelFunds = XRP (1000);
env (create (alice, bob, channelFunds, settleDelay, pk));
env.close();
auto const chan1Str = to_string (channel (*env.current (), alice, bob));
std::string chan1PkStr;
{
auto const r =
env.rpc ("account_channels", alice.human (), bob.human ());
BEAST_EXPECT (r[jss::result][jss::channels].size () == 1);
BEAST_EXPECT (r[jss::result][jss::channels][0u][jss::channel_id] == chan1Str);
chan1PkStr = r[jss::result][jss::channels][0u][jss::public_key].asString();
}
{
auto const r =
env.rpc ("account_channels", bob.human (), alice.human ());
BEAST_EXPECT (r[jss::result][jss::channels].size () == 0);
}
env (create (alice, bob, channelFunds, settleDelay, pk));
env.close();
auto const chan2Str = to_string (channel (*env.current (), alice, bob));
{
auto const r =
env.rpc ("account_channels", alice.human (), bob.human ());
BEAST_EXPECT (r[jss::result][jss::channels].size () == 2);
BEAST_EXPECT (chan1Str != chan2Str);
for (auto const& c : {chan1Str, chan2Str})
BEAST_EXPECT (r[jss::result][jss::channels][0u][jss::channel_id] == c ||
r[jss::result][jss::channels][1u][jss::channel_id] == c);
}
{
// Verify chan1 auth
auto const rs =
env.rpc ("channel_authorize", "alice", chan1Str, "1000");
auto const sig = rs[jss::result][jss::signature].asString ();
BEAST_EXPECT (!sig.empty ());
auto const rv = env.rpc (
"channel_verify", chan1PkStr, chan1Str, "1000", sig);
BEAST_EXPECT (rv[jss::result][jss::signature_verified].asBool ());
}
{
// Try to verify chan2 auth with chan1 key
auto const rs =
env.rpc ("channel_authorize", "alice", chan2Str, "1000");
auto const sig = rs[jss::result][jss::signature].asString ();
BEAST_EXPECT (!sig.empty ());
auto const rv =
env.rpc ("channel_verify", chan1PkStr, chan1Str, "1000", sig);
BEAST_EXPECT (!rv[jss::result][jss::signature_verified].asBool ());
}
}
void
testOptionalFields ()
{
testcase ("Optional Fields");
using namespace jtx;
using namespace std::literals::chrono_literals;
Env env (*this, features (featurePayChan));
auto const alice = Account ("alice");
auto const bob = Account ("bob");
auto const carol = Account ("carol");
auto const dan = Account ("dan");
env.fund (XRP (10000), alice, bob, carol, dan);
auto const pk = alice.pk ();
auto const settleDelay = 3600s;
auto const channelFunds = XRP (1000);
boost::optional<NetClock::time_point> cancelAfter;
{
env (create (alice, bob, channelFunds, settleDelay, pk));
auto const chan = to_string (channel (*env.current (), alice, bob));
auto const r =
env.rpc ("account_channels", alice.human (), bob.human ());
BEAST_EXPECT (r[jss::result][jss::channels].size () == 1);
BEAST_EXPECT (r[jss::result][jss::channels][0u][jss::channel_id] == chan);
BEAST_EXPECT (!r[jss::result][jss::channels][0u].isMember(jss::destination_tag));
}
{
std::uint32_t dstTag=42;
env (create (
alice, carol, channelFunds, settleDelay, pk, cancelAfter, dstTag));
auto const chan = to_string (channel (*env.current (), alice, carol));
auto const r =
env.rpc ("account_channels", alice.human (), carol.human ());
BEAST_EXPECT (r[jss::result][jss::channels].size () == 1);
BEAST_EXPECT (r[jss::result][jss::channels][0u][jss::channel_id] == chan);
BEAST_EXPECT (r[jss::result][jss::channels][0u][jss::destination_tag] == dstTag);
}
}
void
run () override
{
testSimple ();
testCancelAfter ();
testSettleDelay ();
testExpiration ();
testCloseDry ();
testDefaultAmount ();
testDisallowXRP ();
testDstTag ();
testMultiple ();
testRPC ();
testOptionalFields ();
}
};
BEAST_DEFINE_TESTSUITE (PayChan, app, ripple);
} // test
} // ripple

View File

@@ -0,0 +1,214 @@
//------------------------------------------------------------------------------
/*
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/test/jtx.h>
#include <ripple/app/tx/apply.h>
#include <ripple/basics/StringUtilities.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/JsonFields.h>
namespace ripple {
namespace test {
struct Regression_test : public beast::unit_test::suite
{
// OfferCreate, then OfferCreate with cancel
void testOffer1()
{
using namespace jtx;
Env env(*this);
auto const gw = Account("gw");
auto const USD = gw["USD"];
env.fund(XRP(10000), "alice", gw);
env(offer("alice", USD(10), XRP(10)), require(owners("alice", 1)));
env(offer("alice", USD(20), XRP(10)), json(R"raw(
{ "OfferSequence" : 2 }
)raw"), require(owners("alice", 1)));
}
void testLowBalanceDestroy()
{
testcase("Account balance < fee destroys correct amount of XRP");
using namespace jtx;
Env env(*this);
env.memoize("alice");
// The low balance scenario can not deterministically
// be reproduced against an open ledger. Make a local
// closed ledger and work with it directly.
auto closed = std::make_shared<Ledger>(
create_genesis, env.app().config(), env.app().family());
auto expectedDrops = SYSTEM_CURRENCY_START;
BEAST_EXPECT(closed->info().drops == expectedDrops);
auto const aliceXRP = 400;
auto const aliceAmount = XRP(aliceXRP);
auto next = std::make_shared<Ledger>(
*closed,
env.app().timeKeeper().closeTime());
{
// Fund alice
auto const jt = env.jt(pay(env.master, "alice", aliceAmount));
OpenView accum(&*next);
auto const result = ripple::apply(env.app(),
accum, *jt.stx, tapNONE, env.journal);
BEAST_EXPECT(result.first == tesSUCCESS);
BEAST_EXPECT(result.second);
accum.apply(*next);
}
expectedDrops -= next->fees().base;
BEAST_EXPECT(next->info().drops == expectedDrops);
{
auto const sle = next->read(
keylet::account(Account("alice").id()));
BEAST_EXPECT(sle);
auto balance = sle->getFieldAmount(sfBalance);
BEAST_EXPECT(balance == aliceAmount );
}
{
// Specify the seq manually since the env's open ledger
// doesn't know about this account.
auto const jt = env.jt(noop("alice"), fee(expectedDrops),
seq(1));
OpenView accum(&*next);
auto const result = ripple::apply(env.app(),
accum, *jt.stx, tapNONE, env.journal);
BEAST_EXPECT(result.first == tecINSUFF_FEE);
BEAST_EXPECT(result.second);
accum.apply(*next);
}
{
auto const sle = next->read(
keylet::account(Account("alice").id()));
BEAST_EXPECT(sle);
auto balance = sle->getFieldAmount(sfBalance);
BEAST_EXPECT(balance == XRP(0));
}
expectedDrops -= aliceXRP * dropsPerXRP<int>::value;
BEAST_EXPECT(next->info().drops == expectedDrops);
}
void testSecp256r1key ()
{
testcase("Signing with a secp256r1 key should fail gracefully");
using namespace jtx;
Env env(*this);
// Test case we'll use.
auto test256r1key = [&env] (Account const& acct)
{
auto const baseFee = env.current()->fees().base;
std::uint32_t const acctSeq = env.seq (acct);
Json::Value jsonNoop = env.json (
noop (acct), fee(baseFee), seq(acctSeq), sig(acct));
JTx jt = env.jt (jsonNoop);
jt.fill_sig = false;
// Random secp256r1 public key generated by
// https://kjur.github.io/jsrsasign/sample-ecdsa.html
std::string const secp256r1PubKey =
"045d02995ec24988d9a2ae06a3733aa35ba0741e87527"
"ed12909b60bd458052c944b24cbf5893c3e5be321774e"
"5082e11c034b765861d0effbde87423f8476bb2c";
// Set the key in the JSON.
jt.jv["SigningPubKey"] = secp256r1PubKey;
// Set the same key in the STTx.
auto secp256r1Sig = std::make_unique<STTx>(*(jt.stx));
auto pubKeyBlob = strUnHex (secp256r1PubKey);
assert (pubKeyBlob.second); // Hex for public key must be valid
secp256r1Sig->setFieldVL
(sfSigningPubKey, std::move(pubKeyBlob.first));
jt.stx.reset (secp256r1Sig.release());
env (jt, ter (temINVALID));
};
Account const alice {"alice", KeyType::secp256k1};
Account const becky {"becky", KeyType::ed25519};
env.fund(XRP(10000), alice, becky);
test256r1key (alice);
test256r1key (becky);
}
void testFeeEscalationAutofill()
{
testcase("Autofilled fee should use the escalated fee");
using namespace jtx;
Env env(*this, []()
{
auto p = std::make_unique<Config>();
setupConfigForUnitTests(*p);
auto& section = p->section("transaction_queue");
section.set("minimum_txn_in_ledger_standalone", "3");
return p;
}(),
features(featureFeeEscalation));
Env_ss envs(env);
auto const alice = Account("alice");
env.fund(XRP(100000), alice);
auto params = Json::Value(Json::objectValue);
// Max fee = 50k drops
params[jss::fee_mult_max] = 5000;
std::vector<int> const
expectedFees({ 10, 10, 8889, 13889, 20000 });
// We should be able to submit 5 transactions within
// our fee limit.
for (int i = 0; i < 5; ++i)
{
envs(noop(alice), fee(none), seq(none))(params);
auto tx = env.tx();
if (BEAST_EXPECT(tx))
{
BEAST_EXPECT(tx->getAccountID(sfAccount) == alice.id());
BEAST_EXPECT(tx->getTxnType() == ttACCOUNT_SET);
auto const fee = tx->getFieldAmount(sfFee);
BEAST_EXPECT(fee == drops(expectedFees[i]));
}
}
}
void run() override
{
testOffer1();
testLowBalanceDestroy();
testSecp256r1key();
testFeeEscalationAutofill();
}
};
BEAST_DEFINE_TESTSUITE(Regression,app,ripple);
} // test
} // ripple

View File

@@ -0,0 +1,640 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012-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/app/main/Application.h>
#include <ripple/app/misc/SHAMapStore.h>
#include <ripple/core/ConfigSections.h>
#include <ripple/core/DatabaseCon.h>
#include <ripple/core/SociDB.h>
#include <ripple/protocol/JsonFields.h>
#include <ripple/test/jtx.h>
namespace ripple {
namespace test {
class SHAMapStore_test : public beast::unit_test::suite
{
static auto const deleteInterval = 8;
static
std::unique_ptr<Config>
makeConfig()
{
auto p = std::make_unique<Config>();
setupConfigForUnitTests(*p);
p->LEDGER_HISTORY = deleteInterval;
auto& section = p->section(ConfigSection::nodeDatabase());
section.set("online_delete", to_string(deleteInterval));
//section.set("age_threshold", "60");
return p;
}
static
std::unique_ptr<Config>
makeConfigAdvisory()
{
auto p = makeConfig();
auto& section = p->section(ConfigSection::nodeDatabase());
section.set("advisory_delete", "1");
return p;
}
bool goodLedger(jtx::Env& env, Json::Value const& json,
std::string ledgerID, bool checkDB = false)
{
auto good = json.isMember(jss::result)
&& !RPC::contains_error(json[jss::result])
&& json[jss::result][jss::ledger][jss::ledger_index] == ledgerID;
if (!good || !checkDB)
return good;
auto const seq = json[jss::result][jss::ledger_index].asUInt();
std::string outHash;
LedgerIndex outSeq;
std::string outParentHash;
std::string outDrops;
std::uint64_t outCloseTime;
std::uint64_t outParentCloseTime;
std::uint64_t outCloseTimeResolution;
std::uint64_t outCloseFlags;
std::string outAccountHash;
std::string outTxHash;
{
auto db = env.app().getLedgerDB().checkoutDb();
*db << "SELECT LedgerHash,LedgerSeq,PrevHash,TotalCoins, "
"ClosingTime,PrevClosingTime,CloseTimeRes,CloseFlags, "
"AccountSetHash,TransSetHash "
"FROM Ledgers "
"WHERE LedgerSeq = :seq",
soci::use(seq),
soci::into(outHash),
soci::into(outSeq),
soci::into(outParentHash),
soci::into(outDrops),
soci::into(outCloseTime),
soci::into(outParentCloseTime),
soci::into(outCloseTimeResolution),
soci::into(outCloseFlags),
soci::into(outAccountHash),
soci::into(outTxHash);
}
auto const& ledger = json[jss::result][jss::ledger];
return outHash == ledger[jss::hash].asString() &&
outSeq == seq &&
outParentHash == ledger[jss::parent_hash].asString() &&
outDrops == ledger[jss::total_coins].asString() &&
outCloseTime == ledger[jss::close_time].asUInt() &&
outParentCloseTime == ledger[jss::parent_close_time].asUInt() &&
outCloseTimeResolution == ledger[jss::close_time_resolution].asUInt() &&
outCloseFlags == ledger[jss::close_flags].asUInt() &&
outAccountHash == ledger[jss::account_hash].asString() &&
outTxHash == ledger[jss::transaction_hash].asString();
}
bool bad(Json::Value const& json, error_code_i error = rpcLGR_NOT_FOUND)
{
return json.isMember(jss::result)
&& RPC::contains_error(json[jss::result])
&& json[jss::result][jss::error_code] == error;
}
std::string getHash(Json::Value const& json)
{
BEAST_EXPECT(json.isMember(jss::result) &&
json[jss::result].isMember(jss::ledger) &&
json[jss::result][jss::ledger].isMember(jss::hash) &&
json[jss::result][jss::ledger][jss::hash].isString());
return json[jss::result][jss::ledger][jss::hash].asString();
}
void validationCheck(jtx::Env& env, int const expected)
{
auto db = env.app().getLedgerDB().checkoutDb();
int actual;
*db << "SELECT count(*) AS rows FROM Validations;",
soci::into(actual);
BEAST_EXPECT(actual == expected);
}
void ledgerCheck(jtx::Env& env, int const rows,
int const first)
{
auto db = env.app().getLedgerDB().checkoutDb();
int actualRows, actualFirst, actualLast;
*db << "SELECT count(*) AS rows, "
"min(LedgerSeq) as first, "
"max(LedgerSeq) as last "
"FROM Ledgers;",
soci::into(actualRows),
soci::into(actualFirst),
soci::into(actualLast);
BEAST_EXPECT(actualRows == rows);
BEAST_EXPECT(actualFirst == first);
BEAST_EXPECT(actualLast == first + rows - 1);
}
void transactionCheck(jtx::Env& env, int const rows)
{
auto db = env.app().getTxnDB().checkoutDb();
int actualRows;
*db << "SELECT count(*) AS rows "
"FROM Transactions;",
soci::into(actualRows);
BEAST_EXPECT(actualRows == rows);
}
void accountTransactionCheck(jtx::Env& env, int const rows)
{
auto db = env.app().getTxnDB().checkoutDb();
int actualRows;
*db << "SELECT count(*) AS rows "
"FROM AccountTransactions;",
soci::into(actualRows);
BEAST_EXPECT(actualRows == rows);
}
int waitForReady(jtx::Env& env)
{
using namespace std::chrono_literals;
auto& store = env.app().getSHAMapStore();
int ledgerSeq = 3;
store.rendezvous();
BEAST_EXPECT(!store.getLastRotated());
env.close();
store.rendezvous();
auto ledger = env.rpc("ledger", "validated");
BEAST_EXPECT(goodLedger(env, ledger, to_string(ledgerSeq++)));
BEAST_EXPECT(store.getLastRotated() == ledgerSeq - 1);
return ledgerSeq;
}
public:
void testClear()
{
using namespace std::chrono_literals;
testcase("clearPrior");
using namespace jtx;
Env env(*this, makeConfig());
auto& store = env.app().getSHAMapStore();
env.fund(XRP(10000), noripple("alice"));
validationCheck(env, 0);
ledgerCheck(env, 1, 2);
transactionCheck(env, 0);
accountTransactionCheck(env, 0);
std::map<std::uint32_t, Json::Value const> ledgers;
auto ledgerTmp = env.rpc("ledger", "0");
BEAST_EXPECT(bad(ledgerTmp));
ledgers.emplace(std::make_pair(1, env.rpc("ledger", "1")));
BEAST_EXPECT(goodLedger(env, ledgers[1], "1"));
ledgers.emplace(std::make_pair(2, env.rpc("ledger", "2")));
BEAST_EXPECT(goodLedger(env, ledgers[2], "2"));
ledgerTmp = env.rpc("ledger", "current");
BEAST_EXPECT(goodLedger(env, ledgerTmp, "3"));
ledgerTmp = env.rpc("ledger", "4");
BEAST_EXPECT(bad(ledgerTmp));
ledgerTmp = env.rpc("ledger", "100");
BEAST_EXPECT(bad(ledgerTmp));
auto const firstSeq = waitForReady(env);
auto lastRotated = firstSeq - 1;
for (auto i = firstSeq + 1; i < deleteInterval + firstSeq; ++i)
{
env.fund(XRP(10000), noripple("test" + to_string(i)));
env.close();
ledgerTmp = env.rpc("ledger", "current");
BEAST_EXPECT(goodLedger(env, ledgerTmp, to_string(i)));
}
BEAST_EXPECT(store.getLastRotated() == lastRotated);
for (auto i = 3; i < deleteInterval + lastRotated; ++i)
{
ledgers.emplace(std::make_pair(i,
env.rpc("ledger", to_string(i))));
BEAST_EXPECT(goodLedger(env, ledgers[i], to_string(i), true) &&
getHash(ledgers[i]).length());
}
validationCheck(env, 0);
ledgerCheck(env, deleteInterval + 1, 2);
transactionCheck(env, deleteInterval);
accountTransactionCheck(env, 2 * deleteInterval);
{
// Since standalone doesn't _do_ validations, manually
// insert some into the table. Create some with the
// hashes from our real ledgers, and some with fake
// hashes to represent validations that never ended up
// in a validated ledger.
char lh[65];
memset(lh, 'a', 64);
lh[64] = '\0';
std::vector<std::string> preSeqLedgerHashes({
lh
});
std::vector<std::string> badLedgerHashes;
std::vector<LedgerIndex> badLedgerSeqs;
std::vector<std::string> ledgerHashes;
std::vector<LedgerIndex> ledgerSeqs;
for (auto const& lgr : ledgers)
{
ledgerHashes.emplace_back(getHash(lgr.second));
ledgerSeqs.emplace_back(lgr.second[jss::result][jss::ledger_index].asUInt());
}
for (auto i = 0; i < 10; ++i)
{
++lh[30];
preSeqLedgerHashes.emplace_back(lh);
++lh[20];
badLedgerHashes.emplace_back(lh);
badLedgerSeqs.emplace_back(i + 1);
}
auto db = env.app().getLedgerDB().checkoutDb();
// Pre-migration validation - no sequence numbers.
*db << "INSERT INTO Validations "
"(LedgerHash) "
"VALUES "
"(:ledgerHash);",
soci::use(preSeqLedgerHashes);
// Post-migration orphan validation - InitalSeq,
// but no LedgerSeq
*db << "INSERT INTO Validations "
"(LedgerHash, InitialSeq) "
"VALUES "
"(:ledgerHash, :initialSeq);",
soci::use(badLedgerHashes),
soci::use(badLedgerSeqs);
// Post-migration validated ledger.
*db << "INSERT INTO Validations "
"(LedgerHash, LedgerSeq) "
"VALUES "
"(:ledgerHash, :ledgerSeq);",
soci::use(ledgerHashes),
soci::use(ledgerSeqs);
}
validationCheck(env, deleteInterval + 23);
ledgerCheck(env, deleteInterval + 1, 2);
transactionCheck(env, deleteInterval);
accountTransactionCheck(env, 2 * deleteInterval);
{
// Closing one more ledger triggers a rotate
env.close();
auto ledger = env.rpc("ledger", "current");
BEAST_EXPECT(goodLedger(env, ledger, to_string(deleteInterval + 4)));
}
store.rendezvous();
BEAST_EXPECT(store.getLastRotated() == deleteInterval + 3);
lastRotated = store.getLastRotated();
BEAST_EXPECT(lastRotated == 11);
// That took care of the fake hashes
validationCheck(env, deleteInterval + 8);
ledgerCheck(env, deleteInterval + 1, 3);
transactionCheck(env, deleteInterval);
accountTransactionCheck(env, 2 * deleteInterval);
// The last iteration of this loop should trigger a rotate
for (auto i = lastRotated - 1; i < lastRotated + deleteInterval - 1; ++i)
{
validationCheck(env, deleteInterval + i + 1 - lastRotated + 8);
env.close();
ledgerTmp = env.rpc("ledger", "current");
BEAST_EXPECT(goodLedger(env, ledgerTmp, to_string(i + 3)));
ledgers.emplace(std::make_pair(i,
env.rpc("ledger", to_string(i))));
BEAST_EXPECT(store.getLastRotated() == lastRotated ||
i == lastRotated + deleteInterval - 2);
BEAST_EXPECT(goodLedger(env, ledgers[i], to_string(i), true) &&
getHash(ledgers[i]).length());
std::vector<std::string> ledgerHashes({
getHash(ledgers[i])
});
std::vector<LedgerIndex> ledgerSeqs({
ledgers[i][jss::result][jss::ledger_index].asUInt()
});
auto db = env.app().getLedgerDB().checkoutDb();
*db << "INSERT INTO Validations "
"(LedgerHash, LedgerSeq) "
"VALUES "
"(:ledgerHash, :ledgerSeq);",
soci::use(ledgerHashes),
soci::use(ledgerSeqs);
}
store.rendezvous();
BEAST_EXPECT(store.getLastRotated() == deleteInterval + lastRotated);
validationCheck(env, deleteInterval - 1);
ledgerCheck(env, deleteInterval + 1, lastRotated);
transactionCheck(env, 0);
accountTransactionCheck(env, 0);
}
void testAutomatic()
{
testcase("automatic online_delete");
using namespace jtx;
using namespace std::chrono_literals;
Env env(*this, makeConfig());
auto& store = env.app().getSHAMapStore();
auto ledgerSeq = waitForReady(env);
auto lastRotated = ledgerSeq - 1;
BEAST_EXPECT(store.getLastRotated() == lastRotated);
BEAST_EXPECT(lastRotated != 2);
// Because advisory_delete is unset,
// "can_delete" is disabled.
auto const canDelete = env.rpc("can_delete");
BEAST_EXPECT(bad(canDelete, rpcNOT_ENABLED));
// Close ledgers without triggering a rotate
for (; ledgerSeq < lastRotated + deleteInterval; ++ledgerSeq)
{
env.close();
auto ledger = env.rpc("ledger", "validated");
BEAST_EXPECT(goodLedger(env, ledger, to_string(ledgerSeq), true));
}
store.rendezvous();
// The database will always have back to ledger 2,
// regardless of lastRotated.
validationCheck(env, 0);
ledgerCheck(env, ledgerSeq - 2, 2);
BEAST_EXPECT(lastRotated == store.getLastRotated());
{
// Closing one more ledger triggers a rotate
env.close();
auto ledger = env.rpc("ledger", "validated");
BEAST_EXPECT(goodLedger(env, ledger, to_string(ledgerSeq++), true));
}
store.rendezvous();
validationCheck(env, 0);
ledgerCheck(env, ledgerSeq - lastRotated, lastRotated);
BEAST_EXPECT(lastRotated != store.getLastRotated());
lastRotated = store.getLastRotated();
// Close enough ledgers to trigger another rotate
for (; ledgerSeq < lastRotated + deleteInterval + 1; ++ledgerSeq)
{
env.close();
auto ledger = env.rpc("ledger", "validated");
BEAST_EXPECT(goodLedger(env, ledger, to_string(ledgerSeq), true));
}
store.rendezvous();
validationCheck(env, 0);
ledgerCheck(env, deleteInterval + 1, lastRotated);
BEAST_EXPECT(lastRotated != store.getLastRotated());
}
void testCanDelete()
{
testcase("online_delete with advisory_delete");
using namespace jtx;
using namespace std::chrono_literals;
// Same config with advisory_delete enabled
Env env(*this, makeConfigAdvisory());
auto& store = env.app().getSHAMapStore();
auto ledgerSeq = waitForReady(env);
auto lastRotated = ledgerSeq - 1;
BEAST_EXPECT(store.getLastRotated() == lastRotated);
BEAST_EXPECT(lastRotated != 2);
auto canDelete = env.rpc("can_delete");
BEAST_EXPECT(!RPC::contains_error(canDelete[jss::result]));
BEAST_EXPECT(canDelete[jss::result][jss::can_delete] == 0);
canDelete = env.rpc("can_delete", "never");
BEAST_EXPECT(!RPC::contains_error(canDelete[jss::result]));
BEAST_EXPECT(canDelete[jss::result][jss::can_delete] == 0);
auto const firstBatch = deleteInterval + ledgerSeq;
for (; ledgerSeq < firstBatch; ++ledgerSeq)
{
env.close();
auto ledger = env.rpc("ledger", "validated");
BEAST_EXPECT(goodLedger(env, ledger, to_string(ledgerSeq), true));
}
store.rendezvous();
validationCheck(env, 0);
ledgerCheck(env, ledgerSeq - 2, 2);
BEAST_EXPECT(lastRotated == store.getLastRotated());
// This does not kick off a cleanup
canDelete = env.rpc("can_delete", to_string(
ledgerSeq + deleteInterval / 2));
BEAST_EXPECT(!RPC::contains_error(canDelete[jss::result]));
BEAST_EXPECT(canDelete[jss::result][jss::can_delete] ==
ledgerSeq + deleteInterval / 2);
store.rendezvous();
validationCheck(env, 0);
ledgerCheck(env, ledgerSeq - 2, 2);
BEAST_EXPECT(store.getLastRotated() == lastRotated);
{
// This kicks off a cleanup, but it stays small.
env.close();
auto ledger = env.rpc("ledger", "validated");
BEAST_EXPECT(goodLedger(env, ledger, to_string(ledgerSeq++), true));
}
store.rendezvous();
validationCheck(env, 0);
ledgerCheck(env, ledgerSeq - lastRotated, lastRotated);
BEAST_EXPECT(store.getLastRotated() == ledgerSeq - 1);
lastRotated = ledgerSeq - 1;
for (; ledgerSeq < lastRotated + deleteInterval; ++ledgerSeq)
{
// No cleanups in this loop.
env.close();
auto ledger = env.rpc("ledger", "validated");
BEAST_EXPECT(goodLedger(env, ledger, to_string(ledgerSeq), true));
}
store.rendezvous();
BEAST_EXPECT(store.getLastRotated() == lastRotated);
{
// This kicks off another cleanup.
env.close();
auto ledger = env.rpc("ledger", "validated");
BEAST_EXPECT(goodLedger(env, ledger, to_string(ledgerSeq++), true));
}
store.rendezvous();
validationCheck(env, 0);
ledgerCheck(env, ledgerSeq - firstBatch, firstBatch);
BEAST_EXPECT(store.getLastRotated() == ledgerSeq - 1);
lastRotated = ledgerSeq - 1;
// This does not kick off a cleanup
canDelete = env.rpc("can_delete", "always");
BEAST_EXPECT(!RPC::contains_error(canDelete[jss::result]));
BEAST_EXPECT(canDelete[jss::result][jss::can_delete] ==
std::numeric_limits <unsigned int>::max());
for (; ledgerSeq < lastRotated + deleteInterval; ++ledgerSeq)
{
// No cleanups in this loop.
env.close();
auto ledger = env.rpc("ledger", "validated");
BEAST_EXPECT(goodLedger(env, ledger, to_string(ledgerSeq), true));
}
store.rendezvous();
BEAST_EXPECT(store.getLastRotated() == lastRotated);
{
// This kicks off another cleanup.
env.close();
auto ledger = env.rpc("ledger", "validated");
BEAST_EXPECT(goodLedger(env, ledger, to_string(ledgerSeq++), true));
}
store.rendezvous();
validationCheck(env, 0);
ledgerCheck(env, ledgerSeq - lastRotated, lastRotated);
BEAST_EXPECT(store.getLastRotated() == ledgerSeq - 1);
lastRotated = ledgerSeq - 1;
// This does not kick off a cleanup
canDelete = env.rpc("can_delete", "now");
BEAST_EXPECT(!RPC::contains_error(canDelete[jss::result]));
BEAST_EXPECT(canDelete[jss::result][jss::can_delete] == ledgerSeq - 1);
for (; ledgerSeq < lastRotated + deleteInterval; ++ledgerSeq)
{
// No cleanups in this loop.
env.close();
auto ledger = env.rpc("ledger", "validated");
BEAST_EXPECT(goodLedger(env, ledger, to_string(ledgerSeq), true));
}
store.rendezvous();
BEAST_EXPECT(store.getLastRotated() == lastRotated);
{
// This kicks off another cleanup.
env.close();
auto ledger = env.rpc("ledger", "validated");
BEAST_EXPECT(goodLedger(env, ledger, to_string(ledgerSeq++), true));
}
store.rendezvous();
validationCheck(env, 0);
ledgerCheck(env, ledgerSeq - lastRotated, lastRotated);
BEAST_EXPECT(store.getLastRotated() == ledgerSeq - 1);
lastRotated = ledgerSeq - 1;
}
void run()
{
testClear();
testAutomatic();
testCanDelete();
}
};
// VFALCO This test fails because of thread asynchronous issues
BEAST_DEFINE_TESTSUITE(SHAMapStore,app,ripple);
}
}

View File

@@ -0,0 +1,85 @@
//------------------------------------------------------------------------------
/*
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/test/jtx.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/JsonFields.h>
namespace ripple {
namespace test {
struct SetAuth_test : public beast::unit_test::suite
{
// Set just the tfSetfAuth flag on a trust line
// If the trust line does not exist, then it should
// be created under the new rules.
static
Json::Value
auth (jtx::Account const& account,
jtx::Account const& dest,
std::string const& currency)
{
using namespace jtx;
Json::Value jv;
jv[jss::Account] = account.human();
jv[jss::LimitAmount] = STAmount(
{ to_currency(currency), dest }).getJson(0);
jv[jss::TransactionType] = "TrustSet";
jv[jss::Flags] = tfSetfAuth;
return jv;
}
void testAuth()
{
using namespace jtx;
auto const gw = Account("gw");
auto const USD = gw["USD"];
{
Env env(*this);
env.fund(XRP(100000), "alice", gw);
env(fset(gw, asfRequireAuth));
env(auth(gw, "alice", "USD"), ter(tecNO_LINE_REDUNDANT));
}
{
Env env(*this, features(featureTrustSetAuth));
env.fund(XRP(100000), "alice", "bob", gw);
env(fset(gw, asfRequireAuth));
env(auth(gw, "alice", "USD"));
BEAST_EXPECT(env.le(
keylet::line(Account("alice").id(),
gw.id(), USD.currency)));
env(trust("alice", USD(1000)));
env(trust("bob", USD(1000)));
env(pay(gw, "alice", USD(100)));
env(pay(gw, "bob", USD(100)), ter(tecPATH_DRY)); // Should be terNO_AUTH
env(pay("alice", "bob", USD(50)), ter(tecPATH_DRY)); // Should be terNO_AUTH
}
}
void run() override
{
testAuth();
}
};
BEAST_DEFINE_TESTSUITE(SetAuth,test,ripple);
} // test
} // ripple

View File

@@ -0,0 +1,107 @@
//------------------------------------------------------------------------------
/*
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>
namespace ripple {
class SetRegularKey_test : public beast::unit_test::suite
{
public:
void testDisableMasterKey()
{
using namespace test::jtx;
Env env(*this);
Account const alice("alice");
Account const bob("bob");
env.fund(XRP(10000), alice, bob);
// Master and Regular key
env(regkey(alice, bob));
auto const ar = env.le(alice);
BEAST_EXPECT(ar->isFieldPresent(sfRegularKey) && (ar->getAccountID(sfRegularKey) == bob.id()));
env(noop(alice));
env(noop(alice), sig(bob));
env(noop(alice), sig(alice));
// Regular key only
env(fset(alice, asfDisableMaster), sig(alice));
env(noop(alice));
env(noop(alice), sig(bob));
env(noop(alice), sig(alice), ter(tefMASTER_DISABLED));
env(fclear(alice, asfDisableMaster), sig(alice), ter(tefMASTER_DISABLED));
env(fclear(alice, asfDisableMaster), sig(bob));
env(noop(alice), sig(alice));
}
void testPasswordSpent()
{
using namespace test::jtx;
Env env(*this);
Account const alice("alice");
Account const bob("bob");
env.fund(XRP(10000), alice, bob);
auto ar = env.le(alice);
BEAST_EXPECT(ar->isFieldPresent(sfFlags) && ((ar->getFieldU32(sfFlags) & lsfPasswordSpent) == 0));
env(regkey(alice, bob), sig(alice), fee(0));
ar = env.le(alice);
BEAST_EXPECT(ar->isFieldPresent(sfFlags) && ((ar->getFieldU32(sfFlags) & lsfPasswordSpent) == lsfPasswordSpent));
// The second SetRegularKey transaction with Fee=0 should fail.
env(regkey(alice, bob), sig(alice), fee(0), ter(telINSUF_FEE_P));
env.trust(bob["USD"](1), alice);
env(pay(bob, alice, bob["USD"](1)));
ar = env.le(alice);
BEAST_EXPECT(ar->isFieldPresent(sfFlags) && ((ar->getFieldU32(sfFlags) & lsfPasswordSpent) == 0));
}
void testUniversalMaskError()
{
using namespace test::jtx;
Env env(*this);
Account const alice("alice");
Account const bob("bob");
env.fund(XRP(10000), alice, bob);
auto jv = regkey(alice, bob);
jv[sfFlags.fieldName] = tfUniversalMask;
env(jv, ter(temINVALID_FLAG));
}
void run()
{
testDisableMasterKey();
testPasswordSpent();
testUniversalMaskError();
}
};
BEAST_DEFINE_TESTSUITE(SetRegularKey,app,ripple);
}

View File

@@ -0,0 +1,424 @@
//------------------------------------------------------------------------------
/*
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/test/jtx.h>
#include <ripple/app/tx/applySteps.h>
#include <ripple/protocol/digest.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/JsonFields.h>
#include <ripple/protocol/TxFlags.h>
namespace ripple {
namespace test {
struct SusPay_test : public beast::unit_test::suite
{
template <class... Args>
static
uint256
digest (Args&&... args)
{
sha256_hasher h;
using beast::hash_append;
hash_append(h, args...);
auto const d = static_cast<
sha256_hasher::result_type>(h);
uint256 result;
std::memcpy(result.data(), d.data(), d.size());
return result;
}
// Create condition
// First is digest, second is pre-image
static
std::pair<uint256, uint256>
cond (std::string const& receipt)
{
std::pair<uint256, uint256> result;
result.second = digest(receipt);
result.first = digest(result.second);
return result;
}
static
Json::Value
condpay (jtx::Account const& account, jtx::Account const& to,
STAmount const& amount, uint256 const& digest,
NetClock::time_point const& expiry)
{
using namespace jtx;
Json::Value jv;
jv[jss::TransactionType] = "SuspendedPaymentCreate";
jv[jss::Flags] = tfUniversal;
jv[jss::Account] = account.human();
jv[jss::Destination] = to.human();
jv[jss::Amount] = amount.getJson(0);
jv["CancelAfter"] =
expiry.time_since_epoch().count();
jv["Digest"] = to_string(digest);
return jv;
}
static
Json::Value
lockup (jtx::Account const& account, jtx::Account const& to,
STAmount const& amount, NetClock::time_point const& expiry)
{
using namespace jtx;
Json::Value jv;
jv[jss::TransactionType] = "SuspendedPaymentCreate";
jv[jss::Flags] = tfUniversal;
jv[jss::Account] = account.human();
jv[jss::Destination] = to.human();
jv[jss::Amount] = amount.getJson(0);
jv["FinishAfter"] =
expiry.time_since_epoch().count();
return jv;
}
static
Json::Value
finish (jtx::Account const& account,
jtx::Account const& from, std::uint32_t seq)
{
Json::Value jv;
jv[jss::TransactionType] = "SuspendedPaymentFinish";
jv[jss::Flags] = tfUniversal;
jv[jss::Account] = account.human();
jv["Owner"] = from.human();
jv["OfferSequence"] = seq;
return jv;
}
template <class Proof>
static
Json::Value
finish (jtx::Account const& account,
jtx::Account const& from, std::uint32_t seq,
uint256 const& digest, Proof const& proof)
{
Json::Value jv;
jv[jss::TransactionType] = "SuspendedPaymentFinish";
jv[jss::Flags] = tfUniversal;
jv[jss::Account] = account.human();
jv["Owner"] = from.human();
jv["OfferSequence"] = seq;
jv["Method"] = 1;
jv["Digest"] = to_string(digest);
jv["Proof"] = to_string(proof);
return jv;
}
static
Json::Value
cancel (jtx::Account const& account,
jtx::Account const& from, std::uint32_t seq)
{
Json::Value jv;
jv[jss::TransactionType] = "SuspendedPaymentCancel";
jv[jss::Flags] = tfUniversal;
jv[jss::Account] = account.human();
jv["Owner"] = from.human();
jv["OfferSequence"] = seq;
return jv;
}
void
testEnablement()
{
using namespace jtx;
using namespace std::chrono;
using S = seconds;
auto const c = cond("receipt");
{
Env env(*this, features(featureSusPay));
auto T = [&env](NetClock::duration const& d)
{ return env.now() + d; };
env.fund(XRP(5000), "alice", "bob");
// syntax
env(condpay("alice", "bob", XRP(1000), c.first, T(S{1})));
}
{
Env env(*this);
auto T = [&env](NetClock::duration const& d)
{ return env.now() + d; };
env.fund(XRP(5000), "alice", "bob");
// disabled in production
env(condpay("alice", "bob", XRP(1000), c.first, T(S{1})), ter(temDISABLED));
env(finish("bob", "alice", 1), ter(temDISABLED));
env(cancel("bob", "alice", 1), ter(temDISABLED));
}
}
void
testTags()
{
using namespace jtx;
using namespace std::chrono;
using S = seconds;
{
Env env(*this, features(featureSusPay));
auto const alice = Account("alice");
auto T = [&env](NetClock::duration const& d)
{ return env.now() + d; };
env.fund(XRP(5000), alice, "bob");
auto const c = cond("receipt");
auto const seq = env.seq(alice);
// set source and dest tags
env(condpay(alice, "bob", XRP(1000), c.first, T(S{1})), stag(1), dtag(2));
auto const sle = env.le(keylet::susPay(alice.id(), seq));
BEAST_EXPECT((*sle)[sfSourceTag] == 1);
BEAST_EXPECT((*sle)[sfDestinationTag] == 2);
}
}
void
testFails()
{
using namespace jtx;
using namespace std::chrono;
using S = seconds;
{
Env env(*this, features(featureSusPay));
auto T = [&env](NetClock::duration const& d)
{ return env.now() + d; };
env.fund(XRP(5000), "alice", "bob");
auto const c = cond("receipt");
// VFALCO Should we enforce this?
// expiration in the past
//env(condpay("alice", "bob", XRP(1000), c.first, T(S{-1})), ter(tecNO_PERMISSION));
// expiration beyond the limit
env(condpay("alice", "bob", XRP(1000), c.first, T(days(7+1))), ter(tecNO_PERMISSION));
// no destination account
env(condpay("alice", "carol", XRP(1000), c.first, T(S{1})), ter(tecNO_DST));
env.fund(XRP(5000), "carol");
env(condpay("alice", "carol",
XRP(1000), c.first, T(S{1})), stag(2));
env(condpay("alice", "carol",
XRP(1000), c.first, T(S{1})), stag(3), dtag(4));
env(fset("carol", asfRequireDest));
// missing destination tag
env(condpay("alice", "carol", XRP(1000), c.first, T(S{1})), ter(tecDST_TAG_NEEDED));
env(condpay("alice", "carol",
XRP(1000), c.first, T(S{1})), dtag(1));
}
}
void
testLockup()
{
using namespace jtx;
using namespace std::chrono;
using S = seconds;
{
Env env(*this, features(featureSusPay));
auto T = [&env](NetClock::duration const& d)
{ return env.now() + d; };
env.fund(XRP(5000), "alice", "bob");
auto const seq = env.seq("alice");
env(lockup("alice", "alice", XRP(1000), T(S{1})));
env.require(balance("alice", XRP(4000) - drops(10)));
env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
env(finish("bob", "alice", seq), ter(tecNO_PERMISSION));
env.close();
env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
env(finish("bob", "alice", seq));
}
}
void
testCondPay()
{
using namespace jtx;
using namespace std::chrono;
using S = seconds;
{
Env env(*this, features(featureSusPay));
auto T = [&env](NetClock::duration const& d)
{ return env.now() + d; };
env.fund(XRP(5000), "alice", "bob", "carol");
auto const c = cond("receipt");
auto const seq = env.seq("alice");
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 0);
env(condpay("alice", "carol", XRP(1000), c.first, T(S{1})));
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 1);
env.require(balance("alice", XRP(4000) - drops(10)));
env.require(balance("carol", XRP(5000)));
env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 1);
env(finish("bob", "alice", seq, c.first, c.first), ter(temBAD_SIGNATURE));
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 1);
env(finish("bob", "alice", seq, c.first, c.second));
// SLE removed on finish
BEAST_EXPECT(! env.le(keylet::susPay(Account("alice").id(), seq)));
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 0);
env.require(balance("carol", XRP(6000)));
env(cancel("bob", "alice", seq), ter(tecNO_TARGET));
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 0);
env(cancel("bob", "carol", 1), ter(tecNO_TARGET));
env.close();
}
{
Env env(*this, features(featureSusPay));
auto T = [&env](NetClock::duration const& d)
{ return env.now() + d; };
env.fund(XRP(5000), "alice", "bob", "carol");
auto const c = cond("receipt");
auto const seq = env.seq("alice");
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 0);
env(condpay("alice", "carol", XRP(1000), c.first, T(S{1})));
env.close();
env.require(balance("alice", XRP(4000) - drops(10)));
// balance restored on cancel
env(cancel("bob", "alice", seq));
env.require(balance("alice", XRP(5000) - drops(10)));
// SLE removed on cancel
BEAST_EXPECT(! env.le(keylet::susPay(Account("alice").id(), seq)));
}
{
Env env(*this, features(featureSusPay));
auto T = [&env](NetClock::duration const& d)
{ return env.now() + d; };
env.fund(XRP(5000), "alice", "bob", "carol");
env.close();
auto const c = cond("receipt");
auto const seq = env.seq("alice");
env(condpay("alice", "carol", XRP(1000), c.first, T(S{1})));
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 1);
// cancel fails before expiration
env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 1);
env.close();
// finish fails after expiration
env(finish("bob", "alice", seq, c.first, c.second), ter(tecNO_PERMISSION));
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 1);
env.require(balance("carol", XRP(5000)));
}
{
Env env(*this, features(featureSusPay));
auto T = [&env](NetClock::duration const& d)
{ return env.now() + d; };
env.fund(XRP(5000), "alice", "bob", "carol");
auto const c = cond("receipt");
auto const seq = env.seq("alice");
env(condpay("alice", "carol", XRP(1000), c.first, T(S{1})));
// wrong digest
auto const cx = cond("bad");
env(finish("bob", "alice", seq, cx.first, cx.second), ter(tecNO_PERMISSION));
}
{
Env env(*this, features(featureSusPay));
auto T = [&env](NetClock::duration const& d)
{ return env.now() + d; };
env.fund(XRP(5000), "alice", "bob", "carol");
auto const p = from_hex_text<uint128>(
"0102030405060708090A0B0C0D0E0F");
auto const d = digest(p);
auto const seq = env.seq("alice");
env(condpay("alice", "carol", XRP(1000), d, T(S{1})));
// bad digest size
env(finish("bob", "alice", seq, d, p), ter(temMALFORMED));
}
}
void
testMeta()
{
using namespace jtx;
using namespace std::chrono;
Env env(*this, features(featureSusPay));
auto T = [&env](NetClock::duration const& d)
{ return env.now() + d; };
env.fund(XRP(5000), "alice", "bob", "carol");
auto const c = cond("receipt");
env(condpay("alice", "carol", XRP(1000), c.first, T(1s)));
auto const m = env.meta();
BEAST_EXPECT((*m)[sfTransactionResult] == tesSUCCESS);
}
void testConsequences()
{
using namespace jtx;
using namespace std::chrono;
Env env(*this, features(featureSusPay));
auto T = [&env](NetClock::duration const& d)
{
return env.now() + d;
};
env.memoize("alice");
env.memoize("bob");
env.memoize("carol");
auto const c = cond("receipt");
{
auto const jtx = env.jt(
condpay("alice", "carol", XRP(1000), c.first, T(1s)),
seq(1), fee(10));
auto const pf = preflight(env.app(), env.current()->rules(),
*jtx.stx, tapNONE, env.journal);
BEAST_EXPECT(pf.ter == tesSUCCESS);
auto const conseq = calculateConsequences(pf);
BEAST_EXPECT(conseq.category == TxConsequences::normal);
BEAST_EXPECT(conseq.fee == drops(10));
BEAST_EXPECT(conseq.potentialSpend == XRP(1000));
}
{
auto const jtx = env.jt(cancel("bob", "alice", 3),
seq(1), fee(10));
auto const pf = preflight(env.app(), env.current()->rules(),
*jtx.stx, tapNONE, env.journal);
BEAST_EXPECT(pf.ter == tesSUCCESS);
auto const conseq = calculateConsequences(pf);
BEAST_EXPECT(conseq.category == TxConsequences::normal);
BEAST_EXPECT(conseq.fee == drops(10));
BEAST_EXPECT(conseq.potentialSpend == XRP(0));
}
{
auto const jtx = env.jt(
finish("bob", "alice", 3, c.first, c.second),
seq(1), fee(10));
auto const pf = preflight(env.app(), env.current()->rules(),
*jtx.stx, tapNONE, env.journal);
BEAST_EXPECT(pf.ter == tesSUCCESS);
auto const conseq = calculateConsequences(pf);
BEAST_EXPECT(conseq.category == TxConsequences::normal);
BEAST_EXPECT(conseq.fee == drops(10));
BEAST_EXPECT(conseq.potentialSpend == XRP(0));
}
}
void run() override
{
testEnablement();
testTags();
testFails();
testLockup();
testCondPay();
testMeta();
testConsequences();
}
};
BEAST_DEFINE_TESTSUITE(SusPay,app,ripple);
} // test
} // ripple

371
src/test/app/Taker_test.cpp Normal file
View File

@@ -0,0 +1,371 @@
//------------------------------------------------------------------------------
/*
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 <ripple/app/tx/impl/Taker.h>
#include <ripple/beast/unit_test.h>
#include <ripple/beast/core/LexicalCast.h>
#include <type_traits>
namespace ripple {
class Taker_test : public beast::unit_test::suite
{
static bool const Buy = false;
static bool const Sell = true;
class TestTaker
: public BasicTaker
{
STAmount funds_;
STAmount cross_funds;
public:
TestTaker (
CrossType cross_type,
Amounts const& amount,
Quality const& quality,
STAmount const& funds,
std::uint32_t flags,
Rate const& rate_in,
Rate const& rate_out)
: BasicTaker (
cross_type,
AccountID(0x4701),
amount,
quality,
flags,
rate_in,
rate_out)
, funds_ (funds)
{
}
void
set_funds (STAmount const& funds)
{
cross_funds = funds;
}
STAmount
get_funds (AccountID const& owner, STAmount const& funds) const
{
if (owner == account ())
return funds_;
return cross_funds;
}
Amounts
cross (Amounts offer, Quality quality)
{
if (reject (quality))
return Amounts (offer.in.zeroed (), offer.out.zeroed ());
// we need to emulate "unfunded offers" behavior
if (get_funds (AccountID (0x4702), offer.out) == zero)
return Amounts (offer.in.zeroed (), offer.out.zeroed ());
if (done ())
return Amounts (offer.in.zeroed (), offer.out.zeroed ());
auto result = do_cross (offer, quality, AccountID (0x4702));
funds_ -= result.order.in;
return result.order;
}
std::pair<Amounts, Amounts>
cross (Amounts offer1, Quality quality1, Amounts offer2, Quality quality2)
{
/* check if composed quality should be rejected */
Quality const quality (composed_quality (
quality1, quality2));
if (reject (quality))
return std::make_pair(
Amounts { offer1.in.zeroed (), offer1.out.zeroed () },
Amounts { offer2.in.zeroed (), offer2.out.zeroed () });
if (done ())
return std::make_pair(
Amounts { offer1.in.zeroed (), offer1.out.zeroed () },
Amounts { offer2.in.zeroed (), offer2.out.zeroed () });
auto result = do_cross (
offer1, quality1, AccountID (0x4703),
offer2, quality2, AccountID (0x4704));
return std::make_pair (result.first.order, result.second.order);
}
};
private:
Issue const& usd () const
{
static Issue const issue (
Currency (0x5553440000000000), AccountID (0x4985601));
return issue;
}
Issue const& eur () const
{
static Issue const issue (
Currency (0x4555520000000000), AccountID (0x4985602));
return issue;
}
Issue const& xrp () const
{
static Issue const issue (
xrpCurrency (), xrpAccount ());
return issue;
}
STAmount parse_amount (std::string const& amount, Issue const& issue)
{
return amountFromString (issue, amount);
}
Amounts parse_amounts (
std::string const& amount_in, Issue const& issue_in,
std::string const& amount_out, Issue const& issue_out)
{
STAmount const in (parse_amount (amount_in, issue_in));
STAmount const out (parse_amount (amount_out, issue_out));
return { in, out };
}
struct cross_attempt_offer
{
cross_attempt_offer (std::string const& in_,
std::string const &out_)
: in (in_)
, out (out_)
{
}
std::string in;
std::string out;
};
private:
std::string
format_amount (STAmount const& amount)
{
std::string txt = amount.getText ();
txt += "/";
txt += to_string (amount.issue().currency);
return txt;
}
void
attempt (
bool sell,
std::string name,
Quality taker_quality,
cross_attempt_offer const offer,
std::string const funds,
Quality cross_quality,
cross_attempt_offer const cross,
std::string const cross_funds,
cross_attempt_offer const flow,
Issue const& issue_in,
Issue const& issue_out,
Rate rate_in = parityRate,
Rate rate_out = parityRate)
{
Amounts taker_offer (parse_amounts (
offer.in, issue_in,
offer.out, issue_out));
Amounts cross_offer (parse_amounts (
cross.in, issue_in,
cross.out, issue_out));
CrossType cross_type;
if (isXRP (issue_out))
cross_type = CrossType::IouToXrp;
else if (isXRP (issue_in))
cross_type = CrossType::XrpToIou;
else
cross_type = CrossType::IouToIou;
// FIXME: We are always invoking the IOU-to-IOU taker. We should select
// the correct type dynamically.
TestTaker taker (cross_type, taker_offer, taker_quality,
parse_amount (funds, issue_in), sell ? tfSell : 0,
rate_in, rate_out);
taker.set_funds (parse_amount (cross_funds, issue_out));
auto result = taker.cross (cross_offer, cross_quality);
Amounts const expected (parse_amounts (
flow.in, issue_in,
flow.out, issue_out));
BEAST_EXPECT(expected == result);
if (expected != result)
{
log <<
"Expected: " << format_amount (expected.in) <<
" : " << format_amount (expected.out) << '\n' <<
" Actual: " << format_amount (result.in) <<
" : " << format_amount (result.out) << std::endl;
}
}
Quality get_quality(std::string in, std::string out)
{
return Quality (parse_amounts (in, xrp(), out, xrp ()));
}
public:
// Notation for clamp scenario descriptions:
//
// IN:OUT (with the last in the list being limiting factor)
// N = Nothing
// T = Taker Offer Balance
// A = Taker Account Balance
// B = Owner Account Balance
//
// (s) = sell semantics: taker wants unlimited output
// (b) = buy semantics: taker wants a limited amount out
// NIKB TODO: Augment TestTaker so currencies and rates can be specified
// once without need for repetition.
void
test_xrp_to_iou ()
{
testcase ("XRP Quantization: input");
Quality q1 = get_quality ("1", "1");
// TAKER OWNER
// QUAL OFFER FUNDS QUAL OFFER FUNDS EXPECTED
// XRP USD
attempt (Sell, "N:N", q1, { "2", "2" }, "2", q1, { "2", "2" }, "2", { "2", "2" }, xrp(), usd());
attempt (Sell, "N:B", q1, { "2", "2" }, "2", q1, { "2", "2" }, "1.8", { "1", "1.8" }, xrp(), usd());
attempt (Buy, "N:T", q1, { "1", "1" }, "2", q1, { "2", "2" }, "2", { "1", "1" }, xrp(), usd());
attempt (Buy, "N:BT", q1, { "1", "1" }, "2", q1, { "2", "2" }, "1.8", { "1", "1" }, xrp(), usd());
attempt (Buy, "N:TB", q1, { "1", "1" }, "2", q1, { "2", "2" }, "0.8", { "0", "0.8" }, xrp(), usd());
attempt (Sell, "T:N", q1, { "1", "1" }, "2", q1, { "2", "2" }, "2", { "1", "1" }, xrp(), usd());
attempt (Sell, "T:B", q1, { "1", "1" }, "2", q1, { "2", "2" }, "1.8", { "1", "1.8" }, xrp(), usd());
attempt (Buy, "T:T", q1, { "1", "1" }, "2", q1, { "2", "2" }, "2", { "1", "1" }, xrp(), usd());
attempt (Buy, "T:BT", q1, { "1", "1" }, "2", q1, { "2", "2" }, "1.8", { "1", "1" }, xrp(), usd());
attempt (Buy, "T:TB", q1, { "1", "1" }, "2", q1, { "2", "2" }, "0.8", { "0", "0.8" }, xrp(), usd());
attempt (Sell, "A:N", q1, { "2", "2" }, "1", q1, { "2", "2" }, "2", { "1", "1" }, xrp(), usd());
attempt (Sell, "A:B", q1, { "2", "2" }, "1", q1, { "2", "2" }, "1.8", { "1", "1.8" }, xrp(), usd());
attempt (Buy, "A:T", q1, { "2", "2" }, "1", q1, { "3", "3" }, "3", { "1", "1" }, xrp(), usd());
attempt (Buy, "A:BT", q1, { "2", "2" }, "1", q1, { "3", "3" }, "2.4", { "1", "1" }, xrp(), usd());
attempt (Buy, "A:TB", q1, { "2", "2" }, "1", q1, { "3", "3" }, "0.8", { "0", "0.8" }, xrp(), usd());
attempt (Sell, "TA:N", q1, { "2", "2" }, "1", q1, { "2", "2" }, "2", { "1", "1" }, xrp(), usd());
attempt (Sell, "TA:B", q1, { "2", "2" }, "1", q1, { "3", "3" }, "1.8", { "1", "1.8" }, xrp(), usd());
attempt (Buy, "TA:T", q1, { "2", "2" }, "1", q1, { "3", "3" }, "3", { "1", "1" }, xrp(), usd());
attempt (Buy, "TA:BT", q1, { "2", "2" }, "1", q1, { "3", "3" }, "1.8", { "1", "1.8" }, xrp(), usd());
attempt (Buy, "TA:TB", q1, { "2", "2" }, "1", q1, { "3", "3" }, "1.8", { "1", "1.8" }, xrp(), usd());
attempt (Sell, "AT:N", q1, { "2", "2" }, "1", q1, { "3", "3" }, "3", { "1", "1" }, xrp(), usd());
attempt (Sell, "AT:B", q1, { "2", "2" }, "1", q1, { "3", "3" }, "1.8", { "1", "1.8" }, xrp(), usd());
attempt (Buy, "AT:T", q1, { "2", "2" }, "1", q1, { "3", "3" }, "3", { "1", "1" }, xrp(), usd());
attempt (Buy, "AT:BT", q1, { "2", "2" }, "1", q1, { "3", "3" }, "1.8", { "1", "1.8" }, xrp(), usd());
attempt (Buy, "AT:TB", q1, { "2", "2" }, "1", q1, { "3", "3" }, "0.8", { "0", "0.8" }, xrp(), usd());
}
void
test_iou_to_xrp ()
{
testcase ("XRP Quantization: output");
Quality q1 = get_quality ("1", "1");
// TAKER OWNER
// QUAL OFFER FUNDS QUAL OFFER FUNDS EXPECTED
// USD XRP
attempt (Sell, "N:N", q1, { "3", "3" }, "3", q1, { "3", "3" }, "3", { "3", "3" }, usd(), xrp());
attempt (Sell, "N:B", q1, { "3", "3" }, "3", q1, { "3", "3" }, "2", { "2", "2" }, usd(), xrp());
attempt (Buy, "N:T", q1, { "3", "3" }, "2.5", q1, { "5", "5" }, "5", { "2.5", "2" }, usd(), xrp());
attempt (Buy, "N:BT", q1, { "3", "3" }, "1.5", q1, { "5", "5" }, "4", { "1.5", "1" }, usd(), xrp());
attempt (Buy, "N:TB", q1, { "3", "3" }, "2.2", q1, { "5", "5" }, "1", { "1", "1" }, usd(), xrp());
attempt (Sell, "T:N", q1, { "1", "1" }, "2", q1, { "2", "2" }, "2", { "1", "1" }, usd(), xrp());
attempt (Sell, "T:B", q1, { "2", "2" }, "2", q1, { "3", "3" }, "1", { "1", "1" }, usd(), xrp());
attempt (Buy, "T:T", q1, { "1", "1" }, "2", q1, { "2", "2" }, "2", { "1", "1" }, usd(), xrp());
attempt (Buy, "T:BT", q1, { "1", "1" }, "2", q1, { "3", "3" }, "2", { "1", "1" }, usd(), xrp());
attempt (Buy, "T:TB", q1, { "2", "2" }, "2", q1, { "3", "3" }, "1", { "1", "1" }, usd(), xrp());
attempt (Sell, "A:N", q1, { "2", "2" }, "1.5", q1, { "2", "2" }, "2", { "1.5", "1" }, usd(), xrp());
attempt (Sell, "A:B", q1, { "2", "2" }, "1.8", q1, { "3", "3" }, "2", { "1.8", "1" }, usd(), xrp());
attempt (Buy, "A:T", q1, { "2", "2" }, "1.2", q1, { "3", "3" }, "3", { "1.2", "1" }, usd(), xrp());
attempt (Buy, "A:BT", q1, { "2", "2" }, "1.5", q1, { "4", "4" }, "3", { "1.5", "1" }, usd(), xrp());
attempt (Buy, "A:TB", q1, { "2", "2" }, "1.5", q1, { "4", "4" }, "1", { "1", "1" }, usd(), xrp());
attempt (Sell, "TA:N", q1, { "2", "2" }, "1.5", q1, { "2", "2" }, "2", { "1.5", "1" }, usd(), xrp());
attempt (Sell, "TA:B", q1, { "2", "2" }, "1.5", q1, { "3", "3" }, "1", { "1", "1" }, usd(), xrp());
attempt (Buy, "TA:T", q1, { "2", "2" }, "1.5", q1, { "3", "3" }, "3", { "1.5", "1" }, usd(), xrp());
attempt (Buy, "TA:BT", q1, { "2", "2" }, "1.8", q1, { "4", "4" }, "3", { "1.8", "1" }, usd(), xrp());
attempt (Buy, "TA:TB", q1, { "2", "2" }, "1.2", q1, { "3", "3" }, "1", { "1", "1" }, usd(), xrp());
attempt (Sell, "AT:N", q1, { "2", "2" }, "2.5", q1, { "4", "4" }, "4", { "2", "2" }, usd(), xrp());
attempt (Sell, "AT:B", q1, { "2", "2" }, "2.5", q1, { "3", "3" }, "1", { "1", "1" }, usd(), xrp());
attempt (Buy, "AT:T", q1, { "2", "2" }, "2.5", q1, { "3", "3" }, "3", { "2", "2" }, usd(), xrp());
attempt (Buy, "AT:BT", q1, { "2", "2" }, "2.5", q1, { "4", "4" }, "3", { "2", "2" }, usd(), xrp());
attempt (Buy, "AT:TB", q1, { "2", "2" }, "2.5", q1, { "3", "3" }, "1", { "1", "1" }, usd(), xrp());
}
void
test_iou_to_iou ()
{
testcase ("IOU to IOU");
Quality q1 = get_quality ("1", "1");
// Highly exaggerated 50% transfer rate for the input and output:
Rate const rate { parityRate.value + (parityRate.value / 2) };
// TAKER OWNER
// QUAL OFFER FUNDS QUAL OFFER FUNDS EXPECTED
// EUR USD
attempt (Sell, "N:N", q1, { "2", "2" }, "10", q1, { "2", "2" }, "10", { "2", "2" }, eur(), usd(), rate, rate);
attempt (Sell, "N:B", q1, { "4", "4" }, "10", q1, { "4", "4" }, "4", { "2.666666666666666", "2.666666666666666" }, eur(), usd(), rate, rate);
attempt (Buy, "N:T", q1, { "1", "1" }, "10", q1, { "2", "2" }, "10", { "1", "1" }, eur(), usd(), rate, rate);
attempt (Buy, "N:BT", q1, { "2", "2" }, "10", q1, { "6", "6" }, "5", { "2", "2" }, eur(), usd(), rate, rate);
attempt (Buy, "N:TB", q1, { "2", "2" }, "2", q1, { "6", "6" }, "1", { "0.6666666666666667", "0.6666666666666667" }, eur(), usd(), rate, rate);
attempt (Sell, "A:N", q1, { "2", "2" }, "2.5", q1, { "2", "2" }, "10", { "1.666666666666666", "1.666666666666666" }, eur(), usd(), rate, rate);
}
void
run()
{
test_xrp_to_iou ();
test_iou_to_xrp ();
test_iou_to_iou ();
}
};
BEAST_DEFINE_TESTSUITE(Taker,tx,ripple);
}

View File

@@ -0,0 +1,144 @@
//------------------------------------------------------------------------------
/*
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/ErrorCodes.h>
#include <ripple/test/jtx.h>
namespace ripple {
namespace test {
struct Transaction_ordering_test : public beast::unit_test::suite
{
void testCorrectOrder()
{
using namespace jtx;
Env env(*this);
auto const alice = Account("alice");
env.fund(XRP(1000), noripple(alice));
auto const aliceSequence = env.seq(alice);
auto const tx1 = env.jt(noop(alice), seq(aliceSequence));
auto const tx2 = env.jt(noop(alice), seq(aliceSequence + 1),
json(R"({"LastLedgerSequence":7})"));
env(tx1);
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSequence + 1);
env(tx2);
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSequence + 2);
env.close();
{
auto const result = env.rpc("tx", to_string(tx1.stx->getTransactionID()));
BEAST_EXPECT(result["result"]["meta"]["TransactionResult"] == "tesSUCCESS");
}
{
auto const result = env.rpc("tx", to_string(tx2.stx->getTransactionID()));
BEAST_EXPECT(result["result"]["meta"]["TransactionResult"] == "tesSUCCESS");
}
}
void testIncorrectOrder()
{
using namespace jtx;
Env env(*this);
env.app().getJobQueue().setThreadCount(0, false);
auto const alice = Account("alice");
env.fund(XRP(1000), noripple(alice));
auto const aliceSequence = env.seq(alice);
auto const tx1 = env.jt(noop(alice), seq(aliceSequence));
auto const tx2 = env.jt(noop(alice), seq(aliceSequence + 1),
json(R"({"LastLedgerSequence":7})"));
env(tx2, ter(terPRE_SEQ));
BEAST_EXPECT(env.seq(alice) == aliceSequence);
env(tx1);
env.app().getJobQueue().rendezvous();
BEAST_EXPECT(env.seq(alice) == aliceSequence + 2);
env.close();
{
auto const result = env.rpc("tx", to_string(tx1.stx->getTransactionID()));
BEAST_EXPECT(result["result"]["meta"]["TransactionResult"] == "tesSUCCESS");
}
{
auto const result = env.rpc("tx", to_string(tx2.stx->getTransactionID()));
BEAST_EXPECT(result["result"]["meta"]["TransactionResult"] == "tesSUCCESS");
}
}
void testIncorrectOrderMultipleIntermediaries()
{
using namespace jtx;
Env env(*this);
env.app().getJobQueue().setThreadCount(0, false);
auto const alice = Account("alice");
env.fund(XRP(1000), noripple(alice));
auto const aliceSequence = env.seq(alice);
std::vector<JTx> tx;
for (auto i = 0; i < 5; ++i)
{
tx.emplace_back(
env.jt(noop(alice), seq(aliceSequence + i),
json(R"({"LastLedgerSequence":7})"))
);
}
for (auto i = 1; i < 5; ++i)
{
env(tx[i], ter(terPRE_SEQ));
BEAST_EXPECT(env.seq(alice) == aliceSequence);
}
env(tx[0]);
env.app().getJobQueue().rendezvous();
BEAST_EXPECT(env.seq(alice) == aliceSequence + 5);
env.close();
for (auto i = 0; i < 5; ++i)
{
auto const result = env.rpc("tx", to_string(tx[i].stx->getTransactionID()));
BEAST_EXPECT(result["result"]["meta"]["TransactionResult"] == "tesSUCCESS");
}
}
void run() override
{
testCorrectOrder();
testIncorrectOrder();
testIncorrectOrderMultipleIntermediaries();
}
};
BEAST_DEFINE_TESTSUITE(Transaction_ordering,app,ripple);
} // test
} // ripple

2665
src/test/app/TxQ_test.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,300 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright 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/Slice.h>
#include <ripple/basics/TestSuite.h>
#include <ripple/app/misc/ValidatorList.h>
#include <ripple/protocol/SecretKey.h>
namespace ripple {
namespace tests {
class ValidatorList_test : public ripple::TestSuite
{
private:
static
PublicKey
randomNode ()
{
return derivePublicKey (
KeyType::secp256k1,
randomSecretKey());
}
static
PublicKey
randomMasterKey ()
{
return derivePublicKey (
KeyType::ed25519,
randomSecretKey());
}
static
bool
isPresent (
std::vector<PublicKey> container,
PublicKey const& item)
{
auto found = std::find (
std::begin (container),
std::end (container),
item);
return (found != std::end (container));
}
void
testConfigLoad ()
{
testcase ("Config Load");
auto validators = std::make_unique <ValidatorList> (beast::Journal ());
std::vector<PublicKey> network;
network.reserve(8);
while (network.size () != 8)
network.push_back (randomNode());
auto format = [](
PublicKey const &publicKey,
char const* comment = nullptr)
{
auto ret = toBase58(
TokenType::TOKEN_NODE_PUBLIC,
publicKey);
if (comment)
ret += comment;
return ret;
};
Section s1;
// Correct (empty) configuration
BEAST_EXPECT(validators->load (s1));
BEAST_EXPECT(validators->size() == 0);
// Correct configuration
s1.append (format (network[0]));
s1.append (format (network[1], " Comment"));
s1.append (format (network[2], " Multi Word Comment"));
s1.append (format (network[3], " Leading Whitespace"));
s1.append (format (network[4], " Trailing Whitespace "));
s1.append (format (network[5], " Leading & Trailing Whitespace "));
s1.append (format (network[6], " Leading, Trailing & Internal Whitespace "));
s1.append (format (network[7], " "));
BEAST_EXPECT(validators->load (s1));
for (auto const& n : network)
BEAST_EXPECT(validators->trusted (n));
// Incorrect configurations:
Section s2;
s2.append ("NotAPublicKey");
BEAST_EXPECT(!validators->load (s2));
Section s3;
s3.append (format (network[0], "!"));
BEAST_EXPECT(!validators->load (s3));
Section s4;
s4.append (format (network[0], "! Comment"));
BEAST_EXPECT(!validators->load (s4));
// Check if we properly terminate when we encounter
// a malformed or unparseable entry:
auto const node1 = randomNode();
auto const node2 = randomNode ();
Section s5;
s5.append (format (node1, "XXX"));
s5.append (format (node2));
BEAST_EXPECT(!validators->load (s5));
BEAST_EXPECT(!validators->trusted (node1));
BEAST_EXPECT(!validators->trusted (node2));
// Add Ed25519 master public keys to permanent validators list
auto const masterNode1 = randomMasterKey ();
auto const masterNode2 = randomMasterKey ();
Section s6;
s6.append (format (masterNode1));
s6.append (format (masterNode2, " Comment"));
BEAST_EXPECT(validators->load (s6));
BEAST_EXPECT(validators->trusted (masterNode1));
BEAST_EXPECT(validators->trusted (masterNode2));
}
void
testMembership ()
{
// The servers on the permanentValidators
std::vector<PublicKey> permanentValidators;
std::vector<PublicKey> ephemeralValidators;
while (permanentValidators.size () != 64)
permanentValidators.push_back (randomNode());
while (ephemeralValidators.size () != 64)
ephemeralValidators.push_back (randomNode());
{
testcase ("Membership: No Validators");
auto vl = std::make_unique <ValidatorList> (beast::Journal ());
for (auto const& v : permanentValidators)
BEAST_EXPECT(!vl->trusted (v));
for (auto const& v : ephemeralValidators)
BEAST_EXPECT(!vl->trusted (v));
}
{
testcase ("Membership: Non-Empty, Some Present, Some Not Present");
std::vector<PublicKey> p (
permanentValidators.begin (),
permanentValidators.begin () + 16);
while (p.size () != 32)
p.push_back (randomNode());
std::vector<PublicKey> e (
ephemeralValidators.begin (),
ephemeralValidators.begin () + 16);
while (e.size () != 32)
e.push_back (randomNode());
auto vl = std::make_unique <ValidatorList> (beast::Journal ());
for (auto const& v : p)
vl->insertPermanentKey (v, "");
for (auto const& v : e)
vl->insertEphemeralKey (v, "");
for (auto const& v : p)
BEAST_EXPECT(vl->trusted (v));
for (auto const& v : e)
BEAST_EXPECT(vl->trusted (v));
for (auto const& v : permanentValidators)
BEAST_EXPECT(static_cast<bool>(vl->trusted (v)) == isPresent (p, v));
for (auto const& v : ephemeralValidators)
BEAST_EXPECT(static_cast<bool>(vl->trusted (v)) == isPresent (e, v));
}
}
void
testModification ()
{
testcase ("Insertion and Removal");
auto vl = std::make_unique <ValidatorList> (beast::Journal ());
auto const v = randomNode ();
// Inserting a new permanent key succeeds
BEAST_EXPECT(vl->insertPermanentKey (v, "Permanent"));
{
auto member = vl->member (v);
BEAST_EXPECT(static_cast<bool>(member));
BEAST_EXPECT(member->compare("Permanent") == 0);
}
// Inserting the same permanent key fails:
BEAST_EXPECT(!vl->insertPermanentKey (v, ""));
{
auto member = vl->member (v);
BEAST_EXPECT(static_cast<bool>(member));
BEAST_EXPECT(member->compare("Permanent") == 0);
}
// Inserting the same key as ephemeral fails:
BEAST_EXPECT(!vl->insertEphemeralKey (v, "Ephemeral"));
{
auto member = vl->member (v);
BEAST_EXPECT(static_cast<bool>(member));
BEAST_EXPECT(member->compare("Permanent") == 0);
}
// Removing the key as ephemeral fails:
BEAST_EXPECT(!vl->removeEphemeralKey (v));
{
auto member = vl->member (v);
BEAST_EXPECT(static_cast<bool>(member));
BEAST_EXPECT(member->compare("Permanent") == 0);
}
// Deleting the key as permanent succeeds:
BEAST_EXPECT(vl->removePermanentKey (v));
BEAST_EXPECT(!static_cast<bool>(vl->trusted (v)));
// Insert an ephemeral validator key
BEAST_EXPECT(vl->insertEphemeralKey (v, "Ephemeral"));
{
auto member = vl->member (v);
BEAST_EXPECT(static_cast<bool>(member));
BEAST_EXPECT(member->compare("Ephemeral") == 0);
}
// Inserting the same ephemeral key fails
BEAST_EXPECT(!vl->insertEphemeralKey (v, ""));
{
auto member = vl->member (v);
BEAST_EXPECT(static_cast<bool>(member));
BEAST_EXPECT(member->compare("Ephemeral") == 0);
}
// Inserting the same key as permanent fails:
BEAST_EXPECT(!vl->insertPermanentKey (v, "Permanent"));
{
auto member = vl->member (v);
BEAST_EXPECT(static_cast<bool>(member));
BEAST_EXPECT(member->compare("Ephemeral") == 0);
}
// Deleting the key as permanent fails:
BEAST_EXPECT(!vl->removePermanentKey (v));
{
auto member = vl->member (v);
BEAST_EXPECT(static_cast<bool>(member));
BEAST_EXPECT(member->compare("Ephemeral") == 0);
}
// Deleting the key as ephemeral succeeds:
BEAST_EXPECT(vl->removeEphemeralKey (v));
BEAST_EXPECT(!vl->trusted(v));
}
public:
void
run() override
{
testConfigLoad();
testMembership ();
testModification ();
}
};
BEAST_DEFINE_TESTSUITE(ValidatorList, app, ripple);
} // tests
} // ripple