From a50d67257c66bc36c80ebc193aac78076603eff3 Mon Sep 17 00:00:00 2001 From: Nik Bougalis Date: Tue, 8 Sep 2015 11:56:10 -0700 Subject: [PATCH] Limit the total number of offers processed while crossing --- Builds/VisualStudio2015/RippleD.vcxproj | 4 + .../VisualStudio2015/RippleD.vcxproj.filters | 3 + src/ripple/app/tests/CrossingLimits_test.cpp | 162 ++++++++++++++++++ src/ripple/app/tx/impl/CreateOffer.cpp | 11 +- src/ripple/app/tx/impl/CreateOffer.h | 4 + src/ripple/app/tx/impl/OfferStream.cpp | 7 +- src/ripple/app/tx/impl/OfferStream.h | 32 +++- src/ripple/unity/app_tests.cpp | 9 +- 8 files changed, 222 insertions(+), 10 deletions(-) create mode 100644 src/ripple/app/tests/CrossingLimits_test.cpp diff --git a/Builds/VisualStudio2015/RippleD.vcxproj b/Builds/VisualStudio2015/RippleD.vcxproj index 174450b5a3..8c5fc2d7e3 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 55bb6b7b09..be0613c568 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 0000000000..8cecfbb9b1 --- /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 469a21bef1..b81d37a0f8 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 45b8c82492..b1cd5b45ef 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 2e5a43d150..57d486e441 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 1d63252d29..4257d997a4 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 de5f8d9605..5d309ced04 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