diff --git a/Builds/VisualStudio2015/RippleD.vcxproj b/Builds/VisualStudio2015/RippleD.vcxproj
index 174450b5a..8c5fc2d7e 100644
--- a/Builds/VisualStudio2015/RippleD.vcxproj
+++ b/Builds/VisualStudio2015/RippleD.vcxproj
@@ -1689,6 +1689,10 @@
True
True
+
+ True
+ True
+
True
True
diff --git a/Builds/VisualStudio2015/RippleD.vcxproj.filters b/Builds/VisualStudio2015/RippleD.vcxproj.filters
index 55bb6b7b0..be0613c56 100644
--- a/Builds/VisualStudio2015/RippleD.vcxproj.filters
+++ b/Builds/VisualStudio2015/RippleD.vcxproj.filters
@@ -2436,6 +2436,9 @@
ripple\app\tests
+
+ 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 469a21bef..b81d37a0f 100644
--- a/src/ripple/app/tx/impl/CreateOffer.cpp
+++ b/src/ripple/app/tx/impl/CreateOffer.cpp
@@ -251,13 +251,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, j_);
+ Book (taker.issue_in (), taker.issue_out ()),
+ when, stepCounter_, j_);
OfferStream offers_leg1 (view, view_cancel,
- Book (taker.issue_in (), xrpIssue ()), when, j_);
+ Book (taker.issue_in (), xrpIssue ()),
+ when, stepCounter_, j_);
OfferStream offers_leg2 (view, view_cancel,
- Book (xrpIssue (), taker.issue_out ()), when, j_);
+ Book (xrpIssue (), taker.issue_out ()),
+ when, stepCounter_, j_);
TER cross_result = tesSUCCESS;
@@ -397,7 +400,7 @@ CreateOffer::direct_cross (
OfferStream offers (
view, view_cancel,
Book (taker.issue_in (), taker.issue_out ()),
- when, j_);
+ when, stepCounter_, 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 45b8c8249..b1cd5b45e 100644
--- a/src/ripple/app/tx/impl/CreateOffer.h
+++ b/src/ripple/app/tx/impl/CreateOffer.h
@@ -41,6 +41,7 @@ class CreateOffer
public:
CreateOffer (ApplyContext& ctx)
: Transactor(ctx)
+ , stepCounter_ (1000, j_)
{
}
@@ -120,6 +121,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 2e5a43d15..57d486e44 100644
--- a/src/ripple/app/tx/impl/OfferStream.cpp
+++ b/src/ripple/app/tx/impl/OfferStream.cpp
@@ -25,13 +25,14 @@ namespace ripple {
OfferStream::OfferStream (ApplyView& view, ApplyView& cancelView,
BookRef book, Clock::time_point when,
- beast::Journal journal)
+ StepCounter& counter, beast::Journal journal)
: j_ (journal)
, view_ (view)
, cancelView_ (cancelView)
, book_ (book)
, expire_ (when)
, tip_ (view, book_)
+ , counter_ (counter)
{
}
@@ -89,6 +90,10 @@ OfferStream::step ()
std::shared_ptr entry = tip_.entry();
+ // If we exceed the maximum number of allowed steps, we're done.
+ if (!counter_.step ())
+ return false;
+
// Remove if missing
if (! entry)
{
diff --git a/src/ripple/app/tx/impl/OfferStream.h b/src/ripple/app/tx/impl/OfferStream.h
index 1d63252d2..4257d997a 100644
--- a/src/ripple/app/tx/impl/OfferStream.h
+++ b/src/ripple/app/tx/impl/OfferStream.h
@@ -48,6 +48,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_;
ApplyView& view_;
@@ -56,6 +85,7 @@ private:
Clock::time_point const expire_;
BookTip tip_;
Offer offer_;
+ StepCounter& counter_;
void
erase (ApplyView& view);
@@ -63,7 +93,7 @@ private:
public:
OfferStream (ApplyView& view, ApplyView& cancelView,
BookRef book, Clock::time_point when,
- beast::Journal journal);
+ StepCounter& counter, beast::Journal journal);
/** Returns the offer at the tip of the order book.
Offers are always presented in decreasing quality.
diff --git a/src/ripple/unity/app_tests.cpp b/src/ripple/unity/app_tests.cpp
index de5f8d960..5d309ced0 100644
--- a/src/ripple/unity/app_tests.cpp
+++ b/src/ripple/unity/app_tests.cpp
@@ -19,14 +19,15 @@
#include
-#include
-#include
#include
#include
-#include
+#include
#include
#include
#include
#include
-#include
+#include
+#include
+#include
#include
+#include