New classes for processing offers in order books:

* BookTip provides consume-and-step offer traversal
* OfferStream applies offer business rules and presents offers to callers
* Taker class manages state for the active party during order processing
* Offer class wraps book offers for presentation
This commit is contained in:
Vinnie Falco
2014-04-02 14:26:38 -07:00
parent 53bf5e7f36
commit 04ea9ff74c
18 changed files with 1094 additions and 46 deletions

View File

@@ -820,6 +820,12 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild> <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile> </ClCompile>
<ClCompile Include="..\..\src\ripple\validators\ripple_validators.cpp" /> <ClCompile Include="..\..\src\ripple\validators\ripple_validators.cpp" />
<ClCompile Include="..\..\src\ripple_app\book\tests\OfferStream.test.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\ripple_app\book\tests\Quality.test.cpp"> <ClCompile Include="..\..\src\ripple_app\book\tests\Quality.test.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild> <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild> <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
@@ -2485,7 +2491,11 @@
<ClInclude Include="..\..\src\ripple\validators\ripple_validators.h" /> <ClInclude Include="..\..\src\ripple\validators\ripple_validators.h" />
<ClInclude Include="..\..\src\ripple_app\book\Amount.h" /> <ClInclude Include="..\..\src\ripple_app\book\Amount.h" />
<ClInclude Include="..\..\src\ripple_app\book\Amounts.h" /> <ClInclude Include="..\..\src\ripple_app\book\Amounts.h" />
<ClInclude Include="..\..\src\ripple_app\book\BookTip.h" />
<ClInclude Include="..\..\src\ripple_app\book\Offer.h" />
<ClInclude Include="..\..\src\ripple_app\book\OfferStream.h" />
<ClInclude Include="..\..\src\ripple_app\book\Quality.h" /> <ClInclude Include="..\..\src\ripple_app\book\Quality.h" />
<ClInclude Include="..\..\src\ripple_app\book\Taker.h" />
<ClInclude Include="..\..\src\ripple_app\book\Types.h" /> <ClInclude Include="..\..\src\ripple_app\book\Types.h" />
<ClInclude Include="..\..\src\ripple_app\consensus\DisputedTx.h" /> <ClInclude Include="..\..\src\ripple_app\consensus\DisputedTx.h" />
<ClInclude Include="..\..\src\ripple_app\consensus\LedgerConsensus.h" /> <ClInclude Include="..\..\src\ripple_app\consensus\LedgerConsensus.h" />

View File

@@ -1515,6 +1515,9 @@
<ClCompile Include="..\..\src\ripple_app\book\tests\Quality.test.cpp"> <ClCompile Include="..\..\src\ripple_app\book\tests\Quality.test.cpp">
<Filter>[2] Old Ripple\ripple_app\book\tests</Filter> <Filter>[2] Old Ripple\ripple_app\book\tests</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="..\..\src\ripple_app\book\tests\OfferStream.test.cpp">
<Filter>[2] Old Ripple\ripple_app\book\tests</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="..\..\src\ripple_basics\containers\RangeSet.h"> <ClInclude Include="..\..\src\ripple_basics\containers\RangeSet.h">
@@ -3090,6 +3093,18 @@
<ClInclude Include="..\..\src\ripple_app\book\Types.h"> <ClInclude Include="..\..\src\ripple_app\book\Types.h">
<Filter>[2] Old Ripple\ripple_app\book</Filter> <Filter>[2] Old Ripple\ripple_app\book</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="..\..\src\ripple_app\book\OfferStream.h">
<Filter>[2] Old Ripple\ripple_app\book</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple_app\book\Taker.h">
<Filter>[2] Old Ripple\ripple_app\book</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple_app\book\BookTip.h">
<Filter>[2] Old Ripple\ripple_app\book</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple_app\book\Offer.h">
<Filter>[2] Old Ripple\ripple_app\book</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<CustomBuild Include="..\..\src\ripple_data\protocol\ripple.proto"> <CustomBuild Include="..\..\src\ripple_data\protocol\ripple.proto">

View File

@@ -75,11 +75,10 @@ public:
{ {
} }
/** Assignment. /** Assignment. */
This is only valid when ByValue == `true` template <bool MaybeByValue = ByValue, bool OtherByValue>
*/ std::enable_if_t <MaybeByValue, RippleAssetType&>
template <bool OtherByValue> operator= (RippleAssetType <OtherByValue> const& other)
RippleAssetType& operator= (RippleAssetType <OtherByValue> const& other)
{ {
currency = other.currency; currency = other.currency;
issuer = other.issuer; issuer = other.issuer;

View File

@@ -0,0 +1,144 @@
//------------------------------------------------------------------------------
/*
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.
*/
//==============================================================================
#ifndef RIPPLE_CORE_BOOKTIP_H_INCLUDED
#define RIPPLE_CORE_BOOKTIP_H_INCLUDED
#include "Quality.h"
#include "Types.h"
#include "../../beast/beast/utility/noexcept.h"
#include <ostream>
#include <utility>
namespace ripple {
namespace core {
/** Iterates and consumes raw offers in an order book.
Offers are presented from highest quality to lowest quality. This will
return all offers present including missing, invalid, unfunded, etc.
*/
class BookTip
{
private:
std::reference_wrapper <LedgerView> m_view;
bool m_valid;
uint256 m_book;
uint256 m_end;
uint256 m_dir;
uint256 m_index;
SLE::pointer m_entry;
LedgerView&
view() const noexcept
{
return m_view;
}
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))
{
}
uint256 const&
dir() const noexcept
{
return m_dir;
}
uint256 const&
index() const noexcept
{
return m_index;
}
Quality const
quality() const noexcept
{
return Quality (Ledger::getQuality (m_dir));
}
SLE::pointer const&
entry() const noexcept
{
return m_entry;
}
/** Erases the current offer and advance to the next offer.
Complexity: Constant
@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;
}
};
}
}
#endif

104
src/ripple_app/book/Offer.h Normal file
View File

@@ -0,0 +1,104 @@
//------------------------------------------------------------------------------
/*
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.
*/
//==============================================================================
#ifndef RIPPLE_CORE_OFFER_H_INCLUDED
#define RIPPLE_CORE_OFFER_H_INCLUDED
#include "Amounts.h"
#include "Quality.h"
#include "Types.h"
#include "../misc/SerializedLedger.h"
#include "../../ripple_data/protocol/FieldNames.h"
#include <ostream>
namespace ripple {
namespace core {
class Offer
{
public:
typedef Amount amount_type;
private:
SLE::pointer m_entry;
Quality m_quality;
public:
Offer() = default;
Offer (SLE::pointer const& entry, Quality quality)
: m_entry (entry)
, m_quality (quality)
{
}
/** Returns the quality of the offer.
Conceptually, the quality is the ratio of output to input currency.
The implementation calculates it as the ratio of input to output
currency (so it sorts ascending). The quality is computed at the time
the offer is placed, and never changes for the lifetime of the offer.
This is an important business rule that maintains accuracy when an
offer is partially filled; Subsequent partial fills will use the
original quality.
*/
Quality const
quality() const noexcept
{
return m_quality;
}
/** Returns the account id of the offer's owner. */
Account const
account() const
{
return m_entry->getFieldAccount160 (sfAccount);
}
/** Returns the in and out amounts.
Some or all of the out amount may be unfunded.
*/
Amounts const
amount() const noexcept
{
return Amounts (m_entry->getFieldAmount (sfTakerPays),
m_entry->getFieldAmount (sfTakerGets));
}
/** Returns the ledger entry underlying the offer. */
// AVOID USING THIS
SLE::pointer
entry() const noexcept
{
return m_entry;
}
};
inline
std::ostream&
operator<< (std::ostream& os, Offer const& offer)
{
return os << offer.entry()->getIndex();
}
}
}
#endif

View File

@@ -0,0 +1,324 @@
//------------------------------------------------------------------------------
/*
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.
*/
//==============================================================================
#ifndef RIPPLE_CORE_OFFERSTREAM_H_INCLUDED
#define RIPPLE_CORE_OFFERSTREAM_H_INCLUDED
#include "BookTip.h"
#include "Offer.h"
#include "Quality.h"
#include "Types.h"
#include "../../beast/beast/utility/noexcept.h"
#include <ostream>
#include <utility>
namespace ripple {
namespace core {
/** Presents and consumes the offers in an order book.
Two `LedgerView` objects accumulate changes to the ledger. `view`
is applied when the calling transaction succeeds. If the calling
transaction fails, then `view_cancel` is applied.
Certain invalid offers are automatically removed:
- Offers with missing ledger entries
- Offers that expired
- Offers found unfunded:
An offer is found unfunded when the corresponding balance is zero
and the caller has not modified the balance. This is accomplished
by also looking up the balance in the cancel view.
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:
beast::Journal m_journal;
std::reference_wrapper <LedgerView> m_view;
std::reference_wrapper <LedgerView> m_view_cancel;
Book m_book;
Clock::time_point m_when;
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();
}
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)
{
}
LedgerView&
view() noexcept
{
return m_view;
}
LedgerView&
view_cancel() noexcept
{
return m_view_cancel;
}
Book const&
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
{
return m_offer;
}
/** Advance to the next valid offer.
This automatically removes:
- Offers with missing ledger entries
- Offers found unfunded
- expired offers
@return `true` if there is a valid offer.
*/
bool
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
// 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.isPositive())
{
// If the owner's balance in the pristine view is the same,
// we haven't modified the balance and therefore the
// offer is "found unfunded" versus "became unfunded"
if (view_cancel().accountFunds (m_offer.account(),
m_offer.amount().out) == owner_funds)
{
view_cancel().offerDelete (entry->getIndex());
if (m_journal.trace) m_journal.trace <<
"Removing unfunded offer " << entry->getIndex();
}
else
{
if (m_journal.trace) m_journal.trace <<
"Removing became unfunded offer " << entry->getIndex();
}
m_offer = Offer{};
continue;
}
#if 0
// Remove if its our own offer
//
// VFALCO NOTE We might not want this for payments
//
if (m_account == owner)
{
view_cancel().offerDelete (entry->getIndex());
if (m_journal.trace) m_journal.trace <<
"Removing self offer " << entry->getIndex();
continue;
}
#endif
break;
}
return true;
}
/** Updates the offer to reflect remaining funds.
The caller is responsible for following all the rounding rules.
The offer will be considered fully consumed if either the in
or the out amount is zero.
@return `true` If the offer had no funds remaining.
*/
bool
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;
}
};
//------------------------------------------------------------------------------
/**
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

351
src/ripple_app/book/Taker.h Normal file
View File

@@ -0,0 +1,351 @@
//------------------------------------------------------------------------------
/*
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.
*/
//==============================================================================
#ifndef RIPPLE_CORE_TAKER_H_INCLUDED
#define RIPPLE_CORE_TAKER_H_INCLUDED
#include "Amounts.h"
#include "Quality.h"
#include "Types.h"
#include "../../beast/beast/streams/debug_ostream.h"
#include <utility>
namespace ripple {
namespace core {
/** State for the active party during order book or payment operations. */
class Taker
{
public:
struct Options
{
Options() = default;
explicit
Options (std::uint32_t tx_flags)
: sell (isSetBit (tx_flags, tfSell))
, passive (isSetBit (tx_flags, tfPassive))
{
}
bool sell;
bool passive;
};
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;
// Amount of input currency remaining.
Amount m_in;
// Amount of output currency we have received.
Amount m_out;
// Returns the balance of the taker's input currency,
Amount
funds() const
{
return view().accountFunds (account(), m_in);
}
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;
}
LedgerView&
view() const noexcept
{
return m_view;
}
/** Returns the input and output asset pair identifier. */
Book const&
book() const noexcept
{
return m_book;
}
/** Returns the account identifier of the taker. */
Account const&
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.isPositive())
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().isPositive();
}
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
{
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.
*/
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());
// 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.isZero());
}
// Calculate the amount that will flow through the offer
// This does not include the fees.
Amounts const flow ((owner_amount.in < taker_amount.in) ?
owner_amount : taker_amount);
bool const consumed (flow.out >= owner_amount.out);
return std::make_pair (flow, consumed);
}
/** Process the result of fee and funds calculation on the offer.
To protect the ledger, conditions which should never occur are
checked. If the invariants are broken, the processing fails.
If processing succeeds, the funds are distributed to the taker,
owner, and issuers.
@return `false` if processing failed (due to math errors).
*/
TER
process (Amounts const& flow, Offer const& offer)
{
TER result (tesSUCCESS);
// VFALCO For the case of !sell, is it possible for the taker
// to get a tiny bit more than he asked for?
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;
}
};
inline
std::ostream&
operator<< (std::ostream& os, Taker const& taker)
{
return os << taker.account();
}
}
}
/*
// 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

@@ -0,0 +1,46 @@
//------------------------------------------------------------------------------
/*
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"
#include "../../../beast/beast/unit_test/suite.h"
namespace ripple {
namespace core {
class OfferStream_test : public beast::unit_test::suite
{
public:
void
test()
{
pass();
}
void
run()
{
test();
}
};
BEAST_DEFINE_TESTSUITE_MANUAL(OfferStream,core,ripple);
}
}

View File

@@ -27,12 +27,15 @@ class DirectoryEntryIterator
{ {
public: public:
DirectoryEntryIterator ()
: mEntry(0)
{
}
DirectoryEntryIterator () : mEntry(0) DirectoryEntryIterator (uint256 const& index)
{ ; } : mRootIndex(index), mEntry(0)
{
DirectoryEntryIterator (uint256 const& index) : mRootIndex(index), mEntry(0) }
{ ; }
/** Construct from a reference to the root directory /** Construct from a reference to the root directory
*/ */
@@ -42,24 +45,19 @@ public:
mRootIndex = mDirNode->getIndex(); mRootIndex = mDirNode->getIndex();
} }
/** Get the SLE this iterator currently references /** Get the SLE this iterator currently references */
*/
SLE::pointer getEntry (LedgerEntrySet& les, LedgerEntryType type); SLE::pointer getEntry (LedgerEntrySet& les, LedgerEntryType type);
/** Make this iterator point to the first offer /** Make this iterator point to the first offer */
*/
bool firstEntry (LedgerEntrySet&); bool firstEntry (LedgerEntrySet&);
/** Make this iterator point to the next offer /** Make this iterator point to the next offer */
*/
bool nextEntry (LedgerEntrySet&); bool nextEntry (LedgerEntrySet&);
/** Add this iterator's position to a JSON object /** Add this iterator's position to a JSON object */
*/
bool addJson (Json::Value&) const; bool addJson (Json::Value&) const;
/** Set this iterator's position from a JSON object /** Set this iterator's position from a JSON object */
*/
bool setJson (Json::Value const&, LedgerEntrySet& les); bool setJson (Json::Value const&, LedgerEntrySet& les);
uint256 const& getEntryLedgerIndex () const uint256 const& getEntryLedgerIndex () const
@@ -72,8 +70,19 @@ public:
return mDirNode ? mDirNode->getIndex () : uint256(); return mDirNode ? mDirNode->getIndex () : uint256();
} }
private: bool
operator== (DirectoryEntryIterator const& other) const
{
return mEntry == other.mEntry && mDirIndex == other.mDirIndex;
}
bool
operator!= (DirectoryEntryIterator const& other) const
{
return ! (*this == other);
}
private:
uint256 mRootIndex; // ledger index of the root directory uint256 mRootIndex; // ledger index of the root directory
uint256 mDirIndex; // ledger index of the current directory uint256 mDirIndex; // ledger index of the current directory
unsigned int mEntry; // entry index we are on (0 means first is next) unsigned int mEntry; // entry index we are on (0 means first is next)

View File

@@ -29,7 +29,8 @@ class BookDirIterator
public: public:
BookDirIterator () BookDirIterator ()
{ ; } {
}
BookDirIterator ( BookDirIterator (
uint160 const& uInCurrency, uint160 const& uInIssuer, uint160 const& uInCurrency, uint160 const& uInIssuer,
@@ -93,26 +94,40 @@ public:
bool setJson (Json::Value const&); bool setJson (Json::Value const&);
// Does this iterator currently point to a valid directory // Does this iterator currently point to a valid directory
explicit
operator bool () const operator bool () const
{ {
return mOfferDir && (mOfferDir->getIndex() == mIndex); return mOfferDir && (mOfferDir->getIndex() == mIndex);
} }
private: bool
operator== (BookDirIterator const& other) const
{
assert (! mIndex.isZero() && ! other.mIndex.isZero());
return mIndex == other.mIndex;
}
bool
operator!= (BookDirIterator const& other) const
{
return ! (*this == other);
}
private:
uint256 mBase; // The first index a directory in the book can have uint256 mBase; // The first index a directory in the book can have
uint256 mEnd; // The first index a directory in the book cannot have uint256 mEnd; // The first index a directory in the book cannot have
uint256 mIndex; // The index we are currently on uint256 mIndex; // The index we are currently on
SLE::pointer mOfferDir; // The directory page we are currently on SLE::pointer mOfferDir; // The directory page we are currently on
}; };
//------------------------------------------------------------------------------
/** An iterator that walks the offers in a book /** An iterator that walks the offers in a book
CAUTION: The LedgerEntrySet must remain valid for the life of the iterator CAUTION: The LedgerEntrySet must remain valid for the life of the iterator
*/ */
class OrderBookIterator class OrderBookIterator
{ {
public: public:
OrderBookIterator ( OrderBookIterator (
LedgerEntrySet& set, LedgerEntrySet& set,
uint160 const& uInCurrency, uint160 const& uInCurrency,
@@ -121,7 +136,11 @@ public:
uint160 const& uOutIssuer) : uint160 const& uOutIssuer) :
mEntrySet (set), mEntrySet (set),
mDirectoryIterator (uInCurrency, uInIssuer, uOutCurrency, uOutIssuer) mDirectoryIterator (uInCurrency, uInIssuer, uOutCurrency, uOutIssuer)
{ ; } {
}
OrderBookIterator&
operator= (OrderBookIterator const&) = default;
bool addJson (Json::Value&) const; bool addJson (Json::Value&) const;
@@ -193,8 +212,23 @@ public:
return mOfferIterator; return mOfferIterator;
} }
bool
operator== (OrderBookIterator const& other) const
{
return
std::addressof(mEntrySet) == std::addressof(other.mEntrySet) &&
mDirectoryIterator == other.mDirectoryIterator &&
mOfferIterator == mOfferIterator;
}
bool
operator!= (OrderBookIterator const& other) const
{
return ! (*this == other);
}
private: private:
LedgerEntrySet& mEntrySet; std::reference_wrapper <LedgerEntrySet> mEntrySet;
BookDirIterator mDirectoryIterator; BookDirIterator mDirectoryIterator;
DirectoryEntryIterator mOfferIterator; DirectoryEntryIterator mOfferIterator;
}; };

View File

@@ -38,4 +38,5 @@
#include "tx/TransactionEngine.cpp" #include "tx/TransactionEngine.cpp"
#include "tx/TransactionMeta.cpp" #include "tx/TransactionMeta.cpp"
#include "book/tests/OfferStream.test.cpp"
#include "book/tests/Quality.test.cpp" #include "book/tests/Quality.test.cpp"

View File

@@ -55,7 +55,7 @@ TER AccountSetTransactor::doApply ()
if (bSetRequireAuth && !isSetBit (uFlagsIn, lsfRequireAuth)) if (bSetRequireAuth && !isSetBit (uFlagsIn, lsfRequireAuth))
{ {
if (!mEngine->getNodes ().dirIsEmpty (Ledger::getOwnerDirIndex (mTxnAccountID))) if (!mEngine->view().dirIsEmpty (Ledger::getOwnerDirIndex (mTxnAccountID)))
{ {
m_journal.trace << "Retry: Owner directory not empty."; m_journal.trace << "Retry: Owner directory not empty.";

View File

@@ -57,7 +57,7 @@ TER OfferCancelTransactor::doApply ()
m_journal.debug << m_journal.debug <<
"OfferCancel: uOfferSequence=" << uOfferSequence; "OfferCancel: uOfferSequence=" << uOfferSequence;
return mEngine->getNodes ().offerDelete (sleOffer); return mEngine->view ().offerDelete (sleOffer);
} }
m_journal.warning << m_journal.warning <<

View File

@@ -68,7 +68,7 @@ bool OfferCreateTransactor::isValidOffer (
m_journal.trace << m_journal.trace <<
"isValidOffer: saOfferPays=" << saOfferPays.getFullText (); "isValidOffer: saOfferPays=" << saOfferPays.getFullText ();
saOfferFunds = mEngine->getNodes ().accountFunds (uOfferOwnerID, saOfferPays); saOfferFunds = mEngine->view ().accountFunds (uOfferOwnerID, saOfferPays);
if (!saOfferFunds.isPositive ()) if (!saOfferFunds.isPositive ())
{ {
@@ -353,7 +353,7 @@ TER OfferCreateTransactor::takeOffers (
"takeOffers: bSell: " << bSell << "takeOffers: bSell: " << bSell <<
": against book: " << uBookBase.ToString (); ": against book: " << uBookBase.ToString ();
LedgerEntrySet& lesActive = mEngine->getNodes (); LedgerEntrySet& lesActive = mEngine->view ();
std::uint64_t const uTakeQuality = STAmount::getRate (saTakerGets, saTakerPays); std::uint64_t const uTakeQuality = STAmount::getRate (saTakerGets, saTakerPays);
STAmount saTakerRate = STAmount::setRate (uTakeQuality); STAmount saTakerRate = STAmount::setRate (uTakeQuality);
uint160 const uTakerPaysAccountID = saTakerPays.getIssuer (); uint160 const uTakerPaysAccountID = saTakerPays.getIssuer ();
@@ -776,7 +776,7 @@ TER OfferCreateTransactor::doApply ()
std::uint64_t uOwnerNode; std::uint64_t uOwnerNode;
std::uint64_t uBookNode; std::uint64_t uBookNode;
LedgerEntrySet& lesActive = mEngine->getNodes (); LedgerEntrySet& lesActive = mEngine->view ();
LedgerEntrySet lesCheckpoint = lesActive; // Checkpoint with just fees paid. LedgerEntrySet lesCheckpoint = lesActive; // Checkpoint with just fees paid.
lesActive.bumpSeq (); // Begin ledger variance. lesActive.bumpSeq (); // Begin ledger variance.
@@ -869,7 +869,7 @@ TER OfferCreateTransactor::doApply ()
m_journal.warning << m_journal.warning <<
"uCancelSequence=" << uCancelSequence; "uCancelSequence=" << uCancelSequence;
terResult = mEngine->getNodes ().offerDelete (sleCancel); terResult = mEngine->view ().offerDelete (sleCancel);
} }
else else
{ {

View File

@@ -217,7 +217,7 @@ TER PaymentTransactor::doApply ()
terResult = openLedger && tooManyPaths terResult = openLedger && tooManyPaths
? telBAD_PATH_COUNT // Too many paths for proposed ledger. ? telBAD_PATH_COUNT // Too many paths for proposed ledger.
: RippleCalc::rippleCalc ( : RippleCalc::rippleCalc (
mEngine->getNodes (), mEngine->view (),
saMaxAmountAct, saMaxAmountAct,
saDstAmountAct, saDstAmountAct,
vpsExpanded, vpsExpanded,
@@ -236,7 +236,7 @@ TER PaymentTransactor::doApply ()
terResult = tecPATH_DRY; terResult = tecPATH_DRY;
if ((tesSUCCESS == terResult) && (saDstAmountAct != saDstAmount)) if ((tesSUCCESS == terResult) && (saDstAmountAct != saDstAmount))
mEngine->getNodes().setDeliveredAmount (saDstAmountAct); mEngine->view().setDeliveredAmount (saDstAmountAct);
} }
catch (std::exception const& e) catch (std::exception const& e)
{ {

View File

@@ -101,7 +101,7 @@ TER TrustSetTransactor::doApply ()
m_journal.warning << m_journal.warning <<
"Clearing redundant line."; "Clearing redundant line.";
return mEngine->getNodes ().trustDelete ( return mEngine->view ().trustDelete (
selDelete, mTxnAccountID, uDstAccountID); selDelete, mTxnAccountID, uDstAccountID);
} }
else else
@@ -273,7 +273,7 @@ TER TrustSetTransactor::doApply ()
{ {
// Set reserve for low account. // Set reserve for low account.
mEngine->getNodes ().ownerCountAdjust (uLowAccountID, 1, sleLowAccount); mEngine->view ().ownerCountAdjust (uLowAccountID, 1, sleLowAccount);
uFlagsOut |= lsfLowReserve; uFlagsOut |= lsfLowReserve;
if (!bHigh) if (!bHigh)
@@ -284,7 +284,7 @@ TER TrustSetTransactor::doApply ()
{ {
// Clear reserve for low account. // Clear reserve for low account.
mEngine->getNodes ().ownerCountAdjust (uLowAccountID, -1, sleLowAccount); mEngine->view ().ownerCountAdjust (uLowAccountID, -1, sleLowAccount);
uFlagsOut &= ~lsfLowReserve; uFlagsOut &= ~lsfLowReserve;
} }
@@ -292,7 +292,7 @@ TER TrustSetTransactor::doApply ()
{ {
// Set reserve for high account. // Set reserve for high account.
mEngine->getNodes ().ownerCountAdjust (uHighAccountID, 1, sleHighAccount); mEngine->view ().ownerCountAdjust (uHighAccountID, 1, sleHighAccount);
uFlagsOut |= lsfHighReserve; uFlagsOut |= lsfHighReserve;
if (bHigh) if (bHigh)
@@ -303,7 +303,7 @@ TER TrustSetTransactor::doApply ()
{ {
// Clear reserve for high account. // Clear reserve for high account.
mEngine->getNodes ().ownerCountAdjust (uHighAccountID, -1, sleHighAccount); mEngine->view ().ownerCountAdjust (uHighAccountID, -1, sleHighAccount);
uFlagsOut &= ~lsfHighReserve; uFlagsOut &= ~lsfHighReserve;
} }
@@ -314,7 +314,7 @@ TER TrustSetTransactor::doApply ()
{ {
// Delete. // Delete.
terResult = mEngine->getNodes ().trustDelete (sleRippleState, uLowAccountID, uHighAccountID); terResult = mEngine->view ().trustDelete (sleRippleState, uLowAccountID, uHighAccountID);
} }
else if (bReserveIncrease else if (bReserveIncrease
&& mPriorBalance.getNValue () < uReserveCreate) // Reserve is not scaled by load. && mPriorBalance.getNValue () < uReserveCreate) // Reserve is not scaled by load.
@@ -366,7 +366,7 @@ TER TrustSetTransactor::doApply ()
Ledger::getRippleStateIndex (mTxnAccountID, uDstAccountID, uCurrencyID).ToString (); Ledger::getRippleStateIndex (mTxnAccountID, uDstAccountID, uCurrencyID).ToString ();
// Create a new ripple line. // Create a new ripple line.
terResult = mEngine->getNodes ().trustCreate ( terResult = mEngine->view ().trustCreate (
bHigh, bHigh,
mTxnAccountID, mTxnAccountID,
uDstAccountID, uDstAccountID,

View File

@@ -70,7 +70,7 @@ public:
assert (mLedger); assert (mLedger);
} }
LedgerEntrySet& getNodes () LedgerEntrySet& view ()
{ {
return mNodes; return mNodes;
} }
@@ -84,19 +84,23 @@ public:
mLedger = ledger; mLedger = ledger;
} }
SLE::pointer entryCreate (LedgerEntryType type, uint256 const & index) // VFALCO TODO Remove these pointless wrappers
SLE::pointer entryCreate (LedgerEntryType type, uint256 const & index)
{ {
return mNodes.entryCreate (type, index); return mNodes.entryCreate (type, index);
} }
SLE::pointer entryCache (LedgerEntryType type, uint256 const & index)
SLE::pointer entryCache (LedgerEntryType type, uint256 const & index)
{ {
return mNodes.entryCache (type, index); return mNodes.entryCache (type, index);
} }
void entryDelete (SLE::ref sleEntry)
void entryDelete (SLE::ref sleEntry)
{ {
mNodes.entryDelete (sleEntry); mNodes.entryDelete (sleEntry);
} }
void entryModify (SLE::ref sleEntry)
void entryModify (SLE::ref sleEntry)
{ {
mNodes.entryModify (sleEntry); mNodes.entryModify (sleEntry);
} }

View File

@@ -635,8 +635,10 @@ public:
{ {
if (!isZero ()) mIsNegative = !mIsNegative; if (!isZero ()) mIsNegative = !mIsNegative;
} }
void zero () void zero ()
{ {
// VFALCO: Why -100?
mOffset = mIsNative ? 0 : -100; mOffset = mIsNative ? 0 : -100;
mValue = 0; mValue = 0;
mIsNegative = false; mIsNegative = false;
@@ -733,6 +735,11 @@ public:
return multiply (v1, v2, v1); return multiply (v1, v2, v1);
} }
/* addRound, subRound can end up rounding if the amount subtracted is too small
to make a change. Consder (X-d) where d is very small relative to X.
If you ask to round down, then (X-d) should not be X unless d is zero.
If you ask to round up, (X+d) should never be X unless d is zero. (Assuming X and d are positive).
*/
// Add, subtract, multiply, or divide rounding result in specified direction // Add, subtract, multiply, or divide rounding result in specified direction
static STAmount addRound (const STAmount& v1, const STAmount& v2, bool roundUp); static STAmount addRound (const STAmount& v1, const STAmount& v2, bool roundUp);
static STAmount subRound (const STAmount& v1, const STAmount& v2, bool roundUp); static STAmount subRound (const STAmount& v1, const STAmount& v2, bool roundUp);