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
#define RIPPLE_TYPES_RIPPLEASSETS_H_INCLUDED
#include <cassert>
#include <functional>
#include <type_traits>
@@ -66,6 +67,9 @@ public:
: currency (currency_)
, 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>
@@ -87,6 +91,7 @@ public:
bool is_xrp () const
{
assert (currency.isZero () == issuer.isZero ());
if (currency.isZero ())
return true;
return false;

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
/*
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
purpose with or without fee is hereby granted, provided that the above
@@ -191,18 +191,14 @@ public:
if (sig == 0)
return os << "0";
else if (sig < 0)
if (sig < 0)
os << "-";
if (m_integral)
return os << m_mantissa;
if (m_exponent != 0 && (m_exponent < -25 || m_exponent > -5))
return os << m_mantissa << "e" << m_exponent;
//if (m_exponent > 0)
// return os <<
return os;
}
};

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
/*
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
purpose with or without fee is hereby granted, provided that the above
@@ -22,6 +22,8 @@
#include "Amount.h"
#include "../../beast/beast/utility/noexcept.h"
namespace ripple {
namespace core {

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
/*
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
purpose with or without fee is hereby granted, provided that the above
@@ -25,8 +25,7 @@
#include "../../beast/beast/utility/noexcept.h"
#include <ostream>
#include <utility>
#include <functional>
namespace ripple {
namespace core {
@@ -54,15 +53,7 @@ private:
public:
/** Create the iterator. */
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))
{
}
BookTip (LedgerView& view, BookRef book);
uint256 const&
dir() const noexcept
@@ -93,49 +84,7 @@ public:
@return `true` if there is a next offer
*/
bool
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;
}
step ();
};
}

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
/*
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
purpose with or without fee is hereby granted, provided that the above
@@ -27,6 +27,8 @@
#include "../misc/SerializedLedger.h"
#include "../../ripple_data/protocol/FieldNames.h"
#include "../../beast/beast/utility/noexcept.h"
#include <ostream>
namespace ripple {
@@ -76,12 +78,23 @@ public:
Some or all of the out amount may be unfunded.
*/
Amounts const
amount() const noexcept
amount() const
{
return Amounts (m_entry->getFieldAmount (sfTakerPays),
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. */
// AVOID USING THIS
SLE::pointer

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
/*
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
purpose with or without fee is hereby granted, provided that the above
@@ -27,8 +27,7 @@
#include "../../beast/beast/utility/noexcept.h"
#include <ostream>
#include <utility>
#include <functional>
namespace ripple {
namespace core {
@@ -49,12 +48,10 @@ namespace core {
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.
TODO: Remove offers belonging to the taker
*/
class OfferStream
{
protected:
private:
beast::Journal m_journal;
std::reference_wrapper <LedgerView> m_view;
std::reference_wrapper <LedgerView> m_view_cancel;
@@ -63,90 +60,37 @@ protected:
BookTip m_tip;
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
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();
}
erase (LedgerView& view);
public:
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)
{
}
Clock::time_point when, beast::Journal journal);
LedgerView&
view() noexcept
view () noexcept
{
return m_view;
}
LedgerView&
view_cancel() noexcept
view_cancel () noexcept
{
return m_view_cancel;
}
Book const&
book() const noexcept
book () const noexcept
{
return m_book;
}
uint256 const&
dir() const noexcept
{
return m_tip.dir();
}
/** Returns the offer at the tip of the order book.
Offers are always presented in decreasing quality.
Only valid if step() returned `true`.
*/
Offer const&
tip() const
tip () const
{
return m_offer;
}
@@ -159,166 +103,22 @@ dir() const noexcept
@return `true` if there is a valid offer.
*/
bool
step()
{
// Modifying the order or logic of these
// operations causes a protocol breaking change.
step ();
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
// 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.
/** Advance to the next valid offer that is not from the specified account.
This automatically removes:
- Offers with missing ledger entries
- Offers found unfunded
- Offers from the same account
- Expired offers
@return `true` if there is a valid offer.
*/
bool
fill (Amounts const& remaining_funds)
{
// Erase the offer if it is fully consumed (in==0 || out==0)
// This is the same as becoming unfunded
return false;
}
step_account (Account const& account);
};
//------------------------------------------------------------------------------
/**
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

View File

@@ -23,9 +23,7 @@
#include "Amount.h"
#include "Amounts.h"
#include <cassert>
#include <cstdint>
#include <limits>
#include <ostream>
namespace ripple {
@@ -49,57 +47,53 @@ private:
public:
Quality() = default;
/** Create a quality from the integer encoding of an Amount */
explicit
Quality (std::uint64_t value)
: m_value (value)
{
}
Quality (std::uint64_t value);
/** Create a quality from the ratio of two amounts. */
explicit
Quality (Amounts const& amount)
: m_value (Amount::getRate (amount.out, amount.in))
{
}
Quality (Amounts const& amount);
/** Advances to the next higher quality level. */
/** @{ */
Quality&
operator++()
{
assert (m_value > 0);
--m_value;
return *this;
}
operator++();
Quality
operator++ (int)
{
Quality prev (*this);
--*this;
return prev;
}
operator++ (int);
/** @} */
/** Advances to the next lower quality level. */
/** @{ */
Quality&
operator--()
{
assert (m_value < std::numeric_limits<value_type>::max());
++m_value;
return *this;
}
operator--();
Quality
operator-- (int)
{
Quality prev (*this);
++*this;
return prev;
}
operator-- (int);
/** @} */
/** 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`.
Lower quality means the taker receives a worse deal.
Higher quality is better for the taker.
@@ -118,6 +112,13 @@ public:
return lhs.m_value == rhs.m_value;
}
friend
bool
operator!= (Quality const& lhs, Quality const& rhs) noexcept
{
return ! (lhs == rhs);
}
friend
std::ostream&
operator<< (std::ostream& os, Quality const& quality)
@@ -125,72 +126,14 @@ public:
os << quality.m_value;
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
bool
operator!= (Quality const& lhs, Quality const& rhs) noexcept
{
return ! (lhs == rhs);
}
/** Calculate the quality of a two-hop path given the two hops.
@param lhs The first leg of the path: input to intermediate.
@param rhs The second leg of the path: intermediate to output.
*/
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
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
purpose with or without fee is hereby granted, provided that the above
@@ -22,11 +22,14 @@
#include "Amounts.h"
#include "Quality.h"
#include "Offer.h"
#include "Types.h"
#include "../../beast/beast/streams/debug_ostream.h"
#include "../../beast/beast/utility/noexcept.h"
#include <utility>
#include <functional>
//#include <utility>
namespace ripple {
namespace core {
@@ -37,7 +40,7 @@ class Taker
public:
struct Options
{
Options() = default;
Options() = delete;
explicit
Options (std::uint32_t tx_flags)
@@ -56,99 +59,54 @@ public:
private:
std::reference_wrapper <LedgerView> m_view;
Book m_book;
Account m_account;
Options m_options;
Quality m_quality;
Quality m_threshold;
// The original in and out quantities
Amounts m_amount;
// The original in and out quantities.
Amounts const m_amount;
// Amount of input currency remaining.
Amount m_in;
// The amounts still left over for us to try and take.
Amounts m_remain;
// Amount of output currency we have received.
Amount m_out;
private:
Amounts
flow (Amounts amount, Offer const& offer, Account const& taker);
// Returns the balance of the taker's input currency,
Amount
funds() const
{
return view().accountFunds (account(), m_in);
}
TER
fill (Offer const& offer, Amounts const& amount);
TER
fill (Offer const& leg1, Amounts const& amount1,
Offer const& leg2, Amounts const& amount2);
public:
Taker (LedgerView& view, BookRef const& book,
Account const& account, Amounts const& amount,
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;
}
Taker (LedgerView& view, Account const& account,
Amounts const& amount, Options const& options);
LedgerView&
view() const noexcept
view () const noexcept
{
return m_view;
}
/** Returns the input and output asset pair identifier. */
Book const&
book() const noexcept
{
return m_book;
}
/** Returns the amount remaining on the offer.
This is the amount at which the offer should be placed. It may either
be for the full amount when there were no crossing offers, or for zero
when the offer fully crossed, or any amount in between.
It is always at the original offer quality (m_quality)
*/
Amounts
remaining_offer () const;
/** Returns the account identifier of the taker. */
Account const&
account() const noexcept
account () const noexcept
{
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. */
bool
reject (Quality const& quality) const noexcept
@@ -156,138 +114,26 @@ public:
return quality < m_threshold;
}
/** Calcualtes the result of applying the taker's funds to the offer.
@return The flow and flag indicating if the order was consumed.
/** 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.
*/
std::pair <Amounts, bool>
fill (Offer const& offer) const
{
// Best flow the owner can get.
// Start out assuming entire offer will flow.
Amounts owner_amount (offer.amount());
bool
done () const;
// Limit owner's output by available funds less fees
// 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).
/** Perform direct crossing through given offer.
@return tesSUCCESS on success, error code otherwise.
*/
TER
process (Amounts const& flow, Offer const& offer)
{
TER result (tesSUCCESS);
cross (Offer const& offer);
// VFALCO For the case of !sell, is it possible for the taker
// to get a tiny bit more than he asked for?
// DAVIDS Can you verify?
assert (m_options.sell || flow.out <= m_amount.out);
// 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;
}
/** Perform bridged crossing through given offers.
@return tesSUCCESS on success, error code otherwise.
*/
TER
cross (Offer const& leg1, Offer const& leg2);
};
inline
std::ostream&
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

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
/*
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
purpose with or without fee is hereby granted, provided that the above
@@ -22,7 +22,7 @@
#include "../ledger/LedgerEntrySet.h"
#include "../../ripple/types/api/RippleAssets.h"
#include "../../ripple/types/api/UInt160.h"
#include "../../ripple/types/api/base_uint.h"
#include <chrono>
#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;
}
if (isTefFailure (result) || isTemMalformed
(result) || isTelLocal (result))
if (isTefFailure (result) || isTemMalformed (result) ||
isTelLocal (result))
{
// failure
WriteLog (lsDEBUG, LedgerConsensus)

View File

@@ -21,6 +21,11 @@
#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/Change.cpp"

View File

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

View File

@@ -22,20 +22,20 @@
namespace ripple {
class WalletAddTransactorLog;
class AddWalletLog;
template <>
char const*
LogPartition::getPartitionName <WalletAddTransactorLog> ()
LogPartition::getPartitionName <AddWalletLog> ()
{
return "Tx/WalletAdd";
}
class WalletAddTransactor
class AddWallet
: public Transactor
{
public:
WalletAddTransactor (
AddWallet (
SerializedTransaction const& txn,
TransactionEngineParams params,
TransactionEngine* engine)
@@ -43,13 +43,23 @@ public:
txn,
params,
engine,
LogPartition::getJournal <WalletAddTransactorLog> ())
LogPartition::getJournal <AddWalletLog> ())
{
}
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

View File

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

View File

@@ -22,20 +22,20 @@
namespace ripple {
class OfferCancelTransactorLog;
class CancelOfferLog;
template <>
char const*
LogPartition::getPartitionName <OfferCancelTransactorLog> ()
LogPartition::getPartitionName <CancelOfferLog> ()
{
return "Tx/OfferCancel";
}
class OfferCancelTransactor
class CancelOffer
: public Transactor
{
public:
OfferCancelTransactor (
CancelOffer (
SerializedTransaction const& txn,
TransactionEngineParams params,
TransactionEngine* engine)
@@ -43,7 +43,7 @@ public:
txn,
params,
engine,
LogPartition::getJournal <OfferCancelTransactorLog> ())
LogPartition::getJournal <CancelOfferLog> ())
{
}
@@ -51,6 +51,16 @@ public:
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

View File

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

View File

@@ -22,20 +22,20 @@
namespace ripple {
class ChangeTransactorLog;
class ChangeLog;
template <>
char const*
LogPartition::getPartitionName <ChangeTransactorLog> ()
LogPartition::getPartitionName <ChangeLog> ()
{
return "Tx/Change";
}
class ChangeTransactor
class Change
: public Transactor
{
public:
ChangeTransactor (
Change (
SerializedTransaction const& txn,
TransactionEngineParams params,
TransactionEngine* engine)
@@ -43,7 +43,7 @@ public:
txn,
params,
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

View File

@@ -26,10 +26,11 @@
#include "../book/OfferStream.h"
#include "../book/Taker.h"
#include "../book/Types.h"
namespace ripple {
OfferCreateTransactor::OfferCreateTransactor (
CreateOffer::CreateOffer (
SerializedTransaction const& txn,
TransactionEngineParams params,
TransactionEngine* engine)
@@ -37,31 +38,495 @@ OfferCreateTransactor::OfferCreateTransactor (
txn,
params,
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,
TransactionEngineParams params,
TransactionEngine* engine)
{
#if RIPPLE_USE_OLD_CREATE_TRANSACTOR
return std::make_unique <ClassicOfferCreateTransactor> (txn, params, engine);
return std::make_unique <CreateOfferLegacy> (txn, params, engine);
#else
STAmount const& amount_in = txn.getFieldAmount (sfTakerPays);
STAmount const& amount_out = txn.getFieldAmount (sfTakerGets);
// Autobridging is only in effect when an offer does not involve XRP
if (!amount_in.isNative() && !amount_out.isNative ())
{
// 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 <CreateOfferBridged> (txn, params, engine);
return std::make_unique <DirectOfferCreateTransactor> (txn, params, engine);
return std::make_unique <CreateOfferDirect> (txn, params, engine);
#endif
}

View File

@@ -20,45 +20,53 @@
#ifndef 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"
namespace ripple {
class OfferCreateTransactorLog;
class CreateOfferLog;
template <>
char const*
LogPartition::getPartitionName <OfferCreateTransactorLog> ()
LogPartition::getPartitionName <CreateOfferLog> ()
{
return "Tx/OfferCreate";
}
class OfferCreateTransactor
class CreateOffer
: public Transactor
{
private:
template <class T>
static std::string
get_compare_sign (T const& lhs, T const& rhs)
{
if (lhs > rhs)
return ">";
protected:
/** Determine if we are authorized to hold the asset we want to get */
TER checkAcceptAsset(core::AssetRef asset) const;
if (rhs > lhs)
return "<";
/** Fill offer as much as possible by consuming offers already on the books.
We adjusts account balances and charges fees on top to taker.
// If neither is bigger than the other, they must be equal
return "=";
}
@param taker_amount.in How much the taker offers
@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:
OfferCreateTransactor (
CreateOffer (
SerializedTransaction const& txn,
TransactionEngineParams params,
TransactionEngine* engine);
TER doApply () override;
};
std::unique_ptr <Transactor> make_OfferCreateTransactor (
std::unique_ptr <Transactor> make_CreateOffer (
SerializedTransaction const& txn,
TransactionEngineParams params,
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
#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

View File

@@ -23,38 +23,38 @@
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
std::pair<TER,bool>
process_order (
@param taker_amount.in How much the taker offers
@param taker_amount.out How much the taker wants
@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::BookRef const book,
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)
core::Amounts const& taker_amount)
{
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::OfferStream offers (view, view_cancel, book, when, journal);
core::Taker taker (offers.view(), book, account, amount, options);
core::OfferStream offers (view, view_cancel,
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 <<
"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);
TER cross_result (tesSUCCESS);
while (true)
{
@@ -67,27 +67,22 @@ process_order (
if (taker.done())
{
journal.debug << "The taker reports he's done during crossing!";
place_order = false;
m_journal.debug << "The taker reports he's done during crossing!";
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
// 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;
}
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();
auto const& offer (offers.tip());
if (taker.reject (offer.quality()))
{
@@ -98,592 +93,25 @@ process_order (
if (offer.account() == taker.account())
{
if (journal.debug) journal.debug <<
" skipping self-offer " << offer.entry()->getIndex() << std::endl <<
" 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)
// Skip offer from self. The offer will be considered expired and
// will get deleted.
continue;
}
if (journal.debug) journal.debug <<
" offer " << offer.entry()->getIndex() << std::endl <<
" pays/gets " << offer.amount().in << ", " << offer.amount().out
;
if (m_journal.debug) m_journal.debug <<
" Offer: " << offer.entry()->getIndex() << std::endl <<
" " << offer.amount().in << " : " << offer.amount().out;
core::Amounts flow;
bool consumed;
std::tie (flow, consumed) = taker.fill (offer);
cross_result = taker.cross (offer);
result = taker.process (flow, offer);
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)
if (cross_result != tesSUCCESS)
{
// VFALCO TODO Return the tec and let a caller higher
// up convert the error if the ledger is open.
//result = bOpenLedger ?
// telFAILED_PROCESSING : tecFAILED_PROCESSING;
result = tecFAILED_PROCESSING;
cross_result = tecFAILED_PROCESSING;
break;
}
cross_flow.in += flow.in;
cross_flow.out += flow.out;
}
if (result == tesSUCCESS)
{
// 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;
return std::make_pair(cross_result, taker.remaining_offer ());
}
}

View File

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

View File

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

View File

@@ -24,30 +24,15 @@
namespace ripple {
class ClassicOfferCreateTransactor
: public OfferCreateTransactor
class CreateOfferLegacy
: 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:
ClassicOfferCreateTransactor (
CreateOfferLegacy (
SerializedTransaction const& txn,
TransactionEngineParams params,
TransactionEngine* engine)
: OfferCreateTransactor (
: CreateOffer (
txn,
params,
engine)
@@ -57,6 +42,13 @@ public:
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:
bool isValidOffer (
SLE::ref sleOfferDir,

View File

@@ -21,7 +21,7 @@ namespace ripple {
// 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.
std::uint32_t const uTxFlags = mTxn.getFlags ();

View File

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

View File

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

View File

@@ -22,20 +22,20 @@
namespace ripple {
class AccountSetTransactorLog;
class SetAccountLog;
template <>
char const*
LogPartition::getPartitionName <AccountSetTransactorLog> ()
LogPartition::getPartitionName <SetAccountLog> ()
{
return "Tx/AccountSet";
}
class AccountSetTransactor
class SetAccount
: public Transactor
{
public:
AccountSetTransactor (
SetAccount (
SerializedTransaction const& txn,
TransactionEngineParams params,
TransactionEngine* engine)
@@ -43,7 +43,7 @@ public:
txn,
params,
engine,
LogPartition::getJournal <AccountSetTransactorLog> ())
LogPartition::getJournal <SetAccountLog> ())
{
}
@@ -51,6 +51,16 @@ public:
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

View File

@@ -19,7 +19,7 @@
namespace ripple {
std::uint64_t RegularKeySetTransactor::calculateBaseFee ()
std::uint64_t SetRegularKey::calculateBaseFee ()
{
if ( mTxnAccount
&& (! (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 ();

View File

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

View File

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

View File

@@ -22,20 +22,20 @@
namespace ripple {
class TrustSetTransactorLog;
class SetTrustLog;
template <>
char const*
LogPartition::getPartitionName <TrustSetTransactorLog> ()
LogPartition::getPartitionName <SetTrustLog> ()
{
return "Tx/TrustSet";
}
class TrustSetTransactor
class SetTrust
: public Transactor
{
public:
TrustSetTransactor (
SetTrust (
SerializedTransaction const& txn,
TransactionEngineParams params,
TransactionEngine* engine)
@@ -43,13 +43,23 @@ public:
txn,
params,
engine,
LogPartition::getJournal <TrustSetTransactorLog> ())
LogPartition::getJournal <SetTrustLog> ())
{
}
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

View File

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

View File

@@ -262,25 +262,6 @@ bool parseUrl (const std::string& strUrl, std::string& strScheme, std::string& s
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::beast_wchar delimiter)
{

View File

@@ -111,7 +111,6 @@ Blob strCopy (const std::string& strSrc);
std::string strCopy (Blob const& vucSrc);
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)
{

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);
}
// 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)
{
if (den == zero)