mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-20 19:15:54 +00:00
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:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
136
src/ripple/app/ledger/DeferredCredits.cpp
Normal file
136
src/ripple/app/ledger/DeferredCredits.cpp
Normal 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 ();
|
||||
}
|
||||
}
|
||||
59
src/ripple/app/ledger/DeferredCredits.h
Normal file
59
src/ripple/app/ledger/DeferredCredits.h
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
291
src/ripple/app/ledger/tests/DeferredCredits.test.cpp
Normal file
291
src/ripple/app/ledger/tests/DeferredCredits.test.cpp
Normal 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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user