From 8377f2516b70cf93aa85e2bd285c80399093654c Mon Sep 17 00:00:00 2001 From: seelabs Date: Wed, 11 Mar 2015 07:54:34 -0700 Subject: [PATCH] Cache and apply account credits after payment processing (RIPD-821): Credits made to any account during the processing of a payment are delayed until the payment completes, enforcing a new invariant: liquidity for any paths during a payment's execution may never increase. This eliminates the need for special code to handle a variety of corner cases where consuming liquidity in one path increases liquidity in others. --- Builds/VisualStudio2013/RippleD.vcxproj | 14 + .../VisualStudio2013/RippleD.vcxproj.filters | 9 + src/ripple/app/ledger/DeferredCredits.cpp | 136 ++++++++ src/ripple/app/ledger/DeferredCredits.h | 59 ++++ src/ripple/app/ledger/LedgerEntrySet.cpp | 92 +++++- src/ripple/app/ledger/LedgerEntrySet.h | 32 +- .../app/ledger/tests/DeferredCredits.test.cpp | 291 ++++++++++++++++++ src/ripple/app/tests/common_ledger.cpp | 53 ++++ src/ripple/app/tests/common_ledger.h | 48 +++ src/ripple/app/transactors/Payment.cpp | 21 +- src/ripple/unity/app6.cpp | 2 + 11 files changed, 740 insertions(+), 17 deletions(-) create mode 100644 src/ripple/app/ledger/DeferredCredits.cpp create mode 100644 src/ripple/app/ledger/DeferredCredits.h create mode 100644 src/ripple/app/ledger/tests/DeferredCredits.test.cpp diff --git a/Builds/VisualStudio2013/RippleD.vcxproj b/Builds/VisualStudio2013/RippleD.vcxproj index f69aaadb41..c419a2592f 100644 --- a/Builds/VisualStudio2013/RippleD.vcxproj +++ b/Builds/VisualStudio2013/RippleD.vcxproj @@ -1510,6 +1510,14 @@ + + True + True + ..\..\src\soci\src\core;..\..\src\sqlite;%(AdditionalIncludeDirectories) + ..\..\src\soci\src\core;..\..\src\sqlite;%(AdditionalIncludeDirectories) + + + True True @@ -1610,6 +1618,12 @@ + + True + True + ..\..\src\soci\src\core;..\..\src\sqlite;%(AdditionalIncludeDirectories) + ..\..\src\soci\src\core;..\..\src\sqlite;%(AdditionalIncludeDirectories) + True True diff --git a/Builds/VisualStudio2013/RippleD.vcxproj.filters b/Builds/VisualStudio2013/RippleD.vcxproj.filters index c9052fc3f9..614936eb93 100644 --- a/Builds/VisualStudio2013/RippleD.vcxproj.filters +++ b/Builds/VisualStudio2013/RippleD.vcxproj.filters @@ -2220,6 +2220,12 @@ ripple\app\ledger + + ripple\app\ledger + + + ripple\app\ledger + ripple\app\ledger @@ -2298,6 +2304,9 @@ ripple\app\ledger + + ripple\app\ledger\tests + ripple\app\ledger\tests diff --git a/src/ripple/app/ledger/DeferredCredits.cpp b/src/ripple/app/ledger/DeferredCredits.cpp new file mode 100644 index 0000000000..21f37f4a02 --- /dev/null +++ b/src/ripple/app/ledger/DeferredCredits.cpp @@ -0,0 +1,136 @@ +//------------------------------------------------------------------------------ +/* + 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 +#include + +namespace ripple { +template +void maybeLogCredit (Account const& sender, + Account const& receiver, + STAmount const& amount, + TMap const& adjMap) +{ + using std::get; + + if (!ShouldLog (lsTRACE, DeferredCredits)) + return; + + // write the balances to the log + std::stringstream str; + str << "assetXfer: " << sender << ", " << receiver << ", " << amount; + if (!adjMap.empty ()) + { + str << " : "; + } + for (auto i = adjMap.begin (), e = adjMap.end (); + i != e; ++i) + { + if (i != adjMap.begin ()) + { + str << ", "; + } + auto const& k(i->first); + auto const& v(i->second); + str << to_string (get<0> (k)) << " | " << + to_string (get<1> (k)) << " | " << + get<1> (v).getFullText () << " | " << + get<0> (v).getFullText (); + } + WriteLog (lsTRACE, DeferredCredits) << str.str (); +} + +void DeferredCredits::credit (Account const& sender, + Account const& receiver, + STAmount const& amount) +{ + using std::get; + + WriteLog (lsTRACE, DeferredCredits) + << "credit: " << sender << ", " << receiver << ", " << amount; + + assert (sender != receiver); + assert (!amount.negative ()); + + auto const k = makeKey (sender, receiver, amount.getCurrency ()); + auto i = map_.find (k); + if (i == map_.end ()) + { + Value v; + + if (sender < receiver) + { + get<1> (v) = amount; + get<0> (v) = amount.zeroed (); + } + else + { + get<1> (v) = amount.zeroed (); + get<0> (v) = amount; + } + + map_[k] = v; + } + else + { + auto& v = i->second; + if (sender < receiver) + get<1> (v) += amount; + else + get<0> (v) += amount; + } + maybeLogCredit (sender, receiver, amount, map_); +} + +// Get the adjusted balance of main for the +// balance between main and other. +STAmount DeferredCredits::adjustedBalance (Account const& main, + Account const& other, + STAmount const& curBalance) const +{ + using std::get; + STAmount result (curBalance); + + Key const k = makeKey (main, other, curBalance.getCurrency ()); + auto i = map_.find (k); + if (i != map_.end ()) + { + auto const& v = i->second; + if (main < other) + { + result -= get<0> (v); + } + else + { + result -= get<1> (v); + } + } + + WriteLog (lsTRACE, DeferredCredits) + << "adjustedBalance: " << main << ", " << + other << ", " << curBalance << ", " << result; + + return result; +} + +void DeferredCredits::clear () +{ + map_.clear (); +} +} diff --git a/src/ripple/app/ledger/DeferredCredits.h b/src/ripple/app/ledger/DeferredCredits.h new file mode 100644 index 0000000000..7a0594a514 --- /dev/null +++ b/src/ripple/app/ledger/DeferredCredits.h @@ -0,0 +1,59 @@ +//------------------------------------------------------------------------------ +/* + 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. +*/ +//============================================================================== + +#ifndef RIPPLE_APP_LEDGER_CACHEDCREDITS_H_INCLUDED +#define RIPPLE_APP_LEDGER_CACHEDCREDITS_H_INCLUDED + +#include +#include +#include +#include + +namespace ripple { +class DeferredCredits +{ +private: + // lowAccount, highAccount + using Key = std::tuple; + // lowAccountCredits, highAccountCredits + using Value = std::tuple; + static inline + Key makeKey(Account const& a1, Account const& a2, Currency const& c) + { + if (a1 < a2) + return std::make_tuple(a1, a2, c); + else + return std::make_tuple(a2, a1, c); + } + + std::map map_; + +public: + void credit (Account const& sender, + Account const& receiver, + STAmount const& amount); + // Get the adjusted balance of main for the + // balance between main and other. + STAmount adjustedBalance (Account const& main, + Account const& other, + STAmount const& curBalance) const; + void clear (); +}; +} +#endif diff --git a/src/ripple/app/ledger/LedgerEntrySet.cpp b/src/ripple/app/ledger/LedgerEntrySet.cpp index 01095d274f..999742bc62 100644 --- a/src/ripple/app/ledger/LedgerEntrySet.cpp +++ b/src/ripple/app/ledger/LedgerEntrySet.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -40,6 +41,8 @@ void LedgerEntrySet::init (Ledger::ref ledger, uint256 const& transactionID, std::uint32_t ledgerID, TransactionEngineParams params) { mEntries.clear (); + if (mDeferredCredits) + mDeferredCredits->clear (); mLedger = ledger; mSet.init (transactionID, ledgerID); mParams = params; @@ -50,20 +53,24 @@ void LedgerEntrySet::clear () { mEntries.clear (); mSet.clear (); + if (mDeferredCredits) + mDeferredCredits->clear (); } LedgerEntrySet LedgerEntrySet::duplicate () const { - return LedgerEntrySet (mLedger, mEntries, mSet, mSeq + 1); + return LedgerEntrySet (mLedger, mEntries, mSet, mSeq + 1, mDeferredCredits); } void LedgerEntrySet::swapWith (LedgerEntrySet& e) { - std::swap (mLedger, e.mLedger); + using std::swap; + swap (mLedger, e.mLedger); mEntries.swap (e.mEntries); mSet.swap (e.mSet); - std::swap (mParams, e.mParams); - std::swap (mSeq, e.mSeq); + swap (mParams, e.mParams); + swap (mSeq, e.mSeq); + swap (mDeferredCredits, e.mDeferredCredits); } // Find an entry in the set. If it has the wrong sequence number, copy it and update the sequence number. @@ -1125,7 +1132,7 @@ STAmount LedgerEntrySet::rippleHolds ( saBalance.setIssuer (issuer); } - return saBalance; + return adjustedBalance(account, issuer, saBalance); } // Returns the amount an account can spend without going into debt. @@ -1162,6 +1169,8 @@ STAmount LedgerEntrySet::accountHolds ( " saAmount=" << saAmount.getFullText () << " saBalance=" << saBalance.getFullText () << " uReserve=" << uReserve; + + return adjustedBalance(account, issuer, saAmount); } else { @@ -1170,9 +1179,10 @@ STAmount LedgerEntrySet::accountHolds ( WriteLog (lsTRACE, LedgerEntrySet) << "accountHolds:" << " account=" << to_string (account) << " saAmount=" << saAmount.getFullText (); + + return saAmount; } - return saAmount; } bool LedgerEntrySet::isGlobalFrozen (Account const& issuer) @@ -1268,6 +1278,10 @@ TER LedgerEntrySet::trustCreate ( const std::uint32_t uQualityIn, const std::uint32_t uQualityOut) { + WriteLog (lsTRACE, LedgerEntrySet) + << "trustCreate: " << to_string (uSrcAccountID) << ", " + << to_string (uDstAccountID) << ", " << saBalance.getFullText (); + auto const& uLowAccountID = !bSrcHigh ? uSrcAccountID : uDstAccountID; auto const& uHighAccountID = bSrcHigh ? uSrcAccountID : uDstAccountID; @@ -1351,6 +1365,8 @@ TER LedgerEntrySet::trustCreate ( // ONLY: Create ripple balance. sleRippleState->setFieldAmount (sfBalance, bSetHigh ? -saBalance : saBalance); + + cacheCredit (uSrcAccountID, uDstAccountID, saBalance); } return terResult; @@ -1396,6 +1412,42 @@ TER LedgerEntrySet::trustDelete ( return terResult; } +void LedgerEntrySet::enableDeferredCredits (bool enable) +{ + assert(enable == !mDeferredCredits); + + if (!enable) + { + mDeferredCredits.reset (); + return; + } + + if (!mDeferredCredits) + mDeferredCredits.emplace (); +} + +bool LedgerEntrySet::areCreditsDeferred () const +{ + return static_cast (mDeferredCredits); +} + +STAmount LedgerEntrySet::adjustedBalance (Account const& main, + Account const& other, + STAmount const& amount) const +{ + if (mDeferredCredits) + return mDeferredCredits->adjustedBalance (main, other, amount); + return amount; +} + +void LedgerEntrySet::cacheCredit (Account const& sender, + Account const& receiver, + STAmount const& amount) +{ + if (mDeferredCredits) + return mDeferredCredits->credit (sender, receiver, amount); +} + // Direct send w/o fees: // - Redeeming IOUs and/or sending sender's own IOUs. // - Create trust line of needed. @@ -1459,6 +1511,8 @@ TER LedgerEntrySet::rippleCredit ( } else { + cacheCredit (uSenderID, uReceiverID, saAmount); + STAmount saBalance = sleRippleState->getFieldAmount (sfBalance); if (bSenderHigh) @@ -1634,6 +1688,8 @@ TER LedgerEntrySet::accountSend ( return rippleSend (uSenderID, uReceiverID, saAmount, saActual); } + cacheCredit (uSenderID, uReceiverID, saAmount); + /* XRP send which does not check reserve and can do pure adjustment. * Note that sender or receiver may be null and this not a mistake; this * setup is used during pathfinding and it is carefully controlled to @@ -1819,6 +1875,8 @@ TER LedgerEntrySet::issue_iou ( if (bSenderHigh) final_balance.negate (); + cacheCredit (issue.account, account, amount); + // Adjust the balance on the trust line if necessary. We do this even if we // are going to delete the line to reflect the correct balance at the time // of deletion. @@ -1884,6 +1942,8 @@ TER LedgerEntrySet::redeem_iou ( if (bSenderHigh) final_balance.negate (); + cacheCredit (account, issue.account, amount); + // Adjust the balance on the trust line if necessary. We do this even if we // are going to delete the line to reflect the correct balance at the time // of deletion. @@ -1964,4 +2024,24 @@ rippleTransferRate (LedgerEntrySet& ledger, Account const& uSenderID, : rippleTransferRate (ledger, issuer); } +ScopedDeferCredits::ScopedDeferCredits (LedgerEntrySet& l) + : les_ (l), enabled_ (false) +{ + if (!les_.areCreditsDeferred ()) + { + WriteLog (lsTRACE, DeferredCredits) << "Enable"; + les_.enableDeferredCredits (true); + enabled_ = true; + } +} + +ScopedDeferCredits::~ScopedDeferCredits () +{ + if (enabled_) + { + WriteLog (lsTRACE, DeferredCredits) << "Disable"; + les_.enableDeferredCredits (false); + } +} + } // ripple diff --git a/src/ripple/app/ledger/LedgerEntrySet.h b/src/ripple/app/ledger/LedgerEntrySet.h index 3db9e29a7f..2e373555f9 100644 --- a/src/ripple/app/ledger/LedgerEntrySet.h +++ b/src/ripple/app/ledger/LedgerEntrySet.h @@ -21,8 +21,10 @@ #define RIPPLE_APP_LEDGER_LEDGERENTRYSET_H_INCLUDED #include +#include #include #include +#include namespace ripple { @@ -114,6 +116,7 @@ public: void invalidate () { mLedger.reset (); + mDeferredCredits.reset (); } bool isValid () const @@ -203,6 +206,9 @@ public: bool isGlobalFrozen (Account const& issuer); + void enableDeferredCredits (bool enable=true); + bool areCreditsDeferred () const; + TER rippleCredit ( Account const& uSenderID, Account const& uReceiverID, const STAmount & saAmount, bool bCheckIssuer = true); @@ -285,6 +291,8 @@ public: private: Ledger::pointer mLedger; std::map mEntries; // cannot be unordered! + // Defers credits made to accounts until later + boost::optional mDeferredCredits; typedef hash_map NodeToLedgerEntry; @@ -295,9 +303,9 @@ private: LedgerEntrySet ( Ledger::ref ledger, const std::map& e, - const TransactionMetaSet & s, int m) : - mLedger (ledger), mEntries (e), mSet (s), mParams (tapNONE), mSeq (m), - mImmutable (false) + const TransactionMetaSet & s, int m, boost::optional const& ft) : + mLedger (ledger), mEntries (e), mDeferredCredits (ft), mSet (s), mParams (tapNONE), + mSeq (m), mImmutable (false) {} SLE::pointer getForMod ( @@ -328,6 +336,24 @@ private: bool checkState (SLE::pointer state, bool bSenderHigh, Account const& sender, STAmount const& before, STAmount const& after); + + STAmount adjustedBalance (Account const& main, + Account const& other, + STAmount const& amount) const; + + void cacheCredit (Account const& sender, + Account const& receiver, + STAmount const& amount); +}; + +class ScopedDeferCredits +{ +private: + LedgerEntrySet& les_; + bool enabled_; +public: + ScopedDeferCredits(LedgerEntrySet& l); + ~ScopedDeferCredits (); }; // NIKB FIXME: move these to the right place diff --git a/src/ripple/app/ledger/tests/DeferredCredits.test.cpp b/src/ripple/app/ledger/tests/DeferredCredits.test.cpp new file mode 100644 index 0000000000..69e93441e8 --- /dev/null +++ b/src/ripple/app/ledger/tests/DeferredCredits.test.cpp @@ -0,0 +1,291 @@ +//------------------------------------------------------------------------------ +/* + 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 + +namespace ripple { +namespace test { +class DeferredCredits_test : public beast::unit_test::suite +{ + /* + Create paths so one path funds another path. + + Two accounts: sender and receiver. + Two gateways: gw1 and gw2. + Sender and receiver both have trust lines to the gateways. + Sender has 2 gw1/USD and 4 gw2/USD. + Sender has offer to exchange 2 gw1 for gw2 and gw2 for gw1 1-for-1. + Paths are: + 1) GW1 -> [OB GW1/USD->GW2/USD] -> GW2 + 2) GW2 -> [OB GW2/USD->GW1/USD] -> GW1 + + sender pays receiver 4 USD. + Path 1: + 1) Sender exchanges 2 GW1/USD for 2 GW2/USD + 2) Old code: the 2 GW1/USD is available to sender + New code: the 2 GW1/USD is not available until the + end of the transaction. + 3) Receiver gets 2 GW2/USD + Path 2: + 1) Old code: Sender exchanges 2 GW2/USD for 2 GW1/USD + 2) Old code: Receiver get 2 GW1 + 2) New code: Path is dry because sender does not have any + GW1 to spend until the end of the transaction. + */ + void testSelfFunding () + { + testcase ("selfFunding"); + + auto const keyType = KeyType::ed25519; + std::uint64_t const xrp = std::mega::num; + + auto master = createAccount ("masterpassphrase", keyType); + + Ledger::pointer LCL; + Ledger::pointer ledger; + std::tie (LCL, ledger) = createGenesisLedger (100000 * xrp, master); + + auto accounts = + createAndFundAccountsWithFlags (master, + {"snd", "rcv", "gw1", "gw2"}, + keyType, + 10000 * xrp, + ledger, + LCL, + asfDefaultRipple); + auto& gw1 = accounts["gw1"]; + auto& gw2 = accounts["gw2"]; + auto& snd = accounts["snd"]; + auto& rcv = accounts["rcv"]; + + close_and_advance (ledger, LCL); + + trust (snd, gw1, "USD", 10, ledger); + trust (snd, gw2, "USD", 10, ledger); + trust (rcv, gw1, "USD", 100, ledger); + trust (rcv, gw2, "USD", 100, ledger); + + pay (gw1, snd, "USD", "2", ledger); + pay (gw2, snd, "USD", "4", ledger); + + verifyBalance (ledger, snd, Amount (2, "USD", gw1)); + verifyBalance (ledger, snd, Amount (4, "USD", gw2)); + + close_and_advance (ledger, LCL); + + createOfferWithFlags (snd, + Amount (2, "USD", gw1), + Amount (2, "USD", gw2), + ledger, + tfPassive); + createOfferWithFlags (snd, + Amount (2, "USD", gw2), + Amount (2, "USD", gw1), + ledger, + tfPassive); + + close_and_advance (ledger, LCL); + + Json::Value path; + path.append (createPath (gw1, OfferPathNode ("USD", gw2), gw2)); + path.append (createPath (gw2, OfferPathNode ("USD", gw1), gw1)); + + payWithPath (snd, rcv, "USD", "4", ledger, path, + tfNoRippleDirect | tfPartialPayment); + + verifyBalance (ledger, rcv, Amount (0, "USD", gw1)); + verifyBalance (ledger, rcv, Amount (2, "USD", gw2)); + + pass (); + } + + void testSubtractCredits () + { + testcase ("subtractCredits"); + + auto const keyType = KeyType::ed25519; + std::uint64_t const xrp = std::mega::num; + + auto master = createAccount ("masterpassphrase", keyType); + + Ledger::pointer LCL; + Ledger::pointer ledger; + std::tie (LCL, ledger) = createGenesisLedger (100000 * xrp, master); + + auto accounts = + createAndFundAccountsWithFlags (master, + {"alice", "gw1", "gw2"}, + keyType, + 10000 * xrp, + ledger, + LCL, + asfDefaultRipple); + auto& gw1 = accounts["gw1"]; + auto& gw2 = accounts["gw2"]; + auto& alice = accounts["alice"]; + + close_and_advance (ledger, LCL); + + trust (alice, gw1, "USD", 100, ledger); + trust (alice, gw2, "USD", 100, ledger); + + pay (gw1, alice, "USD", "50", ledger); + pay (gw2, alice, "USD", "50", ledger); + + verifyBalance (ledger, alice, Amount (50, "USD", gw1)); + verifyBalance (ledger, alice, Amount (50, "USD", gw2)); + + ripple::Account const gw1Acc (gw1.pk.getAccountID ()); + ripple::Account const aliceAcc (alice.pk.getAccountID ()); + ripple::Currency const usd (to_currency ("USD")); + ripple::Issue const issue (usd, gw1Acc); + STAmount const toCredit (issue, 30); + STAmount const toDebit (issue, 20); + { + // accountSend, no FT + LedgerEntrySet les (ledger, tapNONE); + + expect (!les.areCreditsDeferred ()); + + STAmount const startingAmount = + les.accountHolds (aliceAcc, usd, gw1Acc, fhIGNORE_FREEZE); + + les.accountSend (gw1Acc, aliceAcc, toCredit); + expect (les.accountHolds (aliceAcc, usd, gw1Acc, fhIGNORE_FREEZE) == + startingAmount + toCredit); + + les.accountSend (aliceAcc, gw1Acc, toDebit); + expect (les.accountHolds (aliceAcc, usd, gw1Acc, fhIGNORE_FREEZE) == + startingAmount + toCredit - toDebit); + } + + { + // rippleCredit, no FT + LedgerEntrySet les (ledger, tapNONE); + + expect (!les.areCreditsDeferred ()); + + STAmount const startingAmount = + les.accountHolds (aliceAcc, usd, gw1Acc, fhIGNORE_FREEZE); + + les.rippleCredit (gw1Acc, aliceAcc, toCredit); + expect (les.accountHolds (aliceAcc, usd, gw1Acc, fhIGNORE_FREEZE) == + startingAmount + toCredit); + + les.rippleCredit (aliceAcc, gw1Acc, toDebit); + expect (les.accountHolds (aliceAcc, usd, gw1Acc, fhIGNORE_FREEZE) == + startingAmount + toCredit - toDebit); + } + + { + // accountSend, w/ FT + LedgerEntrySet les (ledger, tapNONE); + les.enableDeferredCredits (); + expect (les.areCreditsDeferred ()); + + STAmount const startingAmount = + les.accountHolds (aliceAcc, usd, gw1Acc, fhIGNORE_FREEZE); + + les.accountSend (gw1Acc, aliceAcc, toCredit); + expect (les.accountHolds (aliceAcc, usd, gw1Acc, fhIGNORE_FREEZE) == + startingAmount); + + les.accountSend (aliceAcc, gw1Acc, toDebit); + expect (les.accountHolds (aliceAcc, usd, gw1Acc, fhIGNORE_FREEZE) == + startingAmount - toDebit); + } + + { + // rippleCredit, w/ FT + LedgerEntrySet les (ledger, tapNONE); + les.enableDeferredCredits (); + expect (les.areCreditsDeferred ()); + + STAmount const startingAmount = + les.accountHolds (aliceAcc, usd, gw1Acc, fhIGNORE_FREEZE); + + les.rippleCredit (gw1Acc, aliceAcc, toCredit); + expect (les.accountHolds (aliceAcc, usd, gw1Acc, fhIGNORE_FREEZE) == + startingAmount); + + les.rippleCredit (aliceAcc, gw1Acc, toDebit); + expect (les.accountHolds (aliceAcc, usd, gw1Acc, fhIGNORE_FREEZE) == + startingAmount - toDebit); + } + + { + // rippleCredit, w/ FT & ScopedDeferCredits + LedgerEntrySet les (ledger, tapNONE); + + STAmount const startingAmount = + les.accountHolds (aliceAcc, usd, gw1Acc, fhIGNORE_FREEZE); + { + ScopedDeferCredits g (les); + les.rippleCredit (gw1Acc, aliceAcc, toCredit); + expect ( + les.accountHolds (aliceAcc, usd, gw1Acc, fhIGNORE_FREEZE) == + startingAmount); + } + expect (les.accountHolds (aliceAcc, usd, gw1Acc, fhIGNORE_FREEZE) == + startingAmount + toCredit); + } + + { + // issue_iou + LedgerEntrySet les (ledger, tapNONE); + STAmount const startingAmount = + les.accountHolds (aliceAcc, usd, gw1Acc, fhIGNORE_FREEZE); + les.enableDeferredCredits (); + expect (les.areCreditsDeferred ()); + + les.redeem_iou (aliceAcc, toDebit, issue); + expect (les.accountHolds (aliceAcc, usd, gw1Acc, fhIGNORE_FREEZE) == + startingAmount - toDebit); + } + { + // redeem_iou + LedgerEntrySet les (ledger, tapNONE); + STAmount const startingAmount = + les.accountHolds (aliceAcc, usd, gw1Acc, fhIGNORE_FREEZE); + { + ScopedDeferCredits g (les); + expect (les.areCreditsDeferred ()); + + les.issue_iou (aliceAcc, toCredit, issue); + expect ( + les.accountHolds (aliceAcc, usd, gw1Acc, fhIGNORE_FREEZE) == + startingAmount); + } + expect (les.accountHolds (aliceAcc, usd, gw1Acc, fhIGNORE_FREEZE) == + startingAmount + toCredit); + } + } + +public: + void run () + { + testSelfFunding (); + testSubtractCredits (); + } +}; + +BEAST_DEFINE_TESTSUITE (DeferredCredits, ledger, ripple); + +} // test +} // ripple diff --git a/src/ripple/app/tests/common_ledger.cpp b/src/ripple/app/tests/common_ledger.cpp index d76a708f4a..8c932905f0 100644 --- a/src/ripple/app/tests/common_ledger.cpp +++ b/src/ripple/app/tests/common_ledger.cpp @@ -356,6 +356,23 @@ payWithPath(TestAccount& from, TestAccount const& to, return tx; } +STTx +payWithPath(TestAccount& from, TestAccount const& to, + std::string const& currency, std::string const& amount, + Ledger::pointer const& ledger, Json::Value const& path, + std::uint32_t flags, bool sign) +{ + auto amountJson = Amount(std::stod(amount), currency, to).getJson(); + Json::Value tx_json = getPaymentJson(from, to, amountJson); + + tx_json[jss::Paths] = path; + tx_json[jss::Flags] = flags; + + auto tx = parseTransaction(from, tx_json, sign); + applyTransaction(ledger, tx, sign); + return tx; +} + void createOffer(TestAccount& from, Amount const& in, Amount const& out, @@ -369,6 +386,21 @@ createOffer(TestAccount& from, Amount const& in, Amount const& out, applyTransaction(ledger, tx, sign); } +void +createOfferWithFlags(TestAccount& from, Amount const& in, Amount const& out, + Ledger::pointer ledger, std::uint32_t flags, + bool sign) +{ + Json::Value tx_json = getCommonTransactionJson(from); + tx_json[jss::TransactionType] = "OfferCreate"; + tx_json[jss::TakerPays] = in.getJson(); + tx_json[jss::TakerGets] = out.getJson(); + tx_json[jss::Flags] = flags; + STTx tx = parseTransaction(from, tx_json, sign); + applyTransaction(ledger, tx, sign); +} + + // As currently implemented, this will cancel only the last offer made // from this account. void @@ -496,5 +528,26 @@ verifyBalance(Ledger::pointer ledger, TestAccount const& account, "balance != amountReq"); } +Json::Value pathNode (TestAccount const& acc) +{ + Json::Value result; + result["account"] = acc.pk.humanAccountID(); + result["type"] = 1; + result["type_hex"] = "0000000000000001"; + return result; +} + +Json::Value pathNode (OfferPathNode const& offer) +{ + Json::Value result; + result["currency"] = offer.currency; + result["type"] = 48; + result["type_hex"] = "0000000000000030"; + if (offer.issuer) + result["issuer"] = offer.issuer->pk.humanAccountID(); + return result; +} + + } } diff --git a/src/ripple/app/tests/common_ledger.h b/src/ripple/app/tests/common_ledger.h index bb443e3031..500e7e99b1 100644 --- a/src/ripple/app/tests/common_ledger.h +++ b/src/ripple/app/tests/common_ledger.h @@ -210,10 +210,22 @@ payWithPath(TestAccount& from, TestAccount const& to, std::string const& currency, std::string const& amount, Ledger::pointer const& ledger, bool sign = true); +STTx +payWithPath(TestAccount& from, TestAccount const& to, + std::string const& currency, std::string const& amount, + Ledger::pointer const& ledger, Json::Value const& path, + std::uint32_t flags, + bool sign = true); + void createOffer(TestAccount& from, Amount const& in, Amount const& out, Ledger::pointer ledger, bool sign = true); +void +createOfferWithFlags(TestAccount& from, Amount const& in, Amount const& out, + Ledger::pointer ledger, std::uint32_t flags, + bool sign = true); + // As currently implemented, this will cancel only the last offer made // from this account. void @@ -232,6 +244,42 @@ Json::Value findPath(Ledger::pointer ledger, TestAccount const& src, Amount const& dstAmount, beast::abstract_ostream& log, boost::optional contextPaths = boost::none); +struct OfferPathNode +{ + std::string currency; + boost::optional issuer; + OfferPathNode(std::string s, TestAccount const& iss) + :currency(std::move(s)), issuer(iss) {} +}; + +Json::Value pathNode (TestAccount const& acc); + +Json::Value pathNode (OfferPathNode const& offer); + +inline void createPathHelper (Json::Value& result) +{ + // base case +} + +template +void createPathHelper (Json::Value& result, + First&& first, + Rest&&... rest) +{ + result.append (pathNode (std::forward (first))); + createPathHelper(result, rest...); +} + +template +Json::Value createPath (First&& first, + Rest&&... rest) +{ + Json::Value result; + createPathHelper (result, first, rest...); + return result; +} + + SLE::pointer get_ledger_entry_ripple_state(Ledger::pointer ledger, RippleAddress account1, RippleAddress account2, diff --git a/src/ripple/app/transactors/Payment.cpp b/src/ripple/app/transactors/Payment.cpp index 32dd4ac576..fe6f575c20 100644 --- a/src/ripple/app/transactors/Payment.cpp +++ b/src/ripple/app/transactors/Payment.cpp @@ -292,14 +292,19 @@ public: } else { - auto rc = path::RippleCalc::rippleCalculate ( - mEngine->view (), - maxSourceAmount, - saDstAmount, - uDstAccountID, - mTxnAccountID, - spsPaths, - &rcInput); + + path::RippleCalc::Output rc; + { + ScopedDeferCredits g (mEngine->view ()); + rc = path::RippleCalc::rippleCalculate ( + mEngine->view (), + maxSourceAmount, + saDstAmount, + uDstAccountID, + mTxnAccountID, + spsPaths, + &rcInput); + } // TODO: is this right? If the amount is the correct amount, was // the delivered amount previously set? diff --git a/src/ripple/unity/app6.cpp b/src/ripple/unity/app6.cpp index 151e87a55b..c1f2b35774 100644 --- a/src/ripple/unity/app6.cpp +++ b/src/ripple/unity/app6.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -31,3 +32,4 @@ #include #include #include +#include