Autobridging:

Complete implementation of bridged offers crossings. While processing an offer
A:B we consider both the A:B order book and the combined A:XRP and XRP:B books
and pick the better offers. The net result is better liquidity and potentially
better rates.

* Rearchitect core::Taker to perform direct and bridged crossings.
* Compute bridged qualities.
* Implement a new Bridged OfferCreate transactor.
* Factor out common code from the Bridged and Direct OfferCreate transactors.
* Perform flow calculations without losing accuracy.
* Rename all transactors.
* Cleanups.
This commit is contained in:
Nik Bougalis
2014-05-16 14:47:12 -07:00
committed by Vinnie Falco
parent 72bc4ebf37
commit 27e8d44a56
43 changed files with 8631 additions and 8246 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -20,6 +20,7 @@
#ifndef RIPPLE_TYPES_RIPPLEASSETS_H_INCLUDED #ifndef RIPPLE_TYPES_RIPPLEASSETS_H_INCLUDED
#define RIPPLE_TYPES_RIPPLEASSETS_H_INCLUDED #define RIPPLE_TYPES_RIPPLEASSETS_H_INCLUDED
#include <cassert>
#include <functional> #include <functional>
#include <type_traits> #include <type_traits>
@@ -66,6 +67,9 @@ public:
: currency (currency_) : currency (currency_)
, issuer (issuer_) , issuer (issuer_)
{ {
// Either XRP and (currency == zero && issuer == zero) or some custom
// currency and (currency != 0 && issuer != 0)
assert (currency.isZero () == issuer.isZero ());
} }
template <bool OtherByValue> template <bool OtherByValue>
@@ -87,6 +91,7 @@ public:
bool is_xrp () const bool is_xrp () const
{ {
assert (currency.isZero () == issuer.isZero ());
if (currency.isZero ()) if (currency.isZero ())
return true; return true;
return false; return false;

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/* /*
This file is part of rippled: https://github.com/ripple/rippled This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc. Copyright (c) 2014 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above purpose with or without fee is hereby granted, provided that the above
@@ -191,18 +191,14 @@ public:
if (sig == 0) if (sig == 0)
return os << "0"; return os << "0";
else if (sig < 0)
if (sig < 0)
os << "-"; os << "-";
if (m_integral) if (m_integral)
return os << m_mantissa; return os << m_mantissa;
if (m_exponent != 0 && (m_exponent < -25 || m_exponent > -5)) if (m_exponent != 0 && (m_exponent < -25 || m_exponent > -5))
return os << m_mantissa << "e" << m_exponent; return os << m_mantissa << "e" << m_exponent;
//if (m_exponent > 0)
// return os <<
return os; return os;
} }
}; };

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/* /*
This file is part of rippled: https://github.com/ripple/rippled This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc. Copyright (c) 2014 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above purpose with or without fee is hereby granted, provided that the above
@@ -22,6 +22,8 @@
#include "Amount.h" #include "Amount.h"
#include "../../beast/beast/utility/noexcept.h"
namespace ripple { namespace ripple {
namespace core { namespace core {

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/* /*
This file is part of rippled: https://github.com/ripple/rippled This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc. Copyright (c) 2014 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above purpose with or without fee is hereby granted, provided that the above
@@ -25,8 +25,7 @@
#include "../../beast/beast/utility/noexcept.h" #include "../../beast/beast/utility/noexcept.h"
#include <ostream> #include <functional>
#include <utility>
namespace ripple { namespace ripple {
namespace core { namespace core {
@@ -54,15 +53,7 @@ private:
public: public:
/** Create the iterator. */ /** Create the iterator. */
BookTip (LedgerView& view, BookRef book) BookTip (LedgerView& view, BookRef book);
: m_view (view)
, m_valid (false)
, m_book (Ledger::getBookBase (
book.in.currency, book.in.issuer,
book.out.currency, book.out.issuer))
, m_end (Ledger::getQualityNext (m_book))
{
}
uint256 const& uint256 const&
dir() const noexcept dir() const noexcept
@@ -93,49 +84,7 @@ public:
@return `true` if there is a next offer @return `true` if there is a next offer
*/ */
bool bool
step () step ();
{
if (m_valid)
{
if (m_entry)
{
view().offerDelete (m_index);
m_entry = nullptr;
}
}
for(;;)
{
// See if there's an entry at or worse than current quality.
auto const page (
view().getNextLedgerIndex (m_book, m_end));
if (page.isZero())
return false;
unsigned int di (0);
SLE::pointer dir;
if (view().dirFirst (page, dir, di, m_index))
{
m_dir = dir->getIndex();
m_entry = view().entryCache (ltOFFER, m_index);
m_valid = true;
// Next query should start before this directory
m_book = page;
// The quality immediately before the next quality
--m_book;
break;
}
// There should never be an empty directory but just in case,
// we handle that case by advancing to the next directory.
m_book = page;
}
return true;
}
}; };
} }

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/* /*
This file is part of rippled: https://github.com/ripple/rippled This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc. Copyright (c) 2014 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above purpose with or without fee is hereby granted, provided that the above
@@ -27,6 +27,8 @@
#include "../misc/SerializedLedger.h" #include "../misc/SerializedLedger.h"
#include "../../ripple_data/protocol/FieldNames.h" #include "../../ripple_data/protocol/FieldNames.h"
#include "../../beast/beast/utility/noexcept.h"
#include <ostream> #include <ostream>
namespace ripple { namespace ripple {
@@ -76,12 +78,23 @@ public:
Some or all of the out amount may be unfunded. Some or all of the out amount may be unfunded.
*/ */
Amounts const Amounts const
amount() const noexcept amount() const
{ {
return Amounts (m_entry->getFieldAmount (sfTakerPays), return Amounts (m_entry->getFieldAmount (sfTakerPays),
m_entry->getFieldAmount (sfTakerGets)); m_entry->getFieldAmount (sfTakerGets));
} }
/** Returns `true` if no more funds can flow through this offer. */
bool
fully_consumed() const
{
if (m_entry->getFieldAmount (sfTakerPays) <= zero)
return true;
if (m_entry->getFieldAmount (sfTakerGets) <= zero)
return true;
return false;
}
/** Returns the ledger entry underlying the offer. */ /** Returns the ledger entry underlying the offer. */
// AVOID USING THIS // AVOID USING THIS
SLE::pointer SLE::pointer

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/* /*
This file is part of rippled: https://github.com/ripple/rippled This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc. Copyright (c) 2014 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above purpose with or without fee is hereby granted, provided that the above
@@ -27,8 +27,7 @@
#include "../../beast/beast/utility/noexcept.h" #include "../../beast/beast/utility/noexcept.h"
#include <ostream> #include <functional>
#include <utility>
namespace ripple { namespace ripple {
namespace core { namespace core {
@@ -49,12 +48,10 @@ namespace core {
When an offer is removed, it is removed from both views. This grooms the When an offer is removed, it is removed from both views. This grooms the
order book regardless of whether or not the transaction is successful. order book regardless of whether or not the transaction is successful.
TODO: Remove offers belonging to the taker
*/ */
class OfferStream class OfferStream
{ {
protected: private:
beast::Journal m_journal; beast::Journal m_journal;
std::reference_wrapper <LedgerView> m_view; std::reference_wrapper <LedgerView> m_view;
std::reference_wrapper <LedgerView> m_view_cancel; std::reference_wrapper <LedgerView> m_view_cancel;
@@ -63,90 +60,37 @@ protected:
BookTip m_tip; BookTip m_tip;
Offer m_offer; Offer m_offer;
// Handle the case where a directory item with no corresponding ledger entry
// is found. This shouldn't happen but if it does we clean it up.
void void
erase (LedgerView& view) erase (LedgerView& view);
{
// VFALCO NOTE
//
// This should be using LedgerView::dirDelete, which will
// correctly remove the directory if its the last entry.
// Unfortunately this is a protocol breaking change.
auto p (view.entryCache (ltDIR_NODE, m_tip.dir()));
if (p == nullptr)
{
if (m_journal.error) m_journal.error <<
"Missing directory " << m_tip.dir() <<
" for offer " << m_tip.index();
return;
}
auto v (p->getFieldV256 (sfIndexes));
auto& x (v.peekValue());
auto it (std::find (x.begin(), x.end(), m_tip.index()));
if (it == x.end())
{
if (m_journal.error) m_journal.error <<
"Missing offer " << m_tip.index() <<
" for directory " << m_tip.dir();
return;
}
x.erase (it);
p->setFieldV256 (sfIndexes, v);
view.entryModify (p);
if (m_journal.trace) m_journal.trace <<
"Missing offer " << m_tip.index() <<
" removed from directory " << m_tip.dir();
}
public: public:
OfferStream (LedgerView& view, LedgerView& view_cancel, BookRef book, OfferStream (LedgerView& view, LedgerView& view_cancel, BookRef book,
Clock::time_point when, beast::Journal journal) Clock::time_point when, beast::Journal journal);
: m_journal (journal)
, m_view (view)
, m_view_cancel (view_cancel)
, m_book (book)
, m_when (when)
, m_tip (view, book)
{
}
LedgerView& LedgerView&
view() noexcept view () noexcept
{ {
return m_view; return m_view;
} }
LedgerView& LedgerView&
view_cancel() noexcept view_cancel () noexcept
{ {
return m_view_cancel; return m_view_cancel;
} }
Book const& Book const&
book() const noexcept book () const noexcept
{ {
return m_book; return m_book;
} }
uint256 const&
dir() const noexcept
{
return m_tip.dir();
}
/** Returns the offer at the tip of the order book. /** Returns the offer at the tip of the order book.
Offers are always presented in decreasing quality. Offers are always presented in decreasing quality.
Only valid if step() returned `true`. Only valid if step() returned `true`.
*/ */
Offer const& Offer const&
tip() const tip () const
{ {
return m_offer; return m_offer;
} }
@@ -159,166 +103,22 @@ dir() const noexcept
@return `true` if there is a valid offer. @return `true` if there is a valid offer.
*/ */
bool bool
step() step ();
{
// Modifying the order or logic of these
// operations causes a protocol breaking change.
for(;;) /** Advance to the next valid offer that is not from the specified account.
{ This automatically removes:
// BookTip::step deletes the current offer from the view before - Offers with missing ledger entries
// advancing to the next (unless the ledger entry is missing). - Offers found unfunded
if (! m_tip.step()) - Offers from the same account
return false; - Expired offers
@return `true` if there is a valid offer.
SLE::pointer const& entry (m_tip.entry());
// Remove if missing
if (! entry)
{
erase (view());
erase (view_cancel());
continue;
}
// Remove if expired
if (entry->isFieldPresent (sfExpiration) &&
entry->getFieldU32 (sfExpiration) <= m_when)
{
view_cancel().offerDelete (entry->getIndex());
if (m_journal.trace) m_journal.trace <<
"Removing expired offer " << entry->getIndex();
continue;
}
m_offer = Offer (entry, m_tip.quality());
Amounts const amount (m_offer.amount());
// Remove if either amount is zero
if (amount.empty())
{
view_cancel().offerDelete (entry->getIndex());
if (m_journal.warning) m_journal.warning <<
"Removing bad offer " << entry->getIndex();
m_offer = Offer{};
continue;
}
// Calculate owner funds
// VFALCO NOTE The calling code also checks the funds,
// how expensive is looking up the funds twice?
Amount const owner_funds (view().accountFunds (
m_offer.account(), m_offer.amount().out));
// Check for unfunded offer
if (owner_funds <= zero)
{
// If the owner's balance in the pristine view is the same,
// we haven't modified the balance and therefore the
// offer is "found unfunded" versus "became unfunded"
if (view_cancel().accountFunds (m_offer.account(),
m_offer.amount().out) == owner_funds)
{
view_cancel().offerDelete (entry->getIndex());
if (m_journal.trace) m_journal.trace <<
"Removing unfunded offer " << entry->getIndex();
}
else
{
if (m_journal.trace) m_journal.trace <<
"Removing became unfunded offer " << entry->getIndex();
}
m_offer = Offer{};
continue;
}
#if 0
// Remove if its our own offer
//
// VFALCO NOTE We might not want this for payments
//
if (m_account == owner)
{
view_cancel().offerDelete (entry->getIndex());
if (m_journal.trace) m_journal.trace <<
"Removing self offer " << entry->getIndex();
continue;
}
#endif
break;
}
return true;
}
/** Updates the offer to reflect remaining funds.
The caller is responsible for following all the rounding rules.
The offer will be considered fully consumed if either the in
or the out amount is zero.
@return `true` If the offer had no funds remaining.
*/ */
bool bool
fill (Amounts const& remaining_funds) step_account (Account const& account);
{
// Erase the offer if it is fully consumed (in==0 || out==0)
// This is the same as becoming unfunded
return false;
}
}; };
//------------------------------------------------------------------------------
/**
Does everything an OfferStream does, and:
- remove offers that became unfunded (if path is used)
*/
#if 0
class PaymentOfferStream : public OfferStream
{
public:
PaymentOfferStream (LedgerView& view_base, BookRef book,
Clock::time_point when, beast::Journal journal)
: m_journal (journal)
, m_view (view_base.duplicate())
, m_view_apply (view_base.duplicate())
, m_book (book)
, m_when (when)
{
}
};
#endif
//------------------------------------------------------------------------------
/*
TakerOfferStream
Does everything a PaymentOfferStream does, and:
- remove offers owned by the taker (if tx succeeds?)
*/
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
} }
} }
/*
OfferStream
- remove offers with missing ledger entries (always)
- remove expired offers (always)
- remove offers found unfunded (always)
PaymentOfferStream
Does everything an OfferStream does, and:
- remove offers that became unfunded (if path is used)
TakerOfferStream
Does everything a PaymentOfferStream does, and:
- remove offers owned by the taker (if tx succeeds?)
*/
#endif #endif

View File

@@ -23,9 +23,7 @@
#include "Amount.h" #include "Amount.h"
#include "Amounts.h" #include "Amounts.h"
#include <cassert>
#include <cstdint> #include <cstdint>
#include <limits>
#include <ostream> #include <ostream>
namespace ripple { namespace ripple {
@@ -49,57 +47,53 @@ private:
public: public:
Quality() = default; Quality() = default;
/** Create a quality from the integer encoding of an Amount */
explicit explicit
Quality (std::uint64_t value) Quality (std::uint64_t value);
: m_value (value)
{
}
/** Create a quality from the ratio of two amounts. */ /** Create a quality from the ratio of two amounts. */
explicit explicit
Quality (Amounts const& amount) Quality (Amounts const& amount);
: m_value (Amount::getRate (amount.out, amount.in))
{
}
/** Advances to the next higher quality level. */ /** Advances to the next higher quality level. */
/** @{ */ /** @{ */
Quality& Quality&
operator++() operator++();
{
assert (m_value > 0);
--m_value;
return *this;
}
Quality Quality
operator++ (int) operator++ (int);
{
Quality prev (*this);
--*this;
return prev;
}
/** @} */ /** @} */
/** Advances to the next lower quality level. */ /** Advances to the next lower quality level. */
/** @{ */ /** @{ */
Quality& Quality&
operator--() operator--();
{
assert (m_value < std::numeric_limits<value_type>::max());
++m_value;
return *this;
}
Quality Quality
operator-- (int) operator-- (int);
{
Quality prev (*this);
++*this;
return prev;
}
/** @} */ /** @} */
/** Returns the quality as Amount. */
Amount
rate () const
{
return Amount::setRate (m_value);
}
/** Returns the scaled amount with in capped.
Math is avoided if the result is exact. The output is clamped
to prevent money creation.
*/
Amounts
ceil_in (Amounts const& amount, Amount const& limit) const;
/** Returns the scaled amount with out capped.
Math is avoided if the result is exact. The input is clamped
to prevent money creation.
*/
Amounts
ceil_out (Amounts const& amount, Amount const& limit) const;
/** Returns `true` if lhs is lower quality than `rhs`. /** Returns `true` if lhs is lower quality than `rhs`.
Lower quality means the taker receives a worse deal. Lower quality means the taker receives a worse deal.
Higher quality is better for the taker. Higher quality is better for the taker.
@@ -118,6 +112,13 @@ public:
return lhs.m_value == rhs.m_value; return lhs.m_value == rhs.m_value;
} }
friend
bool
operator!= (Quality const& lhs, Quality const& rhs) noexcept
{
return ! (lhs == rhs);
}
friend friend
std::ostream& std::ostream&
operator<< (std::ostream& os, Quality const& quality) operator<< (std::ostream& os, Quality const& quality)
@@ -125,72 +126,14 @@ public:
os << quality.m_value; os << quality.m_value;
return os; return os;
} }
/** Returns the quality as Amount. */
Amount
rate() const
{
return Amount::setRate (m_value);
}
/** Returns the scaled amount with in capped.
Math is avoided if the result is exact. The output is clamped
to prevent money creation.
*/
Amounts
ceil_in (Amounts const& amount, Amount const& limit) const
{
if (amount.in > limit)
{
// VFALCO TODO make mulRound avoid math when v2==one
#if 0
Amounts result (limit, Amount::mulRound (
limit, rate(), amount.out, true));
#else
Amounts result (limit, Amount::divRound (
limit, rate(), amount.out, true));
#endif
// Clamp out
if (result.out > amount.out)
result.out = amount.out;
return result;
}
return amount;
}
/** Returns the scaled amount with out capped.
Math is avoided if the result is exact. The input is clamped
to prevent money creation.
*/
Amounts
ceil_out (Amounts const& amount, Amount const& limit) const
{
if (amount.out > limit)
{
// VFALCO TODO make divRound avoid math when v2==one
#if 0
Amounts result (Amount::divRound (
limit, rate(), amount.in, true), limit);
#else
Amounts result (Amount::mulRound (
limit, rate(), amount.in, true), limit);
#endif
// Clamp in
if (result.in > amount.in)
result.in = amount.in;
return result;
}
return amount;
}
}; };
inline /** Calculate the quality of a two-hop path given the two hops.
bool @param lhs The first leg of the path: input to intermediate.
operator!= (Quality const& lhs, Quality const& rhs) noexcept @param rhs The second leg of the path: intermediate to output.
{ */
return ! (lhs == rhs); Quality
} composed_quality (Quality const& lhs, Quality const& rhs);
} }
} }

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/* /*
This file is part of rippled: https://github.com/ripple/rippled This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc. Copyright (c) 2014 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above purpose with or without fee is hereby granted, provided that the above
@@ -22,11 +22,14 @@
#include "Amounts.h" #include "Amounts.h"
#include "Quality.h" #include "Quality.h"
#include "Offer.h"
#include "Types.h" #include "Types.h"
#include "../../beast/beast/streams/debug_ostream.h" #include "../../beast/beast/streams/debug_ostream.h"
#include "../../beast/beast/utility/noexcept.h"
#include <utility> #include <functional>
//#include <utility>
namespace ripple { namespace ripple {
namespace core { namespace core {
@@ -37,7 +40,7 @@ class Taker
public: public:
struct Options struct Options
{ {
Options() = default; Options() = delete;
explicit explicit
Options (std::uint32_t tx_flags) Options (std::uint32_t tx_flags)
@@ -56,99 +59,54 @@ public:
private: private:
std::reference_wrapper <LedgerView> m_view; std::reference_wrapper <LedgerView> m_view;
Book m_book;
Account m_account; Account m_account;
Options m_options; Options m_options;
Quality m_quality; Quality m_quality;
Quality m_threshold; Quality m_threshold;
// The original in and out quantities // The original in and out quantities.
Amounts m_amount; Amounts const m_amount;
// Amount of input currency remaining. // The amounts still left over for us to try and take.
Amount m_in; Amounts m_remain;
// Amount of output currency we have received. private:
Amount m_out; Amounts
flow (Amounts amount, Offer const& offer, Account const& taker);
// Returns the balance of the taker's input currency, TER
Amount fill (Offer const& offer, Amounts const& amount);
funds() const
{ TER
return view().accountFunds (account(), m_in); fill (Offer const& leg1, Amounts const& amount1,
} Offer const& leg2, Amounts const& amount2);
public: public:
Taker (LedgerView& view, BookRef const& book, Taker (LedgerView& view, Account const& account,
Account const& account, Amounts const& amount, Amounts const& amount, Options const& options);
Options const& options)
: m_view (view)
, m_book (book)
, m_account (account)
, m_options (options)
, m_quality (amount)
, m_threshold (m_quality)
, m_amount (amount)
, m_in (amount.in)
, m_out (amount.out.getCurrency(), amount.out.getIssuer())
{
// If this is a passive order (tfPassive), this prevents
// offers at the same quality level from being consumed.
if (m_options.passive)
++m_threshold;
}
LedgerView& LedgerView&
view() const noexcept view () const noexcept
{ {
return m_view; return m_view;
} }
/** Returns the input and output asset pair identifier. */ /** Returns the amount remaining on the offer.
Book const& This is the amount at which the offer should be placed. It may either
book() const noexcept be for the full amount when there were no crossing offers, or for zero
{ when the offer fully crossed, or any amount in between.
return m_book; It is always at the original offer quality (m_quality)
} */
Amounts
remaining_offer () const;
/** Returns the account identifier of the taker. */ /** Returns the account identifier of the taker. */
Account const& Account const&
account() const noexcept account () const noexcept
{ {
return m_account; return m_account;
} }
/** Returns `true` if order crossing should not continue.
Order processing is stopped if the taker's order quantities have
been reached, or if the taker has run out of input funds.
*/
bool
done() const noexcept
{
if (m_options.sell)
{
// With the sell option, we are finished when
// we have consumed all the input currency.
if (m_in <= zero)
return true;
}
else if (m_out >= m_amount.out)
{
// With the buy option (!sell) we are finished when we
// have received the desired amount of output currency.
return true;
}
// We are finished if the taker is out of funds
return funds() <= zero;
}
Quality
threshold() const noexcept
{
return m_threshold;
}
/** Returns `true` if the quality does not meet the taker's requirements. */ /** Returns `true` if the quality does not meet the taker's requirements. */
bool bool
reject (Quality const& quality) const noexcept reject (Quality const& quality) const noexcept
@@ -156,138 +114,26 @@ public:
return quality < m_threshold; return quality < m_threshold;
} }
/** Calcualtes the result of applying the taker's funds to the offer. /** Returns `true` if order crossing should not continue.
@return The flow and flag indicating if the order was consumed. Order processing is stopped if the taker's order quantities have
been reached, or if the taker has run out of input funds.
*/ */
std::pair <Amounts, bool> bool
fill (Offer const& offer) const done () const;
{
// Best flow the owner can get.
// Start out assuming entire offer will flow.
Amounts owner_amount (offer.amount());
// Limit owner's output by available funds less fees /** Perform direct crossing through given offer.
@return tesSUCCESS on success, error code otherwise.
// VFALCO TODO Rename accountFounds to make it clear that
// it can return a clamped value.
Amount const owner_funds (view().accountFunds (
offer.account(), owner_amount.out));
// Get fee rate paid by owner
std::uint32_t const owner_charge_rate (view().rippleTransferRate (
offer.account(), account(), offer.entry()->getFieldAmount (
sfTakerGets).getIssuer()));
Amount const owner_charge (Amount::saFromRate (owner_charge_rate));
// VFALCO Make Amount::divide skip math if v2 == QUALITY_ONE
if (owner_charge_rate == QUALITY_ONE)
{
// Skip some math when there's no fee
owner_amount = offer.quality().ceil_out (
owner_amount, owner_funds);
}
else
{
owner_amount = offer.quality().ceil_out (owner_amount,
Amount::divide (owner_funds, owner_charge));
}
// Best flow the taker can get.
// Start out assuming entire offer will flow.
Amounts taker_amount (offer.amount());
// Limit taker's input by available funds less fees
Amount const taker_funds (view().accountFunds (account(), m_in));
// Get fee rate paid by taker
std::uint32_t const taker_charge_rate (view().rippleTransferRate (
account(), offer.account(), offer.entry()->getFieldAmount (
sfTakerPays).getIssuer()));
Amount const taker_charge (Amount::saFromRate (taker_charge_rate));
// VFALCO Make Amount::divide skip math if v2 == QUALITY_ONE
if (taker_charge_rate == QUALITY_ONE)
{
// Skip some math when there's no fee
taker_amount = offer.quality().ceil_in (
taker_amount, taker_funds);
}
else
{
taker_amount = offer.quality().ceil_in (taker_amount,
Amount::divide (taker_funds, taker_charge));
}
// Limit taker's input by options
if (! m_options.sell)
{
assert (m_out < m_amount.out);
taker_amount = offer.quality().ceil_out (
taker_amount, m_amount.out - m_out);
assert (taker_amount.in != zero);
}
// Calculate the amount that will flow through the offer
// This does not include the fees.
Amounts const flow ((owner_amount.in < taker_amount.in) ?
owner_amount : taker_amount);
bool const consumed (flow.out >= owner_amount.out);
return std::make_pair (flow, consumed);
}
/** Process the result of fee and funds calculation on the offer.
To protect the ledger, conditions which should never occur are
checked. If the invariants are broken, the processing fails.
If processing succeeds, the funds are distributed to the taker,
owner, and issuers.
@return `false` if processing failed (due to math errors).
*/ */
TER TER
process (Amounts const& flow, Offer const& offer) cross (Offer const& offer);
{
TER result (tesSUCCESS);
// VFALCO For the case of !sell, is it possible for the taker /** Perform bridged crossing through given offers.
// to get a tiny bit more than he asked for? @return tesSUCCESS on success, error code otherwise.
// DAVIDS Can you verify? */
assert (m_options.sell || flow.out <= m_amount.out); TER
cross (Offer const& leg1, Offer const& leg2);
// Calculate remaining portion of offer
Amounts const remain (
offer.entry()->getFieldAmount (sfTakerPays) - flow.in,
offer.entry()->getFieldAmount (sfTakerGets) - flow.out);
offer.entry()->setFieldAmount (sfTakerPays, remain.in);
offer.entry()->setFieldAmount (sfTakerGets, remain.out);
view().entryModify (offer.entry());
// Pay the taker
result = view().accountSend (offer.account(), account(), flow.out);
if (result == tesSUCCESS)
{
m_out += flow.out;
// Pay the owner
result = view().accountSend (account(), offer.account(), flow.in);
if (result == tesSUCCESS)
{
m_in -= flow.in;
}
}
return result;
}
}; };
inline inline
std::ostream& std::ostream&
operator<< (std::ostream& os, Taker const& taker) operator<< (std::ostream& os, Taker const& taker)
@@ -298,59 +144,4 @@ operator<< (std::ostream& os, Taker const& taker)
} }
} }
/*
// This code calculates the fees but then we discovered
// that LedgerEntrySet::accountSend does it for you.
Amounts fees;
// Calculate taker fee
if (taker_charge_rate == QUALITY_ONE)
{
// No fee, skip math
fees.in = Amount (flow.in.getCurrency(),
flow.in.getIssuer());
}
else
{
// VFALCO TODO Check the units (versus 4-arg version of mulRound)
Amount const in_plus_fees (Amount::mulRound (
flow.in, taker_charge, true));
// Make sure the taker has enough to pay the fee
if (in_plus_fees > taker_funds)
{
// Not enough funds, stiff the issuer
fees.in = taker_funds - flow.in;
}
else
{
fees.in = in_plus_fees - flow.in;
}
}
// Calculate owner fee
if (owner_charge_rate == QUALITY_ONE)
{
// No fee, skip math
fees.out = Amount (flow.out.getCurrency(),
flow.out.getIssuer());
}
else
{
Amount const out_plus_fees (Amount::mulRound (
flow.out, owner_charge, true));
// Make sure the owner has enough to pay the fee
if (out_plus_fees > owner_funds)
{
// Not enough funds, stiff the issuer
fees.out = owner_funds - flow.out;
}
else
{
fees.out = out_plus_fees - flow.out;
}
}
*/
#endif #endif

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/* /*
This file is part of rippled: https://github.com/ripple/rippled This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc. Copyright (c) 2014 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above purpose with or without fee is hereby granted, provided that the above
@@ -22,7 +22,7 @@
#include "../ledger/LedgerEntrySet.h" #include "../ledger/LedgerEntrySet.h"
#include "../../ripple/types/api/RippleAssets.h" #include "../../ripple/types/api/RippleAssets.h"
#include "../../ripple/types/api/UInt160.h" #include "../../ripple/types/api/base_uint.h"
#include <chrono> #include <chrono>
#include <cstdint> #include <cstdint>

View File

@@ -0,0 +1,81 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2014 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 "../BookTip.h"
namespace ripple {
namespace core {
BookTip::BookTip (LedgerView& view, BookRef book)
: m_view (view)
, m_valid (false)
, m_book (Ledger::getBookBase (
book.in.currency, book.in.issuer,
book.out.currency, book.out.issuer))
, m_end (Ledger::getQualityNext (m_book))
{
}
bool
BookTip::step ()
{
if (m_valid)
{
if (m_entry)
{
view().offerDelete (m_index);
m_entry = nullptr;
}
}
for(;;)
{
// See if there's an entry at or worse than current quality.
auto const page (
view().getNextLedgerIndex (m_book, m_end));
if (page.isZero())
return false;
unsigned int di (0);
SLE::pointer dir;
if (view().dirFirst (page, dir, di, m_index))
{
m_dir = dir->getIndex();
m_entry = view().entryCache (ltOFFER, m_index);
m_valid = true;
// Next query should start before this directory
m_book = page;
// The quality immediately before the next quality
--m_book;
break;
}
// There should never be an empty directory but just in case,
// we handle that case by advancing to the next directory.
m_book = page;
}
return true;
}
}
}

View File

@@ -0,0 +1,170 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include "../OfferStream.h"
namespace ripple {
namespace core {
OfferStream::OfferStream (LedgerView& view, LedgerView& view_cancel,
BookRef book, Clock::time_point when, beast::Journal journal)
: m_journal (journal)
, m_view (view)
, m_view_cancel (view_cancel)
, m_book (book)
, m_when (when)
, m_tip (view, book)
{
}
// Handle the case where a directory item with no corresponding ledger entry
// is found. This shouldn't happen but if it does we clean it up.
void
OfferStream::erase (LedgerView& view)
{
// NIKB NOTE This should be using LedgerView::dirDelete, which would
// correctly remove the directory if its the last entry.
// Unfortunately this is a protocol breaking change.
auto p (view.entryCache (ltDIR_NODE, m_tip.dir()));
if (p == nullptr)
{
if (m_journal.error) m_journal.error <<
"Missing directory " << m_tip.dir() <<
" for offer " << m_tip.index();
return;
}
auto v (p->getFieldV256 (sfIndexes));
auto& x (v.peekValue());
auto it (std::find (x.begin(), x.end(), m_tip.index()));
if (it == x.end())
{
if (m_journal.error) m_journal.error <<
"Missing offer " << m_tip.index() <<
" for directory " << m_tip.dir();
return;
}
x.erase (it);
p->setFieldV256 (sfIndexes, v);
view.entryModify (p);
if (m_journal.trace) m_journal.trace <<
"Missing offer " << m_tip.index() <<
" removed from directory " << m_tip.dir();
}
bool
OfferStream::step ()
{
// Modifying the order or logic of these
// operations causes a protocol breaking change.
for(;;)
{
// BookTip::step deletes the current offer from the view before
// advancing to the next (unless the ledger entry is missing).
if (! m_tip.step())
return false;
SLE::pointer const& entry (m_tip.entry());
// Remove if missing
if (! entry)
{
erase (view());
erase (view_cancel());
continue;
}
// Remove if expired
if (entry->isFieldPresent (sfExpiration) &&
entry->getFieldU32 (sfExpiration) <= m_when)
{
view_cancel().offerDelete (entry->getIndex());
if (m_journal.trace) m_journal.trace <<
"Removing expired offer " << entry->getIndex();
continue;
}
m_offer = Offer (entry, m_tip.quality());
Amounts const amount (m_offer.amount());
// Remove if either amount is zero
if (amount.empty())
{
view_cancel().offerDelete (entry->getIndex());
if (m_journal.warning) m_journal.warning <<
"Removing bad offer " << entry->getIndex();
m_offer = Offer{};
continue;
}
// Calculate owner funds
// NIKB NOTE The calling code also checks the funds, how expensive is
// looking up the funds twice?
Amount const owner_funds (view().accountFunds (
m_offer.account(), m_offer.amount().out));
// Check for unfunded offer
if (owner_funds <= zero)
{
// If the owner's balance in the pristine view is the same,
// we haven't modified the balance and therefore the
// offer is "found unfunded" versus "became unfunded"
if (view_cancel().accountFunds (m_offer.account(),
m_offer.amount().out) == owner_funds)
{
view_cancel().offerDelete (entry->getIndex());
if (m_journal.trace) m_journal.trace <<
"Removing unfunded offer " << entry->getIndex();
}
else
{
if (m_journal.trace) m_journal.trace <<
"Removing became unfunded offer " << entry->getIndex();
}
m_offer = Offer{};
continue;
}
break;
}
return true;
}
bool
OfferStream::step_account (Account const& account)
{
while (step ())
{
if (tip ().account () != account)
return true;
}
return false;
}
}
}

View File

@@ -0,0 +1,120 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include "../Quality.h"
#include <cassert>
#include <limits>
namespace ripple {
namespace core {
Quality::Quality (std::uint64_t value)
: m_value (value)
{
}
Quality::Quality (Amounts const& amount)
: m_value (Amount::getRate (amount.out, amount.in))
{
}
Quality&
Quality::operator++()
{
assert (m_value > 0);
--m_value;
return *this;
}
Quality
Quality::operator++ (int)
{
Quality prev (*this);
--*this;
return prev;
}
Quality&
Quality::operator--()
{
assert (m_value < std::numeric_limits<value_type>::max());
++m_value;
return *this;
}
Quality
Quality::operator-- (int)
{
Quality prev (*this);
++*this;
return prev;
}
Amounts
Quality::ceil_in (Amounts const& amount, Amount const& limit) const
{
if (amount.in > limit)
{
Amounts result (limit, Amount::divRound (
limit, rate(), amount.out, true));
// Clamp out
if (result.out > amount.out)
result.out = amount.out;
return result;
}
return amount;
}
Amounts
Quality::ceil_out (Amounts const& amount, Amount const& limit) const
{
if (amount.out > limit)
{
Amounts result (Amount::mulRound (
limit, rate(), amount.in, true), limit);
// Clamp in
if (result.in > amount.in)
result.in = amount.in;
return result;
}
return amount;
}
Quality
composed_quality (Quality const& lhs, Quality const& rhs)
{
Amount const lhs_rate (lhs.rate ());
assert (lhs_rate != zero);
Amount const rhs_rate (rhs.rate ());
assert (rhs_rate != zero);
Amount const rate (Amount::mulRound (lhs_rate, rhs_rate, true));
std::uint64_t const stored_exponent (rate.getExponent () + 100);
std::uint64_t const stored_mantissa (rate.getMantissa ());
assert ((stored_exponent >= 0) && (stored_exponent <= 255));
return Quality ((stored_exponent << (64 - 8)) | stored_mantissa);
}
}
}

View File

@@ -0,0 +1,281 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2014 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 "../Taker.h"
//#include <utility>
namespace ripple {
namespace core {
Taker::Taker (LedgerView& view, Account const& account,
Amounts const& amount, Options const& options)
: m_view (view)
, m_account (account)
, m_options (options)
, m_quality (amount)
, m_threshold (m_quality)
, m_amount (amount)
, m_remain (amount)
{
// If this is a passive order (tfPassive), this prevents
// offers at the same quality level from being consumed.
if (m_options.passive)
++m_threshold;
}
Amounts
Taker::remaining_offer () const
{
// If the taker is done, then there's no offer to place.
if (done ())
{
return Amounts (
Amount (m_amount.in.getCurrency (), m_amount.in.getIssuer ()),
Amount (m_amount.out.getCurrency (), m_amount.out.getIssuer ()));
}
// Avoid math altogether if we didn't cross.
if (m_amount == m_remain)
return m_amount;
if (m_options.sell)
{
assert (m_remain.in > zero);
// We scale the output based on the remaining input:
return Amounts (m_remain.in, Amount::divRound (
m_remain.in, m_quality.rate (), m_remain.out, true));
}
assert (m_remain.out > zero);
// We scale the input based on the remaining output:
return Amounts (Amount::mulRound (
m_remain.out, m_quality.rate (), m_remain.in, true), m_remain.out);
}
// Calculate the amount particular user could get through an offer.
// @param amount the maximum flow that is available to the taker.
// @param offer the offer to flow through.
// @param taker the person taking the offer.
// @return the maximum amount that can flow through this offer.
Amounts
Taker::flow (Amounts amount, Offer const& offer, Account const& taker)
{
// Limit taker's input by available funds less fees
Amount const taker_funds (view ().accountFunds (taker, amount.in));
// Get fee rate paid by taker
std::uint32_t const taker_charge_rate (view ().rippleTransferRate (
taker, offer.account (), amount.in.getIssuer()));
// Skip some math when there's no fee
if (taker_charge_rate == QUALITY_ONE)
{
amount = offer.quality ().ceil_in (amount, taker_funds);
}
else
{
Amount const taker_charge (Amount::saFromRate (taker_charge_rate));
amount = offer.quality ().ceil_in (amount,
Amount::divide (taker_funds, taker_charge));
}
// Best flow the owner can get.
// Start out assuming entire offer will flow.
Amounts owner_amount (amount);
// Limit owner's output by available funds less fees
Amount const owner_funds (view ().accountFunds (
offer.account (), owner_amount.out));
// Get fee rate paid by owner
std::uint32_t const owner_charge_rate (view ().rippleTransferRate (
offer.account (), taker, amount.out.getIssuer()));
if (owner_charge_rate == QUALITY_ONE)
{
// Skip some math when there's no fee
owner_amount = offer.quality ().ceil_out (owner_amount, owner_funds);
}
else
{
Amount const owner_charge (Amount::saFromRate (owner_charge_rate));
owner_amount = offer.quality ().ceil_out (owner_amount,
Amount::divide (owner_funds, owner_charge));
}
// Calculate the amount that will flow through the offer
// This does not include the fees.
return (owner_amount.in < amount.in)
? owner_amount
: amount;
}
// Fill a direct offer.
// @param offer the offer we are going to use.
// @param amount the amount to flow through the offer.
// @returns: tesSUCCESS if successful, or an error code otherwise.
TER
Taker::fill (Offer const& offer, Amounts const& amount)
{
TER result (tesSUCCESS);
Amounts const remain (
offer.entry ()->getFieldAmount (sfTakerPays) - amount.in,
offer.entry ()->getFieldAmount (sfTakerGets) - amount.out);
offer.entry ()->setFieldAmount (sfTakerPays, remain.in);
offer.entry ()->setFieldAmount (sfTakerGets, remain.out);
view ().entryModify (offer.entry());
// Pay the taker, then the owner
result = view ().accountSend (offer.account(), account(), amount.out);
if (result == tesSUCCESS)
result = view ().accountSend (account(), offer.account(), amount.in);
return result;
}
// Fill a bridged offer.
// @param leg1 the first leg we are going to use.
// @param amount1 the amount to flow through the first leg of the offer.
// @param leg2 the second leg we are going to use.
// @param amount2 the amount to flow through the second leg of the offer.
// @return tesSUCCESS if successful, or an error code otherwise.
TER
Taker::fill (
Offer const& leg1, Amounts const& amount1,
Offer const& leg2, Amounts const& amount2)
{
assert (amount1.out == amount2.in);
TER result (tesSUCCESS);
Amounts const remain1 (
leg1.entry ()->getFieldAmount (sfTakerPays) - amount1.in,
leg1.entry ()->getFieldAmount (sfTakerGets) - amount1.out);
leg1.entry ()->setFieldAmount (sfTakerPays, remain1.in);
leg1.entry ()->setFieldAmount (sfTakerGets, remain1.out);
view ().entryModify (leg1.entry());
Amounts const remain2 (
leg2.entry ()->getFieldAmount (sfTakerPays) - amount2.in,
leg2.entry ()->getFieldAmount (sfTakerGets) - amount2.out);
view ().entryModify (leg2.entry ());
// Execute the payments in order:
// Taker pays Leg1 (amount1.in - A currency)
// Leg1 pays Leg2 (amount1.out == amount2.in, XRP)
// Leg2 pays Taker (amount2.out - B currency)
result = view ().accountSend (m_account, leg1.account (), amount1.in);
if (result == tesSUCCESS)
result = view ().accountSend (leg1.account (), leg2.account (), amount1.out);
if (result == tesSUCCESS)
result = view ().accountSend (leg2.account (), m_account, amount2.out);
return result;
}
bool
Taker::done () const
{
if (m_options.sell)
{
// With the sell option, we are finished when
// we have consumed all the input currency.
if (m_remain.in <= zero)
return true;
}
else if (m_remain.out <= zero)
{
// With the buy option (!sell) we are finished when we
// have received the desired amount of output currency.
return true;
}
// We are finished if the taker is out of funds
return view().accountFunds (account(), m_remain.in) <= zero;
}
TER
Taker::cross (Offer const& offer)
{
assert (!done ());
Amounts limit (offer.amount());
if (m_options.sell)
limit = offer.quality().ceil_in (limit, m_remain.in);
else
limit = offer.quality ().ceil_out (limit, m_remain.out);
assert (limit.out <= offer.amount().out);
assert (limit.in <= offer.amount().in);
Amounts const amount (flow (limit, offer, account ()));
m_remain.out -= amount.out;
m_remain.in -= amount.in;
assert (m_remain.in >= zero);
return fill (offer, amount);
}
TER
Taker::cross (Offer const& leg1, Offer const& leg2)
{
assert (!done ());
assert (leg1.amount ().out.isNative ());
assert (leg2.amount ().in.isNative ());
Amounts amount1 (leg1.amount());
Amounts amount2 (leg2.amount());
if (m_options.sell)
amount1 = leg1.quality().ceil_in (amount1, m_remain.in);
else
amount2 = leg2.quality().ceil_out (amount2, m_remain.out);
if (amount1.out <= amount2.in)
amount2 = leg2.quality().ceil_in (amount2, amount1.out);
else
amount1 = leg1.quality().ceil_out (amount1, amount2.in);
assert (amount1.out == amount2.in);
// As written, flow can't handle a 3-party transfer, but this works for
// us because the output of leg1 and the input leg2 are XRP.
Amounts flow1 (flow (amount1, leg1, m_account));
amount2 = leg2.quality().ceil_in (amount2, flow1.out);
Amounts flow2 (flow (amount2, leg2, m_account));
m_remain.out -= amount2.out;
m_remain.in -= amount1.in;
return fill (leg1, flow1, leg2, flow2);
}
}
}

View File

@@ -1384,8 +1384,8 @@ private:
return resultSuccess; return resultSuccess;
} }
if (isTefFailure (result) || isTemMalformed if (isTefFailure (result) || isTemMalformed (result) ||
(result) || isTelLocal (result)) isTelLocal (result))
{ {
// failure // failure
WriteLog (lsDEBUG, LedgerConsensus) WriteLog (lsDEBUG, LedgerConsensus)

View File

@@ -21,6 +21,11 @@
#include "ripple_app.h" #include "ripple_app.h"
#include "book/impl/Taker.cpp"
#include "book/impl/BookTip.cpp"
#include "book/impl/OfferStream.cpp"
#include "book/impl/Quality.cpp"
#include "transactors/Transactor.cpp" #include "transactors/Transactor.cpp"
#include "transactors/Change.cpp" #include "transactors/Change.cpp"

View File

@@ -19,7 +19,7 @@
namespace ripple { namespace ripple {
TER WalletAddTransactor::doApply () TER AddWallet::doApply ()
{ {
std::uint32_t const uTxFlags = mTxn.getFlags (); std::uint32_t const uTxFlags = mTxn.getFlags ();

View File

@@ -22,20 +22,20 @@
namespace ripple { namespace ripple {
class WalletAddTransactorLog; class AddWalletLog;
template <> template <>
char const* char const*
LogPartition::getPartitionName <WalletAddTransactorLog> () LogPartition::getPartitionName <AddWalletLog> ()
{ {
return "Tx/WalletAdd"; return "Tx/WalletAdd";
} }
class WalletAddTransactor class AddWallet
: public Transactor : public Transactor
{ {
public: public:
WalletAddTransactor ( AddWallet (
SerializedTransaction const& txn, SerializedTransaction const& txn,
TransactionEngineParams params, TransactionEngineParams params,
TransactionEngine* engine) TransactionEngine* engine)
@@ -43,13 +43,23 @@ public:
txn, txn,
params, params,
engine, engine,
LogPartition::getJournal <WalletAddTransactorLog> ()) LogPartition::getJournal <AddWalletLog> ())
{ {
} }
TER doApply (); TER doApply ();
}; };
inline
std::unique_ptr <Transactor>
make_AddWallet (
SerializedTransaction const& txn,
TransactionEngineParams params,
TransactionEngine* engine)
{
return std::make_unique <AddWallet> (txn, params, engine);
}
} }
#endif #endif

View File

@@ -20,7 +20,7 @@
namespace ripple { namespace ripple {
TER OfferCancelTransactor::doApply () TER CancelOffer::doApply ()
{ {
std::uint32_t const uOfferSequence = mTxn.getFieldU32 (sfOfferSequence); std::uint32_t const uOfferSequence = mTxn.getFieldU32 (sfOfferSequence);
std::uint32_t const uAccountSequenceNext = mTxnAccount->getFieldU32 (sfSequence); std::uint32_t const uAccountSequenceNext = mTxnAccount->getFieldU32 (sfSequence);

View File

@@ -22,20 +22,20 @@
namespace ripple { namespace ripple {
class OfferCancelTransactorLog; class CancelOfferLog;
template <> template <>
char const* char const*
LogPartition::getPartitionName <OfferCancelTransactorLog> () LogPartition::getPartitionName <CancelOfferLog> ()
{ {
return "Tx/OfferCancel"; return "Tx/OfferCancel";
} }
class OfferCancelTransactor class CancelOffer
: public Transactor : public Transactor
{ {
public: public:
OfferCancelTransactor ( CancelOffer (
SerializedTransaction const& txn, SerializedTransaction const& txn,
TransactionEngineParams params, TransactionEngineParams params,
TransactionEngine* engine) TransactionEngine* engine)
@@ -43,7 +43,7 @@ public:
txn, txn,
params, params,
engine, engine,
LogPartition::getJournal <OfferCancelTransactorLog> ()) LogPartition::getJournal <CancelOfferLog> ())
{ {
} }
@@ -51,6 +51,16 @@ public:
TER doApply () override; TER doApply () override;
}; };
inline
std::unique_ptr <Transactor>
make_CancelOffer (
SerializedTransaction const& txn,
TransactionEngineParams params,
TransactionEngine* engine)
{
return std::make_unique <CancelOffer> (txn, params, engine);
}
} }
#endif #endif

View File

@@ -19,7 +19,7 @@
namespace ripple { namespace ripple {
TER ChangeTransactor::doApply () TER Change::doApply ()
{ {
if (mTxn.getTxnType () == ttAMENDMENT) if (mTxn.getTxnType () == ttAMENDMENT)
return applyAmendment (); return applyAmendment ();
@@ -30,7 +30,7 @@ TER ChangeTransactor::doApply ()
return temUNKNOWN; return temUNKNOWN;
} }
TER ChangeTransactor::checkSig () TER Change::checkSig ()
{ {
if (mTxn.getFieldAccount160 (sfAccount).isNonZero ()) if (mTxn.getFieldAccount160 (sfAccount).isNonZero ())
{ {
@@ -47,7 +47,7 @@ TER ChangeTransactor::checkSig ()
return tesSUCCESS; return tesSUCCESS;
} }
TER ChangeTransactor::checkSeq () TER Change::checkSeq ()
{ {
if ((mTxn.getSequence () != 0) || mTxn.isFieldPresent (sfPreviousTxnID)) if ((mTxn.getSequence () != 0) || mTxn.isFieldPresent (sfPreviousTxnID))
{ {
@@ -58,7 +58,7 @@ TER ChangeTransactor::checkSeq ()
return tesSUCCESS; return tesSUCCESS;
} }
TER ChangeTransactor::payFee () TER Change::payFee ()
{ {
if (mTxn.getTransactionFee () != STAmount ()) if (mTxn.getTransactionFee () != STAmount ())
{ {
@@ -69,7 +69,7 @@ TER ChangeTransactor::payFee ()
return tesSUCCESS; return tesSUCCESS;
} }
TER ChangeTransactor::preCheck () TER Change::preCheck ()
{ {
mTxnAccountID = mTxn.getSourceAccount ().getAccountID (); mTxnAccountID = mTxn.getSourceAccount ().getAccountID ();
@@ -89,7 +89,7 @@ TER ChangeTransactor::preCheck ()
return tesSUCCESS; return tesSUCCESS;
} }
TER ChangeTransactor::applyAmendment () TER Change::applyAmendment ()
{ {
uint256 amendment (mTxn.getFieldH256 (sfAmendment)); uint256 amendment (mTxn.getFieldH256 (sfAmendment));
@@ -119,7 +119,7 @@ TER ChangeTransactor::applyAmendment ()
return tesSUCCESS; return tesSUCCESS;
} }
TER ChangeTransactor::applyFee () TER Change::applyFee ()
{ {
SLE::pointer feeObject = mEngine->entryCache ( SLE::pointer feeObject = mEngine->entryCache (

View File

@@ -22,20 +22,20 @@
namespace ripple { namespace ripple {
class ChangeTransactorLog; class ChangeLog;
template <> template <>
char const* char const*
LogPartition::getPartitionName <ChangeTransactorLog> () LogPartition::getPartitionName <ChangeLog> ()
{ {
return "Tx/Change"; return "Tx/Change";
} }
class ChangeTransactor class Change
: public Transactor : public Transactor
{ {
public: public:
ChangeTransactor ( Change (
SerializedTransaction const& txn, SerializedTransaction const& txn,
TransactionEngineParams params, TransactionEngineParams params,
TransactionEngine* engine) TransactionEngine* engine)
@@ -43,7 +43,7 @@ public:
txn, txn,
params, params,
engine, engine,
LogPartition::getJournal <ChangeTransactorLog> ()) LogPartition::getJournal <ChangeLog> ())
{ {
} }
@@ -64,6 +64,16 @@ private:
} }
}; };
inline
std::unique_ptr <Transactor>
make_Change (
SerializedTransaction const& txn,
TransactionEngineParams params,
TransactionEngine* engine)
{
return std::make_unique <Change> (txn, params, engine);
}
} }
#endif #endif

View File

@@ -26,10 +26,11 @@
#include "../book/OfferStream.h" #include "../book/OfferStream.h"
#include "../book/Taker.h" #include "../book/Taker.h"
#include "../book/Types.h"
namespace ripple { namespace ripple {
OfferCreateTransactor::OfferCreateTransactor ( CreateOffer::CreateOffer (
SerializedTransaction const& txn, SerializedTransaction const& txn,
TransactionEngineParams params, TransactionEngineParams params,
TransactionEngine* engine) TransactionEngine* engine)
@@ -37,31 +38,495 @@ OfferCreateTransactor::OfferCreateTransactor (
txn, txn,
params, params,
engine, engine,
LogPartition::getJournal <OfferCreateTransactorLog> ()) LogPartition::getJournal <CreateOfferLog> ())
{ {
} }
std::unique_ptr <Transactor> make_OfferCreateTransactor ( TER
CreateOffer::checkAcceptAsset(core::AssetRef asset) const
{
/* Only valid for custom currencies */
assert (!asset.is_xrp ());
SLE::pointer const issuerAccount = mEngine->entryCache (
ltACCOUNT_ROOT, Ledger::getAccountRootIndex (asset.issuer));
if (!issuerAccount)
{
if (m_journal.warning) m_journal.warning <<
"delay: can't receive IOUs from non-existent issuer: " <<
RippleAddress::createHumanAccountID (asset.issuer);
return is_bit_set (mParams, tapRETRY)
? terNO_ACCOUNT
: tecNO_ISSUER;
}
if (is_bit_set (issuerAccount->getFieldU32 (sfFlags), lsfRequireAuth))
{
SLE::pointer const trustLine (mEngine->entryCache (
ltRIPPLE_STATE, Ledger::getRippleStateIndex (
mTxnAccountID, asset.issuer, asset.currency)));
if (!trustLine)
{
return is_bit_set (mParams, tapRETRY)
? terNO_LINE
: tecNO_LINE;
}
// Entries have a canonical representation, determined by a
// lexicographical "greater than" comparison employing strict weak
// ordering. Determine which entry we need to access.
bool const canonical_gt (mTxnAccountID > asset.issuer);
bool const need_auth (is_bit_set (
trustLine->getFieldU32 (sfFlags),
(canonical_gt ? lsfLowAuth : lsfHighAuth)));
if (need_auth)
{
if (m_journal.debug) m_journal.debug <<
"delay: can't receive IOUs from issuer without auth.";
return is_bit_set (mParams, tapRETRY)
? terNO_AUTH
: tecNO_AUTH;
}
}
return tesSUCCESS;
}
TER
CreateOffer::doApply ()
{
if (m_journal.debug) m_journal.debug <<
"OfferCreate> " << mTxn.getJson (0);
std::uint32_t const uTxFlags = mTxn.getFlags ();
bool const bPassive = is_bit_set (uTxFlags, tfPassive);
bool const bImmediateOrCancel = is_bit_set (uTxFlags, tfImmediateOrCancel);
bool const bFillOrKill = is_bit_set (uTxFlags, tfFillOrKill);
bool const bSell = is_bit_set (uTxFlags, tfSell);
STAmount saTakerPays = mTxn.getFieldAmount (sfTakerPays);
STAmount saTakerGets = mTxn.getFieldAmount (sfTakerGets);
if (!saTakerPays.isLegalNet () || !saTakerGets.isLegalNet ())
return temBAD_AMOUNT;
uint160 const& uPaysIssuerID = saTakerPays.getIssuer ();
uint160 const& uPaysCurrency = saTakerPays.getCurrency ();
uint160 const& uGetsIssuerID = saTakerGets.getIssuer ();
uint160 const& uGetsCurrency = saTakerGets.getCurrency ();
bool const bHaveExpiration (mTxn.isFieldPresent (sfExpiration));
bool const bHaveCancel (mTxn.isFieldPresent (sfOfferSequence));
std::uint32_t const uExpiration = mTxn.getFieldU32 (sfExpiration);
std::uint32_t const uCancelSequence = mTxn.getFieldU32 (sfOfferSequence);
// FIXME understand why we use SequenceNext instead of current transaction
// sequence to determine the transaction. Why is the offer seuqnce
// number insufficient?
std::uint32_t const uAccountSequenceNext = mTxnAccount->getFieldU32 (sfSequence);
std::uint32_t const uSequence = mTxn.getSequence ();
const uint256 uLedgerIndex = Ledger::getOfferIndex (mTxnAccountID, uSequence);
if (m_journal.debug)
{
m_journal.debug <<
"Creating offer node: " << to_string (uLedgerIndex) <<
" uSequence=" << uSequence;
if (bImmediateOrCancel)
m_journal.debug << "Transaction: IoC set.";
if (bFillOrKill)
m_journal.debug << "Transaction: FoK set.";
}
// This is the original rate of this offer, and is the rate at which it will
// be placed, even if crossing offers change the amounts.
std::uint64_t const uRate = STAmount::getRate (saTakerGets, saTakerPays);
TER terResult (tesSUCCESS);
uint256 uDirectory;
std::uint64_t uOwnerNode;
std::uint64_t uBookNode;
// This is the ledger view that we work against. Transactions are applied
// as we go on processing transactions.
core::LedgerView& view (mEngine->view ());
// This is a checkpoint with just the fees paid. If something goes wrong
// with this transaction, we roll back to this ledger.
core::LedgerView view_checkpoint (view);
view.bumpSeq (); // Begin ledger variance.
SLE::pointer sleCreator = mEngine->entryCache (
ltACCOUNT_ROOT, Ledger::getAccountRootIndex (mTxnAccountID));
if (uTxFlags & tfOfferCreateMask)
{
if (m_journal.debug) m_journal.debug <<
"Malformed transaction: Invalid flags set.";
terResult = temINVALID_FLAG;
}
else if (bImmediateOrCancel && bFillOrKill)
{
if (m_journal.debug) m_journal.debug <<
"Malformed transaction: both IoC and FoK set.";
terResult = temINVALID_FLAG;
}
else if (bHaveExpiration && !uExpiration)
{
m_journal.warning <<
"Malformed offer: bad expiration";
terResult = temBAD_EXPIRATION;
}
else if (saTakerPays.isNative () && saTakerGets.isNative ())
{
m_journal.warning <<
"Malformed offer: XRP for XRP";
terResult = temBAD_OFFER;
}
else if (saTakerPays <= zero || saTakerGets <= zero)
{
m_journal.warning <<
"Malformed offer: bad amount";
terResult = temBAD_OFFER;
}
else if (uPaysCurrency == uGetsCurrency && uPaysIssuerID == uGetsIssuerID)
{
m_journal.warning <<
"Malformed offer: redundant offer";
terResult = temREDUNDANT;
}
// We don't allow a non-native currency to use the currency code XRP.
else if (CURRENCY_BAD == uPaysCurrency || CURRENCY_BAD == uGetsCurrency)
{
m_journal.warning <<
"Malformed offer: Bad currency.";
terResult = temBAD_CURRENCY;
}
else if (saTakerPays.isNative () != !uPaysIssuerID ||
saTakerGets.isNative () != !uGetsIssuerID)
{
m_journal.warning <<
"Malformed offer: bad issuer";
terResult = temBAD_ISSUER;
}
else if (view.accountFunds (mTxnAccountID, saTakerGets) <= zero)
{
m_journal.warning <<
"delay: Offers must be at least partially funded.";
terResult = tecUNFUNDED_OFFER;
}
// This can probably be simplified to make sure that you cancel sequences
// before the transaction sequence number.
else if (bHaveCancel && (!uCancelSequence || uAccountSequenceNext - 1 <= uCancelSequence))
{
if (m_journal.debug) m_journal.debug <<
"uAccountSequenceNext=" << uAccountSequenceNext <<
" uOfferSequence=" << uCancelSequence;
terResult = temBAD_SEQUENCE;
}
if (terResult != tesSUCCESS)
{
if (m_journal.debug) m_journal.debug <<
"final terResult=" << transToken (terResult);
return terResult;
}
// Process a cancellation request that's passed along with an offer.
if ((terResult == tesSUCCESS) && bHaveCancel)
{
uint256 const uCancelIndex (
Ledger::getOfferIndex (mTxnAccountID, uCancelSequence));
SLE::pointer sleCancel = mEngine->entryCache (ltOFFER, uCancelIndex);
// It's not an error to not find the offer to cancel: it might have
// been consumed or removed as we are processing.
if (sleCancel)
{
m_journal.warning <<
"Cancelling order with sequence " << uCancelSequence;
terResult = view.offerDelete (sleCancel);
}
}
// Expiration is defined in terms of the close time of the parent ledger,
// because we definitively know the time that it closed but we do not
// know the closing time of the ledger that is under construction.
if (bHaveExpiration &&
(mEngine->getLedger ()->getParentCloseTimeNC () >= uExpiration))
{
return tesSUCCESS;
}
// Make sure that we are authorized to hold what the taker will pay us.
if (terResult == tesSUCCESS && !saTakerPays.isNative ())
terResult = checkAcceptAsset (core::Asset (uPaysCurrency, uPaysIssuerID));
bool crossed = false;
bool const bOpenLedger = is_bit_set (mParams, tapOPEN_LEDGER);
if (terResult == tesSUCCESS)
{
// We reverse gets and pays because during offer crossing we are taking.
core::Amounts const taker_amount (saTakerGets, saTakerPays);
// The amount of the offer that we will need to place, after we finish
// offer crossing processing. It may be equal to the original amount,
// empty (fully crossed), or something in-between.
core::Amounts place_offer;
std::tie(terResult, place_offer) = crossOffers (view, taker_amount);
if (terResult == tecFAILED_PROCESSING && bOpenLedger)
terResult = telFAILED_PROCESSING;
if (terResult == tesSUCCESS)
{
// We now need to reduce the offer by the cross flow. We reverse
// in and out here, since during crossing we were takers.
assert (saTakerPays.getCurrency () == place_offer.out.getCurrency ());
assert (saTakerPays.getIssuer () == place_offer.out.getIssuer ());
assert (saTakerGets.getCurrency () == place_offer.in.getCurrency ());
assert (saTakerGets.getIssuer () == place_offer.in.getIssuer ());
if (taker_amount != place_offer)
crossed = true;
if (m_journal.debug)
{
m_journal.debug << "Offer Crossing: " << transToken (terResult);
if (terResult == tesSUCCESS)
{
m_journal.debug <<
" takerPays: " << saTakerPays.getFullText () <<
" -> " << place_offer.out.getFullText ();
m_journal.debug <<
" takerGets: " << saTakerGets.getFullText () <<
" -> " << place_offer.in.getFullText ();
}
}
saTakerPays = place_offer.out;
saTakerGets = place_offer.in;
}
}
if (terResult != tesSUCCESS)
{
m_journal.debug <<
"final terResult=" << transToken (terResult);
return terResult;
}
if (m_journal.debug)
{
m_journal.debug <<
"takeOffers: saTakerPays=" <<saTakerPays.getFullText ();
m_journal.debug <<
"takeOffers: saTakerGets=" << saTakerGets.getFullText ();
m_journal.debug <<
"takeOffers: mTxnAccountID=" <<
RippleAddress::createHumanAccountID (mTxnAccountID);
m_journal.debug <<
"takeOffers: FUNDS=" <<
view.accountFunds (mTxnAccountID, saTakerGets).getFullText ();
}
if (bFillOrKill && (saTakerPays != zero || saTakerGets != zero))
{
// Fill or kill and have leftovers.
view.swapWith (view_checkpoint); // Restore with just fees paid.
return tesSUCCESS;
}
// What the reserve would be if this offer was placed.
auto const accountReserve (mEngine->getLedger ()->getReserve (
sleCreator->getFieldU32 (sfOwnerCount) + 1));
if (saTakerPays <= zero || // Wants nothing more.
saTakerGets <= zero || // Offering nothing more.
bImmediateOrCancel) // Do not persist.
{
// Complete as is.
nothing ();
}
else if (mPriorBalance.getNValue () < accountReserve)
{
// If we are here, the signing account had an insufficient reserve
// *prior* to our processing. We use the prior balance to simplify
// client writing and make the user experience better.
if (bOpenLedger) // Ledger is not final, can vote no.
{
// Hope for more reserve to come in or more offers to consume. If we
// specified a local error this transaction will not be retried, so
// specify a tec to distribute the transaction and allow it to be
// retried. In particular, it may have been successful to a
// degree (partially filled) and if it hasn't, it might succeed.
terResult = tecINSUF_RESERVE_OFFER;
}
else if (!crossed)
{
// Ledger is final, insufficent reserve to create offer, processed
// nothing.
terResult = tecINSUF_RESERVE_OFFER;
}
else
{
// Ledger is final, insufficent reserve to create offer, processed
// something.
// Consider the offer unfunded. Treat as tesSUCCESS.
nothing ();
}
}
else
{
// We need to place the remainder of the offer into its order book.
if (m_journal.debug) m_journal.debug <<
"offer not fully consumed:" <<
" saTakerPays=" << saTakerPays.getFullText () <<
" saTakerGets=" << saTakerGets.getFullText ();
// Add offer to owner's directory.
terResult = view.dirAdd (uOwnerNode,
Ledger::getOwnerDirIndex (mTxnAccountID), uLedgerIndex,
std::bind (&Ledger::ownerDirDescriber, std::placeholders::_1,
std::placeholders::_2, mTxnAccountID));
if (tesSUCCESS == terResult)
{
// Update owner count.
view.ownerCountAdjust (mTxnAccountID, 1, sleCreator);
uint256 uBookBase = Ledger::getBookBase (
uPaysCurrency, uPaysIssuerID,
uGetsCurrency, uGetsIssuerID);
if (m_journal.debug) m_journal.debug <<
"adding to book: " << to_string (uBookBase) <<
" : " << saTakerPays.getHumanCurrency () <<
"/" << RippleAddress::createHumanAccountID (saTakerPays.getIssuer ()) <<
" -> " << saTakerGets.getHumanCurrency () <<
"/" << RippleAddress::createHumanAccountID (saTakerGets.getIssuer ());
// We use the original rate to place the offer.
uDirectory = Ledger::getQualityIndex (uBookBase, uRate);
// Add offer to order book.
terResult = view.dirAdd (uBookNode, uDirectory, uLedgerIndex,
std::bind (&Ledger::qualityDirDescriber, std::placeholders::_1,
std::placeholders::_2, saTakerPays.getCurrency (),
uPaysIssuerID, saTakerGets.getCurrency (),
uGetsIssuerID, uRate));
}
if (tesSUCCESS == terResult)
{
if (m_journal.debug)
{
m_journal.debug <<
"sfAccount=" <<
RippleAddress::createHumanAccountID (mTxnAccountID);
m_journal.debug <<
"uPaysIssuerID=" <<
RippleAddress::createHumanAccountID (uPaysIssuerID);
m_journal.debug <<
"uGetsIssuerID=" <<
RippleAddress::createHumanAccountID (uGetsIssuerID);
m_journal.debug <<
"saTakerPays.isNative()=" <<
saTakerPays.isNative ();
m_journal.debug <<
"saTakerGets.isNative()=" <<
saTakerGets.isNative ();
m_journal.debug <<
"uPaysCurrency=" <<
saTakerPays.getHumanCurrency ();
m_journal.debug <<
"uGetsCurrency=" <<
saTakerGets.getHumanCurrency ();
}
SLE::pointer sleOffer (mEngine->entryCreate (ltOFFER, uLedgerIndex));
sleOffer->setFieldAccount (sfAccount, mTxnAccountID);
sleOffer->setFieldU32 (sfSequence, uSequence);
sleOffer->setFieldH256 (sfBookDirectory, uDirectory);
sleOffer->setFieldAmount (sfTakerPays, saTakerPays);
sleOffer->setFieldAmount (sfTakerGets, saTakerGets);
sleOffer->setFieldU64 (sfOwnerNode, uOwnerNode);
sleOffer->setFieldU64 (sfBookNode, uBookNode);
if (uExpiration)
sleOffer->setFieldU32 (sfExpiration, uExpiration);
if (bPassive)
sleOffer->setFlag (lsfPassive);
if (bSell)
sleOffer->setFlag (lsfSell);
if (m_journal.debug) m_journal.debug <<
"final terResult=" << transToken (terResult) <<
" sleOffer=" << sleOffer->getJson (0);
}
}
if (terResult != tesSUCCESS)
{
m_journal.debug <<
"final terResult=" << transToken (terResult);
}
return terResult;
}
std::unique_ptr <Transactor> make_CreateOffer (
SerializedTransaction const& txn, SerializedTransaction const& txn,
TransactionEngineParams params, TransactionEngineParams params,
TransactionEngine* engine) TransactionEngine* engine)
{ {
#if RIPPLE_USE_OLD_CREATE_TRANSACTOR #if RIPPLE_USE_OLD_CREATE_TRANSACTOR
return std::make_unique <ClassicOfferCreateTransactor> (txn, params, engine); return std::make_unique <CreateOfferLegacy> (txn, params, engine);
#else #else
STAmount const& amount_in = txn.getFieldAmount (sfTakerPays); STAmount const& amount_in = txn.getFieldAmount (sfTakerPays);
STAmount const& amount_out = txn.getFieldAmount (sfTakerGets); STAmount const& amount_out = txn.getFieldAmount (sfTakerGets);
// Autobridging is only in effect when an offer does not involve XRP // Autobridging is only in effect when an offer does not involve XRP
if (!amount_in.isNative() && !amount_out.isNative ()) if (!amount_in.isNative() && !amount_out.isNative ())
{ return std::make_unique <CreateOfferBridged> (txn, params, engine);
// no autobridging transactor exists yet - we create a regular, direct
// transactor for now.
return std::make_unique <DirectOfferCreateTransactor> (txn, params, engine);
}
return std::make_unique <DirectOfferCreateTransactor> (txn, params, engine); return std::make_unique <CreateOfferDirect> (txn, params, engine);
#endif #endif
} }

View File

@@ -20,45 +20,53 @@
#ifndef RIPPLE_TX_OFFERCREATE_H_INCLUDED #ifndef RIPPLE_TX_OFFERCREATE_H_INCLUDED
#define RIPPLE_TX_OFFERCREATE_H_INCLUDED #define RIPPLE_TX_OFFERCREATE_H_INCLUDED
#include "../book/Amounts.h"
#include "../book/Types.h"
#include "../../beast/beast/cxx14/memory.h" #include "../../beast/beast/cxx14/memory.h"
namespace ripple { namespace ripple {
class OfferCreateTransactorLog; class CreateOfferLog;
template <> template <>
char const* char const*
LogPartition::getPartitionName <OfferCreateTransactorLog> () LogPartition::getPartitionName <CreateOfferLog> ()
{ {
return "Tx/OfferCreate"; return "Tx/OfferCreate";
} }
class OfferCreateTransactor class CreateOffer
: public Transactor : public Transactor
{ {
private: protected:
template <class T> /** Determine if we are authorized to hold the asset we want to get */
static std::string TER checkAcceptAsset(core::AssetRef asset) const;
get_compare_sign (T const& lhs, T const& rhs)
{
if (lhs > rhs)
return ">";
if (rhs > lhs) /** Fill offer as much as possible by consuming offers already on the books.
return "<"; We adjusts account balances and charges fees on top to taker.
// If neither is bigger than the other, they must be equal @param taker_amount.in How much the taker offers
return "="; @param taker_amount.out How much the taker wants
}
@return result.first crossing operation success/failure indicator.
result.second amount of offer left unfilled - only meaningful
if result.first is tesSUCCESS.
*/
virtual std::pair<TER, core::Amounts> crossOffers (
core::LedgerView& view,
core::Amounts const& taker_amount) = 0;
public: public:
OfferCreateTransactor ( CreateOffer (
SerializedTransaction const& txn, SerializedTransaction const& txn,
TransactionEngineParams params, TransactionEngineParams params,
TransactionEngine* engine); TransactionEngine* engine);
TER doApply () override;
}; };
std::unique_ptr <Transactor> make_OfferCreateTransactor ( std::unique_ptr <Transactor> make_CreateOffer (
SerializedTransaction const& txn, SerializedTransaction const& txn,
TransactionEngineParams params, TransactionEngineParams params,
TransactionEngine* engine); TransactionEngine* engine);

View File

@@ -17,3 +17,175 @@
*/ */
//============================================================================== //==============================================================================
#include "../book/OfferStream.h"
#include "../book/Taker.h"
#include "../book/Quality.h"
#include "../../beast/beast/streams/debug_ostream.h"
namespace ripple {
std::pair<TER, core::Amounts>
CreateOfferBridged::crossOffers (
core::LedgerView& view,
core::Amounts const& taker_amount)
{
assert (!taker_amount.in.isNative () && !taker_amount.out.isNative ());
if (taker_amount.in.isNative () || taker_amount.out.isNative ())
return std::make_pair (tefINTERNAL, core::Amounts ());
core::Clock::time_point const when (
mEngine->getLedger ()->getParentCloseTimeNC ());
core::Taker::Options const options (mTxn.getFlags());
core::LedgerView view_cancel (view.duplicate());
core::AssetRef const asset_in (
taker_amount.in.getCurrency(), taker_amount.in.getIssuer());
core::AssetRef const asset_out (
taker_amount.out.getCurrency(), taker_amount.out.getIssuer());
core::OfferStream offers_direct (view, view_cancel,
core::Book (asset_in, asset_out), when, m_journal);
core::OfferStream offers_leg1 (view, view_cancel,
core::Book (asset_in, xrp_asset ()), when, m_journal);
core::OfferStream offers_leg2 (view, view_cancel,
core::Book (xrp_asset (), asset_out), when, m_journal);
core::Taker taker (view, mTxnAccountID, taker_amount, options);
if (m_journal.debug) m_journal.debug <<
"process_order: " <<
(options.sell? "sell" : "buy") << " " <<
(options.passive? "passive" : "") << std::endl <<
" taker: " << taker.account() << std::endl <<
" balances: " <<
view.accountFunds (taker.account(), taker_amount.in) << ", " <<
view.accountFunds (taker.account(), taker_amount.out);
TER cross_result (tesSUCCESS);
bool have_bridged (
offers_leg1.step_account (taker.account()) &&
offers_leg2.step_account (taker.account()));
bool have_direct (offers_direct.step_account (taker.account()));
bool place_order (true);
while (have_direct || have_bridged)
{
core::Quality quality;
bool use_direct;
bool leg1_consumed(false);
bool leg2_consumed(false);
bool direct_consumed(false);
// Logic:
// We calculate the qualities of any direct and bridged offers at the
// tip of the order book, and choose the best one of the two.
if (have_direct)
{
core::Quality const direct_quality (offers_direct.tip ().quality ());
if (have_bridged)
{
core::Quality const bridged_quality (core::composed_quality (
offers_leg1.tip ().quality (),
offers_leg2.tip ().quality ()));
if (bridged_quality < direct_quality)
{
use_direct = true;
quality = direct_quality;
}
else
{
use_direct = false;
quality = bridged_quality;
}
}
else
{
use_direct = true;
quality = offers_direct.tip ().quality ();
}
}
else
{
use_direct = false;
quality = core::composed_quality (
offers_leg1.tip ().quality (),
offers_leg2.tip ().quality ());
}
// We are always looking at the best quality available, so if we reject
// that, we know that we are done.
if (taker.reject(quality))
break;
if (use_direct)
{
if (m_journal.debug) m_journal.debug <<
" Offer: " << offers_direct.tip () << std::endl <<
" " << offers_direct.tip ().amount().in <<
" : " << offers_direct.tip ().amount ().out;
cross_result = taker.cross(offers_direct.tip ());
if (offers_direct.tip ().fully_consumed ())
{
direct_consumed = true;
have_direct = offers_direct.step_account (taker.account());
}
}
else
{
if (m_journal.debug) m_journal.debug <<
" Offer1: " << offers_leg1.tip () << std::endl <<
" " << offers_leg1.tip ().amount().in <<
" : " << offers_leg1.tip ().amount ().out << std::endl <<
" Offer2: " << offers_leg2.tip () << std::endl <<
" " << offers_leg2.tip ().amount ().in <<
" : " << offers_leg2.tip ().amount ().out;
cross_result = taker.cross(offers_leg1.tip (), offers_leg2.tip ());
if (offers_leg1.tip ().fully_consumed ())
{
leg1_consumed = true;
have_bridged = offers_leg1.step_account (taker.account ());
}
if (have_bridged && offers_leg2.tip ().fully_consumed ())
{
leg2_consumed = true;
have_bridged = offers_leg2.step_account (taker.account ());
}
}
if (cross_result != tesSUCCESS)
{
cross_result = tecFAILED_PROCESSING;
break;
}
if (taker.done())
{
m_journal.debug << "The taker reports he's done during crossing!";
place_order = false;
break;
}
// Postcondition: If we aren't done, then we must have consumed at
// least one offer fully.
assert (direct_consumed || leg1_consumed || leg2_consumed);
}
return std::make_pair(cross_result, taker.remaining_offer ());
}
}

View File

@@ -20,4 +20,34 @@
#ifndef RIPPLE_TX_BRIDGE_OFFERCREATE_H_INCLUDED #ifndef RIPPLE_TX_BRIDGE_OFFERCREATE_H_INCLUDED
#define RIPPLE_TX_BRIDGE_OFFERCREATE_H_INCLUDED #define RIPPLE_TX_BRIDGE_OFFERCREATE_H_INCLUDED
#include "../book/Amounts.h"
#include <unordered_set>
namespace ripple {
class CreateOfferBridged
: public CreateOffer
{
public:
CreateOfferBridged (
SerializedTransaction const& txn,
TransactionEngineParams params,
TransactionEngine* engine)
: CreateOffer (
txn,
params,
engine)
{
}
private:
std::pair<TER, core::Amounts> crossOffers (
core::LedgerView& view,
core::Amounts const& taker_amount) override;
};
}
#endif #endif

View File

@@ -23,38 +23,38 @@
namespace ripple { namespace ripple {
//------------------------------------------------------------------------------ /** Fill offer as much as possible by consuming offers already on the books.
We adjusts account balances and charges fees on top to taker.
// NIKB Move this in the right place @param taker_amount.in How much the taker offers
std::pair<TER,bool> @param taker_amount.out How much the taker wants
process_order ( @param taker_flow.in What the taker actually paid, not including fees.
@param taker_flow.out What the taker actually got, not including fees.
@return tesSUCCESS, terNO_ACCOUNT, telFAILED_PROCESSING, or
tecFAILED_PROCESSING
*/
std::pair<TER, core::Amounts>
CreateOfferDirect::crossOffers (
core::LedgerView& view, core::LedgerView& view,
core::BookRef const book, core::Amounts const& taker_amount)
core::Account const& account,
core::Amounts const& amount,
core::Amounts& cross_flow,
core::Taker::Options const options,
core::Clock::time_point const when,
beast::Journal& journal)
{ {
TER result (tesSUCCESS); core::Taker::Options const options (mTxn.getFlags());
core::Clock::time_point const when (
mEngine->getLedger ()->getParentCloseTimeNC ());
core::LedgerView view_cancel (view.duplicate()); core::LedgerView view_cancel (view.duplicate());
core::OfferStream offers (view, view_cancel, book, when, journal); core::OfferStream offers (view, view_cancel,
core::Taker taker (offers.view(), book, account, amount, options); core::Book (
core::AssetRef (
taker_amount.in.getCurrency(), taker_amount.in.getIssuer()),
core::AssetRef (
taker_amount.out.getCurrency(), taker_amount.out.getIssuer())),
when, m_journal);
core::Taker taker (offers.view(), mTxnAccountID, taker_amount, options);
if (journal.debug) journal.debug << TER cross_result (tesSUCCESS);
"process_order: " <<
(options.sell? "sell" : "buy") << " " <<
(options.passive? "passive" : "") << std::endl <<
" taker: " << taker.account() << std::endl <<
" balances: " <<
view.accountFunds (taker.account(), amount.in) << ", " <<
view.accountFunds (taker.account(), amount.out);
cross_flow.in.clear (amount.in);
cross_flow.out.clear (amount.out);
bool place_order (true);
while (true) while (true)
{ {
@@ -67,27 +67,22 @@ process_order (
if (taker.done()) if (taker.done())
{ {
journal.debug << "The taker reports he's done during crossing!"; m_journal.debug << "The taker reports he's done during crossing!";
place_order = false;
break; break;
} }
if (! offers.step()) // NIKB CHECKME Investigate whether we can use offer.step_account() here
// or whether doing so would cause a protocol-breaking
// change.
if (! offers.step ())
{ {
// Place the order since there are no // Place the order since there are no
// more offers and the order has a balance. // more offers and the order has a balance.
journal.debug << "No more offers to consider during crossing!"; m_journal.debug << "No more offers to consider during crossing!";
break; break;
} }
auto const offer (offers.tip()); auto const& offer (offers.tip());
if (journal.debug) journal.debug <<
"Considering offer: " << std::endl <<
" Id: " << offer.entry()->getIndex() << std::endl <<
" In: " << offer.amount().in << std::endl <<
" Out: " << offer.amount().out << std::endl <<
" By: " << offer.account();
if (taker.reject (offer.quality())) if (taker.reject (offer.quality()))
{ {
@@ -98,592 +93,25 @@ process_order (
if (offer.account() == taker.account()) if (offer.account() == taker.account())
{ {
if (journal.debug) journal.debug << // Skip offer from self. The offer will be considered expired and
" skipping self-offer " << offer.entry()->getIndex() << std::endl << // will get deleted.
" pays/gets " << offer.amount().in << ", " << offer.amount().out << std::endl <<
" during cross for " << std::endl <<
" pays/gets " << amount.in << ", " << amount.out;
;
// Skip offer from self.
// (Offer will be considered expired, and get deleted)
continue; continue;
} }
if (journal.debug) journal.debug << if (m_journal.debug) m_journal.debug <<
" offer " << offer.entry()->getIndex() << std::endl << " Offer: " << offer.entry()->getIndex() << std::endl <<
" pays/gets " << offer.amount().in << ", " << offer.amount().out " " << offer.amount().in << " : " << offer.amount().out;
;
core::Amounts flow; cross_result = taker.cross (offer);
bool consumed;
std::tie (flow, consumed) = taker.fill (offer);
result = taker.process (flow, offer); if (cross_result != tesSUCCESS)
if (journal.debug) journal.debug <<
" flow " <<
flow.in << ", " << flow.out << std::endl <<
" balances " <<
view.accountFunds (taker.account(), amount.in) << ", " <<
view.accountFunds (taker.account(), amount.out)
;
if (result != tesSUCCESS)
{ {
// VFALCO TODO Return the tec and let a caller higher cross_result = tecFAILED_PROCESSING;
// up convert the error if the ledger is open.
//result = bOpenLedger ?
// telFAILED_PROCESSING : tecFAILED_PROCESSING;
result = tecFAILED_PROCESSING;
break; break;
} }
cross_flow.in += flow.in;
cross_flow.out += flow.out;
} }
if (result == tesSUCCESS) return std::make_pair(cross_result, taker.remaining_offer ());
{
// No point in placing an offer for a fill-or-kill offer - the offer
// will not succeed, since it wasn't filled.
if (options.fill_or_kill)
place_order = false;
// An immediate or cancel order will fill however much it is possible
// to fill and the remainder is not filled.
if (options.immediate_or_cancel)
place_order = false;
}
if (result == tesSUCCESS)
{
if (place_order)
{
}
}
else
{
}
return std::make_pair(result,place_order);
}
/** Take as much as possible.
We adjusts account balances and charges fees on top to taker.
@param saTakerPays What the taker offers (w/ issuer)
@param saTakerGets What the taker wanted (w/ issuer)
@param saTakerPaid What taker could have paid including saved not including
fees. To reduce an offer.
@param saTakerGot What taker got not including fees. To reduce an offer.
@return tesSUCCESS, terNO_ACCOUNT, telFAILED_PROCESSING, or
tecFAILED_PROCESSING
*/
std::pair<TER,bool> DirectOfferCreateTransactor::crossOffers (
core::LedgerView& view,
const STAmount& saTakerPays,
const STAmount& saTakerGets,
STAmount& saTakerPaid,
STAmount& saTakerGot)
{
if (m_journal.debug) m_journal.debug << "takeOffers: ";
core::Book book (
core::AssetRef (
saTakerPays.getCurrency(), saTakerPays.getIssuer()),
core::AssetRef (
saTakerGets.getCurrency(), saTakerGets.getIssuer()));
core::Amounts cross_flow (
core::Amount (saTakerPays.getCurrency(), saTakerPays.getIssuer()),
core::Amount (saTakerGets.getCurrency(), saTakerGets.getIssuer()));
auto const result (process_order (
view, book, mTxnAccountID,
core::Amounts (saTakerPays, saTakerGets), cross_flow,
core::Taker::Options (mTxn.getFlags()),
mEngine->getLedger ()->getParentCloseTimeNC (),
m_journal));
core::Amounts const funds (
view.accountFunds (mTxnAccountID, saTakerPays),
view.accountFunds (mTxnAccountID, saTakerGets));
if (m_journal.debug) m_journal.debug << " cross_flow: " <<
cross_flow.in << ", " << cross_flow.out;
if (m_journal.debug) m_journal.debug << " balances: " <<
funds.in << ", " << funds.out;
saTakerPaid = cross_flow.in;
saTakerGot = cross_flow.out;
if (m_journal.debug) m_journal.debug <<
" result: " << transToken (result.first) <<
(result.second ? " (consumed)" : "");
return result;
}
TER DirectOfferCreateTransactor::doApply ()
{
if (m_journal.debug) m_journal.debug <<
"OfferCreate> " << mTxn.getJson (0);
std::uint32_t const uTxFlags = mTxn.getFlags ();
bool const bPassive = is_bit_set (uTxFlags, tfPassive);
bool const bImmediateOrCancel = is_bit_set (uTxFlags, tfImmediateOrCancel);
bool const bFillOrKill = is_bit_set (uTxFlags, tfFillOrKill);
bool const bSell = is_bit_set (uTxFlags, tfSell);
STAmount saTakerPays = mTxn.getFieldAmount (sfTakerPays);
STAmount saTakerGets = mTxn.getFieldAmount (sfTakerGets);
if (!saTakerPays.isLegalNet () || !saTakerGets.isLegalNet ())
return temBAD_AMOUNT;
uint160 const uPaysIssuerID = saTakerPays.getIssuer ();
uint160 const uGetsIssuerID = saTakerGets.getIssuer ();
bool const bHaveExpiration (mTxn.isFieldPresent (sfExpiration));
bool const bHaveCancel (mTxn.isFieldPresent (sfOfferSequence));
std::uint32_t const uExpiration = mTxn.getFieldU32 (sfExpiration);
std::uint32_t const uCancelSequence = mTxn.getFieldU32 (sfOfferSequence);
// FIXME understand why we use SequenceNext instead of current transaction
// sequence to determine the transaction. Why is the offer seuqnce
// number insufficient?
std::uint32_t const uAccountSequenceNext = mTxnAccount->getFieldU32 (sfSequence);
std::uint32_t const uSequence = mTxn.getSequence ();
const uint256 uLedgerIndex = Ledger::getOfferIndex (mTxnAccountID, uSequence);
if (m_journal.debug)
{
m_journal.debug <<
"Creating offer node: " << to_string (uLedgerIndex) <<
" uSequence=" << uSequence;
if (bImmediateOrCancel)
m_journal.debug << "Transaction: IoC set.";
if (bFillOrKill)
m_journal.debug << "Transaction: FoK set.";
}
uint160 const uPaysCurrency = saTakerPays.getCurrency ();
uint160 const uGetsCurrency = saTakerGets.getCurrency ();
std::uint64_t const uRate = STAmount::getRate (saTakerGets, saTakerPays);
TER terResult (tesSUCCESS);
uint256 uDirectory; // Delete hints.
std::uint64_t uOwnerNode;
std::uint64_t uBookNode;
// This is the ledger view that we work against. Transactions are applied
// as we go on processing transactions.
core::LedgerView& view (mEngine->view ());
// This is a checkpoint with just the fees paid. If something goes wrong
// with this transaction, we roll back to this ledger.
core::LedgerView view_checkpoint (view);
view.bumpSeq (); // Begin ledger variance.
SLE::pointer sleCreator = mEngine->entryCache (
ltACCOUNT_ROOT, Ledger::getAccountRootIndex (mTxnAccountID));
if (uTxFlags & tfOfferCreateMask)
{
if (m_journal.debug) m_journal.debug <<
"Malformed transaction: Invalid flags set.";
return temINVALID_FLAG;
}
else if (bImmediateOrCancel && bFillOrKill)
{
if (m_journal.debug) m_journal.debug <<
"Malformed transaction: both IoC and FoK set.";
return temINVALID_FLAG;
}
else if (bHaveExpiration && !uExpiration)
{
m_journal.warning <<
"Malformed offer: bad expiration";
terResult = temBAD_EXPIRATION;
}
else if (saTakerPays.isNative () && saTakerGets.isNative ())
{
m_journal.warning <<
"Malformed offer: XRP for XRP";
terResult = temBAD_OFFER;
}
else if (saTakerPays <= zero || saTakerGets <= zero)
{
m_journal.warning <<
"Malformed offer: bad amount";
terResult = temBAD_OFFER;
}
else if (uPaysCurrency == uGetsCurrency && uPaysIssuerID == uGetsIssuerID)
{
m_journal.warning <<
"Malformed offer: redundant offer";
terResult = temREDUNDANT;
}
// We don't allow a non-native currency to use the currency code XRP.
else if (CURRENCY_BAD == uPaysCurrency || CURRENCY_BAD == uGetsCurrency)
{
m_journal.warning <<
"Malformed offer: Bad currency.";
terResult = temBAD_CURRENCY;
}
else if (saTakerPays.isNative () != !uPaysIssuerID || saTakerGets.isNative () != !uGetsIssuerID)
{
m_journal.warning <<
"Malformed offer: bad issuer";
terResult = temBAD_ISSUER;
}
else if (view.accountFunds (mTxnAccountID, saTakerGets) <= zero)
{
m_journal.warning <<
"delay: Offers must be at least partially funded.";
terResult = tecUNFUNDED_OFFER;
}
// This can probably be simplified to make sure that you cancel sequences
// before the transaction sequence number.
else if (bHaveCancel && (!uCancelSequence || uAccountSequenceNext - 1 <= uCancelSequence))
{
if (m_journal.debug) m_journal.debug <<
"uAccountSequenceNext=" << uAccountSequenceNext <<
" uOfferSequence=" << uCancelSequence;
terResult = temBAD_SEQUENCE;
}
if (tesSUCCESS != terResult)
{
if (m_journal.debug) m_journal.debug <<
"final terResult=" << transToken (terResult);
return terResult;
}
// Process a cancellation request that's passed along with an offer.
if ((tesSUCCESS == terResult) && bHaveCancel)
{
uint256 const uCancelIndex (
Ledger::getOfferIndex (mTxnAccountID, uCancelSequence));
SLE::pointer sleCancel = mEngine->entryCache (ltOFFER, uCancelIndex);
// It's not an error to not find the offer to cancel: it might have
// been consumed or removed as we are processing. Additionally, it
// might not even have been an offer - we don't care.
if (sleCancel)
{
m_journal.warning <<
"Cancelling order with sequence " << uCancelSequence;
terResult = view.offerDelete (sleCancel);
}
}
// Expiration is defined in terms of the close time of the parent ledger,
// because we definitively know the time that it closed but we do not
// know the closing time of the ledger that is under construction.
if (bHaveExpiration &&
(mEngine->getLedger ()->getParentCloseTimeNC () >= uExpiration))
{
return tesSUCCESS;
}
// If all is well and this isn't an offer to XRP, then we make sure we are
// authorized to hold what the taker will pay.
if (tesSUCCESS == terResult && !saTakerPays.isNative ())
{
SLE::pointer sleTakerPays = mEngine->entryCache (
ltACCOUNT_ROOT, Ledger::getAccountRootIndex (uPaysIssuerID));
if (!sleTakerPays)
{
m_journal.warning <<
"delay: can't receive IOUs from non-existent issuer: " <<
RippleAddress::createHumanAccountID (uPaysIssuerID);
return is_bit_set (mParams, tapRETRY)
? terNO_ACCOUNT
: tecNO_ISSUER;
}
if (is_bit_set (sleTakerPays->getFieldU32 (sfFlags), lsfRequireAuth))
{
SLE::pointer sleRippleState (mEngine->entryCache (
ltRIPPLE_STATE,
Ledger::getRippleStateIndex (
mTxnAccountID, uPaysIssuerID, uPaysCurrency)));
if (!sleRippleState)
{
return is_bit_set (mParams, tapRETRY)
? terNO_LINE
: tecNO_LINE;
}
// Entries have a canonical representation, determined by a
// lexicographical "greater than" comparison employing strict weak
// ordering. Determine which entry we need to access.
bool const canonical_gt (mTxnAccountID > uPaysIssuerID);
bool const need_auth (is_bit_set (
sleRippleState->getFieldU32 (sfFlags),
(canonical_gt ? lsfLowAuth : lsfHighAuth)));
if (need_auth)
{
if (m_journal.debug) m_journal.debug <<
"delay: can't receive IOUs from issuer without auth.";
return is_bit_set (mParams, tapRETRY)
? terNO_AUTH
: tecNO_AUTH;
}
}
}
STAmount saPaid;
STAmount saGot;
bool const bOpenLedger = is_bit_set (mParams, tapOPEN_LEDGER);
bool const placeOffer = true;
if (tesSUCCESS == terResult)
{
// Take using the parameters of the offer.
if (m_journal.debug) m_journal.debug <<
"takeOffers: BEFORE saTakerGets=" << saTakerGets.getFullText ();
auto ret = crossOffers (
view,
saTakerGets, // Reverse as we are the taker for taking.
saTakerPays,
saPaid, // Buy semantics: how much would have sold at full price. Sell semantics: how much was sold.
saGot); // How much was got.
terResult = ret.first;
if (terResult == tecFAILED_PROCESSING && bOpenLedger)
terResult = telFAILED_PROCESSING;
if (m_journal.debug)
{
m_journal.debug << "takeOffers=" << terResult;
m_journal.debug << "takeOffers: saPaid=" << saPaid.getFullText ();
m_journal.debug << "takeOffers: saGot=" << saGot.getFullText ();
}
if (tesSUCCESS == terResult)
{
// Reduce pay in from takers by what offer just got.
saTakerPays -= saGot;
// Reduce pay out to takers by what srcAccount just paid.
saTakerGets -= saPaid;
if (m_journal.debug)
{
m_journal.debug <<
"takeOffers: AFTER saTakerPays=" <<
saTakerPays.getFullText ();
m_journal.debug <<
"takeOffers: AFTER saTakerGets=" <<
saTakerGets.getFullText ();
}
}
}
if (m_journal.debug)
{
m_journal.debug <<
"takeOffers: saTakerPays=" <<saTakerPays.getFullText ();
m_journal.debug <<
"takeOffers: saTakerGets=" << saTakerGets.getFullText ();
m_journal.debug <<
"takeOffers: mTxnAccountID=" <<
RippleAddress::createHumanAccountID (mTxnAccountID);
m_journal.debug <<
"takeOffers: FUNDS=" <<
view.accountFunds (mTxnAccountID, saTakerGets).getFullText ();
}
if (tesSUCCESS != terResult)
{
m_journal.debug <<
"final terResult=" << transToken (terResult);
return terResult;
}
if (bFillOrKill && (saTakerPays || saTakerGets))
{
// Fill or kill and have leftovers.
view.swapWith (view_checkpoint); // Restore with just fees paid.
return tesSUCCESS;
}
if (!placeOffer
|| saTakerPays <= zero // Wants nothing more.
|| saTakerGets <= zero // Offering nothing more.
|| bImmediateOrCancel // Do not persist.
|| view.accountFunds (mTxnAccountID, saTakerGets) <= zero) // Not funded.
{
// Complete as is.
nothing ();
}
else if (mPriorBalance.getNValue () < mEngine->getLedger ()->getReserve (sleCreator->getFieldU32 (sfOwnerCount) + 1))
{
// If we are here, the signing account had an insufficient reserve
// *prior* to our processing. We use the prior balance to simplify
// client writing and make the user experience better.
if (bOpenLedger) // Ledger is not final, can vote no.
{
// Hope for more reserve to come in or more offers to consume. If we
// specified a local error this transaction will not be retried, so
// specify a tec to distribute the transaction and allow it to be
// retried. In particular, it may have been successful to a
// degree (partially filled) and if it hasn't, it might succeed.
terResult = tecINSUF_RESERVE_OFFER;
}
else if (!saPaid && !saGot)
{
// Ledger is final, insufficent reserve to create offer, processed
// nothing.
terResult = tecINSUF_RESERVE_OFFER;
}
else
{
// Ledger is final, insufficent reserve to create offer, processed
// something.
// Consider the offer unfunded. Treat as tesSUCCESS.
nothing ();
}
}
else
{
// We need to place the remainder of the offer into its order book.
if (m_journal.debug) m_journal.debug <<
"offer not fully consumed:" <<
" saTakerPays=" << saTakerPays.getFullText () <<
" saTakerGets=" << saTakerGets.getFullText ();
// Add offer to owner's directory.
terResult = view.dirAdd (uOwnerNode,
Ledger::getOwnerDirIndex (mTxnAccountID), uLedgerIndex,
std::bind (&Ledger::ownerDirDescriber, std::placeholders::_1,
std::placeholders::_2, mTxnAccountID));
if (tesSUCCESS == terResult)
{
// Update owner count.
view.ownerCountAdjust (mTxnAccountID, 1, sleCreator);
uint256 uBookBase = Ledger::getBookBase (
uPaysCurrency,
uPaysIssuerID,
uGetsCurrency,
uGetsIssuerID);
if (m_journal.debug) m_journal.debug <<
"adding to book: " << to_string (uBookBase) <<
" : " << saTakerPays.getHumanCurrency () <<
"/" << RippleAddress::createHumanAccountID (saTakerPays.getIssuer ()) <<
" -> " << saTakerGets.getHumanCurrency () <<
"/" << RippleAddress::createHumanAccountID (saTakerGets.getIssuer ());
// We use the original rate to place the offer.
uDirectory = Ledger::getQualityIndex (uBookBase, uRate);
// Add offer to order book.
terResult = view.dirAdd (uBookNode, uDirectory, uLedgerIndex,
std::bind (&Ledger::qualityDirDescriber, std::placeholders::_1,
std::placeholders::_2, saTakerPays.getCurrency (),
uPaysIssuerID, saTakerGets.getCurrency (),
uGetsIssuerID, uRate));
}
if (tesSUCCESS == terResult)
{
if (m_journal.debug)
{
m_journal.debug <<
"sfAccount=" <<
RippleAddress::createHumanAccountID (mTxnAccountID);
m_journal.debug <<
"uPaysIssuerID=" <<
RippleAddress::createHumanAccountID (uPaysIssuerID);
m_journal.debug <<
"uGetsIssuerID=" <<
RippleAddress::createHumanAccountID (uGetsIssuerID);
m_journal.debug <<
"saTakerPays.isNative()=" <<
saTakerPays.isNative ();
m_journal.debug <<
"saTakerGets.isNative()=" <<
saTakerGets.isNative ();
m_journal.debug <<
"uPaysCurrency=" <<
saTakerPays.getHumanCurrency ();
m_journal.debug <<
"uGetsCurrency=" <<
saTakerGets.getHumanCurrency ();
}
SLE::pointer sleOffer (mEngine->entryCreate (ltOFFER, uLedgerIndex));
sleOffer->setFieldAccount (sfAccount, mTxnAccountID);
sleOffer->setFieldU32 (sfSequence, uSequence);
sleOffer->setFieldH256 (sfBookDirectory, uDirectory);
sleOffer->setFieldAmount (sfTakerPays, saTakerPays);
sleOffer->setFieldAmount (sfTakerGets, saTakerGets);
sleOffer->setFieldU64 (sfOwnerNode, uOwnerNode);
sleOffer->setFieldU64 (sfBookNode, uBookNode);
if (uExpiration)
sleOffer->setFieldU32 (sfExpiration, uExpiration);
if (bPassive)
sleOffer->setFlag (lsfPassive);
if (bSell)
sleOffer->setFlag (lsfSell);
if (m_journal.debug) m_journal.debug <<
"final terResult=" << transToken (terResult) <<
" sleOffer=" << sleOffer->getJson (0);
}
}
if (tesSUCCESS != terResult)
{
m_journal.debug <<
"final terResult=" << transToken (terResult);
}
return terResult;
} }
} }

View File

@@ -20,22 +20,21 @@
#ifndef RIPPLE_TX_DIRECT_OFFERCREATE_H_INCLUDED #ifndef RIPPLE_TX_DIRECT_OFFERCREATE_H_INCLUDED
#define RIPPLE_TX_DIRECT_OFFERCREATE_H_INCLUDED #define RIPPLE_TX_DIRECT_OFFERCREATE_H_INCLUDED
#include "../book/OfferStream.h" #include "../book/Amounts.h"
#include "../book/Taker.h"
#include <unordered_set> #include <unordered_set>
namespace ripple { namespace ripple {
class DirectOfferCreateTransactor class CreateOfferDirect
: public OfferCreateTransactor : public CreateOffer
{ {
public: public:
DirectOfferCreateTransactor ( CreateOfferDirect (
SerializedTransaction const& txn, SerializedTransaction const& txn,
TransactionEngineParams params, TransactionEngineParams params,
TransactionEngine* engine) TransactionEngine* engine)
: OfferCreateTransactor ( : CreateOffer (
txn, txn,
params, params,
engine) engine)
@@ -43,15 +42,10 @@ public:
} }
TER doApply () override;
private: private:
std::pair<TER,bool> crossOffers ( std::pair<TER, core::Amounts> crossOffers (
core::LedgerView& view, core::LedgerView& view,
STAmount const& saTakerPays, core::Amounts const& taker_amount) override;
STAmount const& saTakerGets,
STAmount& saTakerPaid,
STAmount& saTakerGot);
}; };
} }

View File

@@ -22,7 +22,7 @@ namespace ripple {
/** Determine if an order is still valid /** Determine if an order is still valid
If the order is not valid it will be marked as unfunded. If the order is not valid it will be marked as unfunded.
*/ */
bool ClassicOfferCreateTransactor::isValidOffer ( bool CreateOfferLegacy::isValidOffer (
SLE::ref sleOffer, SLE::ref sleOffer,
uint160 const& uOfferOwnerID, uint160 const& uOfferOwnerID,
STAmount const& saOfferPays, STAmount const& saOfferPays,
@@ -97,7 +97,7 @@ bool ClassicOfferCreateTransactor::isValidOffer (
/** /**
*/ */
bool ClassicOfferCreateTransactor::canCross ( bool CreateOfferLegacy::canCross (
STAmount const& saTakerFunds, STAmount const& saTakerFunds,
STAmount const& saSubTakerPays, STAmount const& saSubTakerPays,
STAmount const& saSubTakerGets, STAmount const& saSubTakerGets,
@@ -189,7 +189,7 @@ bool ClassicOfferCreateTransactor::canCross (
books as: "give 0.57 BTC for 400 USD" which is what is left to sell at the books as: "give 0.57 BTC for 400 USD" which is what is left to sell at the
original rate. original rate.
*/ */
bool ClassicOfferCreateTransactor::applyOffer ( bool CreateOfferLegacy::applyOffer (
const bool bSell, const bool bSell,
const std::uint32_t uTakerPaysRate, const std::uint32_t uOfferPaysRate, const std::uint32_t uTakerPaysRate, const std::uint32_t uOfferPaysRate,
const STAmount& saOfferRate, const STAmount& saOfferRate,
@@ -327,7 +327,7 @@ bool ClassicOfferCreateTransactor::applyOffer (
@return tesSUCCESS, terNO_ACCOUNT, telFAILED_PROCESSING, or @return tesSUCCESS, terNO_ACCOUNT, telFAILED_PROCESSING, or
tecFAILED_PROCESSING tecFAILED_PROCESSING
*/ */
TER ClassicOfferCreateTransactor::takeOffers ( TER CreateOfferLegacy::takeOffers (
const bool bOpenLedger, const bool bOpenLedger,
const bool bPassive, const bool bPassive,
const bool bSell, const bool bSell,
@@ -671,7 +671,7 @@ TER ClassicOfferCreateTransactor::takeOffers (
return terResult; return terResult;
} }
TER ClassicOfferCreateTransactor::doApply () TER CreateOfferLegacy::doApply ()
{ {
if (m_journal.debug) m_journal.debug << if (m_journal.debug) m_journal.debug <<
"OfferCreate> " << mTxn.getJson (0); "OfferCreate> " << mTxn.getJson (0);

View File

@@ -24,30 +24,15 @@
namespace ripple { namespace ripple {
class ClassicOfferCreateTransactor class CreateOfferLegacy
: public OfferCreateTransactor : public CreateOffer
{ {
private:
template <class T>
static std::string
get_compare_sign (T const& lhs, T const& rhs)
{
if (lhs > rhs)
return ">";
if (rhs > lhs)
return "<";
// If neither is bigger than the other, they must be equal
return "=";
}
public: public:
ClassicOfferCreateTransactor ( CreateOfferLegacy (
SerializedTransaction const& txn, SerializedTransaction const& txn,
TransactionEngineParams params, TransactionEngineParams params,
TransactionEngine* engine) TransactionEngine* engine)
: OfferCreateTransactor ( : CreateOffer (
txn, txn,
params, params,
engine) engine)
@@ -57,6 +42,13 @@ public:
TER doApply () override; TER doApply () override;
virtual std::pair<TER, core::Amounts> crossOffers (
core::LedgerView& view,
core::Amounts const& taker_amount)
{
return std::make_pair (tesSUCCESS, core::Amounts ());
}
private: private:
bool isValidOffer ( bool isValidOffer (
SLE::ref sleOfferDir, SLE::ref sleOfferDir,

View File

@@ -21,7 +21,7 @@ namespace ripple {
// See https://ripple.com/wiki/Transaction_Format#Payment_.280.29 // See https://ripple.com/wiki/Transaction_Format#Payment_.280.29
TER PaymentTransactor::doApply () TER Payment::doApply ()
{ {
// Ripple if source or destination is non-native or if there are paths. // Ripple if source or destination is non-native or if there are paths.
std::uint32_t const uTxFlags = mTxn.getFlags (); std::uint32_t const uTxFlags = mTxn.getFlags ();

View File

@@ -22,23 +22,23 @@
namespace ripple { namespace ripple {
class PaymentTransactorLog; class PaymentLog;
template <> template <>
char const* char const*
LogPartition::getPartitionName <PaymentTransactorLog> () LogPartition::getPartitionName <PaymentLog> ()
{ {
return "Tx/Payment"; return "Tx/Payment";
} }
class PaymentTransactor class Payment
: public Transactor : public Transactor
{ {
/* The largest number of paths we allow */ /* The largest number of paths we allow */
static std::size_t const MaxPathSize = 6; static std::size_t const MaxPathSize = 6;
public: public:
PaymentTransactor ( Payment (
SerializedTransaction const& txn, SerializedTransaction const& txn,
TransactionEngineParams params, TransactionEngineParams params,
TransactionEngine* engine) TransactionEngine* engine)
@@ -46,7 +46,7 @@ public:
txn, txn,
params, params,
engine, engine,
LogPartition::getJournal <PaymentTransactorLog> ()) LogPartition::getJournal <PaymentLog> ())
{ {
} }
@@ -54,6 +54,16 @@ public:
TER doApply (); TER doApply ();
}; };
inline
std::unique_ptr <Transactor>
make_Payment (
SerializedTransaction const& txn,
TransactionEngineParams params,
TransactionEngine* engine)
{
return std::make_unique <Payment> (txn, params, engine);
}
} }
#endif #endif

View File

@@ -19,7 +19,7 @@
namespace ripple { namespace ripple {
TER AccountSetTransactor::doApply () TER SetAccount::doApply ()
{ {
std::uint32_t const uTxFlags = mTxn.getFlags (); std::uint32_t const uTxFlags = mTxn.getFlags ();

View File

@@ -22,20 +22,20 @@
namespace ripple { namespace ripple {
class AccountSetTransactorLog; class SetAccountLog;
template <> template <>
char const* char const*
LogPartition::getPartitionName <AccountSetTransactorLog> () LogPartition::getPartitionName <SetAccountLog> ()
{ {
return "Tx/AccountSet"; return "Tx/AccountSet";
} }
class AccountSetTransactor class SetAccount
: public Transactor : public Transactor
{ {
public: public:
AccountSetTransactor ( SetAccount (
SerializedTransaction const& txn, SerializedTransaction const& txn,
TransactionEngineParams params, TransactionEngineParams params,
TransactionEngine* engine) TransactionEngine* engine)
@@ -43,7 +43,7 @@ public:
txn, txn,
params, params,
engine, engine,
LogPartition::getJournal <AccountSetTransactorLog> ()) LogPartition::getJournal <SetAccountLog> ())
{ {
} }
@@ -51,6 +51,16 @@ public:
TER doApply () override; TER doApply () override;
}; };
inline
std::unique_ptr <Transactor>
make_SetAccount (
SerializedTransaction const& txn,
TransactionEngineParams params,
TransactionEngine* engine)
{
return std::make_unique <SetAccount> (txn, params, engine);
}
} }
#endif #endif

View File

@@ -19,7 +19,7 @@
namespace ripple { namespace ripple {
std::uint64_t RegularKeySetTransactor::calculateBaseFee () std::uint64_t SetRegularKey::calculateBaseFee ()
{ {
if ( mTxnAccount if ( mTxnAccount
&& (! (mTxnAccount->getFlags () & lsfPasswordSpent)) && (! (mTxnAccount->getFlags () & lsfPasswordSpent))
@@ -33,7 +33,7 @@ std::uint64_t RegularKeySetTransactor::calculateBaseFee ()
} }
TER RegularKeySetTransactor::doApply () TER SetRegularKey::doApply ()
{ {
std::uint32_t const uTxFlags = mTxn.getFlags (); std::uint32_t const uTxFlags = mTxn.getFlags ();

View File

@@ -22,22 +22,22 @@
namespace ripple { namespace ripple {
class RegularKeySetTransactorLog; class SetRegularKeyLog;
template <> template <>
char const* char const*
LogPartition::getPartitionName <RegularKeySetTransactorLog> () LogPartition::getPartitionName <SetRegularKeyLog> ()
{ {
return "Tx/RegularKeySet"; return "Tx/RegularKeySet";
} }
class RegularKeySetTransactor class SetRegularKey
: public Transactor : public Transactor
{ {
std::uint64_t calculateBaseFee (); std::uint64_t calculateBaseFee ();
public: public:
RegularKeySetTransactor ( SetRegularKey (
SerializedTransaction const& txn, SerializedTransaction const& txn,
TransactionEngineParams params, TransactionEngineParams params,
TransactionEngine* engine) TransactionEngine* engine)
@@ -45,7 +45,7 @@ public:
txn, txn,
params, params,
engine, engine,
LogPartition::getJournal <RegularKeySetTransactorLog> ()) LogPartition::getJournal <SetRegularKeyLog> ())
{ {
} }
@@ -54,6 +54,16 @@ public:
TER doApply (); TER doApply ();
}; };
inline
std::unique_ptr <Transactor>
make_SetRegularKey (
SerializedTransaction const& txn,
TransactionEngineParams params,
TransactionEngine* engine)
{
return std::make_unique <SetRegularKey> (txn, params, engine);
}
} }
#endif #endif

View File

@@ -19,7 +19,7 @@
namespace ripple { namespace ripple {
TER TrustSetTransactor::doApply () TER SetTrust::doApply ()
{ {
TER terResult = tesSUCCESS; TER terResult = tesSUCCESS;

View File

@@ -22,20 +22,20 @@
namespace ripple { namespace ripple {
class TrustSetTransactorLog; class SetTrustLog;
template <> template <>
char const* char const*
LogPartition::getPartitionName <TrustSetTransactorLog> () LogPartition::getPartitionName <SetTrustLog> ()
{ {
return "Tx/TrustSet"; return "Tx/TrustSet";
} }
class TrustSetTransactor class SetTrust
: public Transactor : public Transactor
{ {
public: public:
TrustSetTransactor ( SetTrust (
SerializedTransaction const& txn, SerializedTransaction const& txn,
TransactionEngineParams params, TransactionEngineParams params,
TransactionEngine* engine) TransactionEngine* engine)
@@ -43,13 +43,23 @@ public:
txn, txn,
params, params,
engine, engine,
LogPartition::getJournal <TrustSetTransactorLog> ()) LogPartition::getJournal <SetTrustLog> ())
{ {
} }
TER doApply (); TER doApply ();
}; };
inline
std::unique_ptr <Transactor>
make_SetTrust (
SerializedTransaction const& txn,
TransactionEngineParams params,
TransactionEngine* engine)
{
return std::make_unique <SetTrust> (txn, params, engine);
}
} }
#endif #endif

View File

@@ -37,36 +37,29 @@ std::unique_ptr<Transactor> Transactor::makeTransactor (
switch (txn.getTxnType ()) switch (txn.getTxnType ())
{ {
case ttPAYMENT: case ttPAYMENT:
return std::unique_ptr<Transactor> ( return make_Payment (txn, params, engine);
new PaymentTransactor (txn, params, engine));
case ttACCOUNT_SET: case ttACCOUNT_SET:
return std::unique_ptr<Transactor> ( return make_SetAccount (txn, params, engine);
new AccountSetTransactor (txn, params, engine));
case ttREGULAR_KEY_SET: case ttREGULAR_KEY_SET:
return std::unique_ptr<Transactor> ( return make_SetRegularKey (txn, params, engine);
new RegularKeySetTransactor (txn, params, engine));
case ttTRUST_SET: case ttTRUST_SET:
return std::unique_ptr<Transactor> ( return make_SetTrust (txn, params, engine);
new TrustSetTransactor (txn, params, engine));
case ttOFFER_CREATE: case ttOFFER_CREATE:
return make_OfferCreateTransactor (txn, params, engine); return make_CreateOffer (txn, params, engine);
case ttOFFER_CANCEL: case ttOFFER_CANCEL:
return std::unique_ptr<Transactor> ( return make_CancelOffer (txn, params, engine);
new OfferCancelTransactor (txn, params, engine));
case ttWALLET_ADD: case ttWALLET_ADD:
return std::unique_ptr<Transactor> ( return make_AddWallet (txn, params, engine);
new WalletAddTransactor (txn, params, engine));
case ttAMENDMENT: case ttAMENDMENT:
case ttFEE: case ttFEE:
return std::unique_ptr<Transactor> ( return make_Change (txn, params, engine);
new ChangeTransactor (txn, params, engine));
default: default:
return std::unique_ptr<Transactor> (); return std::unique_ptr<Transactor> ();
@@ -229,11 +222,12 @@ TER Transactor::preCheck ()
} }
// Extract signing key // Extract signing key
// Transactions contain a signing key. This allows us to trivially verify a transaction has at least been properly signed // Transactions contain a signing key. This allows us to trivially verify a
// without going to disk. Each transaction also notes a source account id. This is used to verify that the signing key is // transaction has at least been properly signed without going to disk.
// associated with the account. // Each transaction also notes a source account id. This is used to verify
// that the signing key is associated with the account.
// XXX This could be a lot cleaner to prevent unnecessary copying. // XXX This could be a lot cleaner to prevent unnecessary copying.
mSigningPubKey = RippleAddress::createAccountPublic (mTxn.getSigningPubKey ()); mSigningPubKey = RippleAddress::createAccountPublic (mTxn.getSigningPubKey ());
// Consistency: really signed. // Consistency: really signed.
if (!mTxn.isKnownGood ()) if (!mTxn.isKnownGood ())

View File

@@ -262,25 +262,6 @@ bool parseUrl (const std::string& strUrl, std::string& strScheme, std::string& s
return bMatch; return bMatch;
} }
//
// Quality parsing
// - integers as is.
// - floats multiplied by a billion
bool parseQuality (const std::string& strSource, std::uint32_t& uQuality)
{
uQuality = beast::lexicalCast <std::uint32_t> (strSource);
if (!uQuality)
{
float fQuality = beast::lexicalCast <float> (strSource);
if (fQuality)
uQuality = (std::uint32_t) (QUALITY_ONE * fQuality);
}
return !!uQuality;
}
beast::StringPairArray parseDelimitedKeyValueString (beast::String parameters, beast::StringPairArray parseDelimitedKeyValueString (beast::String parameters,
beast::beast_wchar delimiter) beast::beast_wchar delimiter)
{ {

View File

@@ -111,7 +111,6 @@ Blob strCopy (const std::string& strSrc);
std::string strCopy (Blob const& vucSrc); std::string strCopy (Blob const& vucSrc);
bool parseIpPort (const std::string& strSource, std::string& strIP, int& iPort); bool parseIpPort (const std::string& strSource, std::string& strIP, int& iPort);
bool parseQuality (const std::string& strSource, std::uint32_t& uQuality);
inline std::string strGetEnv (const std::string& strKey) inline std::string strGetEnv (const std::string& strKey)
{ {

View File

@@ -920,6 +920,7 @@ STAmount operator- (const STAmount& v1, const STAmount& v2)
return STAmount (v1.getFName (), v1.mCurrency, v1.mIssuer, -fv, ov1, true); return STAmount (v1.getFName (), v1.mCurrency, v1.mIssuer, -fv, ov1, true);
} }
// NIKB TODO Make Amount::divide skip math if den == QUALITY_ONE
STAmount STAmount::divide (const STAmount& num, const STAmount& den, const uint160& uCurrencyID, const uint160& uIssuerID) STAmount STAmount::divide (const STAmount& num, const STAmount& den, const uint160& uCurrencyID, const uint160& uIssuerID)
{ {
if (den == zero) if (den == zero)