diff --git a/Builds/VisualStudio2013/RippleD.vcxproj b/Builds/VisualStudio2013/RippleD.vcxproj
index fd6110a9d..2569d12d3 100644
--- a/Builds/VisualStudio2013/RippleD.vcxproj
+++ b/Builds/VisualStudio2013/RippleD.vcxproj
@@ -1705,6 +1705,10 @@
+
+ True
+ True
+
True
True
diff --git a/Builds/VisualStudio2013/RippleD.vcxproj.filters b/Builds/VisualStudio2013/RippleD.vcxproj.filters
index e0e7611ba..2d03db6a5 100644
--- a/Builds/VisualStudio2013/RippleD.vcxproj.filters
+++ b/Builds/VisualStudio2013/RippleD.vcxproj.filters
@@ -2454,6 +2454,9 @@
ripple\app\paths
+
+ ripple\app\tests
+
ripple\app\tests
diff --git a/src/ripple/app/tests/CrossingLimits_test.cpp b/src/ripple/app/tests/CrossingLimits_test.cpp
new file mode 100644
index 000000000..8cecfbb9b
--- /dev/null
+++ b/src/ripple/app/tests/CrossingLimits_test.cpp
@@ -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
+#include
+#include
+
+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
diff --git a/src/ripple/app/tx/impl/CreateOffer.cpp b/src/ripple/app/tx/impl/CreateOffer.cpp
index dc93c140e..cdbc4a332 100644
--- a/src/ripple/app/tx/impl/CreateOffer.cpp
+++ b/src/ripple/app/tx/impl/CreateOffer.cpp
@@ -154,13 +154,16 @@ CreateOffer::bridged_cross (
throw std::logic_error ("Bridging with XRP and an endpoint.");
OfferStream offers_direct (view, view_cancel,
- Book (taker.issue_in (), taker.issue_out ()), when, ctx_.config, j_);
+ Book (taker.issue_in (), taker.issue_out ()), when, stepCounter_,
+ ctx_.config, j_);
OfferStream offers_leg1 (view, view_cancel,
- Book (taker.issue_in (), xrpIssue ()), when, ctx_.config, j_);
+ Book (taker.issue_in (), xrpIssue ()), when, stepCounter_,
+ ctx_.config, j_);
OfferStream offers_leg2 (view, view_cancel,
- Book (xrpIssue (), taker.issue_out ()), when, ctx_.config, j_);
+ Book (xrpIssue (), taker.issue_out ()), when, stepCounter_,
+ ctx_.config, j_);
TER cross_result = tesSUCCESS;
@@ -303,7 +306,7 @@ CreateOffer::direct_cross (
OfferStream offers (
view, view_cancel,
Book (taker.issue_in (), taker.issue_out ()),
- when, ctx_.config, j_);
+ when, stepCounter_, ctx_.config, j_);
TER cross_result (tesSUCCESS);
int count = 0;
diff --git a/src/ripple/app/tx/impl/CreateOffer.h b/src/ripple/app/tx/impl/CreateOffer.h
index 5c6936268..e864fbf39 100644
--- a/src/ripple/app/tx/impl/CreateOffer.h
+++ b/src/ripple/app/tx/impl/CreateOffer.h
@@ -43,6 +43,7 @@ public:
CreateOffer (Args&&... args)
: Transactor(std::forward<
Args>(args)...)
+ , stepCounter_ (1000, j_)
{
}
@@ -118,6 +119,9 @@ private:
// What kind of offer we are placing
CrossType cross_type_;
std::uint32_t deprecatedWrongOwnerCount_;
+
+ // The number of steps to take through order books while crossing
+ OfferStream::StepCounter stepCounter_;
};
}
diff --git a/src/ripple/app/tx/impl/OfferStream.cpp b/src/ripple/app/tx/impl/OfferStream.cpp
index 12124a3a1..17de0a53a 100644
--- a/src/ripple/app/tx/impl/OfferStream.cpp
+++ b/src/ripple/app/tx/impl/OfferStream.cpp
@@ -23,7 +23,7 @@
namespace ripple {
OfferStream::OfferStream (ApplyView& view, ApplyView& view_cancel,
- BookRef book, Clock::time_point when,
+ BookRef book, Clock::time_point when, StepCounter& counter,
Config const& config, beast::Journal journal)
: j_ (journal)
, m_view (view)
@@ -32,6 +32,7 @@ OfferStream::OfferStream (ApplyView& view, ApplyView& view_cancel,
, m_when (when)
, m_tip (view, book)
, config_ (config)
+ , counter_ (counter)
{
}
@@ -87,6 +88,10 @@ OfferStream::step ()
if (! m_tip.step())
return false;
+ // If we exceed the maximum number of allowed steps, we're done.
+ if (!counter_.step ())
+ return false;
+
std::shared_ptr entry = m_tip.entry();
// Remove if missing
diff --git a/src/ripple/app/tx/impl/OfferStream.h b/src/ripple/app/tx/impl/OfferStream.h
index 027f896a4..bb24ee43a 100644
--- a/src/ripple/app/tx/impl/OfferStream.h
+++ b/src/ripple/app/tx/impl/OfferStream.h
@@ -49,6 +49,35 @@ namespace ripple {
*/
class OfferStream
{
+public:
+ class StepCounter
+ {
+ private:
+ std::uint32_t const limit_;
+ std::uint32_t count_;
+ beast::Journal j_;
+
+ public:
+ StepCounter (std::uint32_t limit, beast::Journal j)
+ : limit_ (limit)
+ , count_ (0)
+ , j_ (j)
+ {
+ }
+
+ bool
+ step ()
+ {
+ if (count_ >= limit_)
+ {
+ j_.debug << "Exceeded " << limit_ << " step limit.";
+ return false;
+ }
+ count_++;
+ return true;
+ }
+ };
+
private:
beast::Journal j_;
std::reference_wrapper m_view;
@@ -58,15 +87,15 @@ private:
BookTip m_tip;
Offer m_offer;
Config const& config_;
+ StepCounter& counter_;
void
erase (ApplyView& view);
public:
OfferStream (ApplyView& view, ApplyView& view_cancel,
- BookRef book, Clock::time_point when,
- Config const& config,
- beast::Journal journal);
+ BookRef book, Clock::time_point when, StepCounter& counter,
+ Config const& config, beast::Journal journal);
ApplyView&
view () noexcept
diff --git a/src/ripple/unity/app_tests.cpp b/src/ripple/unity/app_tests.cpp
index f27ccf031..ff7134534 100644
--- a/src/ripple/unity/app_tests.cpp
+++ b/src/ripple/unity/app_tests.cpp
@@ -20,3 +20,4 @@
#include
#include
+#include