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.
This commit is contained in:
seelabs
2015-03-11 07:54:34 -07:00
committed by Tom Ritchford
parent fd1135315c
commit 8377f2516b
11 changed files with 740 additions and 17 deletions

View File

@@ -1510,6 +1510,14 @@
</ClCompile>
<ClInclude Include="..\..\src\ripple\app\ledger\ConsensusTransSetSF.h">
</ClInclude>
<ClCompile Include="..\..\src\ripple\app\ledger\DeferredCredits.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
<AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='debug.classic|x64'">..\..\src\soci\src\core;..\..\src\sqlite;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='release.classic|x64'">..\..\src\soci\src\core;..\..\src\sqlite;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<ClInclude Include="..\..\src\ripple\app\ledger\DeferredCredits.h">
</ClInclude>
<ClCompile Include="..\..\src\ripple\app\ledger\DirectoryEntryIterator.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
@@ -1610,6 +1618,12 @@
</ClCompile>
<ClInclude Include="..\..\src\ripple\app\ledger\OrderBookIterator.h">
</ClInclude>
<ClCompile Include="..\..\src\ripple\app\ledger\tests\DeferredCredits.test.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
<AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='debug.classic|x64'">..\..\src\soci\src\core;..\..\src\sqlite;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='release.classic|x64'">..\..\src\soci\src\core;..\..\src\sqlite;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<ClCompile Include="..\..\src\ripple\app\ledger\tests\Ledger_test.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>

View File

@@ -2220,6 +2220,12 @@
<ClInclude Include="..\..\src\ripple\app\ledger\ConsensusTransSetSF.h">
<Filter>ripple\app\ledger</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ripple\app\ledger\DeferredCredits.cpp">
<Filter>ripple\app\ledger</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ripple\app\ledger\DeferredCredits.h">
<Filter>ripple\app\ledger</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ripple\app\ledger\DirectoryEntryIterator.cpp">
<Filter>ripple\app\ledger</Filter>
</ClCompile>
@@ -2298,6 +2304,9 @@
<ClInclude Include="..\..\src\ripple\app\ledger\OrderBookIterator.h">
<Filter>ripple\app\ledger</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ripple\app\ledger\tests\DeferredCredits.test.cpp">
<Filter>ripple\app\ledger\tests</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ripple\app\ledger\tests\Ledger_test.cpp">
<Filter>ripple\app\ledger\tests</Filter>
</ClCompile>

View File

@@ -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 <ripple/app/ledger/DeferredCredits.h>
#include <ripple/basics/Log.h>
namespace ripple {
template <class TMap>
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 ();
}
}

View File

@@ -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 <ripple/protocol/UintTypes.h>
#include <ripple/protocol/STAmount.h>
#include <map>
#include <tuple>
namespace ripple {
class DeferredCredits
{
private:
// lowAccount, highAccount
using Key = std::tuple<Account, Account, Currency>;
// lowAccountCredits, highAccountCredits
using Value = std::tuple<STAmount, STAmount>;
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<Key, Value> 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

View File

@@ -20,6 +20,7 @@
#include <BeastConfig.h>
#include <ripple/app/book/Quality.h>
#include <ripple/app/ledger/LedgerEntrySet.h>
#include <ripple/app/ledger/DeferredCredits.h>
#include <ripple/basics/Log.h>
#include <ripple/basics/StringUtilities.h>
#include <ripple/json/to_string.h>
@@ -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<bool> (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

View File

@@ -21,8 +21,10 @@
#define RIPPLE_APP_LEDGER_LEDGERENTRYSET_H_INCLUDED
#include <ripple/app/ledger/Ledger.h>
#include <ripple/app/ledger/DeferredCredits.h>
#include <ripple/basics/CountedObject.h>
#include <ripple/protocol/STLedgerEntry.h>
#include <boost/optional.hpp>
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<uint256, LedgerEntrySetEntry> mEntries; // cannot be unordered!
// Defers credits made to accounts until later
boost::optional<DeferredCredits> mDeferredCredits;
typedef hash_map<uint256, SLE::pointer> NodeToLedgerEntry;
@@ -295,9 +303,9 @@ private:
LedgerEntrySet (
Ledger::ref ledger, const std::map<uint256, LedgerEntrySetEntry>& 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<DeferredCredits> 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

View File

@@ -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 <ripple/app/tests/common_ledger.h>
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

View File

@@ -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;
}
}
}

View File

@@ -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<Json::Value> contextPaths = boost::none);
struct OfferPathNode
{
std::string currency;
boost::optional<TestAccount> 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<class First, class... Rest>
void createPathHelper (Json::Value& result,
First&& first,
Rest&&... rest)
{
result.append (pathNode (std::forward<First> (first)));
createPathHelper(result, rest...);
}
template<class First, class... Rest>
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,

View File

@@ -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?

View File

@@ -23,6 +23,7 @@
#include <ripple/app/ledger/AcceptedLedger.cpp>
#include <ripple/app/ledger/DirectoryEntryIterator.cpp>
#include <ripple/app/ledger/OrderBookIterator.cpp>
#include <ripple/app/ledger/DeferredCredits.cpp>
#include <ripple/app/consensus/DisputedTx.cpp>
#include <ripple/app/misc/HashRouter.cpp>
#include <ripple/app/paths/AccountCurrencies.cpp>
@@ -31,3 +32,4 @@
#include <ripple/app/paths/Pathfinder.cpp>
#include <ripple/app/misc/AmendmentTableImpl.cpp>
#include <ripple/app/misc/tests/AmendmentTable.test.cpp>
#include <ripple/app/ledger/tests/DeferredCredits.test.cpp>