Merge branch 'develop' into ximinez/number-maxint-range

This commit is contained in:
Ed Hennis
2026-02-18 20:18:56 -04:00
committed by GitHub
442 changed files with 4368 additions and 3449 deletions

View File

@@ -208,7 +208,8 @@ SharedIntrusive<T>::operator->() const noexcept
}
template <class T>
SharedIntrusive<T>::operator bool() const noexcept
SharedIntrusive<T>::
operator bool() const noexcept
{
return bool(unsafeGetRawPtr());
}
@@ -503,7 +504,8 @@ SharedWeakUnion<T>::getStrong() const
}
template <class T>
SharedWeakUnion<T>::operator bool() const noexcept
SharedWeakUnion<T>::
operator bool() const noexcept
{
return bool(get());
}

View File

@@ -63,7 +63,8 @@ SharedWeakCachePointer<T>::getStrong() const
}
template <class T>
SharedWeakCachePointer<T>::operator bool() const noexcept
SharedWeakCachePointer<T>::
operator bool() const noexcept
{
return !!std::get_if<std::shared_ptr<T>>(&combo_);
}

View File

@@ -215,7 +215,7 @@ public:
// clang-format off
if (!buf) [[unlikely]]
return nullptr;
// clang-format on
// clang-format on
#if BOOST_OS_LINUX
// When allocating large blocks, attempt to leverage Linux's

View File

@@ -0,0 +1,93 @@
#pragma once
#include <xrpl/basics/Buffer.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/conditions/detail/utils.h>
#include <cstdint>
#include <set>
namespace xrpl {
namespace cryptoconditions {
enum class Type : std::uint8_t {
preimageSha256 = 0,
prefixSha256 = 1,
thresholdSha256 = 2,
rsaSha256 = 3,
ed25519Sha256 = 4
};
class Condition
{
public:
/** The largest binary condition we support.
@note This value will be increased in the future, but it
must never decrease, as that could cause conditions
that were previously considered valid to no longer
be allowed.
*/
static constexpr std::size_t maxSerializedCondition = 128;
/** Load a condition from its binary form
@param s The buffer containing the fulfillment to load.
@param ec Set to the error, if any occurred.
The binary format for a condition is specified in the
cryptoconditions RFC. See:
https://tools.ietf.org/html/draft-thomas-crypto-conditions-02#section-7.2
*/
static std::unique_ptr<Condition>
deserialize(Slice s, std::error_code& ec);
public:
Type type;
/** An identifier for this condition.
This fingerprint is meant to be unique only with
respect to other conditions of the same type.
*/
Buffer fingerprint;
/** The cost associated with this condition. */
std::uint32_t cost;
/** For compound conditions, set of conditions includes */
std::set<Type> subtypes;
Condition(Type t, std::uint32_t c, Slice fp) : type(t), fingerprint(fp), cost(c)
{
}
Condition(Type t, std::uint32_t c, Buffer&& fp) : type(t), fingerprint(std::move(fp)), cost(c)
{
}
~Condition() = default;
Condition(Condition const&) = default;
Condition(Condition&&) = default;
Condition() = delete;
};
inline bool
operator==(Condition const& lhs, Condition const& rhs)
{
return lhs.type == rhs.type && lhs.cost == rhs.cost && lhs.subtypes == rhs.subtypes &&
lhs.fingerprint == rhs.fingerprint;
}
inline bool
operator!=(Condition const& lhs, Condition const& rhs)
{
return !(lhs == rhs);
}
} // namespace cryptoconditions
} // namespace xrpl

View File

@@ -0,0 +1,122 @@
#pragma once
#include <xrpl/basics/Buffer.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/conditions/Condition.h>
namespace xrpl {
namespace cryptoconditions {
struct Fulfillment
{
public:
/** The largest binary fulfillment we support.
@note This value will be increased in the future, but it
must never decrease, as that could cause fulfillments
that were previously considered valid to no longer
be allowed.
*/
static constexpr std::size_t maxSerializedFulfillment = 256;
/** Load a fulfillment from its binary form
@param s The buffer containing the fulfillment to load.
@param ec Set to the error, if any occurred.
The binary format for a fulfillment is specified in the
cryptoconditions RFC. See:
https://tools.ietf.org/html/draft-thomas-crypto-conditions-02#section-7.3
*/
static std::unique_ptr<Fulfillment>
deserialize(Slice s, std::error_code& ec);
public:
virtual ~Fulfillment() = default;
/** Returns the fulfillment's fingerprint:
The fingerprint is an octet string uniquely
representing this fulfillment's condition
with respect to other conditions of the
same type.
*/
virtual Buffer
fingerprint() const = 0;
/** Returns the type of this condition. */
virtual Type
type() const = 0;
/** Validates a fulfillment. */
virtual bool
validate(Slice data) const = 0;
/** Calculates the cost associated with this fulfillment. *
The cost function is deterministic and depends on the
type and properties of the condition and the fulfillment
that the condition is generated from.
*/
virtual std::uint32_t
cost() const = 0;
/** Returns the condition associated with the given fulfillment.
This process is completely deterministic. All implementations
will, if compliant, produce the identical condition for the
same fulfillment.
*/
virtual Condition
condition() const = 0;
};
inline bool
operator==(Fulfillment const& lhs, Fulfillment const& rhs)
{
// FIXME: for compound conditions, need to also check subtypes
return lhs.type() == rhs.type() && lhs.cost() == rhs.cost() && lhs.fingerprint() == rhs.fingerprint();
}
inline bool
operator!=(Fulfillment const& lhs, Fulfillment const& rhs)
{
return !(lhs == rhs);
}
/** Determine whether the given fulfillment and condition match */
bool
match(Fulfillment const& f, Condition const& c);
/** Verify if the given message satisfies the fulfillment.
@param f The fulfillment
@param c The condition
@param m The message
@note the message is not relevant for some conditions
and a fulfillment will successfully satisfy its
condition for any given message.
*/
bool
validate(Fulfillment const& f, Condition const& c, Slice m);
/** Verify a cryptoconditional trigger.
A cryptoconditional trigger is a cryptocondition with
an empty message.
When using such triggers, it is recommended that the
trigger be of type preimage, prefix or threshold. If
a signature type is used (i.e. Ed25519 or RSA-SHA256)
then the Ed25519 or RSA keys should be single-use keys.
@param f The fulfillment
@param c The condition
*/
bool
validate(Fulfillment const& f, Condition const& c);
} // namespace cryptoconditions
} // namespace xrpl

View File

@@ -0,0 +1,131 @@
#pragma once
#include <xrpl/basics/Buffer.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/conditions/Condition.h>
#include <xrpl/conditions/Fulfillment.h>
#include <xrpl/conditions/detail/error.h>
#include <xrpl/protocol/digest.h>
#include <memory>
namespace xrpl {
namespace cryptoconditions {
class PreimageSha256 final : public Fulfillment
{
public:
/** The maximum allowed length of a preimage.
The specification does not specify a minimum supported
length, nor does it require all conditions to support
the same minimum length.
While future versions of this code will never lower
this limit, they may opt to raise it.
*/
static constexpr std::size_t maxPreimageLength = 128;
/** Parse the payload for a PreimageSha256 condition
@param s A slice containing the DER encoded payload
@param ec indicates success or failure of the operation
@return the preimage, if successful; empty pointer otherwise.
*/
static std::unique_ptr<Fulfillment>
deserialize(Slice s, std::error_code& ec)
{
// Per the RFC, a preimage fulfillment is defined as
// follows:
//
// PreimageFulfillment ::= SEQUENCE {
// preimage OCTET STRING
// }
using namespace der;
auto p = parsePreamble(s, ec);
if (ec)
return nullptr;
if (!isPrimitive(p) || !isContextSpecific(p))
{
ec = error::incorrect_encoding;
return {};
}
if (p.tag != 0)
{
ec = error::unexpected_tag;
return {};
}
if (s.size() != p.length)
{
ec = error::trailing_garbage;
return {};
}
if (s.size() > maxPreimageLength)
{
ec = error::preimage_too_long;
return {};
}
auto b = parseOctetString(s, p.length, ec);
if (ec)
return {};
return std::make_unique<PreimageSha256>(std::move(b));
}
private:
Buffer payload_;
public:
PreimageSha256(Buffer&& b) noexcept : payload_(std::move(b))
{
}
PreimageSha256(Slice s) noexcept : payload_(s)
{
}
Type
type() const override
{
return Type::preimageSha256;
}
Buffer
fingerprint() const override
{
sha256_hasher h;
h(payload_.data(), payload_.size());
auto const d = static_cast<sha256_hasher::result_type>(h);
return {d.data(), d.size()};
}
std::uint32_t
cost() const override
{
return static_cast<std::uint32_t>(payload_.size());
}
Condition
condition() const override
{
return {type(), cost(), fingerprint()};
}
bool
validate(Slice) const override
{
// Perhaps counterintuitively, the message isn't
// relevant.
return true;
}
};
} // namespace cryptoconditions
} // namespace xrpl

View File

@@ -0,0 +1,44 @@
#pragma once
#include <system_error>
namespace xrpl {
namespace cryptoconditions {
enum class error {
generic = 1,
unsupported_type,
unsupported_subtype,
unknown_type,
unknown_subtype,
fingerprint_size,
incorrect_encoding,
trailing_garbage,
buffer_empty,
buffer_overfull,
buffer_underfull,
malformed_encoding,
short_preamble,
unexpected_tag,
long_tag,
large_size,
preimage_too_long
};
std::error_code
make_error_code(error ev);
} // namespace cryptoconditions
} // namespace xrpl
namespace std {
template <>
struct is_error_code_enum<xrpl::cryptoconditions::error>
{
explicit is_error_code_enum() = default;
static bool const value = true;
};
} // namespace std

View File

@@ -0,0 +1,209 @@
#pragma once
#include <xrpl/basics/Buffer.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/conditions/detail/error.h>
#include <boost/dynamic_bitset.hpp>
#include <limits>
namespace xrpl {
namespace cryptoconditions {
// A collection of functions to decode binary blobs
// encoded with X.690 Distinguished Encoding Rules.
//
// This is a very trivial decoder and only implements
// the bare minimum needed to support PreimageSha256.
namespace der {
// The preamble encapsulates the DER identifier and
// length octets:
struct Preamble
{
explicit Preamble() = default;
std::uint8_t type = 0;
std::size_t tag = 0;
std::size_t length = 0;
};
inline bool
isPrimitive(Preamble const& p)
{
return (p.type & 0x20) == 0;
}
inline bool
isConstructed(Preamble const& p)
{
return !isPrimitive(p);
}
inline bool
isUniversal(Preamble const& p)
{
return (p.type & 0xC0) == 0;
}
inline bool
isApplication(Preamble const& p)
{
return (p.type & 0xC0) == 0x40;
}
inline bool
isContextSpecific(Preamble const& p)
{
return (p.type & 0xC0) == 0x80;
}
inline bool
isPrivate(Preamble const& p)
{
return (p.type & 0xC0) == 0xC0;
}
inline Preamble
parsePreamble(Slice& s, std::error_code& ec)
{
Preamble p;
if (s.size() < 2)
{
ec = error::short_preamble;
return p;
}
p.type = s[0] & 0xE0;
p.tag = s[0] & 0x1F;
s += 1;
if (p.tag == 0x1F)
{ // Long tag form, which we do not support:
ec = error::long_tag;
return p;
}
p.length = s[0];
s += 1;
if (p.length & 0x80)
{ // Long form length:
std::size_t const cnt = p.length & 0x7F;
if (cnt == 0)
{
ec = error::malformed_encoding;
return p;
}
if (cnt > sizeof(std::size_t))
{
ec = error::large_size;
return p;
}
if (cnt > s.size())
{
ec = error::short_preamble;
return p;
}
p.length = 0;
for (std::size_t i = 0; i != cnt; ++i)
p.length = (p.length << 8) + s[i];
s += cnt;
if (p.length == 0)
{
ec = error::malformed_encoding;
return p;
}
}
return p;
}
inline Buffer
parseOctetString(Slice& s, std::uint32_t count, std::error_code& ec)
{
if (count > s.size())
{
ec = error::buffer_underfull;
return {};
}
if (count > 65535)
{
ec = error::large_size;
return {};
}
Buffer b(s.data(), count);
s += count;
return b;
}
template <class Integer>
Integer
parseInteger(Slice& s, std::size_t count, std::error_code& ec)
{
Integer v{0};
if (s.empty())
{
// can never have zero sized integers
ec = error::malformed_encoding;
return v;
}
if (count > s.size())
{
ec = error::buffer_underfull;
return v;
}
bool const isSigned = std::numeric_limits<Integer>::is_signed;
// unsigned types may have a leading zero octet
size_t const maxLength = isSigned ? sizeof(Integer) : sizeof(Integer) + 1;
if (count > maxLength)
{
ec = error::large_size;
return v;
}
if (!isSigned && (s[0] & (1 << 7)))
{
// trying to decode a negative number into a positive value
ec = error::malformed_encoding;
return v;
}
if (!isSigned && count == sizeof(Integer) + 1 && s[0])
{
// since integers are coded as two's complement, the first byte may
// be zero for unsigned reps
ec = error::malformed_encoding;
return v;
}
v = 0;
for (size_t i = 0; i < count; ++i)
v = (v << 8) | (s[i] & 0xff);
if (isSigned && (s[0] & (1 << 7)))
{
for (int i = count; i < sizeof(Integer); ++i)
v |= (Integer(0xff) << (8 * i));
}
s += count;
return v;
}
} // namespace der
} // namespace cryptoconditions
} // namespace xrpl

View File

@@ -0,0 +1,254 @@
#pragma once
#include <xrpl/basics/CountedObject.h>
#include <xrpl/basics/UnorderedContainers.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/beast/container/aged_unordered_map.h>
#include <optional>
#include <set>
namespace xrpl {
enum class HashRouterFlags : std::uint16_t {
// Public flags
UNDEFINED = 0x00,
BAD = 0x02, // Temporarily bad
SAVED = 0x04,
HELD = 0x08, // Held by LedgerMaster after potential processing failure
TRUSTED = 0x10, // Comes from a trusted source
// Private flags (used internally in apply.cpp)
// Do not attempt to read, set, or reuse.
PRIVATE1 = 0x0100,
PRIVATE2 = 0x0200,
PRIVATE3 = 0x0400,
PRIVATE4 = 0x0800,
PRIVATE5 = 0x1000,
PRIVATE6 = 0x2000
};
constexpr HashRouterFlags
operator|(HashRouterFlags lhs, HashRouterFlags rhs)
{
return static_cast<HashRouterFlags>(
static_cast<std::underlying_type_t<HashRouterFlags>>(lhs) |
static_cast<std::underlying_type_t<HashRouterFlags>>(rhs));
}
constexpr HashRouterFlags&
operator|=(HashRouterFlags& lhs, HashRouterFlags rhs)
{
lhs = lhs | rhs;
return lhs;
}
constexpr HashRouterFlags
operator&(HashRouterFlags lhs, HashRouterFlags rhs)
{
return static_cast<HashRouterFlags>(
static_cast<std::underlying_type_t<HashRouterFlags>>(lhs) &
static_cast<std::underlying_type_t<HashRouterFlags>>(rhs));
}
constexpr HashRouterFlags&
operator&=(HashRouterFlags& lhs, HashRouterFlags rhs)
{
lhs = lhs & rhs;
return lhs;
}
constexpr bool
any(HashRouterFlags flags)
{
return static_cast<std::underlying_type_t<HashRouterFlags>>(flags) != 0;
}
class Config;
/** Routing table for objects identified by hash.
This table keeps track of which hashes have been received by which peers.
It is used to manage the routing and broadcasting of messages in the peer
to peer overlay.
*/
class HashRouter
{
public:
// The type here *MUST* match the type of Peer::id_t
using PeerShortID = std::uint32_t;
/** Structure used to customize @ref HashRouter behavior.
*
* Even though these items are configurable, they are undocumented. Don't
* change them unless there is a good reason, and network-wide coordination
* to do it.
*
* Configuration is processed in setup_HashRouter.
*/
struct Setup
{
/// Default constructor
explicit Setup() = default;
using seconds = std::chrono::seconds;
/** Expiration time for a hash entry
*/
seconds holdTime{300};
/** Amount of time required before a relayed item will be relayed again.
*/
seconds relayTime{30};
};
private:
/** An entry in the routing table.
*/
class Entry : public CountedObject<Entry>
{
public:
Entry()
{
}
void
addPeer(PeerShortID peer)
{
if (peer != 0)
peers_.insert(peer);
}
HashRouterFlags
getFlags(void) const
{
return flags_;
}
void
setFlags(HashRouterFlags flagsToSet)
{
flags_ |= flagsToSet;
}
/** Return set of peers we've relayed to and reset tracking */
std::set<PeerShortID>
releasePeerSet()
{
return std::move(peers_);
}
/** Return seated relay time point if the message has been relayed */
std::optional<Stopwatch::time_point>
relayed() const
{
return relayed_;
}
/** Determines if this item should be relayed.
Checks whether the item has been recently relayed.
If it has, return false. If it has not, update the
last relay timestamp and return true.
*/
bool
shouldRelay(Stopwatch::time_point const& now, std::chrono::seconds relayTime)
{
if (relayed_ && *relayed_ + relayTime > now)
return false;
relayed_.emplace(now);
return true;
}
bool
shouldProcess(Stopwatch::time_point now, std::chrono::seconds interval)
{
if (processed_ && ((*processed_ + interval) > now))
return false;
processed_.emplace(now);
return true;
}
private:
HashRouterFlags flags_ = HashRouterFlags::UNDEFINED;
std::set<PeerShortID> peers_;
// This could be generalized to a map, if more
// than one flag needs to expire independently.
std::optional<Stopwatch::time_point> relayed_;
std::optional<Stopwatch::time_point> processed_;
};
public:
HashRouter(Setup const& setup, Stopwatch& clock) : setup_(setup), suppressionMap_(clock)
{
}
HashRouter&
operator=(HashRouter const&) = delete;
virtual ~HashRouter() = default;
// VFALCO TODO Replace "Suppression" terminology with something more
// semantically meaningful.
void
addSuppression(uint256 const& key);
bool
addSuppressionPeer(uint256 const& key, PeerShortID peer);
/** Add a suppression peer and get message's relay status.
* Return pair:
* element 1: true if the peer is added.
* element 2: optional is seated to the relay time point or
* is unseated if has not relayed yet. */
std::pair<bool, std::optional<Stopwatch::time_point>>
addSuppressionPeerWithStatus(uint256 const& key, PeerShortID peer);
bool
addSuppressionPeer(uint256 const& key, PeerShortID peer, HashRouterFlags& flags);
// Add a peer suppression and return whether the entry should be processed
bool
shouldProcess(uint256 const& key, PeerShortID peer, HashRouterFlags& flags, std::chrono::seconds tx_interval);
/** Set the flags on a hash.
@return `true` if the flags were changed. `false` if unchanged.
*/
bool
setFlags(uint256 const& key, HashRouterFlags flags);
HashRouterFlags
getFlags(uint256 const& key);
/** Determines whether the hashed item should be relayed.
Effects:
If the item should be relayed, this function will not
return a seated optional again until the relay time has expired.
The internal set of peers will also be reset.
@return A `std::optional` set of peers which do not need to be
relayed to. If the result is unseated, the item should
_not_ be relayed.
*/
std::optional<std::set<PeerShortID>>
shouldRelay(uint256 const& key);
private:
// pair.second indicates whether the entry was created
std::pair<Entry&, bool>
emplace(uint256 const&);
std::mutex mutable mutex_;
// Configurable parameters
Setup const setup_;
// Stores all suppressed hashes and their expiration time
beast::aged_unordered_map<uint256, Entry, Stopwatch::clock_type, hardened_hash<strong_hash>> suppressionMap_;
};
} // namespace xrpl

View File

@@ -0,0 +1,33 @@
#pragma once
#include <cstdint>
namespace xrpl {
/** Service that provides access to the network ID.
This service provides read-only access to the network ID configured
for this server. The network ID identifies which network (mainnet,
testnet, devnet, or custom network) this server is configured to
connect to.
Well-known network IDs:
- 0: Mainnet
- 1: Testnet
- 2: Devnet
- 1025+: Custom networks (require NetworkID field in transactions)
*/
class NetworkIDService
{
public:
virtual ~NetworkIDService() = default;
/** Get the configured network ID
*
* @return The network ID this server is configured for
*/
virtual std::uint32_t
getNetworkID() const noexcept = 0;
};
} // namespace xrpl

View File

@@ -0,0 +1,98 @@
#pragma once
#include <xrpl/beast/hash/hash_append.h>
#include <xrpl/beast/hash/uhash.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/protocol/PublicKey.h>
#include <mutex>
#include <optional>
#include <string>
#include <unordered_set>
#include <vector>
namespace xrpl {
class DatabaseCon;
// Value type for reservations.
struct PeerReservation final
{
public:
PublicKey nodeId;
std::string description{};
auto
toJson() const -> Json::Value;
template <typename Hasher>
friend void
hash_append(Hasher& h, PeerReservation const& x) noexcept
{
using beast::hash_append;
hash_append(h, x.nodeId);
}
friend bool
operator<(PeerReservation const& a, PeerReservation const& b)
{
return a.nodeId < b.nodeId;
}
};
// TODO: When C++20 arrives, take advantage of "equivalence" instead of
// "equality". Add an overload for `(PublicKey, PeerReservation)`, and just
// pass a `PublicKey` directly to `unordered_set.find`.
struct KeyEqual final
{
bool
operator()(PeerReservation const& lhs, PeerReservation const& rhs) const
{
return lhs.nodeId == rhs.nodeId;
}
};
class PeerReservationTable final
{
public:
explicit PeerReservationTable(beast::Journal journal = beast::Journal(beast::Journal::getNullSink()))
: journal_(journal)
{
}
std::vector<PeerReservation>
list() const;
bool
contains(PublicKey const& nodeId)
{
std::lock_guard lock(this->mutex_);
return table_.find({nodeId}) != table_.end();
}
// Because `ApplicationImp` has two-phase initialization, so must we.
// Our dependencies are not prepared until the second phase.
bool
load(DatabaseCon& connection);
/**
* @return the replaced reservation if it existed
* @throw soci::soci_error
*/
std::optional<PeerReservation>
insert_or_assign(PeerReservation const& reservation);
/**
* @return the erased reservation if it existed
*/
std::optional<PeerReservation>
erase(PublicKey const& nodeId);
private:
beast::Journal mutable journal_;
std::mutex mutable mutex_;
DatabaseCon* connection_;
std::unordered_set<PeerReservation, beast::uhash<>, KeyEqual> table_;
};
} // namespace xrpl

View File

@@ -5,6 +5,8 @@
#include <xrpl/basics/TaggedCache.h>
#include <xrpl/ledger/CachedSLEs.h>
#include <boost/asio.hpp>
namespace xrpl {
// Forward declarations
@@ -18,6 +20,10 @@ namespace perf {
class PerfLog;
}
// This is temporary until we migrate all code to use ServiceRegistry.
class Application;
// Forward declarations
class AcceptedLedger;
class AmendmentTable;
class Cluster;
@@ -35,6 +41,7 @@ class LoadFeeTrack;
class LoadManager;
class ManifestCache;
class NetworkOPs;
class NetworkIDService;
class OpenLedger;
class OrderBookDB;
class Overlay;
@@ -93,6 +100,9 @@ public:
virtual CachedSLEs&
cachedSLEs() = 0;
virtual NetworkIDService&
getNetworkIDService() = 0;
// Protocol and validation services
virtual AmendmentTable&
getAmendmentTable() = 0;
@@ -194,6 +204,31 @@ public:
virtual perf::PerfLog&
getPerfLog() = 0;
// Configuration and state
virtual bool
isStopping() const = 0;
virtual beast::Journal
journal(std::string const& name) = 0;
virtual boost::asio::io_context&
getIOContext() = 0;
virtual Logs&
logs() = 0;
virtual std::optional<uint256> const&
trapTxID() const = 0;
/** Retrieve the "wallet database" */
virtual DatabaseCon&
getWalletDB() = 0;
// Temporary: Get the underlying Application for functions that haven't
// been migrated yet. This should be removed once all code is migrated.
virtual Application&
app() = 0;
};
} // namespace xrpl

View File

@@ -0,0 +1,16 @@
#pragma once
#include <iosfwd>
#include <type_traits>
namespace xrpl {
enum class StartUpType { FRESH, NORMAL, LOAD, LOAD_FILE, REPLAY, NETWORK };
inline std::ostream&
operator<<(std::ostream& os, StartUpType const& type)
{
return os << static_cast<std::underlying_type_t<StartUpType>>(type);
}
} // namespace xrpl

View File

@@ -0,0 +1,87 @@
#pragma once
#include <xrpl/basics/CountedObject.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TxMeta.h>
#include <boost/container/flat_set.hpp>
namespace xrpl {
/**
A transaction that is in a closed ledger.
Description
An accepted ledger transaction contains additional information that the
server needs to tell clients about the transaction. For example,
- The transaction in JSON form
- Which accounts are affected
* This is used by InfoSub to report to clients
- Cached stuff
*/
class AcceptedLedgerTx : public CountedObject<AcceptedLedgerTx>
{
public:
AcceptedLedgerTx(
std::shared_ptr<ReadView const> const& ledger,
std::shared_ptr<STTx const> const&,
std::shared_ptr<STObject const> const&);
std::shared_ptr<STTx const> const&
getTxn() const
{
return mTxn;
}
TxMeta const&
getMeta() const
{
return mMeta;
}
boost::container::flat_set<AccountID> const&
getAffected() const
{
return mAffected;
}
TxID
getTransactionID() const
{
return mTxn->getTransactionID();
}
TxType
getTxnType() const
{
return mTxn->getTxnType();
}
TER
getResult() const
{
return mMeta.getResultTER();
}
std::uint32_t
getTxnSeq() const
{
return mMeta.getIndex();
}
std::string
getEscMeta() const;
Json::Value const&
getJson() const
{
return mJson;
}
private:
std::shared_ptr<STTx const> mTxn;
TxMeta mMeta;
boost::container::flat_set<AccountID> mAffected;
Blob mRawMeta;
Json::Value mJson;
};
} // namespace xrpl

View File

@@ -0,0 +1,175 @@
#pragma once
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/STValidation.h>
#include <xrpl/shamap/SHAMap.h>
#include <optional>
namespace xrpl {
class ServiceRegistry;
/** The amendment table stores the list of enabled and potential amendments.
Individuals amendments are voted on by validators during the consensus
process.
*/
class AmendmentTable
{
public:
struct FeatureInfo
{
FeatureInfo() = delete;
FeatureInfo(std::string const& n, uint256 const& f, VoteBehavior v) : name(n), feature(f), vote(v)
{
}
std::string const name;
uint256 const feature;
VoteBehavior const vote;
};
virtual ~AmendmentTable() = default;
virtual uint256
find(std::string const& name) const = 0;
virtual bool
veto(uint256 const& amendment) = 0;
virtual bool
unVeto(uint256 const& amendment) = 0;
virtual bool
enable(uint256 const& amendment) = 0;
virtual bool
isEnabled(uint256 const& amendment) const = 0;
virtual bool
isSupported(uint256 const& amendment) const = 0;
/**
* @brief returns true if one or more amendments on the network
* have been enabled that this server does not support
*
* @return true if an unsupported feature is enabled on the network
*/
virtual bool
hasUnsupportedEnabled() const = 0;
virtual std::optional<NetClock::time_point>
firstUnsupportedExpected() const = 0;
virtual Json::Value
getJson(bool isAdmin) const = 0;
/** Returns a Json::objectValue. */
virtual Json::Value
getJson(uint256 const& amendment, bool isAdmin) const = 0;
/** Called when a new fully-validated ledger is accepted. */
void
doValidatedLedger(std::shared_ptr<ReadView const> const& lastValidatedLedger)
{
if (needValidatedLedger(lastValidatedLedger->seq()))
doValidatedLedger(
lastValidatedLedger->seq(),
getEnabledAmendments(*lastValidatedLedger),
getMajorityAmendments(*lastValidatedLedger));
}
/** Called to determine whether the amendment logic needs to process
a new validated ledger. (If it could have changed things.)
*/
virtual bool
needValidatedLedger(LedgerIndex seq) const = 0;
virtual void
doValidatedLedger(
LedgerIndex ledgerSeq,
std::set<uint256> const& enabled,
majorityAmendments_t const& majority) = 0;
// Called when the set of trusted validators changes.
virtual void
trustChanged(hash_set<PublicKey> const& allTrusted) = 0;
// Called by the consensus code when we need to
// inject pseudo-transactions
virtual std::map<uint256, std::uint32_t>
doVoting(
Rules const& rules,
NetClock::time_point closeTime,
std::set<uint256> const& enabledAmendments,
majorityAmendments_t const& majorityAmendments,
std::vector<std::shared_ptr<STValidation>> const& valSet) = 0;
// Called by the consensus code when we need to
// add feature entries to a validation
virtual std::vector<uint256>
doValidation(std::set<uint256> const& enabled) const = 0;
// The set of amendments to enable in the genesis ledger
// This will return all known, non-vetoed amendments.
// If we ever have two amendments that should not both be
// enabled at the same time, we should ensure one is vetoed.
virtual std::vector<uint256>
getDesired() const = 0;
// The function below adapts the API callers expect to the
// internal amendment table API. This allows the amendment
// table implementation to be independent of the ledger
// implementation. These APIs will merge when the view code
// supports a full ledger API
void
doVoting(
std::shared_ptr<ReadView const> const& lastClosedLedger,
std::vector<std::shared_ptr<STValidation>> const& parentValidations,
std::shared_ptr<SHAMap> const& initialPosition,
beast::Journal j)
{
// Ask implementation what to do
auto actions = doVoting(
lastClosedLedger->rules(),
lastClosedLedger->parentCloseTime(),
getEnabledAmendments(*lastClosedLedger),
getMajorityAmendments(*lastClosedLedger),
parentValidations);
// Inject appropriate pseudo-transactions
for (auto const& it : actions)
{
STTx amendTx(ttAMENDMENT, [&it, seq = lastClosedLedger->seq() + 1](auto& obj) {
obj.setAccountID(sfAccount, AccountID());
obj.setFieldH256(sfAmendment, it.first);
obj.setFieldU32(sfLedgerSequence, seq);
if (it.second != 0)
obj.setFieldU32(sfFlags, it.second);
});
Serializer s;
amendTx.add(s);
JLOG(j.debug()) << "Amendments: Adding pseudo-transaction: " << amendTx.getTransactionID() << ": "
<< strHex(s.slice()) << ": " << amendTx;
initialPosition->addGiveItem(
SHAMapNodeType::tnTRANSACTION_NM, make_shamapitem(amendTx.getTransactionID(), s.slice()));
}
}
};
std::unique_ptr<AmendmentTable>
make_AmendmentTable(
ServiceRegistry& registry,
std::chrono::seconds majorityTime,
std::vector<AmendmentTable::FeatureInfo> const& supported,
Section const& enabled,
Section const& vetoed,
beast::Journal journal);
} // namespace xrpl

View File

@@ -0,0 +1,51 @@
#pragma once
#include <xrpl/protocol/MultiApiJson.h>
#include <xrpl/server/InfoSub.h>
#include <memory>
#include <mutex>
namespace xrpl {
/** Listen to public/subscribe messages from a book. */
class BookListeners
{
public:
using pointer = std::shared_ptr<BookListeners>;
BookListeners()
{
}
/** Add a new subscription for this book
*/
void
addSubscriber(InfoSub::ref sub);
/** Stop publishing to a subscriber
*/
void
removeSubscriber(std::uint64_t sub);
/** Publish a transaction to subscribers
Publish a transaction to clients subscribed to changes on this book.
Uses havePublished to prevent sending duplicate transactions to clients
that have subscribed to multiple books.
@param jvObj JSON transaction data to publish
@param havePublished InfoSub sequence numbers that have already
published this transaction.
*/
void
publish(MultiApiJson const& jvObj, hash_set<std::uint64_t>& havePublished);
private:
std::recursive_mutex mLock;
hash_map<std::uint64_t, InfoSub::wptr> mListeners;
};
} // namespace xrpl

View File

@@ -0,0 +1,93 @@
#pragma once
#include <xrpl/ledger/AcceptedLedgerTx.h>
#include <xrpl/ledger/BookListeners.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/Book.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/MultiApiJson.h>
#include <xrpl/protocol/UintTypes.h>
#include <memory>
#include <optional>
#include <vector>
namespace xrpl {
/** Tracks order books in the ledger.
This interface provides access to order book information, including:
- Which order books exist in the ledger
- Querying order books by issue
- Managing order book subscriptions
The order book database is updated as ledgers are accepted and provides
efficient lookup of order book information for pathfinding and client
subscriptions.
*/
class OrderBookDB
{
public:
virtual ~OrderBookDB() = default;
/** Initialize or update the order book database with a new ledger.
This method should be called when a new ledger is accepted to update
the order book database with the current state of all order books.
@param ledger The ledger to scan for order books
*/
virtual void
setup(std::shared_ptr<ReadView const> const& ledger) = 0;
/** Add an order book to track.
@param book The order book to add
*/
virtual void
addOrderBook(Book const& book) = 0;
/** Get all order books that want a specific issue.
Returns a list of all order books where the taker pays the specified
issue. This is useful for pathfinding to find all possible next hops
from a given currency.
@param issue The issue to search for
@param domain Optional domain restriction for the order book
@return Vector of books that want this issue
*/
virtual std::vector<Book>
getBooksByTakerPays(Issue const& issue, std::optional<Domain> const& domain = std::nullopt) = 0;
/** Get the count of order books that want a specific issue.
@param issue The issue to search for
@param domain Optional domain restriction for the order book
@return Number of books that want this issue
*/
virtual int
getBookSize(Issue const& issue, std::optional<Domain> const& domain = std::nullopt) = 0;
/** Check if an order book to XRP exists for the given issue.
@param issue The issue to check
@param domain Optional domain restriction for the order book
@return true if a book from this issue to XRP exists
*/
virtual bool
isBookToXRP(Issue const& issue, std::optional<Domain> domain = std::nullopt) = 0;
virtual void
processTxn(
std::shared_ptr<ReadView const> const& ledger,
AcceptedLedgerTx const& alTx,
MultiApiJson const& jvObj) = 0;
virtual BookListeners::pointer
getBookListeners(Book const&) = 0;
virtual BookListeners::pointer
makeBookListeners(Book const&) = 0;
};
} // namespace xrpl

View File

@@ -133,10 +133,6 @@ public:
std::uint32_t ledgerSeq,
std::function<void(std::shared_ptr<NodeObject> const&)>&& callback);
/** Remove expired entries from the positive and negative caches. */
virtual void
sweep() = 0;
/** Gather statistics pertaining to read and write activities.
*
* @param obj Json object reference into which to place counters.

View File

@@ -23,32 +23,6 @@ public:
beast::Journal j)
: Database(scheduler, readThreads, config, j), backend_(std::move(backend))
{
std::optional<int> cacheSize, cacheAge;
if (config.exists("cache_size"))
{
cacheSize = get<int>(config, "cache_size");
if (cacheSize.value() < 0)
{
Throw<std::runtime_error>("Specified negative value for cache_size");
}
}
if (config.exists("cache_age"))
{
cacheAge = get<int>(config, "cache_age");
if (cacheAge.value() < 0)
{
Throw<std::runtime_error>("Specified negative value for cache_age");
}
}
if (cacheSize != 0 || cacheAge != 0)
{
cache_ = std::make_shared<TaggedCache<uint256, NodeObject>>(
"DatabaseNodeImp", cacheSize.value_or(0), std::chrono::minutes(cacheAge.value_or(0)), stopwatch(), j);
}
XRPL_ASSERT(
backend_,
"xrpl::NodeStore::DatabaseNodeImp::DatabaseNodeImp : non-null "
@@ -103,13 +77,7 @@ public:
std::uint32_t ledgerSeq,
std::function<void(std::shared_ptr<NodeObject> const&)>&& callback) override;
void
sweep() override;
private:
// Cache for database objects. This cache is not always initialized. Check
// for null before using.
std::shared_ptr<TaggedCache<uint256, NodeObject>> cache_;
// Persistent key/value storage
std::shared_ptr<Backend> backend_;

View File

@@ -55,9 +55,6 @@ public:
void
sync() override;
void
sweep() override;
private:
std::shared_ptr<Backend> writableBackend_;
std::shared_ptr<Backend> archiveBackend_;

View File

@@ -86,9 +86,9 @@ message TMPublicKey {
// you must first combine coins from one address to another.
enum TransactionStatus {
tsNEW = 1; // origin node did/could not validate
tsCURRENT = 2; // scheduled to go in this ledger
tsCOMMITED = 3; // in a closed ledger
tsNEW = 1; // origin node did/could not validate
tsCURRENT = 2; // scheduled to go in this ledger
tsCOMMITTED = 3; // in a closed ledger
tsREJECT_CONFLICT = 4;
tsREJECT_INVALID = 5;
tsREJECT_FUNDS = 6;

View File

@@ -296,7 +296,7 @@ public:
friend FeatureBitset
operator^(FeatureBitset const& lhs, uint256 const& rhs)
{
return lhs ^ FeatureBitset { rhs };
return lhs ^ FeatureBitset{rhs};
}
friend FeatureBitset

View File

@@ -110,7 +110,8 @@ IOUAmount::operator=(beast::Zero)
return *this;
}
inline IOUAmount::operator Number() const
inline IOUAmount::
operator Number() const
{
return Number{mantissa_, exponent_};
}
@@ -140,7 +141,8 @@ IOUAmount::operator<(IOUAmount const& other) const
return Number{*this} < Number{other};
}
inline IOUAmount::operator bool() const noexcept
inline IOUAmount::
operator bool() const noexcept
{
return mantissa_ != 0;
}

View File

@@ -0,0 +1,22 @@
#pragma once
namespace xrpl {
/**
* @brief Enumeration of ledger shortcuts for specifying which ledger to use.
*
* These shortcuts provide a convenient way to reference commonly used ledgers
* without needing to specify their exact hash or sequence number.
*/
enum class LedgerShortcut {
/** The current working ledger (open, not yet closed) */
Current,
/** The most recently closed ledger (may not be validated) */
Closed,
/** The most recently validated ledger */
Validated
};
} // namespace xrpl

View File

@@ -93,7 +93,8 @@ MPTAmount::operator=(beast::Zero)
}
/** Returns true if the amount is not zero */
constexpr MPTAmount::operator bool() const noexcept
constexpr MPTAmount::
operator bool() const noexcept
{
return value_ != 0;
}

View File

@@ -107,7 +107,7 @@ struct MultiApiJson
// unsigned int version, extra arguments
template <typename Json, typename Version, typename... Args, typename Fn>
requires(!some_integral_constant<Version>) && std::convertible_to<Version, unsigned> &&
std::same_as<std::remove_cvref_t<Json>, MultiApiJson>
std::same_as<std::remove_cvref_t<Json>, MultiApiJson>
auto
operator()(Json& json, Version version, Fn fn, Args&&... args) const
-> std::invoke_result_t<Fn, decltype(json.val[0]), Version, Args&&...>
@@ -122,7 +122,7 @@ struct MultiApiJson
// unsigned int version, Json only
template <typename Json, typename Version, typename Fn>
requires(!some_integral_constant<Version>) && std::convertible_to<Version, unsigned> &&
std::same_as<std::remove_cvref_t<Json>, MultiApiJson>
std::same_as<std::remove_cvref_t<Json>, MultiApiJson>
auto
operator()(Json& json, Version version, Fn fn) const -> std::invoke_result_t<Fn, decltype(json.val[0])>
{

View File

@@ -253,6 +253,16 @@ std::uint8_t constexpr maxAssetCheckDepth = 5;
/** A ledger index. */
using LedgerIndex = std::uint32_t;
std::uint32_t constexpr FLAG_LEDGER_INTERVAL = 256;
/** Returns true if the given ledgerIndex is a voting ledgerIndex */
bool
isVotingLedger(LedgerIndex seq);
/** Returns true if the given ledgerIndex is a flag ledgerIndex */
bool
isFlagLedger(LedgerIndex seq);
/** A transaction identifier.
The value is computed as the hash of the
canonicalized, serialized transaction object.

View File

@@ -480,12 +480,14 @@ STAmount::zeroed() const
return STAmount(mAsset);
}
inline STAmount::operator bool() const noexcept
inline STAmount::
operator bool() const noexcept
{
return *this != beast::zero;
}
inline STAmount::operator Number() const
inline STAmount::
operator Number() const
{
if (native())
return xrp();

View File

@@ -169,7 +169,8 @@ STBitString<Bits>::value() const
}
template <int Bits>
STBitString<Bits>::operator value_type() const
STBitString<Bits>::
operator value_type() const
{
return value_;
}

View File

@@ -134,7 +134,8 @@ STInteger<Integer>::setValue(Integer v)
}
template <typename Integer>
inline STInteger<Integer>::operator Integer() const
inline STInteger<Integer>::
operator Integer() const
{
return value_;
}

View File

@@ -147,9 +147,12 @@ public:
int
getCount() const;
bool setFlag(std::uint32_t);
bool clearFlag(std::uint32_t);
bool isFlag(std::uint32_t) const;
bool
setFlag(std::uint32_t);
bool
clearFlag(std::uint32_t);
bool
isFlag(std::uint32_t) const;
std::uint32_t
getFlags() const;
@@ -799,7 +802,8 @@ STObject::ValueProxy<T>::operator-=(U const& u)
}
template <class T>
STObject::ValueProxy<T>::operator value_type() const
STObject::ValueProxy<T>::
operator value_type() const
{
return this->value();
}
@@ -812,13 +816,15 @@ STObject::ValueProxy<T>::ValueProxy(STObject* st, TypedField<T> const* f) : Prox
//------------------------------------------------------------------------------
template <class T>
STObject::OptionalProxy<T>::operator bool() const noexcept
STObject::OptionalProxy<T>::
operator bool() const noexcept
{
return engaged();
}
template <class T>
STObject::OptionalProxy<T>::operator typename STObject::OptionalProxy<T>::optional_type() const
STObject::OptionalProxy<T>::
operator typename STObject::OptionalProxy<T>::optional_type() const
{
return optional_value();
}

View File

@@ -135,7 +135,8 @@ STVector256::setValue(STVector256 const& v)
}
/** Retrieve a copy of the vector we contain */
inline STVector256::operator std::vector<uint256>() const
inline STVector256::
operator std::vector<uint256>() const
{
return mValue;
}

View File

@@ -484,60 +484,54 @@ public:
// Only enabled if both arguments return int if TERtiInt is called with them.
template <typename L, typename R>
constexpr auto
operator==(L const& lhs, R const& rhs)
-> std::enable_if_t<
std::is_same<decltype(TERtoInt(lhs)), int>::value && std::is_same<decltype(TERtoInt(rhs)), int>::value,
bool>
operator==(L const& lhs, R const& rhs) -> std::enable_if_t<
std::is_same<decltype(TERtoInt(lhs)), int>::value && std::is_same<decltype(TERtoInt(rhs)), int>::value,
bool>
{
return TERtoInt(lhs) == TERtoInt(rhs);
}
template <typename L, typename R>
constexpr auto
operator!=(L const& lhs, R const& rhs)
-> std::enable_if_t<
std::is_same<decltype(TERtoInt(lhs)), int>::value && std::is_same<decltype(TERtoInt(rhs)), int>::value,
bool>
operator!=(L const& lhs, R const& rhs) -> std::enable_if_t<
std::is_same<decltype(TERtoInt(lhs)), int>::value && std::is_same<decltype(TERtoInt(rhs)), int>::value,
bool>
{
return TERtoInt(lhs) != TERtoInt(rhs);
}
template <typename L, typename R>
constexpr auto
operator<(L const& lhs, R const& rhs)
-> std::enable_if_t<
std::is_same<decltype(TERtoInt(lhs)), int>::value && std::is_same<decltype(TERtoInt(rhs)), int>::value,
bool>
operator<(L const& lhs, R const& rhs) -> std::enable_if_t<
std::is_same<decltype(TERtoInt(lhs)), int>::value && std::is_same<decltype(TERtoInt(rhs)), int>::value,
bool>
{
return TERtoInt(lhs) < TERtoInt(rhs);
}
template <typename L, typename R>
constexpr auto
operator<=(L const& lhs, R const& rhs)
-> std::enable_if_t<
std::is_same<decltype(TERtoInt(lhs)), int>::value && std::is_same<decltype(TERtoInt(rhs)), int>::value,
bool>
operator<=(L const& lhs, R const& rhs) -> std::enable_if_t<
std::is_same<decltype(TERtoInt(lhs)), int>::value && std::is_same<decltype(TERtoInt(rhs)), int>::value,
bool>
{
return TERtoInt(lhs) <= TERtoInt(rhs);
}
template <typename L, typename R>
constexpr auto
operator>(L const& lhs, R const& rhs)
-> std::enable_if_t<
std::is_same<decltype(TERtoInt(lhs)), int>::value && std::is_same<decltype(TERtoInt(rhs)), int>::value,
bool>
operator>(L const& lhs, R const& rhs) -> std::enable_if_t<
std::is_same<decltype(TERtoInt(lhs)), int>::value && std::is_same<decltype(TERtoInt(rhs)), int>::value,
bool>
{
return TERtoInt(lhs) > TERtoInt(rhs);
}
template <typename L, typename R>
constexpr auto
operator>=(L const& lhs, R const& rhs)
-> std::enable_if_t<
std::is_same<decltype(TERtoInt(lhs)), int>::value && std::is_same<decltype(TERtoInt(rhs)), int>::value,
bool>
operator>=(L const& lhs, R const& rhs) -> std::enable_if_t<
std::is_same<decltype(TERtoInt(lhs)), int>::value && std::is_same<decltype(TERtoInt(rhs)), int>::value,
bool>
{
return TERtoInt(lhs) >= TERtoInt(rhs);
}

View File

@@ -0,0 +1,7 @@
#pragma once
namespace xrpl {
enum class TxSearched { all, some, unknown };
}

View File

@@ -15,7 +15,7 @@
// Add new amendments to the top of this list.
// Keep it sorted in reverse chronological order.
XRPL_FIX (PermissionedDomainInvariant, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (ExpiredNFTokenOfferRemoval, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (BatchInnerSigs, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(LendingProtocol, Supported::yes, VoteBehavior::DefaultNo)

View File

@@ -8,11 +8,11 @@
* To ease maintenance, you may replace any unneeded values with "..."
* e.g. #define TRANSACTION(tag, value, name, ...)
*
* You must define a transactor class in the `ripple` namespace named `name`,
* You must define a transactor class in the `xrpl` namespace named `name`,
* and include its header alongside the TRANSACTOR definition using this
* format:
* #if TRANSACTION_INCLUDE
* # include <xrpld/app/tx/detail/HEADER.h>
* # include <xrpl/tx/transactors/HEADER.h>
* #endif
*
* The `privileges` parameter of the TRANSACTION macro is a bitfield
@@ -22,7 +22,7 @@
/** This transaction type executes a payment. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/Payment.h>
# include <xrpl/tx/transactors/Payment.h>
#endif
TRANSACTION(ttPAYMENT, 0, Payment,
Delegation::delegable,
@@ -42,7 +42,7 @@ TRANSACTION(ttPAYMENT, 0, Payment,
/** This transaction type creates an escrow object. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/Escrow.h>
# include <xrpl/tx/transactors/Escrow.h>
#endif
TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate,
Delegation::delegable,
@@ -73,7 +73,7 @@ TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish,
/** This transaction type adjusts various account settings. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/SetAccount.h>
# include <xrpl/tx/transactors/SetAccount.h>
#endif
TRANSACTION(ttACCOUNT_SET, 3, AccountSet,
Delegation::notDelegable,
@@ -94,7 +94,7 @@ TRANSACTION(ttACCOUNT_SET, 3, AccountSet,
/** This transaction type cancels an existing escrow. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/Escrow.h>
# include <xrpl/tx/transactors/Escrow.h>
#endif
TRANSACTION(ttESCROW_CANCEL, 4, EscrowCancel,
Delegation::delegable,
@@ -107,7 +107,7 @@ TRANSACTION(ttESCROW_CANCEL, 4, EscrowCancel,
/** This transaction type sets or clears an account's "regular key". */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/SetRegularKey.h>
# include <xrpl/tx/transactors/SetRegularKey.h>
#endif
TRANSACTION(ttREGULAR_KEY_SET, 5, SetRegularKey,
Delegation::notDelegable,
@@ -121,7 +121,7 @@ TRANSACTION(ttREGULAR_KEY_SET, 5, SetRegularKey,
/** This transaction type creates an offer to trade one asset for another. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/CreateOffer.h>
# include <xrpl/tx/transactors/Offer/CreateOffer.h>
#endif
TRANSACTION(ttOFFER_CREATE, 7, OfferCreate,
Delegation::delegable,
@@ -137,7 +137,7 @@ TRANSACTION(ttOFFER_CREATE, 7, OfferCreate,
/** This transaction type cancels existing offers to trade one asset for another. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/CancelOffer.h>
# include <xrpl/tx/transactors/Offer/CancelOffer.h>
#endif
TRANSACTION(ttOFFER_CANCEL, 8, OfferCancel,
Delegation::delegable,
@@ -151,7 +151,7 @@ TRANSACTION(ttOFFER_CANCEL, 8, OfferCancel,
/** This transaction type creates a new set of tickets. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/CreateTicket.h>
# include <xrpl/tx/transactors/CreateTicket.h>
#endif
TRANSACTION(ttTICKET_CREATE, 10, TicketCreate,
Delegation::delegable,
@@ -167,7 +167,7 @@ TRANSACTION(ttTICKET_CREATE, 10, TicketCreate,
// The SignerEntries are optional because a SignerList is deleted by
// setting the SignerQuorum to zero and omitting SignerEntries.
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/SetSignerList.h>
# include <xrpl/tx/transactors/SetSignerList.h>
#endif
TRANSACTION(ttSIGNER_LIST_SET, 12, SignerListSet,
Delegation::notDelegable,
@@ -180,7 +180,7 @@ TRANSACTION(ttSIGNER_LIST_SET, 12, SignerListSet,
/** This transaction type creates a new unidirectional XRP payment channel. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/PayChan.h>
# include <xrpl/tx/transactors/PayChan.h>
#endif
TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate,
Delegation::delegable,
@@ -222,7 +222,7 @@ TRANSACTION(ttPAYCHAN_CLAIM, 15, PaymentChannelClaim,
/** This transaction type creates a new check. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/CreateCheck.h>
# include <xrpl/tx/transactors/Check/CreateCheck.h>
#endif
TRANSACTION(ttCHECK_CREATE, 16, CheckCreate,
Delegation::delegable,
@@ -238,7 +238,7 @@ TRANSACTION(ttCHECK_CREATE, 16, CheckCreate,
/** This transaction type cashes an existing check. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/CashCheck.h>
# include <xrpl/tx/transactors/Check/CashCheck.h>
#endif
TRANSACTION(ttCHECK_CASH, 17, CheckCash,
Delegation::delegable,
@@ -252,7 +252,7 @@ TRANSACTION(ttCHECK_CASH, 17, CheckCash,
/** This transaction type cancels an existing check. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/CancelCheck.h>
# include <xrpl/tx/transactors/Check/CancelCheck.h>
#endif
TRANSACTION(ttCHECK_CANCEL, 18, CheckCancel,
Delegation::delegable,
@@ -264,7 +264,7 @@ TRANSACTION(ttCHECK_CANCEL, 18, CheckCancel,
/** This transaction type grants or revokes authorization to transfer funds. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/DepositPreauth.h>
# include <xrpl/tx/transactors/DepositPreauth.h>
#endif
TRANSACTION(ttDEPOSIT_PREAUTH, 19, DepositPreauth,
Delegation::delegable,
@@ -279,7 +279,7 @@ TRANSACTION(ttDEPOSIT_PREAUTH, 19, DepositPreauth,
/** This transaction type modifies a trustline between two accounts. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/SetTrust.h>
# include <xrpl/tx/transactors/SetTrust.h>
#endif
TRANSACTION(ttTRUST_SET, 20, TrustSet,
Delegation::delegable,
@@ -293,7 +293,7 @@ TRANSACTION(ttTRUST_SET, 20, TrustSet,
/** This transaction type deletes an existing account. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/DeleteAccount.h>
# include <xrpl/tx/transactors/DeleteAccount.h>
#endif
TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete,
Delegation::notDelegable,
@@ -309,7 +309,7 @@ TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete,
/** This transaction mints a new NFT. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/NFTokenMint.h>
# include <xrpl/tx/transactors/NFT/NFTokenMint.h>
#endif
TRANSACTION(ttNFTOKEN_MINT, 25, NFTokenMint,
Delegation::delegable,
@@ -327,7 +327,7 @@ TRANSACTION(ttNFTOKEN_MINT, 25, NFTokenMint,
/** This transaction burns (i.e. destroys) an existing NFT. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/NFTokenBurn.h>
# include <xrpl/tx/transactors/NFT/NFTokenBurn.h>
#endif
TRANSACTION(ttNFTOKEN_BURN, 26, NFTokenBurn,
Delegation::delegable,
@@ -340,7 +340,7 @@ TRANSACTION(ttNFTOKEN_BURN, 26, NFTokenBurn,
/** This transaction creates a new offer to buy or sell an NFT. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/NFTokenCreateOffer.h>
# include <xrpl/tx/transactors/NFT/NFTokenCreateOffer.h>
#endif
TRANSACTION(ttNFTOKEN_CREATE_OFFER, 27, NFTokenCreateOffer,
Delegation::delegable,
@@ -356,7 +356,7 @@ TRANSACTION(ttNFTOKEN_CREATE_OFFER, 27, NFTokenCreateOffer,
/** This transaction cancels an existing offer to buy or sell an existing NFT. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/NFTokenCancelOffer.h>
# include <xrpl/tx/transactors/NFT/NFTokenCancelOffer.h>
#endif
TRANSACTION(ttNFTOKEN_CANCEL_OFFER, 28, NFTokenCancelOffer,
Delegation::delegable,
@@ -368,7 +368,7 @@ TRANSACTION(ttNFTOKEN_CANCEL_OFFER, 28, NFTokenCancelOffer,
/** This transaction accepts an existing offer to buy or sell an existing NFT. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/NFTokenAcceptOffer.h>
# include <xrpl/tx/transactors/NFT/NFTokenAcceptOffer.h>
#endif
TRANSACTION(ttNFTOKEN_ACCEPT_OFFER, 29, NFTokenAcceptOffer,
Delegation::delegable,
@@ -382,7 +382,7 @@ TRANSACTION(ttNFTOKEN_ACCEPT_OFFER, 29, NFTokenAcceptOffer,
/** This transaction claws back issued tokens. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/Clawback.h>
# include <xrpl/tx/transactors/Clawback.h>
#endif
TRANSACTION(ttCLAWBACK, 30, Clawback,
Delegation::delegable,
@@ -395,7 +395,7 @@ TRANSACTION(ttCLAWBACK, 30, Clawback,
/** This transaction claws back tokens from an AMM pool. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/AMMClawback.h>
# include <xrpl/tx/transactors/AMM/AMMClawback.h>
#endif
TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback,
Delegation::delegable,
@@ -410,7 +410,7 @@ TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback,
/** This transaction type creates an AMM instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/AMMCreate.h>
# include <xrpl/tx/transactors/AMM/AMMCreate.h>
#endif
TRANSACTION(ttAMM_CREATE, 35, AMMCreate,
Delegation::delegable,
@@ -424,7 +424,7 @@ TRANSACTION(ttAMM_CREATE, 35, AMMCreate,
/** This transaction type deposits into an AMM instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/AMMDeposit.h>
# include <xrpl/tx/transactors/AMM/AMMDeposit.h>
#endif
TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit,
Delegation::delegable,
@@ -442,7 +442,7 @@ TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit,
/** This transaction type withdraws from an AMM instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/AMMWithdraw.h>
# include <xrpl/tx/transactors/AMM/AMMWithdraw.h>
#endif
TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw,
Delegation::delegable,
@@ -459,7 +459,7 @@ TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw,
/** This transaction type votes for the trading fee */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/AMMVote.h>
# include <xrpl/tx/transactors/AMM/AMMVote.h>
#endif
TRANSACTION(ttAMM_VOTE, 38, AMMVote,
Delegation::delegable,
@@ -473,7 +473,7 @@ TRANSACTION(ttAMM_VOTE, 38, AMMVote,
/** This transaction type bids for the auction slot */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/AMMBid.h>
# include <xrpl/tx/transactors/AMM/AMMBid.h>
#endif
TRANSACTION(ttAMM_BID, 39, AMMBid,
Delegation::delegable,
@@ -489,7 +489,7 @@ TRANSACTION(ttAMM_BID, 39, AMMBid,
/** This transaction type deletes AMM in the empty state */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/AMMDelete.h>
# include <xrpl/tx/transactors/AMM/AMMDelete.h>
#endif
TRANSACTION(ttAMM_DELETE, 40, AMMDelete,
Delegation::delegable,
@@ -502,7 +502,7 @@ TRANSACTION(ttAMM_DELETE, 40, AMMDelete,
/** This transactions creates a crosschain sequence number */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/XChainBridge.h>
# include <xrpl/tx/transactors/XChainBridge.h>
#endif
TRANSACTION(ttXCHAIN_CREATE_CLAIM_ID, 41, XChainCreateClaimID,
Delegation::delegable,
@@ -617,7 +617,7 @@ TRANSACTION(ttXCHAIN_CREATE_BRIDGE, 48, XChainCreateBridge,
/** This transaction type creates or updates a DID */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/DID.h>
# include <xrpl/tx/transactors/DID.h>
#endif
TRANSACTION(ttDID_SET, 49, DIDSet,
Delegation::delegable,
@@ -638,7 +638,7 @@ TRANSACTION(ttDID_DELETE, 50, DIDDelete,
/** This transaction type creates an Oracle instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/SetOracle.h>
# include <xrpl/tx/transactors/SetOracle.h>
#endif
TRANSACTION(ttORACLE_SET, 51, OracleSet,
Delegation::delegable,
@@ -655,7 +655,7 @@ TRANSACTION(ttORACLE_SET, 51, OracleSet,
/** This transaction type deletes an Oracle instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/DeleteOracle.h>
# include <xrpl/tx/transactors/DeleteOracle.h>
#endif
TRANSACTION(ttORACLE_DELETE, 52, OracleDelete,
Delegation::delegable,
@@ -667,7 +667,7 @@ TRANSACTION(ttORACLE_DELETE, 52, OracleDelete,
/** This transaction type fixes a problem in the ledger state */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/LedgerStateFix.h>
# include <xrpl/tx/transactors/LedgerStateFix.h>
#endif
TRANSACTION(ttLEDGER_STATE_FIX, 53, LedgerStateFix,
Delegation::delegable,
@@ -680,7 +680,7 @@ TRANSACTION(ttLEDGER_STATE_FIX, 53, LedgerStateFix,
/** This transaction type creates a MPTokensIssuance instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/MPTokenIssuanceCreate.h>
# include <xrpl/tx/transactors/MPT/MPTokenIssuanceCreate.h>
#endif
TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 54, MPTokenIssuanceCreate,
Delegation::delegable,
@@ -697,7 +697,7 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 54, MPTokenIssuanceCreate,
/** This transaction type destroys a MPTokensIssuance instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/MPTokenIssuanceDestroy.h>
# include <xrpl/tx/transactors/MPT/MPTokenIssuanceDestroy.h>
#endif
TRANSACTION(ttMPTOKEN_ISSUANCE_DESTROY, 55, MPTokenIssuanceDestroy,
Delegation::delegable,
@@ -709,7 +709,7 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_DESTROY, 55, MPTokenIssuanceDestroy,
/** This transaction type sets flags on a MPTokensIssuance or MPToken instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/MPTokenIssuanceSet.h>
# include <xrpl/tx/transactors/MPT/MPTokenIssuanceSet.h>
#endif
TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 56, MPTokenIssuanceSet,
Delegation::delegable,
@@ -726,7 +726,7 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 56, MPTokenIssuanceSet,
/** This transaction type authorizes a MPToken instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/MPTokenAuthorize.h>
# include <xrpl/tx/transactors/MPT/MPTokenAuthorize.h>
#endif
TRANSACTION(ttMPTOKEN_AUTHORIZE, 57, MPTokenAuthorize,
Delegation::delegable,
@@ -739,7 +739,7 @@ TRANSACTION(ttMPTOKEN_AUTHORIZE, 57, MPTokenAuthorize,
/** This transaction type create an Credential instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/Credentials.h>
# include <xrpl/tx/transactors/Credentials.h>
#endif
TRANSACTION(ttCREDENTIAL_CREATE, 58, CredentialCreate,
Delegation::delegable,
@@ -775,7 +775,7 @@ TRANSACTION(ttCREDENTIAL_DELETE, 60, CredentialDelete,
/** This transaction type modify a NFToken */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/NFTokenModify.h>
# include <xrpl/tx/transactors/NFT/NFTokenModify.h>
#endif
TRANSACTION(ttNFTOKEN_MODIFY, 61, NFTokenModify,
Delegation::delegable,
@@ -789,7 +789,7 @@ TRANSACTION(ttNFTOKEN_MODIFY, 61, NFTokenModify,
/** This transaction type creates or modifies a Permissioned Domain */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/PermissionedDomainSet.h>
# include <xrpl/tx/transactors/PermissionedDomain/PermissionedDomainSet.h>
#endif
TRANSACTION(ttPERMISSIONED_DOMAIN_SET, 62, PermissionedDomainSet,
Delegation::delegable,
@@ -802,7 +802,7 @@ TRANSACTION(ttPERMISSIONED_DOMAIN_SET, 62, PermissionedDomainSet,
/** This transaction type deletes a Permissioned Domain */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/PermissionedDomainDelete.h>
# include <xrpl/tx/transactors/PermissionedDomain/PermissionedDomainDelete.h>
#endif
TRANSACTION(ttPERMISSIONED_DOMAIN_DELETE, 63, PermissionedDomainDelete,
Delegation::delegable,
@@ -814,7 +814,7 @@ TRANSACTION(ttPERMISSIONED_DOMAIN_DELETE, 63, PermissionedDomainDelete,
/** This transaction type delegates authorized account specified permissions */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/DelegateSet.h>
# include <xrpl/tx/transactors/Delegate/DelegateSet.h>
#endif
TRANSACTION(ttDELEGATE_SET, 64, DelegateSet,
Delegation::notDelegable,
@@ -827,7 +827,7 @@ TRANSACTION(ttDELEGATE_SET, 64, DelegateSet,
/** This transaction creates a single asset vault. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/VaultCreate.h>
# include <xrpl/tx/transactors/Vault/VaultCreate.h>
#endif
TRANSACTION(ttVAULT_CREATE, 65, VaultCreate,
Delegation::delegable,
@@ -845,7 +845,7 @@ TRANSACTION(ttVAULT_CREATE, 65, VaultCreate,
/** This transaction updates a single asset vault. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/VaultSet.h>
# include <xrpl/tx/transactors/Vault/VaultSet.h>
#endif
TRANSACTION(ttVAULT_SET, 66, VaultSet,
Delegation::delegable,
@@ -860,7 +860,7 @@ TRANSACTION(ttVAULT_SET, 66, VaultSet,
/** This transaction deletes a single asset vault. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/VaultDelete.h>
# include <xrpl/tx/transactors/Vault/VaultDelete.h>
#endif
TRANSACTION(ttVAULT_DELETE, 67, VaultDelete,
Delegation::delegable,
@@ -872,7 +872,7 @@ TRANSACTION(ttVAULT_DELETE, 67, VaultDelete,
/** This transaction trades assets for shares with a vault. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/VaultDeposit.h>
# include <xrpl/tx/transactors/Vault/VaultDeposit.h>
#endif
TRANSACTION(ttVAULT_DEPOSIT, 68, VaultDeposit,
Delegation::delegable,
@@ -885,7 +885,7 @@ TRANSACTION(ttVAULT_DEPOSIT, 68, VaultDeposit,
/** This transaction trades shares for assets with a vault. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/VaultWithdraw.h>
# include <xrpl/tx/transactors/Vault/VaultWithdraw.h>
#endif
TRANSACTION(ttVAULT_WITHDRAW, 69, VaultWithdraw,
Delegation::delegable,
@@ -900,7 +900,7 @@ TRANSACTION(ttVAULT_WITHDRAW, 69, VaultWithdraw,
/** This transaction claws back tokens from a vault. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/VaultClawback.h>
# include <xrpl/tx/transactors/Vault/VaultClawback.h>
#endif
TRANSACTION(ttVAULT_CLAWBACK, 70, VaultClawback,
Delegation::delegable,
@@ -914,7 +914,7 @@ TRANSACTION(ttVAULT_CLAWBACK, 70, VaultClawback,
/** This transaction type batches together transactions. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/Batch.h>
# include <xrpl/tx/transactors/Batch.h>
#endif
TRANSACTION(ttBATCH, 71, Batch,
Delegation::notDelegable,
@@ -929,7 +929,7 @@ TRANSACTION(ttBATCH, 71, Batch,
/** This transaction creates and updates a Loan Broker */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/LoanBrokerSet.h>
# include <xrpl/tx/transactors/Lending/LoanBrokerSet.h>
#endif
TRANSACTION(ttLOAN_BROKER_SET, 74, LoanBrokerSet,
Delegation::delegable,
@@ -946,7 +946,7 @@ TRANSACTION(ttLOAN_BROKER_SET, 74, LoanBrokerSet,
/** This transaction deletes a Loan Broker */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/LoanBrokerDelete.h>
# include <xrpl/tx/transactors/Lending/LoanBrokerDelete.h>
#endif
TRANSACTION(ttLOAN_BROKER_DELETE, 75, LoanBrokerDelete,
Delegation::delegable,
@@ -957,7 +957,7 @@ TRANSACTION(ttLOAN_BROKER_DELETE, 75, LoanBrokerDelete,
/** This transaction deposits First Loss Capital into a Loan Broker */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/LoanBrokerCoverDeposit.h>
# include <xrpl/tx/transactors/Lending/LoanBrokerCoverDeposit.h>
#endif
TRANSACTION(ttLOAN_BROKER_COVER_DEPOSIT, 76, LoanBrokerCoverDeposit,
Delegation::delegable,
@@ -969,7 +969,7 @@ TRANSACTION(ttLOAN_BROKER_COVER_DEPOSIT, 76, LoanBrokerCoverDeposit,
/** This transaction withdraws First Loss Capital from a Loan Broker */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/LoanBrokerCoverWithdraw.h>
# include <xrpl/tx/transactors/Lending/LoanBrokerCoverWithdraw.h>
#endif
TRANSACTION(ttLOAN_BROKER_COVER_WITHDRAW, 77, LoanBrokerCoverWithdraw,
Delegation::delegable,
@@ -984,7 +984,7 @@ TRANSACTION(ttLOAN_BROKER_COVER_WITHDRAW, 77, LoanBrokerCoverWithdraw,
/** This transaction claws back First Loss Capital from a Loan Broker to
the issuer of the capital */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/LoanBrokerCoverClawback.h>
# include <xrpl/tx/transactors/Lending/LoanBrokerCoverClawback.h>
#endif
TRANSACTION(ttLOAN_BROKER_COVER_CLAWBACK, 78, LoanBrokerCoverClawback,
Delegation::delegable,
@@ -996,7 +996,7 @@ TRANSACTION(ttLOAN_BROKER_COVER_CLAWBACK, 78, LoanBrokerCoverClawback,
/** This transaction creates a Loan */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/LoanSet.h>
# include <xrpl/tx/transactors/Lending/LoanSet.h>
#endif
TRANSACTION(ttLOAN_SET, 80, LoanSet,
Delegation::delegable,
@@ -1023,7 +1023,7 @@ TRANSACTION(ttLOAN_SET, 80, LoanSet,
/** This transaction deletes an existing Loan */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/LoanDelete.h>
# include <xrpl/tx/transactors/Lending/LoanDelete.h>
#endif
TRANSACTION(ttLOAN_DELETE, 81, LoanDelete,
Delegation::delegable,
@@ -1034,7 +1034,7 @@ TRANSACTION(ttLOAN_DELETE, 81, LoanDelete,
/** This transaction is used to change the delinquency status of an existing Loan */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/LoanManage.h>
# include <xrpl/tx/transactors/Lending/LoanManage.h>
#endif
TRANSACTION(ttLOAN_MANAGE, 82, LoanManage,
Delegation::delegable,
@@ -1048,7 +1048,7 @@ TRANSACTION(ttLOAN_MANAGE, 82, LoanManage,
/** The Borrower uses this transaction to make a Payment on the Loan. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/LoanPay.h>
# include <xrpl/tx/transactors/Lending/LoanPay.h>
#endif
TRANSACTION(ttLOAN_PAY, 84, LoanPay,
Delegation::delegable,
@@ -1063,7 +1063,7 @@ TRANSACTION(ttLOAN_PAY, 84, LoanPay,
For details, see: https://xrpl.org/amendments.html
*/
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/Change.h>
# include <xrpl/tx/transactors/Change.h>
#endif
TRANSACTION(ttAMENDMENT, 100, EnableAmendment,
Delegation::notDelegable,

118
include/xrpl/rdb/DBInit.h Normal file
View File

@@ -0,0 +1,118 @@
#pragma once
#include <array>
#include <cstdint>
namespace xrpl {
////////////////////////////////////////////////////////////////////////////////
// These pragmas are built at startup and applied to all database
// connections, unless otherwise noted.
inline constexpr char const* CommonDBPragmaJournal{"PRAGMA journal_mode=%s;"};
inline constexpr char const* CommonDBPragmaSync{"PRAGMA synchronous=%s;"};
inline constexpr char const* CommonDBPragmaTemp{"PRAGMA temp_store=%s;"};
// A warning will be logged if any lower-safety sqlite tuning settings
// are used and at least this much ledger history is configured. This
// includes full history nodes. This is because such a large amount of
// data will be more difficult to recover if a rare failure occurs,
// which are more likely with some of the other available tuning settings.
inline constexpr std::uint32_t SQLITE_TUNING_CUTOFF = 10'000'000;
// Ledger database holds ledgers and ledger confirmations
inline constexpr auto LgrDBName{"ledger.db"};
inline constexpr std::array<char const*, 5> LgrDBInit{
{"BEGIN TRANSACTION;",
"CREATE TABLE IF NOT EXISTS Ledgers ( \
LedgerHash CHARACTER(64) PRIMARY KEY, \
LedgerSeq BIGINT UNSIGNED, \
PrevHash CHARACTER(64), \
TotalCoins BIGINT UNSIGNED, \
ClosingTime BIGINT UNSIGNED, \
PrevClosingTime BIGINT UNSIGNED, \
CloseTimeRes BIGINT UNSIGNED, \
CloseFlags BIGINT UNSIGNED, \
AccountSetHash CHARACTER(64), \
TransSetHash CHARACTER(64) \
);",
"CREATE INDEX IF NOT EXISTS SeqLedger ON Ledgers(LedgerSeq);",
// Old table and indexes no longer needed
"DROP TABLE IF EXISTS Validations;",
"END TRANSACTION;"}};
////////////////////////////////////////////////////////////////////////////////
// Transaction database holds transactions and public keys
inline constexpr auto TxDBName{"transaction.db"};
inline constexpr std::array<char const*, 8> TxDBInit{
{"BEGIN TRANSACTION;",
"CREATE TABLE IF NOT EXISTS Transactions ( \
TransID CHARACTER(64) PRIMARY KEY, \
TransType CHARACTER(24), \
FromAcct CHARACTER(35), \
FromSeq BIGINT UNSIGNED, \
LedgerSeq BIGINT UNSIGNED, \
Status CHARACTER(1), \
RawTxn BLOB, \
TxnMeta BLOB \
);",
"CREATE INDEX IF NOT EXISTS TxLgrIndex ON \
Transactions(LedgerSeq);",
"CREATE TABLE IF NOT EXISTS AccountTransactions ( \
TransID CHARACTER(64), \
Account CHARACTER(64), \
LedgerSeq BIGINT UNSIGNED, \
TxnSeq INTEGER \
);",
"CREATE INDEX IF NOT EXISTS AcctTxIDIndex ON \
AccountTransactions(TransID);",
"CREATE INDEX IF NOT EXISTS AcctTxIndex ON \
AccountTransactions(Account, LedgerSeq, TxnSeq, TransID);",
"CREATE INDEX IF NOT EXISTS AcctLgrIndex ON \
AccountTransactions(LedgerSeq, Account, TransID);",
"END TRANSACTION;"}};
////////////////////////////////////////////////////////////////////////////////
inline constexpr auto WalletDBName{"wallet.db"};
inline constexpr std::array<char const*, 6> WalletDBInit{
{"BEGIN TRANSACTION;",
// A node's identity must be persisted, including
// for clustering purposes. This table holds one
// entry: the server's unique identity, but the
// value can be overriden by specifying a node
// identity in the config file using a [node_seed]
// entry.
"CREATE TABLE IF NOT EXISTS NodeIdentity ( \
PublicKey CHARACTER(53), \
PrivateKey CHARACTER(52) \
);",
// Peer reservations
"CREATE TABLE IF NOT EXISTS PeerReservations ( \
PublicKey CHARACTER(53) UNIQUE NOT NULL, \
Description CHARACTER(64) NOT NULL \
);",
// Validator Manifests
"CREATE TABLE IF NOT EXISTS ValidatorManifests ( \
RawData BLOB NOT NULL \
);",
"CREATE TABLE IF NOT EXISTS PublisherManifests ( \
RawData BLOB NOT NULL \
);",
"END TRANSACTION;"}};
} // namespace xrpl

View File

@@ -0,0 +1,231 @@
#pragma once
#include <xrpl/core/PerfLog.h>
#include <xrpl/core/StartUpType.h>
#include <xrpl/rdb/DBInit.h>
#include <xrpl/rdb/SociDB.h>
#include <boost/filesystem/path.hpp>
#include <mutex>
#include <optional>
#include <string>
namespace soci {
class session;
}
namespace xrpl {
class LockedSociSession
{
public:
using mutex = std::recursive_mutex;
private:
std::shared_ptr<soci::session> session_;
std::unique_lock<mutex> lock_;
public:
LockedSociSession(std::shared_ptr<soci::session> it, mutex& m) : session_(std::move(it)), lock_(m)
{
}
LockedSociSession(LockedSociSession&& rhs) noexcept : session_(std::move(rhs.session_)), lock_(std::move(rhs.lock_))
{
}
LockedSociSession() = delete;
LockedSociSession(LockedSociSession const& rhs) = delete;
LockedSociSession&
operator=(LockedSociSession const& rhs) = delete;
soci::session*
get()
{
return session_.get();
}
soci::session&
operator*()
{
return *session_;
}
soci::session*
operator->()
{
return session_.get();
}
explicit
operator bool() const
{
return bool(session_);
}
};
class DatabaseCon
{
public:
struct Setup
{
explicit Setup() = default;
StartUpType startUp = StartUpType::NORMAL;
bool standAlone = false;
boost::filesystem::path dataDir;
// Indicates whether or not to return the `globalPragma`
// from commonPragma()
bool useGlobalPragma = false;
std::vector<std::string> const*
commonPragma() const
{
XRPL_ASSERT(
!useGlobalPragma || globalPragma,
"xrpl::DatabaseCon::Setup::commonPragma : consistent global "
"pragma");
return useGlobalPragma && globalPragma ? globalPragma.get() : nullptr;
}
static std::unique_ptr<std::vector<std::string> const> globalPragma;
std::array<std::string, 4> txPragma;
std::array<std::string, 1> lgrPragma;
};
struct CheckpointerSetup
{
JobQueue* jobQueue;
Logs* logs;
};
template <std::size_t N, std::size_t M>
DatabaseCon(
Setup const& setup,
std::string const& dbName,
std::array<std::string, N> const& pragma,
std::array<char const*, M> const& initSQL,
beast::Journal journal)
// Use temporary files or regular DB files?
: DatabaseCon(
setup.standAlone && setup.startUp != StartUpType::LOAD && setup.startUp != StartUpType::LOAD_FILE &&
setup.startUp != StartUpType::REPLAY
? ""
: (setup.dataDir / dbName),
setup.commonPragma(),
pragma,
initSQL,
journal)
{
}
// Use this constructor to setup checkpointing
template <std::size_t N, std::size_t M>
DatabaseCon(
Setup const& setup,
std::string const& dbName,
std::array<std::string, N> const& pragma,
std::array<char const*, M> const& initSQL,
CheckpointerSetup const& checkpointerSetup,
beast::Journal journal)
: DatabaseCon(setup, dbName, pragma, initSQL, journal)
{
setupCheckpointing(checkpointerSetup.jobQueue, *checkpointerSetup.logs);
}
template <std::size_t N, std::size_t M>
DatabaseCon(
boost::filesystem::path const& dataDir,
std::string const& dbName,
std::array<std::string, N> const& pragma,
std::array<char const*, M> const& initSQL,
beast::Journal journal)
: DatabaseCon(dataDir / dbName, nullptr, pragma, initSQL, journal)
{
}
// Use this constructor to setup checkpointing
template <std::size_t N, std::size_t M>
DatabaseCon(
boost::filesystem::path const& dataDir,
std::string const& dbName,
std::array<std::string, N> const& pragma,
std::array<char const*, M> const& initSQL,
CheckpointerSetup const& checkpointerSetup,
beast::Journal journal)
: DatabaseCon(dataDir, dbName, pragma, initSQL, journal)
{
setupCheckpointing(checkpointerSetup.jobQueue, *checkpointerSetup.logs);
}
~DatabaseCon();
soci::session&
getSession()
{
return *session_;
}
LockedSociSession
checkoutDb()
{
using namespace std::chrono_literals;
LockedSociSession session =
perf::measureDurationAndLog([&]() { return LockedSociSession(session_, lock_); }, "checkoutDb", 10ms, j_);
return session;
}
private:
void
setupCheckpointing(JobQueue*, Logs&);
template <std::size_t N, std::size_t M>
DatabaseCon(
boost::filesystem::path const& pPath,
std::vector<std::string> const* commonPragma,
std::array<std::string, N> const& pragma,
std::array<char const*, M> const& initSQL,
beast::Journal journal)
: session_(std::make_shared<soci::session>()), j_(journal)
{
open(*session_, "sqlite", pPath.string());
for (auto const& p : pragma)
{
soci::statement st = session_->prepare << p;
st.execute(true);
}
if (commonPragma)
{
for (auto const& p : *commonPragma)
{
soci::statement st = session_->prepare << p;
st.execute(true);
}
}
for (auto const& sql : initSQL)
{
soci::statement st = session_->prepare << sql;
st.execute(true);
}
}
LockedSociSession::mutex lock_;
// checkpointer may outlive the DatabaseCon when the checkpointer jobQueue
// callback locks a weak pointer and the DatabaseCon is then destroyed. In
// this case, the checkpointer needs to make sure it doesn't use an already
// destroyed session. Thus this class keeps a shared_ptr to the session (so
// the checkpointer can keep a weak_ptr) and the checkpointer is a
// shared_ptr in this class. session_ will never be null.
std::shared_ptr<soci::session> const session_;
std::shared_ptr<Checkpointer> checkpointer_;
beast::Journal const j_;
};
// Return the checkpointer from its id. If the checkpointer no longer exists, an
// nullptr is returned
std::shared_ptr<Checkpointer>
checkpointerFromId(std::uintptr_t id);
} // namespace xrpl

View File

@@ -0,0 +1,475 @@
#pragma once
#include <xrpl/basics/RangeSet.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/LedgerHeader.h>
#include <xrpl/protocol/LedgerShortcut.h>
#include <xrpl/protocol/TxMeta.h>
#include <xrpl/protocol/TxSearched.h>
#include <xrpl/rdb/DatabaseCon.h>
#include <boost/filesystem.hpp>
#include <boost/variant.hpp>
namespace xrpl {
class Transaction;
class Ledger;
struct LedgerHashPair
{
uint256 ledgerHash;
uint256 parentHash;
};
struct LedgerRange
{
uint32_t min;
uint32_t max;
};
class RelationalDatabase
{
public:
struct CountMinMax
{
std::size_t numberOfRows;
LedgerIndex minLedgerSequence;
LedgerIndex maxLedgerSequence;
};
struct AccountTxMarker
{
std::uint32_t ledgerSeq = 0;
std::uint32_t txnSeq = 0;
};
struct AccountTxOptions
{
AccountID const& account;
std::uint32_t minLedger;
std::uint32_t maxLedger;
std::uint32_t offset;
std::uint32_t limit;
bool bUnlimited;
};
struct AccountTxPageOptions
{
AccountID const& account;
std::uint32_t minLedger;
std::uint32_t maxLedger;
std::optional<AccountTxMarker> marker;
std::uint32_t limit;
bool bAdmin;
};
using AccountTx = std::pair<std::shared_ptr<Transaction>, std::shared_ptr<TxMeta>>;
using AccountTxs = std::vector<AccountTx>;
using txnMetaLedgerType = std::tuple<Blob, Blob, std::uint32_t>;
using MetaTxsList = std::vector<txnMetaLedgerType>;
using LedgerSequence = uint32_t;
using LedgerHash = uint256;
using LedgerSpecifier = std::variant<LedgerRange, LedgerShortcut, LedgerSequence, LedgerHash>;
struct AccountTxArgs
{
AccountID account;
std::optional<LedgerSpecifier> ledger;
bool binary = false;
bool forward = false;
uint32_t limit = 0;
std::optional<AccountTxMarker> marker;
};
struct AccountTxResult
{
std::variant<AccountTxs, MetaTxsList> transactions;
LedgerRange ledgerRange;
uint32_t limit;
std::optional<AccountTxMarker> marker;
};
virtual ~RelationalDatabase() = default;
/**
* @brief getMinLedgerSeq Returns the minimum ledger sequence in the Ledgers
* table.
* @return Ledger sequence or no value if no ledgers exist.
*/
virtual std::optional<LedgerIndex>
getMinLedgerSeq() = 0;
/**
* @brief getMaxLedgerSeq Returns the maximum ledger sequence in the Ledgers
* table.
* @return Ledger sequence or none if no ledgers exist.
*/
virtual std::optional<LedgerIndex>
getMaxLedgerSeq() = 0;
/**
* @brief getLedgerInfoByIndex Returns a ledger by its sequence.
* @param ledgerSeq Ledger sequence.
* @return The ledger if found, otherwise no value.
*/
virtual std::optional<LedgerHeader>
getLedgerInfoByIndex(LedgerIndex ledgerSeq) = 0;
/**
* @brief getNewestLedgerInfo Returns the info of the newest saved ledger.
* @return Ledger info if found, otherwise no value.
*/
virtual std::optional<LedgerHeader>
getNewestLedgerInfo() = 0;
/**
* @brief getLedgerInfoByHash Returns the info of the ledger with given
* hash.
* @param ledgerHash Hash of the ledger.
* @return Ledger if found, otherwise no value.
*/
virtual std::optional<LedgerHeader>
getLedgerInfoByHash(uint256 const& ledgerHash) = 0;
/**
* @brief getHashByIndex Returns the hash of the ledger with the given
* sequence.
* @param ledgerIndex Ledger sequence.
* @return Hash of the ledger.
*/
virtual uint256
getHashByIndex(LedgerIndex ledgerIndex) = 0;
/**
* @brief getHashesByIndex Returns the hashes of the ledger and its parent
* as specified by the ledgerIndex.
* @param ledgerIndex Ledger sequence.
* @return Struct LedgerHashPair which contains hashes of the ledger and
* its parent.
*/
virtual std::optional<LedgerHashPair>
getHashesByIndex(LedgerIndex ledgerIndex) = 0;
/**
* @brief getHashesByIndex Returns hashes of each ledger and its parent for
* all ledgers within the provided range.
* @param minSeq Minimum ledger sequence.
* @param maxSeq Maximum ledger sequence.
* @return Container that maps the sequence number of a found ledger to the
* struct LedgerHashPair which contains the hashes of the ledger and
* its parent.
*/
virtual std::map<LedgerIndex, LedgerHashPair>
getHashesByIndex(LedgerIndex minSeq, LedgerIndex maxSeq) = 0;
/**
* @brief getTxHistory Returns the 20 most recent transactions starting from
* the given number.
* @param startIndex First number of returned entry.
* @return Vector of shared pointers to transactions sorted in
* descending order by ledger sequence.
*/
virtual std::vector<std::shared_ptr<Transaction>>
getTxHistory(LedgerIndex startIndex) = 0;
/**
* @brief getTransactionsMinLedgerSeq Returns the minimum ledger sequence
* stored in the Transactions table.
* @return Ledger sequence or no value if no ledgers exist.
*/
virtual std::optional<LedgerIndex>
getTransactionsMinLedgerSeq() = 0;
/**
* @brief getAccountTransactionsMinLedgerSeq Returns the minimum ledger
* sequence stored in the AccountTransactions table.
* @return Ledger sequence or no value if no ledgers exist.
*/
virtual std::optional<LedgerIndex>
getAccountTransactionsMinLedgerSeq() = 0;
/**
* @brief deleteTransactionByLedgerSeq Deletes transactions from the ledger
* with the given sequence.
* @param ledgerSeq Ledger sequence.
*/
virtual void
deleteTransactionByLedgerSeq(LedgerIndex ledgerSeq) = 0;
/**
* @brief deleteBeforeLedgerSeq Deletes all ledgers with a sequence number
* less than or equal to the given ledger sequence.
* @param ledgerSeq Ledger sequence.
*/
virtual void
deleteBeforeLedgerSeq(LedgerIndex ledgerSeq) = 0;
/**
* @brief deleteTransactionsBeforeLedgerSeq Deletes all transactions with
* a sequence number less than or equal to the given ledger
* sequence.
* @param ledgerSeq Ledger sequence.
*/
virtual void
deleteTransactionsBeforeLedgerSeq(LedgerIndex ledgerSeq) = 0;
/**
* @brief deleteAccountTransactionsBeforeLedgerSeq Deletes all account
* transactions with a sequence number less than or equal to the
* given ledger sequence.
* @param ledgerSeq Ledger sequence.
*/
virtual void
deleteAccountTransactionsBeforeLedgerSeq(LedgerIndex ledgerSeq) = 0;
/**
* @brief getTransactionCount Returns the number of transactions.
* @return Number of transactions.
*/
virtual std::size_t
getTransactionCount() = 0;
/**
* @brief getAccountTransactionCount Returns the number of account
* transactions.
* @return Number of account transactions.
*/
virtual std::size_t
getAccountTransactionCount() = 0;
/**
* @brief getLedgerCountMinMax Returns the minimum ledger sequence,
* maximum ledger sequence and total number of saved ledgers.
* @return Struct CountMinMax which contains the minimum sequence,
* maximum sequence and number of ledgers.
*/
virtual struct CountMinMax
getLedgerCountMinMax() = 0;
/**
* @brief saveValidatedLedger Saves a ledger into the database.
* @param ledger The ledger.
* @param current True if the ledger is current.
* @return True if saving was successful.
*/
virtual bool
saveValidatedLedger(std::shared_ptr<Ledger const> const& ledger, bool current) = 0;
/**
* @brief getLimitedOldestLedgerInfo Returns the info of the oldest ledger
* whose sequence number is greater than or equal to the given
* sequence number.
* @param ledgerFirstIndex Minimum ledger sequence.
* @return Ledger info if found, otherwise no value.
*/
virtual std::optional<LedgerHeader>
getLimitedOldestLedgerInfo(LedgerIndex ledgerFirstIndex) = 0;
/**
* @brief getLimitedNewestLedgerInfo Returns the info of the newest ledger
* whose sequence number is greater than or equal to the given
* sequence number.
* @param ledgerFirstIndex Minimum ledger sequence.
* @return Ledger info if found, otherwise no value.
*/
virtual std::optional<LedgerHeader>
getLimitedNewestLedgerInfo(LedgerIndex ledgerFirstIndex) = 0;
/**
* @brief getOldestAccountTxs Returns the oldest transactions for the
* account that matches the given criteria starting from the provided
* offset.
* @param options Struct AccountTxOptions which contains the criteria to
* match: the account, ledger search range, the offset of the first
* entry to return, the number of transactions to return, a flag if
* this number is unlimited.
* @return Vector of pairs of found transactions and their metadata
* sorted in ascending order by account sequence.
*/
virtual AccountTxs
getOldestAccountTxs(AccountTxOptions const& options) = 0;
/**
* @brief getNewestAccountTxs Returns the newest transactions for the
* account that matches the given criteria starting from the provided
* offset.
* @param options Struct AccountTxOptions which contains the criteria to
* match: the account, the ledger search range, the offset of the
* first entry to return, the number of transactions to return, a
* flag if this number unlimited.
* @return Vector of pairs of found transactions and their metadata
* sorted in descending order by account sequence.
*/
virtual AccountTxs
getNewestAccountTxs(AccountTxOptions const& options) = 0;
/**
* @brief getOldestAccountTxsB Returns the oldest transactions in binary
* form for the account that matches the given criteria starting from
* the provided offset.
* @param options Struct AccountTxOptions which contains the criteria to
* match: the account, the ledger search range, the offset of the
* first entry to return, the number of transactions to return, a
* flag if this number unlimited.
* @return Vector of tuples of found transactions, their metadata and
* account sequences sorted in ascending order by account sequence.
*/
virtual MetaTxsList
getOldestAccountTxsB(AccountTxOptions const& options) = 0;
/**
* @brief getNewestAccountTxsB Returns the newest transactions in binary
* form for the account that matches the given criteria starting from
* the provided offset.
* @param options Struct AccountTxOptions which contains the criteria to
* match: the account, the ledger search range, the offset of the
* first entry to return, the number of transactions to return, a
* flag if this number is unlimited.
* @return Vector of tuples of found transactions, their metadata and
* account sequences sorted in descending order by account
* sequence.
*/
virtual MetaTxsList
getNewestAccountTxsB(AccountTxOptions const& options) = 0;
/**
* @brief oldestAccountTxPage Returns the oldest transactions for the
* account that matches the given criteria starting from the
* provided marker.
* @param options Struct AccountTxPageOptions which contains the criteria to
* match: the account, the ledger search range, the marker of first
* returned entry, the number of transactions to return, a flag if
* this number is unlimited.
* @return Vector of pairs of found transactions and their metadata
* sorted in ascending order by account sequence and a marker
* for the next search if the search was not finished.
*/
virtual std::pair<AccountTxs, std::optional<AccountTxMarker>>
oldestAccountTxPage(AccountTxPageOptions const& options) = 0;
/**
* @brief newestAccountTxPage Returns the newest transactions for the
* account that matches the given criteria starting from the provided
* marker.
* @param options Struct AccountTxPageOptions which contains the criteria to
* match: the account, the ledger search range, the marker of the
* first returned entry, the number of transactions to return, a flag
* if this number unlimited.
* @return Vector of pairs of found transactions and their metadata
* sorted in descending order by account sequence and a marker
* for the next search if the search was not finished.
*/
virtual std::pair<AccountTxs, std::optional<AccountTxMarker>>
newestAccountTxPage(AccountTxPageOptions const& options) = 0;
/**
* @brief oldestAccountTxPageB Returns the oldest transactions in binary
* form for the account that matches the given criteria starting from
* the provided marker.
* @param options Struct AccountTxPageOptions which contains criteria to
* match: the account, the ledger search range, the marker of the
* first returned entry, the number of transactions to return, a flag
* if this number unlimited.
* @return Vector of tuples of found transactions, their metadata and
* account sequences sorted in ascending order by account
* sequence and a marker for the next search if the search was not
* finished.
*/
virtual std::pair<MetaTxsList, std::optional<AccountTxMarker>>
oldestAccountTxPageB(AccountTxPageOptions const& options) = 0;
/**
* @brief newestAccountTxPageB Returns the newest transactions in binary
* form for the account that matches the given criteria starting from
* the provided marker.
* @param options Struct AccountTxPageOptions which contains the criteria to
* match: the account, the ledger search range, the marker of the
* first returned entry, the number of transactions to return, a flag
* if this number is unlimited.
* @return Vector of tuples of found transactions, their metadata and
* account sequences sorted in descending order by account
* sequence and a marker for the next search if the search was not
* finished.
*/
virtual std::pair<MetaTxsList, std::optional<AccountTxMarker>>
newestAccountTxPageB(AccountTxPageOptions const& options) = 0;
/**
* @brief getTransaction Returns the transaction with the given hash. If a
* range is provided but the transaction is not found, then check if
* all ledgers in the range are present in the database.
* @param id Hash of the transaction.
* @param range Range of ledgers to check, if present.
* @param ec Default error code value.
* @return Transaction and its metadata if found, otherwise TxSearched::all
* if a range is provided and all ledgers from the range are present
* in the database, TxSearched::some if a range is provided and not
* all ledgers are present, TxSearched::unknown if the range is not
* provided or a deserializing error occurred. In the last case the
* error code is returned via the ec parameter, in other cases the
* default error code is not changed.
*/
virtual std::variant<AccountTx, TxSearched>
getTransaction(uint256 const& id, std::optional<ClosedInterval<uint32_t>> const& range, error_code_i& ec) = 0;
/**
* @brief getKBUsedAll Returns the amount of space used by all databases.
* @return Space in kilobytes.
*/
virtual uint32_t
getKBUsedAll() = 0;
/**
* @brief getKBUsedLedger Returns the amount of space space used by the
* ledger database.
* @return Space in kilobytes.
*/
virtual uint32_t
getKBUsedLedger() = 0;
/**
* @brief getKBUsedTransaction Returns the amount of space used by the
* transaction database.
* @return Space in kilobytes.
*/
virtual uint32_t
getKBUsedTransaction() = 0;
/**
* @brief Closes the ledger database
*/
virtual void
closeLedgerDB() = 0;
/**
* @brief Closes the transaction database
*/
virtual void
closeTransactionDB() = 0;
};
template <class T, class C>
T
rangeCheckedCast(C c)
{
if ((c > std::numeric_limits<T>::max()) || (!std::numeric_limits<T>::is_signed && c < 0) ||
(std::numeric_limits<T>::is_signed && std::numeric_limits<C>::is_signed &&
c < std::numeric_limits<T>::lowest()))
{
// This should never happen
// LCOV_EXCL_START
UNREACHABLE("xrpl::rangeCheckedCast : domain error");
JLOG(debugLog().error()) << "rangeCheckedCast domain error:"
<< " value = " << c << " min = " << std::numeric_limits<T>::lowest()
<< " max: " << std::numeric_limits<T>::max();
// LCOV_EXCL_STOP
}
return static_cast<T>(c);
}
} // namespace xrpl

120
include/xrpl/rdb/SociDB.h Normal file
View File

@@ -0,0 +1,120 @@
#pragma once
/** An embedded database wrapper with an intuitive, type-safe interface.
This collection of classes let's you access embedded SQLite databases
using C++ syntax that is very similar to regular SQL.
This module requires the @ref beast_sqlite external module.
*/
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
#endif
#include <xrpl/basics/Log.h>
#include <xrpl/core/JobQueue.h>
#define SOCI_USE_BOOST
#include <soci/soci.h>
#include <cstdint>
#include <string>
#include <vector>
namespace sqlite_api {
struct sqlite3;
}
namespace xrpl {
class BasicConfig;
/**
DBConfig is used when a client wants to delay opening a soci::session after
parsing the config parameters. If a client want to open a session
immediately, use the free function "open" below.
*/
class DBConfig
{
std::string connectionString_;
explicit DBConfig(std::string const& dbPath);
public:
DBConfig(BasicConfig const& config, std::string const& dbName);
std::string
connectionString() const;
void
open(soci::session& s) const;
};
/**
Open a soci session.
@param s Session to open.
@param config Parameters to pick the soci backend and how to connect to that
backend.
@param dbName Name of the database. This has different meaning for different
backends. Sometimes it is part of a filename (sqlite3),
other times it is a database name (postgresql).
*/
void
open(soci::session& s, BasicConfig const& config, std::string const& dbName);
/**
* Open a soci session.
*
* @param s Session to open.
* @param beName Backend name.
* @param connectionString Connection string to forward to soci::open.
* see the soci::open documentation for how to use this.
*
*/
void
open(soci::session& s, std::string const& beName, std::string const& connectionString);
std::uint32_t
getKBUsedAll(soci::session& s);
std::uint32_t
getKBUsedDB(soci::session& s);
void
convert(soci::blob& from, std::vector<std::uint8_t>& to);
void
convert(soci::blob& from, std::string& to);
void
convert(std::vector<std::uint8_t> const& from, soci::blob& to);
void
convert(std::string const& from, soci::blob& to);
class Checkpointer : public std::enable_shared_from_this<Checkpointer>
{
public:
virtual std::uintptr_t
id() const = 0;
virtual ~Checkpointer() = default;
virtual void
schedule() = 0;
virtual void
checkpoint() = 0;
};
/** Returns a new checkpointer which makes checkpoints of a
soci database every checkpointPageCount pages, using a job on the job queue.
The checkpointer contains references to the session and job queue
and so must outlive them both.
*/
std::shared_ptr<Checkpointer>
makeCheckpointer(std::uintptr_t id, std::weak_ptr<soci::session>, JobQueue&, Logs&);
} // namespace xrpl
#if defined(__clang__)
#pragma clang diagnostic pop
#endif

View File

@@ -0,0 +1,224 @@
#pragma once
#include <xrpl/basics/CountedObject.h>
#include <xrpl/json/json_value.h>
#include <xrpl/protocol/Book.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/resource/Consumer.h>
#include <xrpl/server/Manifest.h>
namespace xrpl {
// Operations that clients may wish to perform against the network
// Master operational handler, server sequencer, network tracker
class InfoSubRequest : public CountedObject<InfoSubRequest>
{
public:
using pointer = std::shared_ptr<InfoSubRequest>;
virtual ~InfoSubRequest() = default;
virtual Json::Value
doClose() = 0;
virtual Json::Value
doStatus(Json::Value const&) = 0;
};
/** Manages a client's subscription to data feeds.
*/
class InfoSub : public CountedObject<InfoSub>
{
public:
using pointer = std::shared_ptr<InfoSub>;
// VFALCO TODO Standardize on the names of weak / strong pointer type
// aliases.
using wptr = std::weak_ptr<InfoSub>;
using ref = std::shared_ptr<InfoSub> const&;
using Consumer = Resource::Consumer;
public:
/** Abstracts the source of subscription data.
*/
class Source
{
public:
virtual ~Source() = default;
// For some reason, these were originally called "rt"
// for "real time". They actually refer to whether
// you get transactions as they occur or once their
// results are confirmed
virtual void
subAccount(ref ispListener, hash_set<AccountID> const& vnaAccountIDs, bool realTime) = 0;
// for normal use, removes from InfoSub and server
virtual void
unsubAccount(ref isplistener, hash_set<AccountID> const& vnaAccountIDs, bool realTime) = 0;
// for use during InfoSub destruction
// Removes only from the server
virtual void
unsubAccountInternal(std::uint64_t uListener, hash_set<AccountID> const& vnaAccountIDs, bool realTime) = 0;
/**
* subscribe an account's new transactions and retrieve the account's
* historical transactions
* @return rpcSUCCESS if successful, otherwise an error code
*/
virtual error_code_i
subAccountHistory(ref ispListener, AccountID const& account) = 0;
/**
* unsubscribe an account's transactions
* @param historyOnly if true, only stop historical transactions
* @note once a client receives enough historical transactions,
* it should unsubscribe with historyOnly == true to stop receiving
* more historical transactions. It will continue to receive new
* transactions.
*/
virtual void
unsubAccountHistory(ref ispListener, AccountID const& account, bool historyOnly) = 0;
virtual void
unsubAccountHistoryInternal(std::uint64_t uListener, AccountID const& account, bool historyOnly) = 0;
// VFALCO TODO Document the bool return value
virtual bool
subLedger(ref ispListener, Json::Value& jvResult) = 0;
virtual bool
unsubLedger(std::uint64_t uListener) = 0;
virtual bool
subBookChanges(ref ispListener) = 0;
virtual bool
unsubBookChanges(std::uint64_t uListener) = 0;
virtual bool
subManifests(ref ispListener) = 0;
virtual bool
unsubManifests(std::uint64_t uListener) = 0;
virtual void
pubManifest(Manifest const&) = 0;
virtual bool
subServer(ref ispListener, Json::Value& jvResult, bool admin) = 0;
virtual bool
unsubServer(std::uint64_t uListener) = 0;
virtual bool
subBook(ref ispListener, Book const&) = 0;
virtual bool
unsubBook(std::uint64_t uListener, Book const&) = 0;
virtual bool
subTransactions(ref ispListener) = 0;
virtual bool
unsubTransactions(std::uint64_t uListener) = 0;
virtual bool
subRTTransactions(ref ispListener) = 0;
virtual bool
unsubRTTransactions(std::uint64_t uListener) = 0;
virtual bool
subValidations(ref ispListener) = 0;
virtual bool
unsubValidations(std::uint64_t uListener) = 0;
virtual bool
subPeerStatus(ref ispListener) = 0;
virtual bool
unsubPeerStatus(std::uint64_t uListener) = 0;
virtual void
pubPeerStatus(std::function<Json::Value(void)> const&) = 0;
virtual bool
subConsensus(ref ispListener) = 0;
virtual bool
unsubConsensus(std::uint64_t uListener) = 0;
// VFALCO TODO Remove
// This was added for one particular partner, it
// "pushes" subscription data to a particular URL.
//
virtual pointer
findRpcSub(std::string const& strUrl) = 0;
virtual pointer
addRpcSub(std::string const& strUrl, ref rspEntry) = 0;
virtual bool
tryRemoveRpcSub(std::string const& strUrl) = 0;
};
public:
InfoSub(Source& source);
InfoSub(Source& source, Consumer consumer);
virtual ~InfoSub();
Consumer&
getConsumer();
virtual void
send(Json::Value const& jvObj, bool broadcast) = 0;
std::uint64_t
getSeq();
void
onSendEmpty();
void
insertSubAccountInfo(AccountID const& account, bool rt);
void
deleteSubAccountInfo(AccountID const& account, bool rt);
// return false if already subscribed to this account
bool
insertSubAccountHistory(AccountID const& account);
void
deleteSubAccountHistory(AccountID const& account);
void
clearRequest();
void
setRequest(std::shared_ptr<InfoSubRequest> const& req);
std::shared_ptr<InfoSubRequest> const&
getRequest();
void
setApiVersion(unsigned int apiVersion);
unsigned int
getApiVersion() const noexcept;
protected:
std::mutex mLock;
private:
Consumer m_consumer;
Source& m_source;
hash_set<AccountID> realTimeSubscriptions_;
hash_set<AccountID> normalSubscriptions_;
std::shared_ptr<InfoSubRequest> request_;
std::uint64_t mSeq;
hash_set<AccountID> accountHistorySubscriptions_;
unsigned int apiVersion_ = 0;
static int
assign_id()
{
static std::atomic<std::uint64_t> id(0);
return ++id;
}
};
} // namespace xrpl

View File

@@ -0,0 +1,137 @@
#pragma once
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/protocol/XRPAmount.h>
#include <algorithm>
#include <cstdint>
#include <mutex>
namespace xrpl {
struct Fees;
/** Manages the current fee schedule.
The "base" fee is the cost to send a reference transaction under no load,
expressed in millionths of one XRP.
The "load" fee is how much the local server currently charges to send a
reference transaction. This fee fluctuates based on the load of the
server.
*/
class LoadFeeTrack final
{
public:
explicit LoadFeeTrack(beast::Journal journal = beast::Journal(beast::Journal::getNullSink()))
: j_(journal)
, localTxnLoadFee_(lftNormalFee)
, remoteTxnLoadFee_(lftNormalFee)
, clusterTxnLoadFee_(lftNormalFee)
, raiseCount_(0)
{
}
~LoadFeeTrack() = default;
void
setRemoteFee(std::uint32_t f)
{
JLOG(j_.trace()) << "setRemoteFee: " << f;
std::lock_guard sl(lock_);
remoteTxnLoadFee_ = f;
}
std::uint32_t
getRemoteFee() const
{
std::lock_guard sl(lock_);
return remoteTxnLoadFee_;
}
std::uint32_t
getLocalFee() const
{
std::lock_guard sl(lock_);
return localTxnLoadFee_;
}
std::uint32_t
getClusterFee() const
{
std::lock_guard sl(lock_);
return clusterTxnLoadFee_;
}
std::uint32_t
getLoadBase() const
{
return lftNormalFee;
}
std::uint32_t
getLoadFactor() const
{
std::lock_guard sl(lock_);
return std::max({clusterTxnLoadFee_, localTxnLoadFee_, remoteTxnLoadFee_});
}
std::pair<std::uint32_t, std::uint32_t>
getScalingFactors() const
{
std::lock_guard sl(lock_);
return std::make_pair(
std::max(localTxnLoadFee_, remoteTxnLoadFee_), std::max(remoteTxnLoadFee_, clusterTxnLoadFee_));
}
void
setClusterFee(std::uint32_t fee)
{
JLOG(j_.trace()) << "setClusterFee: " << fee;
std::lock_guard sl(lock_);
clusterTxnLoadFee_ = fee;
}
bool
raiseLocalFee();
bool
lowerLocalFee();
bool
isLoadedLocal() const
{
std::lock_guard sl(lock_);
return (raiseCount_ != 0) || (localTxnLoadFee_ != lftNormalFee);
}
bool
isLoadedCluster() const
{
std::lock_guard sl(lock_);
return (raiseCount_ != 0) || (localTxnLoadFee_ != lftNormalFee) || (clusterTxnLoadFee_ != lftNormalFee);
}
private:
static std::uint32_t constexpr lftNormalFee = 256; // 256 is the minimum/normal load factor
static std::uint32_t constexpr lftFeeIncFraction = 4; // increase fee by 1/4
static std::uint32_t constexpr lftFeeDecFraction = 4; // decrease fee by 1/4
static std::uint32_t constexpr lftFeeMax = lftNormalFee * 1000000;
beast::Journal const j_;
std::mutex mutable lock_;
std::uint32_t localTxnLoadFee_; // Scale factor, lftNormalFee = normal fee
std::uint32_t remoteTxnLoadFee_; // Scale factor, lftNormalFee = normal fee
std::uint32_t clusterTxnLoadFee_; // Scale factor, lftNormalFee = normal fee
std::uint32_t raiseCount_;
};
//------------------------------------------------------------------------------
// Scale using load as well as base rate
XRPAmount
scaleFeeLoad(XRPAmount fee, LoadFeeTrack const& feeTrack, Fees const& fees, bool bUnlimited);
} // namespace xrpl

View File

@@ -0,0 +1,428 @@
#pragma once
#include <xrpl/basics/UnorderedContainers.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/protocol/PublicKey.h>
#include <xrpl/protocol/SecretKey.h>
#include <optional>
#include <shared_mutex>
#include <string>
namespace xrpl {
/*
Validator key manifests
-----------------------
Suppose the secret keys installed on a Ripple validator are compromised. Not
only do you have to generate and install new key pairs on each validator,
EVERY rippled needs to have its config updated with the new public keys, and
is vulnerable to forged validation signatures until this is done. The
solution is a new layer of indirection: A master secret key under
restrictive access control is used to sign a "manifest": essentially, a
certificate including the master public key, an ephemeral public key for
verifying validations (which will be signed by its secret counterpart), a
sequence number, and a digital signature.
The manifest has two serialized forms: one which includes the digital
signature and one which doesn't. There is an obvious causal dependency
relationship between the (latter) form with no signature, the signature
of that form, and the (former) form which includes that signature. In
other words, a message can't contain a signature of itself. The code
below stores a serialized manifest which includes the signature, and
dynamically generates the signatureless form when it needs to verify
the signature.
An instance of ManifestCache stores, for each trusted validator, (a) its
master public key, and (b) the most senior of all valid manifests it has
seen for that validator, if any. On startup, the [validator_token] config
entry (which contains the manifest for this validator) is decoded and
added to the manifest cache. Other manifests are added as "gossip"
received from rippled peers.
When an ephemeral key is compromised, a new signing key pair is created,
along with a new manifest vouching for it (with a higher sequence number),
signed by the master key. When a rippled peer receives the new manifest,
it verifies it with the master key and (assuming it's valid) discards the
old ephemeral key and stores the new one. If the master key itself gets
compromised, a manifest with sequence number 0xFFFFFFFF will supersede a
prior manifest and discard any existing ephemeral key without storing a
new one. These revocation manifests are loaded from the
[validator_key_revocation] config entry as well as received as gossip from
peers. Since no further manifests for this master key will be accepted
(since no higher sequence number is possible), and no signing key is on
record, no validations will be accepted from the compromised validator.
*/
//------------------------------------------------------------------------------
struct Manifest
{
/// The manifest in serialized form.
std::string serialized;
/// The master key associated with this manifest.
PublicKey masterKey;
/// The ephemeral key associated with this manifest.
// A revoked manifest does not have a signingKey
// This field is specified as "optional" in manifestFormat's
// SOTemplate
std::optional<PublicKey> signingKey;
/// The sequence number of this manifest.
std::uint32_t sequence = 0;
/// The domain, if one was specified in the manifest; empty otherwise.
std::string domain;
Manifest() = delete;
Manifest(
std::string const& serialized_,
PublicKey const& masterKey_,
std::optional<PublicKey> const& signingKey_,
std::uint32_t seq,
std::string const& domain_)
: serialized(serialized_), masterKey(masterKey_), signingKey(signingKey_), sequence(seq), domain(domain_)
{
}
Manifest(Manifest const& other) = delete;
Manifest&
operator=(Manifest const& other) = delete;
Manifest(Manifest&& other) = default;
Manifest&
operator=(Manifest&& other) = default;
/// Returns `true` if manifest signature is valid
bool
verify() const;
/// Returns hash of serialized manifest data
uint256
hash() const;
/// Returns `true` if manifest revokes master key
// The maximum possible sequence number means that the master key has
// been revoked
static bool
revoked(std::uint32_t sequence);
/// Returns `true` if manifest revokes master key
bool
revoked() const;
/// Returns manifest signature
std::optional<Blob>
getSignature() const;
/// Returns manifest master key signature
Blob
getMasterSignature() const;
};
/** Format the specified manifest to a string for debugging purposes. */
std::string
to_string(Manifest const& m);
/** Constructs Manifest from serialized string
@param s Serialized manifest string
@return `std::nullopt` if string is invalid
@note This does not verify manifest signatures.
`Manifest::verify` should be called after constructing manifest.
*/
/** @{ */
std::optional<Manifest>
deserializeManifest(Slice s, beast::Journal journal);
inline std::optional<Manifest>
deserializeManifest(std::string const& s, beast::Journal journal = beast::Journal(beast::Journal::getNullSink()))
{
return deserializeManifest(makeSlice(s), journal);
}
template <class T, class = std::enable_if_t<std::is_same<T, char>::value || std::is_same<T, unsigned char>::value>>
std::optional<Manifest>
deserializeManifest(std::vector<T> const& v, beast::Journal journal = beast::Journal(beast::Journal::getNullSink()))
{
return deserializeManifest(makeSlice(v), journal);
}
/** @} */
inline bool
operator==(Manifest const& lhs, Manifest const& rhs)
{
// In theory, comparing the two serialized strings should be
// sufficient.
return lhs.sequence == rhs.sequence && lhs.masterKey == rhs.masterKey && lhs.signingKey == rhs.signingKey &&
lhs.domain == rhs.domain && lhs.serialized == rhs.serialized;
}
inline bool
operator!=(Manifest const& lhs, Manifest const& rhs)
{
return !(lhs == rhs);
}
struct ValidatorToken
{
std::string manifest;
SecretKey validationSecret;
};
std::optional<ValidatorToken>
loadValidatorToken(
std::vector<std::string> const& blob,
beast::Journal journal = beast::Journal(beast::Journal::getNullSink()));
enum class ManifestDisposition {
/// Manifest is valid
accepted = 0,
/// Sequence is too old
stale,
/// The master key is not acceptable to us
badMasterKey,
/// The ephemeral key is not acceptable to us
badEphemeralKey,
/// Timely, but invalid signature
invalid
};
inline std::string
to_string(ManifestDisposition m)
{
switch (m)
{
case ManifestDisposition::accepted:
return "accepted";
case ManifestDisposition::stale:
return "stale";
case ManifestDisposition::badMasterKey:
return "badMasterKey";
case ManifestDisposition::badEphemeralKey:
return "badEphemeralKey";
case ManifestDisposition::invalid:
return "invalid";
default:
return "unknown";
}
}
class DatabaseCon;
/** Remembers manifests with the highest sequence number. */
class ManifestCache
{
private:
beast::Journal j_;
std::shared_mutex mutable mutex_;
/** Active manifests stored by master public key. */
hash_map<PublicKey, Manifest> map_;
/** Master public keys stored by current ephemeral public key. */
hash_map<PublicKey, PublicKey> signingToMasterKeys_;
std::atomic<std::uint32_t> seq_{0};
public:
explicit ManifestCache(beast::Journal j = beast::Journal(beast::Journal::getNullSink())) : j_(j)
{
}
/** A monotonically increasing number used to detect new manifests. */
std::uint32_t
sequence() const
{
return seq_.load();
}
/** Returns master key's current signing key.
@param pk Master public key
@return pk if no known signing key from a manifest
@par Thread Safety
May be called concurrently
*/
std::optional<PublicKey>
getSigningKey(PublicKey const& pk) const;
/** Returns ephemeral signing key's master public key.
@param pk Ephemeral signing public key
@return pk if signing key is not in a valid manifest
@par Thread Safety
May be called concurrently
*/
PublicKey
getMasterKey(PublicKey const& pk) const;
/** Returns master key's current manifest sequence.
@return sequence corresponding to Master public key
if configured or std::nullopt otherwise
*/
std::optional<std::uint32_t>
getSequence(PublicKey const& pk) const;
/** Returns domain claimed by a given public key
@return domain corresponding to Master public key
if present, otherwise std::nullopt
*/
std::optional<std::string>
getDomain(PublicKey const& pk) const;
/** Returns manifest corresponding to a given public key
@return manifest corresponding to Master public key
if present, otherwise std::nullopt
*/
std::optional<std::string>
getManifest(PublicKey const& pk) const;
/** Returns `true` if master key has been revoked in a manifest.
@param pk Master public key
@par Thread Safety
May be called concurrently
*/
bool
revoked(PublicKey const& pk) const;
/** Add manifest to cache.
@param m Manifest to add
@return `ManifestDisposition::accepted` if successful, or
`stale` or `invalid` otherwise
@par Thread Safety
May be called concurrently
*/
ManifestDisposition
applyManifest(Manifest m);
/** Populate manifest cache with manifests in database and config.
@param dbCon Database connection with dbTable
@param dbTable Database table
@param configManifest Base64 encoded manifest for local node's
validator keys
@param configRevocation Base64 encoded validator key revocation
from the config
@par Thread Safety
May be called concurrently
*/
bool
load(
DatabaseCon& dbCon,
std::string const& dbTable,
std::string const& configManifest,
std::vector<std::string> const& configRevocation);
/** Populate manifest cache with manifests in database.
@param dbCon Database connection with dbTable
@param dbTable Database table
@par Thread Safety
May be called concurrently
*/
void
load(DatabaseCon& dbCon, std::string const& dbTable);
/** Save cached manifests to database.
@param dbCon Database connection with `ValidatorManifests` table
@param isTrusted Function that returns true if manifest is trusted
@par Thread Safety
May be called concurrently
*/
void
save(DatabaseCon& dbCon, std::string const& dbTable, std::function<bool(PublicKey const&)> const& isTrusted);
/** Invokes the callback once for every populated manifest.
@note Do not call ManifestCache member functions from within the
callback. This can re-lock the mutex from the same thread, which is UB.
@note Do not write ManifestCache member variables from within the
callback. This can lead to data races.
@param f Function called for each manifest
@par Thread Safety
May be called concurrently
*/
template <class Function>
void
for_each_manifest(Function&& f) const
{
std::shared_lock lock{mutex_};
for (auto const& [_, manifest] : map_)
{
(void)_;
f(manifest);
}
}
/** Invokes the callback once for every populated manifest.
@note Do not call ManifestCache member functions from within the
callback. This can re-lock the mutex from the same thread, which is UB.
@note Do not write ManifestCache member variables from
within the callback. This can lead to data races.
@param pf Pre-function called with the maximum number of times f will be
called (useful for memory allocations)
@param f Function called for each manifest
@par Thread Safety
May be called concurrently
*/
template <class PreFun, class EachFun>
void
for_each_manifest(PreFun&& pf, EachFun&& f) const
{
std::shared_lock lock{mutex_};
pf(map_.size());
for (auto const& [_, manifest] : map_)
{
(void)_;
f(manifest);
}
}
};
} // namespace xrpl

View File

@@ -0,0 +1,250 @@
#pragma once
#include <xrpl/core/JobQueue.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/protocol/STValidation.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/messages.h>
#include <xrpl/server/InfoSub.h>
#include <xrpl/shamap/SHAMap.h>
#include <boost/asio.hpp>
#include <memory>
namespace xrpl {
// Operations that clients may wish to perform against the network
// Master operational handler, server sequencer, network tracker
class Peer;
class STTx;
class ReadView;
class LedgerMaster;
class Transaction;
class ValidatorKeys;
class CanonicalTXSet;
class RCLCxPeerPos;
// This is the primary interface into the "client" portion of the program.
// Code that wants to do normal operations on the network such as
// creating and monitoring accounts, creating transactions, and so on
// should use this interface. The RPC code will primarily be a light wrapper
// over this code.
//
// Eventually, it will check the node's operating mode (synced, unsynced,
// etcetera) and defer to the correct means of processing. The current
// code assumes this node is synced (and will continue to do so until
// there's a functional network.
//
/** Specifies the mode under which the server believes it's operating.
This has implications about how the server processes transactions and
how it responds to requests (e.g. account balance request).
@note Other code relies on the numerical values of these constants; do
not change them without verifying each use and ensuring that it is
not a breaking change.
*/
enum class OperatingMode {
DISCONNECTED = 0, //!< not ready to process requests
CONNECTED = 1, //!< convinced we are talking to the network
SYNCING = 2, //!< fallen slightly behind
TRACKING = 3, //!< convinced we agree with the network
FULL = 4 //!< we have the ledger and can even validate
};
/** Provides server functionality for clients.
Clients include backend applications, local commands, and connected
clients. This class acts as a proxy, fulfilling the command with local
data if possible, or asking the network and returning the results if
needed.
A backend application or local client can trust a local instance of
rippled / NetworkOPs. However, client software connecting to non-local
instances of rippled will need to be hardened to protect against hostile
or unreliable servers.
*/
class NetworkOPs : public InfoSub::Source
{
public:
using clock_type = beast::abstract_clock<std::chrono::steady_clock>;
enum class FailHard : unsigned char { no, yes };
static inline FailHard
doFailHard(bool noMeansDont)
{
return noMeansDont ? FailHard::yes : FailHard::no;
}
public:
~NetworkOPs() override = default;
virtual void
stop() = 0;
//--------------------------------------------------------------------------
//
// Network information
//
virtual OperatingMode
getOperatingMode() const = 0;
virtual std::string
strOperatingMode(OperatingMode const mode, bool const admin = false) const = 0;
virtual std::string
strOperatingMode(bool const admin = false) const = 0;
//--------------------------------------------------------------------------
//
// Transaction processing
//
// must complete immediately
virtual void
submitTransaction(std::shared_ptr<STTx const> const&) = 0;
/**
* Process transactions as they arrive from the network or which are
* submitted by clients. Process local transactions synchronously
*
* @param transaction Transaction object
* @param bUnlimited Whether a privileged client connection submitted it.
* @param bLocal Client submission.
* @param failType fail_hard setting from transaction submission.
*/
virtual void
processTransaction(std::shared_ptr<Transaction>& transaction, bool bUnlimited, bool bLocal, FailHard failType) = 0;
/**
* Process a set of transactions synchronously, and ensuring that they are
* processed in one batch.
*
* @param set Transaction object set
*/
virtual void
processTransactionSet(CanonicalTXSet const& set) = 0;
//--------------------------------------------------------------------------
//
// Owner functions
//
virtual Json::Value
getOwnerInfo(std::shared_ptr<ReadView const> lpLedger, AccountID const& account) = 0;
//--------------------------------------------------------------------------
//
// Book functions
//
virtual void
getBookPage(
std::shared_ptr<ReadView const>& lpLedger,
Book const& book,
AccountID const& uTakerID,
bool const bProof,
unsigned int iLimit,
Json::Value const& jvMarker,
Json::Value& jvResult) = 0;
//--------------------------------------------------------------------------
// ledger proposal/close functions
virtual bool
processTrustedProposal(RCLCxPeerPos peerPos) = 0;
virtual bool
recvValidation(std::shared_ptr<STValidation> const& val, std::string const& source) = 0;
virtual void
mapComplete(std::shared_ptr<SHAMap> const& map, bool fromAcquire) = 0;
// network state machine
virtual bool
beginConsensus(uint256 const& netLCL, std::unique_ptr<std::stringstream> const& clog) = 0;
virtual void
endConsensus(std::unique_ptr<std::stringstream> const& clog) = 0;
virtual void
setStandAlone() = 0;
virtual void
setStateTimer() = 0;
virtual void
setNeedNetworkLedger() = 0;
virtual void
clearNeedNetworkLedger() = 0;
virtual bool
isNeedNetworkLedger() = 0;
virtual bool
isFull() = 0;
virtual void
setMode(OperatingMode om) = 0;
virtual bool
isBlocked() = 0;
virtual bool
isAmendmentBlocked() = 0;
virtual void
setAmendmentBlocked() = 0;
virtual bool
isAmendmentWarned() = 0;
virtual void
setAmendmentWarned() = 0;
virtual void
clearAmendmentWarned() = 0;
virtual bool
isUNLBlocked() = 0;
virtual void
setUNLBlocked() = 0;
virtual void
clearUNLBlocked() = 0;
virtual void
consensusViewChange() = 0;
virtual Json::Value
getConsensusInfo() = 0;
virtual Json::Value
getServerInfo(bool human, bool admin, bool counters) = 0;
virtual void
clearLedgerFetch() = 0;
virtual Json::Value
getLedgerFetchInfo() = 0;
/** Accepts the current transaction tree, return the new ledger's sequence
This API is only used via RPC with the server in STANDALONE mode and
performs a virtual consensus round, with all the transactions we are
proposing being accepted.
*/
virtual std::uint32_t
acceptLedger(std::optional<std::chrono::milliseconds> consensusDelay = std::nullopt) = 0;
virtual void
reportFeeChange() = 0;
virtual void
updateLocalTx(ReadView const& newValidLedger) = 0;
virtual std::size_t
getLocalTxCount() = 0;
//--------------------------------------------------------------------------
//
// Monitoring: publisher side
//
virtual void
pubLedger(std::shared_ptr<ReadView const> const& lpAccepted) = 0;
virtual void
pubProposedTransaction(
std::shared_ptr<ReadView const> const& ledger,
std::shared_ptr<STTx const> const& transaction,
TER result) = 0;
virtual void
pubValidation(std::shared_ptr<STValidation> const& val) = 0;
virtual void
stateAccounting(Json::Value& obj) = 0;
};
} // namespace xrpl

View File

@@ -0,0 +1,71 @@
#pragma once
#include <xrpl/protocol/Protocol.h>
#include <xrpl/rdb/DatabaseCon.h>
#include <xrpl/server/Manifest.h>
#include <boost/filesystem.hpp>
namespace xrpl {
struct SavedState
{
std::string writableDb;
std::string archiveDb;
LedgerIndex lastRotated;
};
/**
* @brief initStateDB Opens a session with the State database.
* @param session Provides a session with the database.
* @param config Path to the database and other opening parameters.
* @param dbName Name of the database.
*/
void
initStateDB(soci::session& session, BasicConfig const& config, std::string const& dbName);
/**
* @brief getCanDelete Returns the ledger sequence which can be deleted.
* @param session Session with the database.
* @return Ledger sequence.
*/
LedgerIndex
getCanDelete(soci::session& session);
/**
* @brief setCanDelete Updates the ledger sequence which can be deleted.
* @param session Session with the database.
* @param canDelete Ledger sequence to save.
* @return Previous value of the ledger sequence which can be deleted.
*/
LedgerIndex
setCanDelete(soci::session& session, LedgerIndex canDelete);
/**
* @brief getSavedState Returns the saved state.
* @param session Session with the database.
* @return The SavedState structure which contains the names of the writable
* database, the archive database and the last rotated ledger sequence.
*/
SavedState
getSavedState(soci::session& session);
/**
* @brief setSavedState Saves the given state.
* @param session Session with the database.
* @param state The SavedState structure which contains the names of the
* writable database, the archive database and the last rotated ledger
* sequence.
*/
void
setSavedState(soci::session& session, SavedState const& state);
/**
* @brief setLastRotated Updates the last rotated ledger sequence.
* @param session Session with the database.
* @param seq New value of the last rotated ledger sequence.
*/
void
setLastRotated(soci::session& session, LedgerIndex seq);
} // namespace xrpl

View File

@@ -0,0 +1,16 @@
#pragma once
#include <xrpl/rdb/DatabaseCon.h>
namespace xrpl {
/**
* @brief doVacuumDB Creates, initialises, and performs cleanup on a database.
* @param setup Path to the database and other opening parameters.
* @param j Journal.
* @return True if the vacuum process completed successfully.
*/
bool
doVacuumDB(DatabaseCon::Setup const& setup, beast::Journal j);
} // namespace xrpl

View File

@@ -0,0 +1,145 @@
#pragma once
#include <xrpl/core/PeerReservationTable.h>
#include <xrpl/rdb/DatabaseCon.h>
#include <xrpl/server/Manifest.h>
namespace xrpl {
/**
* @brief makeWalletDB Opens the wallet database and returns it.
* @param setup Path to the database and other opening parameters.
* @param j Journal.
* @return Unique pointer to the database descriptor.
*/
std::unique_ptr<DatabaseCon>
makeWalletDB(DatabaseCon::Setup const& setup, beast::Journal j);
/**
* @brief makeTestWalletDB Opens a test wallet database with an arbitrary name.
* @param setup Path to the database and other opening parameters.
* @param dbname Name of the database.
* @param j Journal.
* @return Unique pointer to the database descriptor.
*/
std::unique_ptr<DatabaseCon>
makeTestWalletDB(DatabaseCon::Setup const& setup, std::string const& dbname, beast::Journal j);
/**
* @brief getManifests Loads a manifest from the wallet database and stores it
* in the cache.
* @param session Session with the database.
* @param dbTable Name of the database table from which the manifest will be
* extracted.
* @param mCache Cache for storing the manifest.
* @param j Journal.
*/
void
getManifests(soci::session& session, std::string const& dbTable, ManifestCache& mCache, beast::Journal j);
/**
* @brief saveManifests Saves all given manifests to the database.
* @param session Session with the database.
* @param dbTable Name of the database table that will store the manifest.
* @param isTrusted Callback that returns true if the key is trusted.
* @param map Maps public keys to manifests.
* @param j Journal.
*/
void
saveManifests(
soci::session& session,
std::string const& dbTable,
std::function<bool(PublicKey const&)> const& isTrusted,
hash_map<PublicKey, Manifest> const& map,
beast::Journal j);
/**
* @brief addValidatorManifest Saves the manifest of a validator to the
* database.
* @param session Session with the database.
* @param serialized Manifest of the validator in raw format.
*/
void
addValidatorManifest(soci::session& session, std::string const& serialized);
/** Delete any saved public/private key associated with this node. */
void
clearNodeIdentity(soci::session& session);
/** Returns a stable public and private key for this node.
The node's public identity is defined by a secp256k1 keypair
that is (normally) randomly generated. This function will
return such a keypair, securely generating one if needed.
@param session Session with the database.
@return Pair of public and private secp256k1 keys.
*/
std::pair<PublicKey, SecretKey>
getNodeIdentity(soci::session& session);
/**
* @brief getPeerReservationTable Returns the peer reservation table.
* @param session Session with the database.
* @param j Journal.
* @return Peer reservation hash table.
*/
std::unordered_set<PeerReservation, beast::uhash<>, KeyEqual>
getPeerReservationTable(soci::session& session, beast::Journal j);
/**
* @brief insertPeerReservation Adds an entry to the peer reservation table.
* @param session Session with the database.
* @param nodeId Public key of the node.
* @param description Description of the node.
*/
void
insertPeerReservation(soci::session& session, PublicKey const& nodeId, std::string const& description);
/**
* @brief deletePeerReservation Deletes an entry from the peer reservation
* table.
* @param session Session with the database.
* @param nodeId Public key of the node to remove.
*/
void
deletePeerReservation(soci::session& session, PublicKey const& nodeId);
/**
* @brief createFeatureVotes Creates the FeatureVote table if it does not exist.
* @param session Session with the wallet database.
* @return true if the table already exists
*/
bool
createFeatureVotes(soci::session& session);
// For historical reasons the up-vote and down-vote integer representations
// are unintuitive.
enum class AmendmentVote : int { obsolete = -1, up = 0, down = 1 };
/**
* @brief readAmendments Reads all amendments from the FeatureVotes table.
* @param session Session with the wallet database.
* @param callback Callback called for each amendment with its hash, name and
* optionally a flag denoting whether the amendment should be vetoed.
*/
void
readAmendments(
soci::session& session,
std::function<void(
boost::optional<std::string> amendment_hash,
boost::optional<std::string> amendment_name,
boost::optional<AmendmentVote> vote)> const& callback);
/**
* @brief voteAmendment Set the veto value for a particular amendment.
* @param session Session with the wallet database.
* @param amendment Hash of the amendment.
* @param name Name of the amendment.
* @param vote Whether to vote in favor of this amendment.
*/
void
voteAmendment(soci::session& session, uint256 const& amendment, std::string const& name, AmendmentVote vote);
} // namespace xrpl

View File

@@ -218,7 +218,7 @@ void
BaseHTTPPeer<Handler, Impl>::close()
{
if (!strand_.running_in_this_thread())
return post(strand_, std::bind((void(BaseHTTPPeer::*)(void)) & BaseHTTPPeer::close, impl().shared_from_this()));
return post(strand_, std::bind((void (BaseHTTPPeer::*)(void))&BaseHTTPPeer::close, impl().shared_from_this()));
boost::beast::get_lowest_layer(impl().stream_).close();
}
@@ -436,7 +436,7 @@ BaseHTTPPeer<Handler, Impl>::close(bool graceful)
return post(
strand_,
std::bind(
(void(BaseHTTPPeer::*)(bool)) & BaseHTTPPeer<Handler, Impl>::close,
(void (BaseHTTPPeer::*)(bool))&BaseHTTPPeer<Handler, Impl>::close,
impl().shared_from_this(),
graceful));

View File

@@ -178,8 +178,9 @@ BaseWSPeer<Handler, Impl>::run()
impl().ws_.control_callback(control_callback_);
start_timer();
close_on_timer_ = true;
impl().ws_.set_option(boost::beast::websocket::stream_base::decorator(
[](auto& res) { res.set(boost::beast::http::field::server, BuildInfo::getFullVersionString()); }));
impl().ws_.set_option(boost::beast::websocket::stream_base::decorator([](auto& res) {
res.set(boost::beast::http::field::server, BuildInfo::getFullVersionString());
}));
impl().ws_.async_accept(
request_,
bind_executor(

View File

@@ -0,0 +1,129 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyViewImpl.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/XRPAmount.h>
#include <optional>
namespace xrpl {
/** State information when applying a tx. */
class ApplyContext
{
public:
explicit ApplyContext(
ServiceRegistry& registry,
OpenView& base,
std::optional<uint256 const> const& parentBatchId,
STTx const& tx,
TER preclaimResult,
XRPAmount baseFee,
ApplyFlags flags,
beast::Journal journal = beast::Journal{beast::Journal::getNullSink()});
explicit ApplyContext(
ServiceRegistry& registry,
OpenView& base,
STTx const& tx,
TER preclaimResult,
XRPAmount baseFee,
ApplyFlags flags,
beast::Journal journal = beast::Journal{beast::Journal::getNullSink()})
: ApplyContext(registry, base, std::nullopt, tx, preclaimResult, baseFee, flags, journal)
{
XRPL_ASSERT((flags & tapBATCH) == 0, "Batch apply flag should not be set");
}
ServiceRegistry& registry;
STTx const& tx;
TER const preclaimResult;
XRPAmount const baseFee;
beast::Journal const journal;
ApplyView&
view()
{
return *view_;
}
ApplyView const&
view() const
{
return *view_;
}
// VFALCO Unfortunately this is necessary
RawView&
rawView()
{
return *view_;
}
ApplyFlags const&
flags() const
{
return flags_;
}
/** Sets the DeliveredAmount field in the metadata */
void
deliver(STAmount const& amount)
{
view_->deliver(amount);
}
/** Discard changes and start fresh. */
void
discard();
/** Apply the transaction result to the base. */
std::optional<TxMeta> apply(TER);
/** Get the number of unapplied changes. */
std::size_t
size();
/** Visit unapplied changes. */
void
visit(
std::function<void(
uint256 const& key,
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after)> const& func);
void
destroyXRP(XRPAmount const& fee)
{
view_->rawDestroyXRP(fee);
}
/** Applies all invariant checkers one by one.
@param result the result generated by processing this transaction.
@param fee the fee charged for this transaction
@return the result code that should be returned for this transaction.
*/
TER
checkInvariants(TER const result, XRPAmount const fee);
private:
TER
failInvariantCheck(TER const result);
template <std::size_t... Is>
TER
checkInvariantsHelper(TER const result, XRPAmount const fee, std::index_sequence<Is...>);
OpenView& base_;
ApplyFlags flags_;
std::optional<ApplyViewImpl> view_;
// The ID of the batch transaction we are executing under, if seated.
std::optional<uint256 const> parentBatchId_;
};
} // namespace xrpl

View File

@@ -0,0 +1,723 @@
#pragma once
#include <xrpl/basics/Number.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <cstdint>
#include <tuple>
#include <unordered_set>
namespace xrpl {
class ReadView;
#if GENERATING_DOCS
/**
* @brief Prototype for invariant check implementations.
*
* __THIS CLASS DOES NOT EXIST__ - or rather it exists in documentation only to
* communicate the interface required of any invariant checker. Any invariant
* check implementation should implement the public methods documented here.
*
*/
class InvariantChecker_PROTOTYPE
{
public:
explicit InvariantChecker_PROTOTYPE() = default;
/**
* @brief called for each ledger entry in the current transaction.
*
* @param isDelete true if the SLE is being deleted
* @param before ledger entry before modification by the transaction
* @param after ledger entry after modification by the transaction
*/
void
visitEntry(bool isDelete, std::shared_ptr<SLE const> const& before, std::shared_ptr<SLE const> const& after);
/**
* @brief called after all ledger entries have been visited to determine
* the final status of the check
*
* @param tx the transaction being applied
* @param tec the current TER result of the transaction
* @param fee the fee actually charged for this transaction
* @param view a ReadView of the ledger being modified
* @param j journal for logging
*
* @return true if check passes, false if it fails
*/
bool
finalize(STTx const& tx, TER const tec, XRPAmount const fee, ReadView const& view, beast::Journal const& j);
};
#endif
/**
* @brief Invariant: We should never charge a transaction a negative fee or a
* fee that is larger than what the transaction itself specifies.
*
* We can, in some circumstances, charge less.
*/
class TransactionFeeCheck
{
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
/**
* @brief Invariant: A transaction must not create XRP and should only destroy
* the XRP fee.
*
* We iterate through all account roots, payment channels and escrow entries
* that were modified and calculate the net change in XRP caused by the
* transactions.
*/
class XRPNotCreated
{
std::int64_t drops_ = 0;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
/**
* @brief Invariant: we cannot remove an account ledger entry
*
* We iterate all account roots that were modified, and ensure that any that
* were present before the transaction was applied continue to be present
* afterwards unless they were explicitly deleted by a successful
* AccountDelete transaction.
*/
class AccountRootsNotDeleted
{
std::uint32_t accountsDeleted_ = 0;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
/**
* @brief Invariant: a deleted account must not have any objects left
*
* We iterate all deleted account roots, and ensure that there are no
* objects left that are directly accessible with that account's ID.
*
* There should only be one deleted account, but that's checked by
* AccountRootsNotDeleted. This invariant will handle multiple deleted account
* roots without a problem.
*/
class AccountRootsDeletedClean
{
// Pair is <before, after>. Before is used for most of the checks, so that
// if, for example, an object ID field is cleared, but the object is not
// deleted, it can still be found. After is used specifically for any checks
// that are expected as part of the deletion, such as zeroing out the
// balance.
std::vector<std::pair<std::shared_ptr<SLE const>, std::shared_ptr<SLE const>>> accountsDeleted_;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
/**
* @brief Invariant: An account XRP balance must be in XRP and take a value
* between 0 and INITIAL_XRP drops, inclusive.
*
* We iterate all account roots modified by the transaction and ensure that
* their XRP balances are reasonable.
*/
class XRPBalanceChecks
{
bool bad_ = false;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
/**
* @brief Invariant: corresponding modified ledger entries should match in type
* and added entries should be a valid type.
*/
class LedgerEntryTypesMatch
{
bool typeMismatch_ = false;
bool invalidTypeAdded_ = false;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
/**
* @brief Invariant: Trust lines using XRP are not allowed.
*
* We iterate all the trust lines created by this transaction and ensure
* that they are against a valid issuer.
*/
class NoXRPTrustLines
{
bool xrpTrustLine_ = false;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
/**
* @brief Invariant: Trust lines with deep freeze flag are not allowed if normal
* freeze flag is not set.
*
* We iterate all the trust lines created by this transaction and ensure
* that they don't have deep freeze flag set without normal freeze flag set.
*/
class NoDeepFreezeTrustLinesWithoutFreeze
{
bool deepFreezeWithoutFreeze_ = false;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
/**
* @brief Invariant: frozen trust line balance change is not allowed.
*
* We iterate all affected trust lines and ensure that they don't have
* unexpected change of balance if they're frozen.
*/
class TransfersNotFrozen
{
struct BalanceChange
{
std::shared_ptr<SLE const> const line;
int const balanceChangeSign;
};
struct IssuerChanges
{
std::vector<BalanceChange> senders;
std::vector<BalanceChange> receivers;
};
using ByIssuer = std::map<Issue, IssuerChanges>;
ByIssuer balanceChanges_;
std::map<AccountID, std::shared_ptr<SLE const> const> possibleIssuers_;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
private:
bool
isValidEntry(std::shared_ptr<SLE const> const& before, std::shared_ptr<SLE const> const& after);
STAmount
calculateBalanceChange(
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after,
bool isDelete);
void
recordBalance(Issue const& issue, BalanceChange change);
void
recordBalanceChanges(std::shared_ptr<SLE const> const& after, STAmount const& balanceChange);
std::shared_ptr<SLE const>
findIssuer(AccountID const& issuerID, ReadView const& view);
bool
validateIssuerChanges(
std::shared_ptr<SLE const> const& issuer,
IssuerChanges const& changes,
STTx const& tx,
beast::Journal const& j,
bool enforce);
bool
validateFrozenState(
BalanceChange const& change,
bool high,
STTx const& tx,
beast::Journal const& j,
bool enforce,
bool globalFreeze);
};
/**
* @brief Invariant: offers should be for non-negative amounts and must not
* be XRP to XRP.
*
* Examine all offers modified by the transaction and ensure that there are
* no offers which contain negative amounts or which exchange XRP for XRP.
*/
class NoBadOffers
{
bool bad_ = false;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
/**
* @brief Invariant: an escrow entry must take a value between 0 and
* INITIAL_XRP drops exclusive.
*/
class NoZeroEscrow
{
bool bad_ = false;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
/**
* @brief Invariant: a new account root must be the consequence of a payment,
* must have the right starting sequence, and the payment
* may not create more than one new account root.
*/
class ValidNewAccountRoot
{
std::uint32_t accountsCreated_ = 0;
std::uint32_t accountSeq_ = 0;
bool pseudoAccount_ = false;
std::uint32_t flags_ = 0;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
/**
* @brief Invariant: Validates several invariants for NFToken pages.
*
* The following checks are made:
* - The page is correctly associated with the owner.
* - The page is correctly ordered between the next and previous links.
* - The page contains at least one and no more than 32 NFTokens.
* - The NFTokens on this page do not belong on a lower or higher page.
* - The NFTokens are correctly sorted on the page.
* - Each URI, if present, is not empty.
*/
class ValidNFTokenPage
{
bool badEntry_ = false;
bool badLink_ = false;
bool badSort_ = false;
bool badURI_ = false;
bool invalidSize_ = false;
bool deletedFinalPage_ = false;
bool deletedLink_ = false;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
/**
* @brief Invariant: Validates counts of NFTokens after all transaction types.
*
* The following checks are made:
* - The number of minted or burned NFTokens can only be changed by
* NFTokenMint or NFTokenBurn transactions.
* - A successful NFTokenMint must increase the number of NFTokens.
* - A failed NFTokenMint must not change the number of minted NFTokens.
* - An NFTokenMint transaction cannot change the number of burned NFTokens.
* - A successful NFTokenBurn must increase the number of burned NFTokens.
* - A failed NFTokenBurn must not change the number of burned NFTokens.
* - An NFTokenBurn transaction cannot change the number of minted NFTokens.
*/
class NFTokenCountTracking
{
std::uint32_t beforeMintedTotal = 0;
std::uint32_t beforeBurnedTotal = 0;
std::uint32_t afterMintedTotal = 0;
std::uint32_t afterBurnedTotal = 0;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
/**
* @brief Invariant: Token holder's trustline balance cannot be negative after
* Clawback.
*
* We iterate all the trust lines affected by this transaction and ensure
* that no more than one trustline is modified, and also holder's balance is
* non-negative.
*/
class ValidClawback
{
std::uint32_t trustlinesChanged = 0;
std::uint32_t mptokensChanged = 0;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
class ValidMPTIssuance
{
std::uint32_t mptIssuancesCreated_ = 0;
std::uint32_t mptIssuancesDeleted_ = 0;
std::uint32_t mptokensCreated_ = 0;
std::uint32_t mptokensDeleted_ = 0;
// non-MPT transactions may attempt to create
// MPToken by an issuer
bool mptCreatedByIssuer_ = false;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
/**
* @brief Invariants: Permissioned Domains must have some rules and
* AcceptedCredentials must have length between 1 and 10 inclusive.
*
* Since only permissions constitute rules, an empty credentials list
* means that there are no rules and the invariant is violated.
*
* Credentials must be sorted and no duplicates allowed
*
*/
class ValidPermissionedDomain
{
struct SleStatus
{
std::size_t credentialsSize_{0};
bool isSorted_ = false;
bool isUnique_ = false;
bool isDelete_ = false;
};
std::vector<SleStatus> sleStatus_;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
/**
* @brief Invariants: Pseudo-accounts have valid and consistent properties
*
* Pseudo-accounts have certain properties, and some of those properties are
* unique to pseudo-accounts. Check that all pseudo-accounts are following the
* rules, and that only pseudo-accounts look like pseudo-accounts.
*
*/
class ValidPseudoAccounts
{
std::vector<std::string> errors_;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
class ValidPermissionedDEX
{
bool regularOffers_ = false;
bool badHybrids_ = false;
hash_set<uint256> domains_;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
class ValidAMM
{
std::optional<AccountID> ammAccount_;
std::optional<STAmount> lptAMMBalanceAfter_;
std::optional<STAmount> lptAMMBalanceBefore_;
bool ammPoolChanged_;
public:
enum class ZeroAllowed : bool { No = false, Yes = true };
ValidAMM() : ammPoolChanged_{false}
{
}
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
private:
bool
finalizeBid(bool enforce, beast::Journal const&) const;
bool
finalizeVote(bool enforce, beast::Journal const&) const;
bool
finalizeCreate(STTx const&, ReadView const&, bool enforce, beast::Journal const&) const;
bool
finalizeDelete(bool enforce, TER res, beast::Journal const&) const;
bool
finalizeDeposit(STTx const&, ReadView const&, bool enforce, beast::Journal const&) const;
// Includes clawback
bool
finalizeWithdraw(STTx const&, ReadView const&, bool enforce, beast::Journal const&) const;
bool
finalizeDEX(bool enforce, beast::Journal const&) const;
bool
generalInvariant(STTx const&, ReadView const&, ZeroAllowed zeroAllowed, beast::Journal const&) const;
};
/**
* @brief Invariants: Some fields are unmodifiable
*
* Check that any fields specified as unmodifiable are not modified when the
* object is modified. Creation and deletion are ignored.
*
*/
class NoModifiedUnmodifiableFields
{
// Pair is <before, after>.
std::set<std::pair<SLE::const_pointer, SLE::const_pointer>> changedEntries_;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
/**
* @brief Invariants: Loan brokers are internally consistent
*
* 1. If `LoanBroker.OwnerCount = 0` the `DirectoryNode` will have at most one
* node (the root), which will only hold entries for `RippleState` or
* `MPToken` objects.
*
*/
class ValidLoanBroker
{
// Not all of these elements will necessarily be populated. Remaining items
// will be looked up as needed.
struct BrokerInfo
{
SLE::const_pointer brokerBefore = nullptr;
// After is used for most of the checks, except
// those that check changed values.
SLE::const_pointer brokerAfter = nullptr;
};
// Collect all the LoanBrokers found directly or indirectly through
// pseudo-accounts. Key is the brokerID / index. It will be used to find the
// LoanBroker object if brokerBefore and brokerAfter are nullptr
std::map<uint256, BrokerInfo> brokers_;
// Collect all the modified trust lines. Their high and low accounts will be
// loaded to look for LoanBroker pseudo-accounts.
std::vector<SLE::const_pointer> lines_;
// Collect all the modified MPTokens. Their accounts will be loaded to look
// for LoanBroker pseudo-accounts.
std::vector<SLE::const_pointer> mpts_;
bool
goodZeroDirectory(ReadView const& view, SLE::const_ref dir, beast::Journal const& j) const;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
/**
* @brief Invariants: Loans are internally consistent
*
* 1. If `Loan.PaymentRemaining = 0` then `Loan.PrincipalOutstanding = 0`
*
*/
class ValidLoan
{
// Pair is <before, after>. After is used for most of the checks, except
// those that check changed values.
std::vector<std::pair<SLE::const_pointer, SLE::const_pointer>> loans_;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
/*
* @brief Invariants: Vault object and MPTokenIssuance for vault shares
*
* - vault deleted and vault created is empty
* - vault created must be linked to pseudo-account for shares and assets
* - vault must have MPTokenIssuance for shares
* - vault without shares outstanding must have no shares
* - loss unrealized does not exceed the difference between assets total and
* assets available
* - assets available do not exceed assets total
* - vault deposit increases assets and share issuance, and adds to:
* total assets, assets available, shares outstanding
* - vault withdrawal and clawback reduce assets and share issuance, and
* subtracts from: total assets, assets available, shares outstanding
* - vault set must not alter the vault assets or shares balance
* - no vault transaction can change loss unrealized (it's updated by loan
* transactions)
*
*/
class ValidVault
{
Number static constexpr zero{};
struct Vault final
{
uint256 key = beast::zero;
Asset asset = {};
AccountID pseudoId = {};
AccountID owner = {};
uint192 shareMPTID = beast::zero;
Number assetsTotal = 0;
Number assetsAvailable = 0;
Number assetsMaximum = 0;
Number lossUnrealized = 0;
Vault static make(SLE const&);
};
struct Shares final
{
MPTIssue share = {};
std::uint64_t sharesTotal = 0;
std::uint64_t sharesMaximum = 0;
Shares static make(SLE const&);
};
std::vector<Vault> afterVault_ = {};
std::vector<Shares> afterMPTs_ = {};
std::vector<Vault> beforeVault_ = {};
std::vector<Shares> beforeMPTs_ = {};
std::unordered_map<uint256, Number> deltas_ = {};
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
// additional invariant checks can be declared above and then added to this
// tuple
using InvariantChecks = std::tuple<
TransactionFeeCheck,
AccountRootsNotDeleted,
AccountRootsDeletedClean,
LedgerEntryTypesMatch,
XRPBalanceChecks,
XRPNotCreated,
NoXRPTrustLines,
NoDeepFreezeTrustLinesWithoutFreeze,
TransfersNotFrozen,
NoBadOffers,
NoZeroEscrow,
ValidNewAccountRoot,
ValidNFTokenPage,
NFTokenCountTracking,
ValidClawback,
ValidMPTIssuance,
ValidPermissionedDomain,
ValidPermissionedDEX,
ValidAMM,
NoModifiedUnmodifiableFields,
ValidPseudoAccounts,
ValidLoanBroker,
ValidLoan,
ValidVault>;
/**
* @brief get a tuple of all invariant checks
*
* @return std::tuple of instances that implement the required invariant check
* methods
*
* @see xrpl::InvariantChecker_PROTOTYPE
*/
inline InvariantChecks
getInvariantChecks()
{
return InvariantChecks{};
}
} // namespace xrpl

View File

@@ -0,0 +1,64 @@
#pragma once
#include <xrpl/basics/Expected.h> //
#include <xrpl/beast/utility/Journal.h> // beast::Journal
#include <xrpl/protocol/TER.h> // temMALFORMED
#include <xrpl/protocol/UintTypes.h> // AccountID
#include <xrpl/tx/Transactor.h> // NotTEC
#include <optional>
#include <string_view>
namespace xrpl {
// Forward declarations
class STObject;
// Support for SignerEntries that is needed by a few Transactors.
//
// SignerEntries is represented as a std::vector<SignerEntries::SignerEntry>.
// There is no direct constructor for SignerEntries.
//
// o A std::vector<SignerEntries::SignerEntry> is a SignerEntries.
// o More commonly, SignerEntries are extracted from an STObject by
// calling SignerEntries::deserialize().
class SignerEntries
{
public:
explicit SignerEntries() = delete;
struct SignerEntry
{
AccountID account;
std::uint16_t weight;
std::optional<uint256> tag;
SignerEntry(AccountID const& inAccount, std::uint16_t inWeight, std::optional<uint256> inTag)
: account(inAccount), weight(inWeight), tag(inTag)
{
}
// For sorting to look for duplicate accounts
friend bool
operator<(SignerEntry const& lhs, SignerEntry const& rhs)
{
return lhs.account < rhs.account;
}
friend bool
operator==(SignerEntry const& lhs, SignerEntry const& rhs)
{
return lhs.account == rhs.account;
}
};
// Deserialize a SignerEntries array from the network or from the ledger.
//
// obj Contains a SignerEntries field that is an STArray.
// journal For reporting error conditions.
// annotation Source of SignerEntries, like "ledger" or "transaction".
static Expected<std::vector<SignerEntry>, NotTEC>
deserialize(STObject const& obj, beast::Journal journal, std::string_view annotation);
};
} // namespace xrpl

View File

@@ -0,0 +1,419 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/WrappedSink.h>
#include <xrpl/protocol/Permissions.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/ApplyContext.h>
#include <xrpl/tx/applySteps.h>
namespace xrpl {
/** State information when preflighting a tx. */
struct PreflightContext
{
public:
ServiceRegistry& registry;
STTx const& tx;
Rules const rules;
ApplyFlags flags;
std::optional<uint256 const> parentBatchId;
beast::Journal const j;
PreflightContext(
ServiceRegistry& registry_,
STTx const& tx_,
uint256 parentBatchId_,
Rules const& rules_,
ApplyFlags flags_,
beast::Journal j_ = beast::Journal{beast::Journal::getNullSink()})
: registry(registry_), tx(tx_), rules(rules_), flags(flags_), parentBatchId(parentBatchId_), j(j_)
{
XRPL_ASSERT((flags_ & tapBATCH) == tapBATCH, "Batch apply flag should be set");
}
PreflightContext(
ServiceRegistry& registry_,
STTx const& tx_,
Rules const& rules_,
ApplyFlags flags_,
beast::Journal j_ = beast::Journal{beast::Journal::getNullSink()})
: registry(registry_), tx(tx_), rules(rules_), flags(flags_), j(j_)
{
XRPL_ASSERT((flags_ & tapBATCH) == 0, "Batch apply flag should not be set");
}
PreflightContext&
operator=(PreflightContext const&) = delete;
};
/** State information when determining if a tx is likely to claim a fee. */
struct PreclaimContext
{
public:
ServiceRegistry& registry;
ReadView const& view;
TER preflightResult;
ApplyFlags flags;
STTx const& tx;
std::optional<uint256 const> const parentBatchId;
beast::Journal const j;
PreclaimContext(
ServiceRegistry& registry_,
ReadView const& view_,
TER preflightResult_,
STTx const& tx_,
ApplyFlags flags_,
std::optional<uint256> parentBatchId_,
beast::Journal j_ = beast::Journal{beast::Journal::getNullSink()})
: registry(registry_)
, view(view_)
, preflightResult(preflightResult_)
, flags(flags_)
, tx(tx_)
, parentBatchId(parentBatchId_)
, j(j_)
{
XRPL_ASSERT(
parentBatchId.has_value() == ((flags_ & tapBATCH) == tapBATCH),
"Parent Batch ID should be set if batch apply flag is set");
}
PreclaimContext(
ServiceRegistry& registry_,
ReadView const& view_,
TER preflightResult_,
STTx const& tx_,
ApplyFlags flags_,
beast::Journal j_ = beast::Journal{beast::Journal::getNullSink()})
: PreclaimContext(registry_, view_, preflightResult_, tx_, flags_, std::nullopt, j_)
{
XRPL_ASSERT((flags_ & tapBATCH) == 0, "Batch apply flag should not be set");
}
PreclaimContext&
operator=(PreclaimContext const&) = delete;
};
class TxConsequences;
struct PreflightResult;
// Needed for preflight specialization
class Change;
class Transactor
{
protected:
ApplyContext& ctx_;
beast::WrappedSink sink_;
beast::Journal const j_;
AccountID const account_;
XRPAmount mPriorBalance; // Balance before fees.
XRPAmount mSourceBalance; // Balance after fees.
virtual ~Transactor() = default;
Transactor(Transactor const&) = delete;
Transactor&
operator=(Transactor const&) = delete;
public:
enum ConsequencesFactoryType { Normal, Blocker, Custom };
/** Process the transaction. */
ApplyResult
operator()();
ApplyView&
view()
{
return ctx_.view();
}
ApplyView const&
view() const
{
return ctx_.view();
}
/////////////////////////////////////////////////////
/*
These static functions are called from invoke_preclaim<Tx>
using name hiding to accomplish compile-time polymorphism,
so derived classes can override for different or extra
functionality. Use with care, as these are not really
virtual and so don't have the compiler-time protection that
comes with it.
*/
static NotTEC
checkSeqProxy(ReadView const& view, STTx const& tx, beast::Journal j);
static NotTEC
checkPriorTxAndLastLedger(PreclaimContext const& ctx);
static TER
checkFee(PreclaimContext const& ctx, XRPAmount baseFee);
static NotTEC
checkSign(PreclaimContext const& ctx);
static NotTEC
checkBatchSign(PreclaimContext const& ctx);
// Returns the fee in fee units, not scaled for load.
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);
/* Do NOT define an invokePreflight function in a derived class.
Instead, define:
// Optional if the transaction is gated on an amendment that
// isn't specified in transactions.macro
static bool
checkExtraFeatures(PreflightContext const& ctx);
// Optional if the transaction uses any flags other than tfUniversal
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
// Required, even if it just returns tesSUCCESS.
static NotTEC
preflight(PreflightContext const& ctx);
// Optional, rarely needed, if the transaction does any expensive
// checks after the signature is verified.
static NotTEC preflightSigValidated(PreflightContext const& ctx);
* Do not try to call preflight1 or preflight2 directly.
* Do not check whether relevant amendments are enabled in preflight.
Instead, define checkExtraFeatures.
* Do not check flags in preflight. Instead, define getFlagsMask.
*/
template <class T>
static NotTEC
invokePreflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx)
{
// Most transactors do nothing
// after checkSeq/Fee/Sign.
return tesSUCCESS;
}
static NotTEC
checkPermission(ReadView const& view, STTx const& tx);
/////////////////////////////////////////////////////
// Interface used by DeleteAccount
static TER
ticketDelete(ApplyView& view, AccountID const& account, uint256 const& ticketIndex, beast::Journal j);
protected:
TER
apply();
explicit Transactor(ApplyContext& ctx);
virtual void
preCompute();
virtual TER
doApply() = 0;
/** Compute the minimum fee required to process a transaction
with a given baseFee based on the current server load.
@param registry The service registry.
@param baseFee The base fee of a candidate transaction
@see xrpl::calculateBaseFee
@param fees Fee settings from the current ledger
@param flags Transaction processing fees
*/
static XRPAmount
minimumFee(ServiceRegistry& registry, XRPAmount baseFee, Fees const& fees, ApplyFlags flags);
// Returns the fee in fee units, not scaled for load.
static XRPAmount
calculateOwnerReserveFee(ReadView const& view, STTx const& tx);
static NotTEC
checkSign(
ReadView const& view,
ApplyFlags flags,
std::optional<uint256 const> const& parentBatchId,
AccountID const& idAccount,
STObject const& sigObject,
beast::Journal const j);
// Base class always returns true
static bool
checkExtraFeatures(PreflightContext const& ctx);
// Base class always returns tfUniversalMask
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
// Base class always returns tesSUCCESS
static NotTEC
preflightSigValidated(PreflightContext const& ctx);
static bool
validDataLength(std::optional<Slice> const& slice, std::size_t maxLength);
template <class T>
static bool
validNumericRange(std::optional<T> value, T max, T min = T{});
template <class T, class Unit>
static bool
validNumericRange(
std::optional<T> value,
unit::ValueUnit<Unit, T> max,
unit::ValueUnit<Unit, T> min = unit::ValueUnit<Unit, T>{});
/// Minimum will usually be zero.
template <class T>
static bool
validNumericMinimum(std::optional<T> value, T min = T{});
/// Minimum will usually be zero.
template <class T, class Unit>
static bool
validNumericMinimum(std::optional<T> value, unit::ValueUnit<Unit, T> min = unit::ValueUnit<Unit, T>{});
private:
std::pair<TER, XRPAmount>
reset(XRPAmount fee);
TER
consumeSeqProxy(SLE::pointer const& sleAccount);
TER
payFee();
static NotTEC
checkSingleSign(
ReadView const& view,
AccountID const& idSigner,
AccountID const& idAccount,
std::shared_ptr<SLE const> sleAccount,
beast::Journal const j);
static NotTEC
checkMultiSign(
ReadView const& view,
ApplyFlags flags,
AccountID const& id,
STObject const& sigObject,
beast::Journal const j);
void trapTransaction(uint256) const;
/** Performs early sanity checks on the account and fee fields.
(And passes flagMask to preflight0)
Do not try to call preflight1 from preflight() in derived classes. See
the description of invokePreflight for details.
*/
static NotTEC
preflight1(PreflightContext const& ctx, std::uint32_t flagMask);
/** Checks whether the signature appears valid
Do not try to call preflight2 from preflight() in derived classes. See
the description of invokePreflight for details.
*/
static NotTEC
preflight2(PreflightContext const& ctx);
};
inline bool
Transactor::checkExtraFeatures(PreflightContext const& ctx)
{
return true;
}
/** Performs early sanity checks on the txid and flags */
NotTEC
preflight0(PreflightContext const& ctx, std::uint32_t flagMask);
namespace detail {
/** Checks the validity of the transactor signing key.
*
* Normally called from preflight1 with ctx.tx.
*/
NotTEC
preflightCheckSigningKey(STObject const& sigObject, beast::Journal j);
/** Checks the special signing key state needed for simulation
*
* Normally called from preflight2 with ctx.tx.
*/
std::optional<NotTEC>
preflightCheckSimulateKeys(ApplyFlags flags, STObject const& sigObject, beast::Journal j);
} // namespace detail
// Defined in Change.cpp
template <>
NotTEC
Transactor::invokePreflight<Change>(PreflightContext const& ctx);
template <class T>
NotTEC
Transactor::invokePreflight(PreflightContext const& ctx)
{
// Using this lookup does NOT require checking the fixDelegateV1_1. The data
// exists regardless of whether it is enabled.
auto const feature = Permission::getInstance().getTxFeature(ctx.tx.getTxnType());
if (feature && !ctx.rules.enabled(*feature))
return temDISABLED;
if (!T::checkExtraFeatures(ctx))
return temDISABLED;
if (auto const ret = preflight1(ctx, T::getFlagsMask(ctx)))
return ret;
if (auto const ret = T::preflight(ctx))
return ret;
if (auto const ret = preflight2(ctx))
return ret;
return T::preflightSigValidated(ctx);
}
template <class T>
bool
Transactor::validNumericRange(std::optional<T> value, T max, T min)
{
if (!value)
return true;
return value >= min && value <= max;
}
template <class T, class Unit>
bool
Transactor::validNumericRange(std::optional<T> value, unit::ValueUnit<Unit, T> max, unit::ValueUnit<Unit, T> min)
{
return validNumericRange(value, max.value(), min.value());
}
template <class T>
bool
Transactor::validNumericMinimum(std::optional<T> value, T min)
{
if (!value)
return true;
return value >= min;
}
template <class T, class Unit>
bool
Transactor::validNumericMinimum(std::optional<T> value, unit::ValueUnit<Unit, T> min)
{
return validNumericMinimum(value, min.value());
}
} // namespace xrpl

129
include/xrpl/tx/apply.h Normal file
View File

@@ -0,0 +1,129 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/View.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/tx/applySteps.h>
#include <utility>
namespace xrpl {
class HashRouter;
class ServiceRegistry;
/** Describes the pre-processing validity of a transaction.
@see checkValidity, forceValidity
*/
enum class Validity {
/// Signature is bad. Didn't do local checks.
SigBad,
/// Signature is good, but local checks fail.
SigGoodOnly,
/// Signature and local checks are good / passed.
Valid
};
/** Checks transaction signature and local checks.
@return A `Validity` enum representing how valid the
`STTx` is and, if not `Valid`, a reason string.
@note Results are cached internally, so tests will not be
repeated over repeated calls, unless cache expires.
@return `std::pair`, where `.first` is the status, and
`.second` is the reason if appropriate.
@see Validity
*/
std::pair<Validity, std::string>
checkValidity(HashRouter& router, STTx const& tx, Rules const& rules);
/** Sets the validity of a given transaction in the cache.
@warning Use with extreme care.
@note Can only raise the validity to a more valid state,
and can not override anything cached bad.
@see checkValidity, Validity
*/
void
forceValidity(HashRouter& router, uint256 const& txid, Validity validity);
/** Apply a transaction to an `OpenView`.
This function is the canonical way to apply a transaction
to a ledger. It rolls the validation and application
steps into one function. To do the steps manually, the
correct calling order is:
@code{.cpp}
preflight -> preclaim -> doApply
@endcode
The result of one function must be passed to the next.
The `preflight` result can be safely cached and reused
asynchronously, but `preclaim` and `doApply` must be called
in the same thread and with the same view.
@note Does not throw.
For open ledgers, the `Transactor` will catch exceptions
and return `tefEXCEPTION`. For closed ledgers, the
`Transactor` will attempt to only charge a fee,
and return `tecFAILED_PROCESSING`.
If the `Transactor` gets an exception while trying
to charge the fee, it will be caught and
turned into `tefEXCEPTION`.
For network health, a `Transactor` makes its
best effort to at least charge a fee if the
ledger is closed.
@param app The current running `Application`.
@param view The open ledger that the transaction
will attempt to be applied to.
@param tx The transaction to be checked.
@param flags `ApplyFlags` describing processing options.
@param journal A journal.
@see preflight, preclaim, doApply
@return A pair with the `TER` and a `bool` indicating
whether or not the transaction was applied.
*/
ApplyResult
apply(ServiceRegistry& registry, OpenView& view, STTx const& tx, ApplyFlags flags, beast::Journal journal);
/** Enum class for return value from `applyTransaction`
@see applyTransaction
*/
enum class ApplyTransactionResult {
/// Applied to this ledger
Success,
/// Should not be retried in this ledger
Fail,
/// Should be retried in this ledger
Retry
};
/** Transaction application helper
Provides more detailed logging and decodes the
correct behavior based on the `TER` type
@see ApplyTransactionResult
*/
ApplyTransactionResult
applyTransaction(
ServiceRegistry& registry,
OpenView& view,
STTx const& tx,
bool retryAssured,
ApplyFlags flags,
beast::Journal journal);
} // namespace xrpl

View File

@@ -0,0 +1,338 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyViewImpl.h>
namespace xrpl {
class ServiceRegistry;
class STTx;
class TxQ;
struct ApplyResult
{
TER ter;
bool applied;
std::optional<TxMeta> metadata;
ApplyResult(TER t, bool a, std::optional<TxMeta> m = std::nullopt) : ter(t), applied(a), metadata(std::move(m))
{
}
};
/** Return true if the transaction can claim a fee (tec),
and the `ApplyFlags` do not allow soft failures.
*/
inline bool
isTecClaimHardFail(TER ter, ApplyFlags flags)
{
return isTecClaim(ter) && !(flags & tapRETRY);
}
/** Class describing the consequences to the account
of applying a transaction if the transaction consumes
the maximum XRP allowed.
*/
class TxConsequences
{
public:
/// Describes how the transaction affects subsequent
/// transactions
enum Category {
/// Moves currency around, creates offers, etc.
normal = 0,
/// Affects the ability of subsequent transactions
/// to claim a fee. Eg. `SetRegularKey`
blocker
};
private:
/// Describes how the transaction affects subsequent
/// transactions
bool isBlocker_;
/// Transaction fee
XRPAmount fee_;
/// Does NOT include the fee.
XRPAmount potentialSpend_;
/// SeqProxy of transaction.
SeqProxy seqProx_;
/// Number of sequences consumed.
std::uint32_t sequencesConsumed_;
public:
// Constructor if preflight returns a value other than tesSUCCESS.
// Asserts if tesSUCCESS is passed.
explicit TxConsequences(NotTEC pfResult);
/// Constructor if the STTx has no notable consequences for the TxQ.
explicit TxConsequences(STTx const& tx);
/// Constructor for a blocker.
TxConsequences(STTx const& tx, Category category);
/// Constructor for an STTx that may consume more XRP than the fee.
TxConsequences(STTx const& tx, XRPAmount potentialSpend);
/// Constructor for an STTx that consumes more than the usual sequences.
TxConsequences(STTx const& tx, std::uint32_t sequencesConsumed);
/// Copy constructor
TxConsequences(TxConsequences const&) = default;
/// Copy assignment operator
TxConsequences&
operator=(TxConsequences const&) = default;
/// Move constructor
TxConsequences(TxConsequences&&) = default;
/// Move assignment operator
TxConsequences&
operator=(TxConsequences&&) = default;
/// Fee
XRPAmount
fee() const
{
return fee_;
}
/// Potential Spend
XRPAmount const&
potentialSpend() const
{
return potentialSpend_;
}
/// SeqProxy
SeqProxy
seqProxy() const
{
return seqProx_;
}
/// Sequences consumed
std::uint32_t
sequencesConsumed() const
{
return sequencesConsumed_;
}
/// Returns true if the transaction is a blocker.
bool
isBlocker() const
{
return isBlocker_;
}
// Return the SeqProxy that would follow this.
SeqProxy
followingSeq() const
{
SeqProxy following = seqProx_;
following.advanceBy(sequencesConsumed());
return following;
}
};
/** Describes the results of the `preflight` check
@note All members are const to make it more difficult
to "fake" a result without calling `preflight`.
@see preflight, preclaim, doApply, apply
*/
struct PreflightResult
{
public:
/// From the input - the transaction
STTx const& tx;
/// From the input - the batch identifier, if part of a batch
std::optional<uint256 const> const parentBatchId;
/// From the input - the rules
Rules const rules;
/// Consequences of the transaction
TxConsequences const consequences;
/// From the input - the flags
ApplyFlags const flags;
/// From the input - the journal
beast::Journal const j;
/// Intermediate transaction result
NotTEC const ter;
/// Constructor
template <class Context>
PreflightResult(Context const& ctx_, std::pair<NotTEC, TxConsequences> const& result)
: tx(ctx_.tx)
, parentBatchId(ctx_.parentBatchId)
, rules(ctx_.rules)
, consequences(result.second)
, flags(ctx_.flags)
, j(ctx_.j)
, ter(result.first)
{
}
PreflightResult(PreflightResult const&) = default;
/// Deleted copy assignment operator
PreflightResult&
operator=(PreflightResult const&) = delete;
};
/** Describes the results of the `preclaim` check
@note All members are const to make it more difficult
to "fake" a result without calling `preclaim`.
@see preflight, preclaim, doApply, apply
*/
struct PreclaimResult
{
public:
/// From the input - the ledger view
ReadView const& view;
/// From the input - the transaction
STTx const& tx;
/// From the input - the batch identifier, if part of a batch
std::optional<uint256 const> const parentBatchId;
/// From the input - the flags
ApplyFlags const flags;
/// From the input - the journal
beast::Journal const j;
/// Intermediate transaction result
TER const ter;
/// Success flag - whether the transaction is likely to
/// claim a fee
bool const likelyToClaimFee;
/// Constructor
template <class Context>
PreclaimResult(Context const& ctx_, TER ter_)
: view(ctx_.view)
, tx(ctx_.tx)
, parentBatchId(ctx_.parentBatchId)
, flags(ctx_.flags)
, j(ctx_.j)
, ter(ter_)
, likelyToClaimFee(ter == tesSUCCESS || isTecClaimHardFail(ter, flags))
{
}
PreclaimResult(PreclaimResult const&) = default;
/// Deleted copy assignment operator
PreclaimResult&
operator=(PreclaimResult const&) = delete;
};
/** Gate a transaction based on static information.
The transaction is checked against all possible
validity constraints that do not require a ledger.
@param app The current running `Application`.
@param rules The `Rules` in effect at the time of the check.
@param tx The transaction to be checked.
@param flags `ApplyFlags` describing processing options.
@param j A journal.
@see PreflightResult, preclaim, doApply, apply
@return A `PreflightResult` object containing, among
other things, the `TER` code.
*/
/** @{ */
PreflightResult
preflight(ServiceRegistry& registry, Rules const& rules, STTx const& tx, ApplyFlags flags, beast::Journal j);
PreflightResult
preflight(
ServiceRegistry& registry,
Rules const& rules,
uint256 const& parentBatchId,
STTx const& tx,
ApplyFlags flags,
beast::Journal j);
/** @} */
/** Gate a transaction based on static ledger information.
The transaction is checked against all possible
validity constraints that DO require a ledger.
If preclaim succeeds, then the transaction is very
likely to claim a fee. This will determine if the
transaction is safe to relay without being applied
to the open ledger.
"Succeeds" in this case is defined as returning a
`tes` or `tec`, since both lead to claiming a fee.
@pre The transaction has been checked
and validated using `preflight`
@param preflightResult The result of a previous
call to `preflight` for the transaction.
@param app The current running `Application`.
@param view The open ledger that the transaction
will attempt to be applied to.
@see PreclaimResult, preflight, doApply, apply
@return A `PreclaimResult` object containing, among
other things the `TER` code and the base fee value for
this transaction.
*/
PreclaimResult
preclaim(PreflightResult const& preflightResult, ServiceRegistry& registry, OpenView const& view);
/** Compute only the expected base fee for a transaction.
Base fees are transaction specific, so any calculation
needing them must get the base fee for each transaction.
No validation is done or implied by this function.
Caller is responsible for handling any exceptions.
Since none should be thrown, that will usually
mean terminating.
@param view The current open ledger.
@param tx The transaction to be checked.
@return The base fee.
*/
XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);
/** Return the minimum fee that an "ordinary" transaction would pay.
When computing the FeeLevel for a transaction the TxQ sometimes needs
the know what an "ordinary" or reference transaction would be required
to pay.
@param view The current open ledger.
@param tx The transaction so the correct multisigner count is used.
@return The base fee in XRPAmount.
*/
XRPAmount
calculateDefaultBaseFee(ReadView const& view, STTx const& tx);
/** Apply a prechecked transaction to an OpenView.
@pre The transaction has been checked
and validated using `preflight` and `preclaim`
@param preclaimResult The result of a previous
call to `preclaim` for the transaction.
@param registry The service registry.
@param view The open ledger that the transaction
will attempt to be applied to.
@see preflight, preclaim, apply
@return A pair with the `TER` and a `bool` indicating
whether or not the transaction was applied.
*/
ApplyResult
doApply(PreclaimResult const& preclaimResult, ServiceRegistry& registry, OpenView& view);
} // namespace xrpl

View File

@@ -0,0 +1,63 @@
#pragma once
#include <xrpl/ledger/View.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Quality.h>
namespace xrpl {
class Logs;
/** 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:
ApplyView& view_;
bool m_valid;
uint256 m_book;
uint256 m_end;
uint256 m_dir;
uint256 m_index;
std::shared_ptr<SLE> m_entry;
Quality m_quality;
public:
/** Create the iterator. */
BookTip(ApplyView& view, Book const& book);
uint256 const&
dir() const noexcept
{
return m_dir;
}
uint256 const&
index() const noexcept
{
return m_index;
}
Quality const&
quality() const noexcept
{
return m_quality;
}
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(beast::Journal j);
};
} // namespace xrpl

View File

@@ -0,0 +1,52 @@
#pragma once
#include <xrpl/protocol/Quality.h>
#include <xrpl/tx/paths/RippleCalc.h>
#include <xrpl/tx/paths/detail/Steps.h>
namespace xrpl {
namespace path {
namespace detail {
struct FlowDebugInfo;
}
} // namespace path
/**
Make a payment from the src account to the dst account
@param view Trust lines and balances
@param deliver Amount to deliver to the dst account
@param src Account providing input funds for the payment
@param dst Account receiving the payment
@param paths Set of paths to explore for liquidity
@param defaultPaths Include defaultPaths in the path set
@param partialPayment If the payment cannot deliver the entire
requested amount, deliver as much as possible, given the constraints
@param ownerPaysTransferFee If true then owner, not sender, pays fee
@param offerCrossing If Yes or Sell then flow is executing offer crossing, not
payments
@param limitQuality Do not use liquidity below this quality threshold
@param sendMax Do not spend more than this amount
@param j Journal to write journal messages to
@param flowDebugInfo If non-null a pointer to FlowDebugInfo for debugging
@return Actual amount in and out, and the result code
*/
path::RippleCalc::Output
flow(
PaymentSandbox& view,
STAmount const& deliver,
AccountID const& src,
AccountID const& dst,
STPathSet const& paths,
bool defaultPaths,
bool partialPayment,
bool ownerPaysTransferFee,
OfferCrossing offerCrossing,
std::optional<Quality> const& limitQuality,
std::optional<STAmount> const& sendMax,
std::optional<uint256> const& domainID,
beast::Journal j,
path::detail::FlowDebugInfo* flowDebugInfo = nullptr);
} // namespace xrpl

View File

@@ -0,0 +1,302 @@
#pragma once
#include <xrpl/basics/Log.h>
#include <xrpl/basics/contract.h>
#include <xrpl/ledger/View.h>
#include <xrpl/protocol/Quality.h>
#include <xrpl/protocol/Rules.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <stdexcept>
namespace xrpl {
template <class TIn, class TOut>
class TOfferBase
{
protected:
Issue issIn_;
Issue issOut_;
};
template <>
class TOfferBase<STAmount, STAmount>
{
public:
explicit TOfferBase() = default;
};
template <class TIn = STAmount, class TOut = STAmount>
class TOffer : private TOfferBase<TIn, TOut>
{
private:
SLE::pointer m_entry;
Quality m_quality;
AccountID m_account;
TAmounts<TIn, TOut> m_amounts;
void
setFieldAmounts();
public:
TOffer() = default;
TOffer(SLE::pointer const& entry, 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
quality() const noexcept
{
return m_quality;
}
/** Returns the account id of the offer's owner. */
AccountID const&
owner() const
{
return m_account;
}
/** Returns the in and out amounts.
Some or all of the out amount may be unfunded.
*/
TAmounts<TIn, TOut> const&
amount() const
{
return m_amounts;
}
/** Returns `true` if no more funds can flow through this offer. */
bool
fully_consumed() const
{
if (m_amounts.in <= beast::zero)
return true;
if (m_amounts.out <= beast::zero)
return true;
return false;
}
/** Adjusts the offer to indicate that we consumed some (or all) of it. */
void
consume(ApplyView& view, TAmounts<TIn, TOut> const& consumed)
{
if (consumed.in > m_amounts.in)
Throw<std::logic_error>("can't consume more than is available.");
if (consumed.out > m_amounts.out)
Throw<std::logic_error>("can't produce more than is available.");
m_amounts -= consumed;
setFieldAmounts();
view.update(m_entry);
}
std::string
id() const
{
return to_string(m_entry->key());
}
std::optional<uint256>
key() const
{
return m_entry->key();
}
Issue const&
issueIn() const;
Issue const&
issueOut() const;
TAmounts<TIn, TOut>
limitOut(TAmounts<TIn, TOut> const& offerAmount, TOut const& limit, bool roundUp) const;
TAmounts<TIn, TOut>
limitIn(TAmounts<TIn, TOut> const& offerAmount, TIn const& limit, bool roundUp) const;
template <typename... Args>
static TER
send(Args&&... args);
bool
isFunded() const
{
// Offer owner is issuer; they have unlimited funds
return m_account == issueOut().account;
}
static std::pair<std::uint32_t, std::uint32_t>
adjustRates(std::uint32_t ofrInRate, std::uint32_t ofrOutRate)
{
// CLOB offer pays the transfer fee
return {ofrInRate, ofrOutRate};
}
/** Check any required invariant. Limit order book offer
* always returns true.
*/
bool
checkInvariant(TAmounts<TIn, TOut> const& consumed, beast::Journal j) const
{
if (!isFeatureEnabled(fixAMMv1_3))
return true;
if (consumed.in > m_amounts.in || consumed.out > m_amounts.out)
{
// LCOV_EXCL_START
JLOG(j.error()) << "AMMOffer::checkInvariant failed: consumed " << to_string(consumed.in) << " "
<< to_string(consumed.out) << " amounts " << to_string(m_amounts.in) << " "
<< to_string(m_amounts.out);
return false;
// LCOV_EXCL_STOP
}
return true;
}
};
using Offer = TOffer<>;
template <class TIn, class TOut>
TOffer<TIn, TOut>::TOffer(SLE::pointer const& entry, Quality quality)
: m_entry(entry), m_quality(quality), m_account(m_entry->getAccountID(sfAccount))
{
auto const tp = m_entry->getFieldAmount(sfTakerPays);
auto const tg = m_entry->getFieldAmount(sfTakerGets);
m_amounts.in = toAmount<TIn>(tp);
m_amounts.out = toAmount<TOut>(tg);
this->issIn_ = tp.issue();
this->issOut_ = tg.issue();
}
template <>
inline TOffer<STAmount, STAmount>::TOffer(SLE::pointer const& entry, Quality quality)
: m_entry(entry)
, m_quality(quality)
, m_account(m_entry->getAccountID(sfAccount))
, m_amounts(m_entry->getFieldAmount(sfTakerPays), m_entry->getFieldAmount(sfTakerGets))
{
}
template <class TIn, class TOut>
void
TOffer<TIn, TOut>::setFieldAmounts()
{
// LCOV_EXCL_START
#ifdef _MSC_VER
UNREACHABLE("xrpl::TOffer::setFieldAmounts : must be specialized");
#else
static_assert(sizeof(TOut) == -1, "Must be specialized");
#endif
// LCOV_EXCL_STOP
}
template <class TIn, class TOut>
TAmounts<TIn, TOut>
TOffer<TIn, TOut>::limitOut(TAmounts<TIn, TOut> const& offerAmount, TOut const& limit, bool roundUp) const
{
// It turns out that the ceil_out implementation has some slop in
// it, which ceil_out_strict removes.
return quality().ceil_out_strict(offerAmount, limit, roundUp);
}
template <class TIn, class TOut>
TAmounts<TIn, TOut>
TOffer<TIn, TOut>::limitIn(TAmounts<TIn, TOut> const& offerAmount, TIn const& limit, bool roundUp) const
{
if (auto const& rules = getCurrentTransactionRules(); rules && rules->enabled(fixReducedOffersV2))
// It turns out that the ceil_in implementation has some slop in
// it. ceil_in_strict removes that slop. But removing that slop
// affects transaction outcomes, so the change must be made using
// an amendment.
return quality().ceil_in_strict(offerAmount, limit, roundUp);
return m_quality.ceil_in(offerAmount, limit);
}
template <class TIn, class TOut>
template <typename... Args>
TER
TOffer<TIn, TOut>::send(Args&&... args)
{
return accountSend(std::forward<Args>(args)...);
}
template <>
inline void
TOffer<STAmount, STAmount>::setFieldAmounts()
{
m_entry->setFieldAmount(sfTakerPays, m_amounts.in);
m_entry->setFieldAmount(sfTakerGets, m_amounts.out);
}
template <>
inline void
TOffer<IOUAmount, IOUAmount>::setFieldAmounts()
{
m_entry->setFieldAmount(sfTakerPays, toSTAmount(m_amounts.in, issIn_));
m_entry->setFieldAmount(sfTakerGets, toSTAmount(m_amounts.out, issOut_));
}
template <>
inline void
TOffer<IOUAmount, XRPAmount>::setFieldAmounts()
{
m_entry->setFieldAmount(sfTakerPays, toSTAmount(m_amounts.in, issIn_));
m_entry->setFieldAmount(sfTakerGets, toSTAmount(m_amounts.out));
}
template <>
inline void
TOffer<XRPAmount, IOUAmount>::setFieldAmounts()
{
m_entry->setFieldAmount(sfTakerPays, toSTAmount(m_amounts.in));
m_entry->setFieldAmount(sfTakerGets, toSTAmount(m_amounts.out, issOut_));
}
template <class TIn, class TOut>
Issue const&
TOffer<TIn, TOut>::issueIn() const
{
return this->issIn_;
}
template <>
inline Issue const&
TOffer<STAmount, STAmount>::issueIn() const
{
return m_amounts.in.issue();
}
template <class TIn, class TOut>
Issue const&
TOffer<TIn, TOut>::issueOut() const
{
return this->issOut_;
}
template <>
inline Issue const&
TOffer<STAmount, STAmount>::issueOut() const
{
return m_amounts.out.issue();
}
template <class TIn, class TOut>
inline std::ostream&
operator<<(std::ostream& os, TOffer<TIn, TOut> const& offer)
{
return os << offer.id();
}
} // namespace xrpl

View File

@@ -0,0 +1,174 @@
#pragma once
#include <xrpl/basics/Log.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/View.h>
#include <xrpl/tx/paths/BookTip.h>
#include <xrpl/tx/paths/Offer.h>
#include <boost/container/flat_set.hpp>
namespace xrpl {
template <class TIn, class TOut>
class TOfferStreamBase
{
public:
class StepCounter
{
private:
std::uint32_t const limit_;
std::uint32_t count_;
beast::Journal j_;
public:
StepCounter(std::uint32_t limit, beast::Journal j) : limit_(limit), count_(0), j_(j)
{
}
bool
step()
{
if (count_ >= limit_)
{
JLOG(j_.debug()) << "Exceeded " << limit_ << " step limit.";
return false;
}
count_++;
return true;
}
std::uint32_t
count() const
{
return count_;
}
};
protected:
beast::Journal const j_;
ApplyView& view_;
ApplyView& cancelView_;
Book book_;
bool validBook_;
NetClock::time_point const expire_;
BookTip tip_;
TOffer<TIn, TOut> offer_;
std::optional<TOut> ownerFunds_;
StepCounter& counter_;
void
erase(ApplyView& view);
virtual void
permRmOffer(uint256 const& offerIndex) = 0;
template <class TTakerPays, class TTakerGets>
bool
shouldRmSmallIncreasedQOffer() const;
public:
TOfferStreamBase(
ApplyView& view,
ApplyView& cancelView,
Book const& book,
NetClock::time_point when,
StepCounter& counter,
beast::Journal journal);
virtual ~TOfferStreamBase() = default;
/** Returns the offer at the tip of the order book.
Offers are always presented in decreasing quality.
Only valid if step() returned `true`.
*/
TOffer<TIn, TOut>&
tip() const
{
return const_cast<TOfferStreamBase*>(this)->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();
TOut
ownerFunds() const
{
return *ownerFunds_;
}
};
/** Presents and consumes the offers in an order book.
Two `ApplyView` 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.
*/
class OfferStream : public TOfferStreamBase<STAmount, STAmount>
{
protected:
void
permRmOffer(uint256 const& offerIndex) override;
public:
using TOfferStreamBase<STAmount, STAmount>::TOfferStreamBase;
};
/** Presents and consumes the offers in an order book.
The `view_' ` `ApplyView` accumulates changes to the ledger.
The `cancelView_` is used to determine if an offer is found
unfunded or became unfunded.
The `permToRemove` collection identifies offers that should be
removed even if the strand associated with this OfferStream
is not applied.
Certain invalid offers are added to the `permToRemove` collection:
- 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.
*/
template <class TIn, class TOut>
class FlowOfferStream : public TOfferStreamBase<TIn, TOut>
{
private:
boost::container::flat_set<uint256> permToRemove_;
public:
using TOfferStreamBase<TIn, TOut>::TOfferStreamBase;
// The following interface allows offer crossing to permanently
// remove self crossed offers. The motivation is somewhat
// unintuitive. See the discussion in the comments for
// BookOfferCrossingStep::limitSelfCrossQuality().
void
permRmOffer(uint256 const& offerIndex) override;
boost::container::flat_set<uint256> const&
permToRemove() const
{
return permToRemove_;
}
};
} // namespace xrpl

View File

@@ -0,0 +1,109 @@
#pragma once
#include <xrpl/basics/Log.h>
#include <xrpl/ledger/PaymentSandbox.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/TER.h>
#include <boost/container/flat_set.hpp>
namespace xrpl {
class Config;
namespace path {
namespace detail {
struct FlowDebugInfo;
}
/** RippleCalc calculates the quality of a payment path.
Quality is the amount of input required to produce a given output along a
specified path - another name for this is exchange rate.
*/
class RippleCalc
{
public:
struct Input
{
explicit Input() = default;
bool partialPaymentAllowed = false;
bool defaultPathsAllowed = true;
bool limitQuality = false;
bool isLedgerOpen = true;
};
struct Output
{
explicit Output() = default;
// The computed input amount.
STAmount actualAmountIn;
// The computed output amount.
STAmount actualAmountOut;
// Collection of offers found expired or unfunded. When a payment
// succeeds, unfunded and expired offers are removed. When a payment
// fails, they are not removed. This vector contains the offers that
// could have been removed but were not because the payment fails. It is
// useful for offer crossing, which does remove the offers.
boost::container::flat_set<uint256> removableOffers;
private:
TER calculationResult_ = temUNKNOWN;
public:
TER
result() const
{
return calculationResult_;
}
void
setResult(TER const value)
{
calculationResult_ = value;
}
};
static Output
rippleCalculate(
PaymentSandbox& view,
// Compute paths using this ledger entry set. Up to caller to actually
// apply to ledger.
// Issuer:
// XRP: xrpAccount()
// non-XRP: uSrcAccountID (for any issuer) or another account with
// trust node.
STAmount const& saMaxAmountReq, // --> -1 = no limit.
// Issuer:
// XRP: xrpAccount()
// non-XRP: uDstAccountID (for any issuer) or another account with
// trust node.
STAmount const& saDstAmountReq,
AccountID const& uDstAccountID,
AccountID const& uSrcAccountID,
// A set of paths that are included in the transaction that we'll
// explore for liquidity.
STPathSet const& spsPaths,
std::optional<uint256> const& domainID,
Logs& l,
Input const* const pInputs = nullptr);
// The view we are currently working on
PaymentSandbox& view;
// If the transaction fails to meet some constraint, still need to delete
// unfunded offers in a deterministic order (hence the ordered container).
//
// Offers that were found unfunded.
boost::container::flat_set<uint256> permanentlyUnfundedOffers_;
};
} // namespace path
} // namespace xrpl

View File

@@ -0,0 +1,198 @@
#pragma once
#include <xrpl/protocol/IOUAmount.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/XRPAmount.h>
#include <optional>
namespace xrpl {
struct AmountSpec
{
explicit AmountSpec() = default;
bool native;
union
{
XRPAmount xrp;
IOUAmount iou = {};
};
std::optional<AccountID> issuer;
std::optional<Currency> currency;
friend std::ostream&
operator<<(std::ostream& stream, AmountSpec const& amt)
{
if (amt.native)
stream << to_string(amt.xrp);
else
stream << to_string(amt.iou);
if (amt.currency)
stream << "/(" << *amt.currency << ")";
if (amt.issuer)
stream << "/" << *amt.issuer << "";
return stream;
}
};
struct EitherAmount
{
#ifndef NDEBUG
bool native = false;
#endif
union
{
IOUAmount iou = {};
XRPAmount xrp;
};
EitherAmount() = default;
explicit EitherAmount(IOUAmount const& a) : iou(a)
{
}
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic push
// ignore warning about half of iou amount being uninitialized
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#endif
explicit EitherAmount(XRPAmount const& a) : xrp(a)
{
#ifndef NDEBUG
native = true;
#endif
}
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic pop
#endif
explicit EitherAmount(AmountSpec const& a)
{
#ifndef NDEBUG
native = a.native;
#endif
if (a.native)
xrp = a.xrp;
else
iou = a.iou;
}
#ifndef NDEBUG
friend std::ostream&
operator<<(std::ostream& stream, EitherAmount const& amt)
{
if (amt.native)
stream << to_string(amt.xrp);
else
stream << to_string(amt.iou);
return stream;
}
#endif
};
template <class T>
T&
get(EitherAmount& amt)
{
static_assert(sizeof(T) == -1, "Must used specialized function");
return T(0);
}
template <>
inline IOUAmount&
get<IOUAmount>(EitherAmount& amt)
{
XRPL_ASSERT(!amt.native, "xrpl::get<IOUAmount>(EitherAmount&) : is not XRP");
return amt.iou;
}
template <>
inline XRPAmount&
get<XRPAmount>(EitherAmount& amt)
{
XRPL_ASSERT(amt.native, "xrpl::get<XRPAmount>(EitherAmount&) : is XRP");
return amt.xrp;
}
template <class T>
T const&
get(EitherAmount const& amt)
{
static_assert(sizeof(T) == -1, "Must used specialized function");
return T(0);
}
template <>
inline IOUAmount const&
get<IOUAmount>(EitherAmount const& amt)
{
XRPL_ASSERT(!amt.native, "xrpl::get<IOUAmount>(EitherAmount const&) : is not XRP");
return amt.iou;
}
template <>
inline XRPAmount const&
get<XRPAmount>(EitherAmount const& amt)
{
XRPL_ASSERT(amt.native, "xrpl::get<XRPAmount>(EitherAmount const&) : is XRP");
return amt.xrp;
}
inline AmountSpec
toAmountSpec(STAmount const& amt)
{
XRPL_ASSERT(
amt.mantissa() < std::numeric_limits<std::int64_t>::max(),
"xrpl::toAmountSpec(STAmount const&) : maximum mantissa");
bool const isNeg = amt.negative();
std::int64_t const sMant = isNeg ? -std::int64_t(amt.mantissa()) : amt.mantissa();
AmountSpec result;
result.native = isXRP(amt);
if (result.native)
{
result.xrp = XRPAmount(sMant);
}
else
{
result.iou = IOUAmount(sMant, amt.exponent());
result.issuer = amt.issue().account;
result.currency = amt.issue().currency;
}
return result;
}
inline EitherAmount
toEitherAmount(STAmount const& amt)
{
if (isXRP(amt))
return EitherAmount{amt.xrp()};
return EitherAmount{amt.iou()};
}
inline AmountSpec
toAmountSpec(EitherAmount const& ea, std::optional<Currency> const& c)
{
AmountSpec r;
r.native = (!c || isXRP(*c));
r.currency = c;
XRPL_ASSERT(
ea.native == r.native,
"xrpl::toAmountSpec(EitherAmount const&&, std::optional<Currency>) : "
"matching native");
if (r.native)
{
r.xrp = ea.xrp;
}
else
{
r.iou = ea.iou;
}
return r;
}
} // namespace xrpl

View File

@@ -0,0 +1,24 @@
#pragma once
#include <boost/container/flat_set.hpp>
namespace xrpl {
/** Given two flat sets dst and src, compute dst = dst union src
@param dst set to store the resulting union, and also a source of elements
for the union
@param src second source of elements for the union
*/
template <class T>
void
SetUnion(boost::container::flat_set<T>& dst, boost::container::flat_set<T> const& src)
{
if (src.empty())
return;
dst.reserve(dst.size() + src.size());
dst.insert(boost::container::ordered_unique_range_t{}, src.begin(), src.end());
}
} // namespace xrpl

View File

@@ -0,0 +1,329 @@
#pragma once
#include <xrpl/ledger/PaymentSandbox.h>
#include <xrpl/protocol/IOUAmount.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/paths/detail/AmountSpec.h>
#include <boost/container/flat_map.hpp>
#include <chrono>
#include <optional>
#include <sstream>
namespace xrpl {
namespace path {
namespace detail {
// Track performance information of a single payment
struct FlowDebugInfo
{
using clock = std::chrono::high_resolution_clock;
using time_point = clock::time_point;
boost::container::flat_map<std::string, std::pair<time_point, time_point>> timePoints;
boost::container::flat_map<std::string, std::size_t> counts;
struct PassInfo
{
PassInfo() = delete;
PassInfo(bool nativeIn_, bool nativeOut_) : nativeIn(nativeIn_), nativeOut(nativeOut_)
{
}
bool const nativeIn;
bool const nativeOut;
std::vector<EitherAmount> in;
std::vector<EitherAmount> out;
std::vector<size_t> numActive;
std::vector<std::vector<EitherAmount>> liquiditySrcIn;
std::vector<std::vector<EitherAmount>> liquiditySrcOut;
void
reserve(size_t s)
{
in.reserve(s);
out.reserve(s);
liquiditySrcIn.reserve(s);
liquiditySrcOut.reserve(s);
numActive.reserve(s);
}
size_t
size() const
{
return in.size();
}
void
push_back(EitherAmount const& in_amt, EitherAmount const& out_amt, std::size_t active)
{
in.push_back(in_amt);
out.push_back(out_amt);
numActive.push_back(active);
}
void
pushLiquiditySrc(EitherAmount const& eIn, EitherAmount const& eOut)
{
XRPL_ASSERT(
!liquiditySrcIn.empty(),
"xrpl::path::detail::FlowDebugInfo::pushLiquiditySrc : "
"non-empty liquidity source");
liquiditySrcIn.back().push_back(eIn);
liquiditySrcOut.back().push_back(eOut);
}
void
newLiquidityPass()
{
auto const s = liquiditySrcIn.size();
size_t const r = !numActive.empty() ? numActive.back() : 16;
liquiditySrcIn.resize(s + 1);
liquiditySrcIn.back().reserve(r);
liquiditySrcOut.resize(s + 1);
liquiditySrcOut.back().reserve(r);
}
};
PassInfo passInfo;
FlowDebugInfo() = delete;
FlowDebugInfo(bool nativeIn, bool nativeOut) : passInfo(nativeIn, nativeOut)
{
timePoints.reserve(16);
counts.reserve(16);
passInfo.reserve(64);
}
auto
duration(std::string const& tag) const
{
auto i = timePoints.find(tag);
if (i == timePoints.end())
{
// LCOV_EXCL_START
UNREACHABLE(
"xrpl::path::detail::FlowDebugInfo::duration : timepoint not "
"found");
return std::chrono::duration<double>(0);
// LCOV_EXCL_STOP
}
auto const& t = i->second;
return std::chrono::duration_cast<std::chrono::duration<double>>(t.second - t.first);
}
std::size_t
count(std::string const& tag) const
{
auto i = counts.find(tag);
if (i == counts.end())
return 0;
return i->second;
}
// Time the duration of the existence of the result
auto
timeBlock(std::string name)
{
struct Stopper
{
std::string tag;
FlowDebugInfo* info;
Stopper(std::string name, FlowDebugInfo& pi) : tag(std::move(name)), info(&pi)
{
auto const start = FlowDebugInfo::clock::now();
info->timePoints.emplace(tag, std::make_pair(start, start));
}
~Stopper()
{
auto const end = FlowDebugInfo::clock::now();
info->timePoints[tag].second = end;
}
Stopper(Stopper&&) = default;
};
return Stopper(std::move(name), *this);
}
void
inc(std::string const& tag)
{
auto i = counts.find(tag);
if (i == counts.end())
{
counts[tag] = 1;
}
++i->second;
}
void
setCount(std::string const& tag, std::size_t c)
{
counts[tag] = c;
}
std::size_t
passCount() const
{
return passInfo.size();
}
void
pushPass(EitherAmount const& in, EitherAmount const& out, std::size_t activeStrands)
{
passInfo.push_back(in, out, activeStrands);
}
void
pushLiquiditySrc(EitherAmount const& in, EitherAmount const& out)
{
passInfo.pushLiquiditySrc(in, out);
}
void
newLiquidityPass()
{
passInfo.newLiquidityPass();
}
std::string
to_string(bool writePassInfo) const
{
std::ostringstream ostr;
auto const d = duration("main");
ostr << "duration: " << d.count() << ", pass_count: " << passCount();
if (writePassInfo)
{
auto write_list = [&ostr](auto const& vals, auto&& fun, char delim = ';') {
ostr << '[';
if (!vals.empty())
{
ostr << fun(vals[0]);
for (size_t i = 1, e = vals.size(); i < e; ++i)
ostr << delim << fun(vals[i]);
}
ostr << ']';
};
auto writeXrpAmtList = [&write_list](std::vector<EitherAmount> const& amts, char delim = ';') {
auto get_val = [](EitherAmount const& a) -> std::string { return xrpl::to_string(a.xrp); };
write_list(amts, get_val, delim);
};
auto writeIouAmtList = [&write_list](std::vector<EitherAmount> const& amts, char delim = ';') {
auto get_val = [](EitherAmount const& a) -> std::string { return xrpl::to_string(a.iou); };
write_list(amts, get_val, delim);
};
auto writeIntList = [&write_list](std::vector<size_t> const& vals, char delim = ';') {
auto get_val = [](size_t const& v) -> size_t const& { return v; };
write_list(vals, get_val);
};
auto writeNestedIouAmtList = [&ostr, &writeIouAmtList](std::vector<std::vector<EitherAmount>> const& amts) {
ostr << '[';
if (!amts.empty())
{
writeIouAmtList(amts[0], '|');
for (size_t i = 1, e = amts.size(); i < e; ++i)
{
ostr << ';';
writeIouAmtList(amts[i], '|');
}
}
ostr << ']';
};
auto writeNestedXrpAmtList = [&ostr, &writeXrpAmtList](std::vector<std::vector<EitherAmount>> const& amts) {
ostr << '[';
if (!amts.empty())
{
writeXrpAmtList(amts[0], '|');
for (size_t i = 1, e = amts.size(); i < e; ++i)
{
ostr << ';';
writeXrpAmtList(amts[i], '|');
}
}
ostr << ']';
};
ostr << ", in_pass: ";
if (passInfo.nativeIn)
writeXrpAmtList(passInfo.in);
else
writeIouAmtList(passInfo.in);
ostr << ", out_pass: ";
if (passInfo.nativeOut)
writeXrpAmtList(passInfo.out);
else
writeIouAmtList(passInfo.out);
ostr << ", num_active: ";
writeIntList(passInfo.numActive);
if (!passInfo.liquiditySrcIn.empty() && !passInfo.liquiditySrcIn.back().empty())
{
ostr << ", l_src_in: ";
if (passInfo.nativeIn)
writeNestedXrpAmtList(passInfo.liquiditySrcIn);
else
writeNestedIouAmtList(passInfo.liquiditySrcIn);
ostr << ", l_src_out: ";
if (passInfo.nativeOut)
writeNestedXrpAmtList(passInfo.liquiditySrcOut);
else
writeNestedIouAmtList(passInfo.liquiditySrcOut);
}
}
return ostr.str();
}
};
inline void
writeDiffElement(std::ostringstream& ostr, std::pair<std::tuple<AccountID, AccountID, Currency>, STAmount> const& elem)
{
using namespace std;
auto const k = elem.first;
auto const v = elem.second;
ostr << '[' << get<0>(k) << '|' << get<1>(k) << '|' << get<2>(k) << '|' << v << ']';
};
template <class Iter>
void
writeDiffs(std::ostringstream& ostr, Iter begin, Iter end)
{
ostr << '[';
if (begin != end)
{
writeDiffElement(ostr, *begin);
++begin;
}
for (; begin != end; ++begin)
{
ostr << ';';
writeDiffElement(ostr, *begin);
}
ostr << ']';
};
using BalanceDiffs = std::pair<std::map<std::tuple<AccountID, AccountID, Currency>, STAmount>, XRPAmount>;
inline BalanceDiffs
balanceDiffs(PaymentSandbox const& sb, ReadView const& rv)
{
return {sb.balanceChanges(rv), sb.xrpDestroyed()};
}
inline std::string
balanceDiffsToString(std::optional<BalanceDiffs> const& bd)
{
if (!bd)
return std::string{};
auto const& diffs = bd->first;
auto const& xrpDestroyed = bd->second;
std::ostringstream ostr;
ostr << ", xrpDestroyed: " << to_string(xrpDestroyed);
ostr << ", balanceDiffs: ";
writeDiffs(ostr, diffs.begin(), diffs.end());
return ostr.str();
};
} // namespace detail
} // namespace path
} // namespace xrpl

View File

@@ -0,0 +1,591 @@
#pragma once
#include <xrpl/basics/Log.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/protocol/Quality.h>
#include <xrpl/protocol/QualityFunction.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/tx/paths/detail/AmountSpec.h>
#include <boost/container/flat_set.hpp>
#include <optional>
namespace xrpl {
class PaymentSandbox;
class ReadView;
class ApplyView;
class AMMContext;
enum class DebtDirection { issues, redeems };
enum class QualityDirection { in, out };
enum class StrandDirection { forward, reverse };
enum OfferCrossing { no = 0, yes = 1, sell = 2 };
inline bool
redeems(DebtDirection dir)
{
return dir == DebtDirection::redeems;
}
inline bool
issues(DebtDirection dir)
{
return dir == DebtDirection::issues;
}
/**
A step in a payment path
There are five concrete step classes:
DirectStepI is an IOU step between accounts
BookStepII is an IOU/IOU offer book
BookStepIX is an IOU/XRP offer book
BookStepXI is an XRP/IOU offer book
XRPEndpointStep is the source or destination account for XRP
Amounts may be transformed through a step in either the forward or the
reverse direction. In the forward direction, the function `fwd` is used to
find the amount the step would output given an input amount. In the reverse
direction, the function `rev` is used to find the amount of input needed to
produce the desired output.
Amounts are always transformed using liquidity with the same quality (quality
is the amount out/amount in). For example, a BookStep may use multiple offers
when executing `fwd` or `rev`, but all those offers will be from the same
quality directory.
A step may not have enough liquidity to transform the entire requested
amount. Both `fwd` and `rev` return a pair of amounts (one for input amount,
one for output amount) that show how much of the requested amount the step
was actually able to use.
*/
class Step
{
public:
virtual ~Step() = default;
/**
Find the amount we need to put into the step to get the requested out
subject to liquidity limits
@param sb view with the strand's state of balances and offers
@param afView view the state of balances before the strand runs
this determines if an offer becomes unfunded or is found unfunded
@param ofrsToRm offers found unfunded or in an error state are added to
this collection
@param out requested step output
@return actual step input and output
*/
virtual std::pair<EitherAmount, EitherAmount>
rev(PaymentSandbox& sb,
ApplyView& afView,
boost::container::flat_set<uint256>& ofrsToRm,
EitherAmount const& out) = 0;
/**
Find the amount we get out of the step given the input
subject to liquidity limits
@param sb view with the strand's state of balances and offers
@param afView view the state of balances before the strand runs
this determines if an offer becomes unfunded or is found unfunded
@param ofrsToRm offers found unfunded or in an error state are added to
this collection
@param in requested step input
@return actual step input and output
*/
virtual std::pair<EitherAmount, EitherAmount>
fwd(PaymentSandbox& sb,
ApplyView& afView,
boost::container::flat_set<uint256>& ofrsToRm,
EitherAmount const& in) = 0;
/**
Amount of currency computed coming into the Step the last time the
step ran in reverse.
*/
virtual std::optional<EitherAmount>
cachedIn() const = 0;
/**
Amount of currency computed coming out of the Step the last time the
step ran in reverse.
*/
virtual std::optional<EitherAmount>
cachedOut() const = 0;
/**
If this step is DirectStepI (IOU->IOU direct step), return the src
account. This is needed for checkNoRipple.
*/
virtual std::optional<AccountID>
directStepSrcAcct() const
{
return std::nullopt;
}
// for debugging. Return the src and dst accounts for a direct step
// For XRP endpoints, one of src or dst will be the root account
virtual std::optional<std::pair<AccountID, AccountID>>
directStepAccts() const
{
return std::nullopt;
}
/**
If this step is a DirectStepI and the src redeems to the dst, return
true, otherwise return false. If this step is a BookStep, return false if
the owner pays the transfer fee, otherwise return true.
@param sb view with the strand's state of balances and offers
@param dir reverse -> called from rev(); forward -> called from fwd().
*/
virtual DebtDirection
debtDirection(ReadView const& sb, StrandDirection dir) const = 0;
/**
If this step is a DirectStepI, return the quality in of the dst account.
*/
virtual std::uint32_t
lineQualityIn(ReadView const&) const
{
return QUALITY_ONE;
}
// clang-format off
/**
Find an upper bound of quality for the step
@param v view to query the ledger state from
@param prevStepDir Set to DebtDirection::redeems if the previous step redeems.
@return A pair. The first element is the upper bound of quality for the step, or std::nullopt if the
step is dry. The second element will be set to DebtDirection::redeems if this steps redeems,
DebtDirection:issues if this step issues.
@note it is an upper bound because offers on the books may be unfunded.
If there is always a funded offer at the tip of the book, then we could
rename this `theoreticalQuality` rather than `qualityUpperBound`. It
could still differ from the actual quality, but except for "dust" amounts,
it should be a good estimate for the actual quality.
*/
// clang-format on
virtual std::pair<std::optional<Quality>, DebtDirection>
qualityUpperBound(ReadView const& v, DebtDirection prevStepDir) const = 0;
/** Get QualityFunction. Used in one path optimization where
* the quality function is non-constant (has AMM) and there is
* limitQuality. QualityFunction allows calculation of
* required path output given requested limitQuality.
* All steps, except for BookStep have the default
* implementation.
*/
virtual std::pair<std::optional<QualityFunction>, DebtDirection>
getQualityFunc(ReadView const& v, DebtDirection prevStepDir) const;
/** Return the number of offers consumed or partially consumed the last time
the step ran, including expired and unfunded offers.
N.B. This this not the total number offers consumed by this step for the
entire payment, it is only the number the last time it ran. Offers may
be partially consumed multiple times during a payment.
*/
virtual std::uint32_t
offersUsed() const
{
return 0;
}
/**
If this step is a BookStep, return the book.
*/
virtual std::optional<Book>
bookStepBook() const
{
return std::nullopt;
}
/**
Check if amount is zero
*/
virtual bool
isZero(EitherAmount const& out) const = 0;
/**
Return true if the step should be considered inactive.
A strand that has additional liquidity may be marked inactive if a step
has consumed too many offers.
*/
virtual bool
inactive() const
{
return false;
}
/**
Return true if Out of lhs == Out of rhs.
*/
virtual bool
equalOut(EitherAmount const& lhs, EitherAmount const& rhs) const = 0;
/**
Return true if In of lhs == In of rhs.
*/
virtual bool
equalIn(EitherAmount const& lhs, EitherAmount const& rhs) const = 0;
/**
Check that the step can correctly execute in the forward direction
@param sb view with the strands state of balances and offers
@param afView view the state of balances before the strand runs
this determines if an offer becomes unfunded or is found unfunded
@param in requested step input
@return first element is true if step is valid, second element is out
amount
*/
virtual std::pair<bool, EitherAmount>
validFwd(PaymentSandbox& sb, ApplyView& afView, EitherAmount const& in) = 0;
/** Return true if lhs == rhs.
@param lhs Step to compare.
@param rhs Step to compare.
@return true if lhs == rhs.
*/
friend bool
operator==(Step const& lhs, Step const& rhs)
{
return lhs.equal(rhs);
}
/** Return true if lhs != rhs.
@param lhs Step to compare.
@param rhs Step to compare.
@return true if lhs != rhs.
*/
friend bool
operator!=(Step const& lhs, Step const& rhs)
{
return !(lhs == rhs);
}
/** Streaming operator for a Step. */
friend std::ostream&
operator<<(std::ostream& stream, Step const& step)
{
stream << step.logString();
return stream;
}
private:
virtual std::string
logString() const = 0;
virtual bool
equal(Step const& rhs) const = 0;
};
inline std::pair<std::optional<QualityFunction>, DebtDirection>
Step::getQualityFunc(ReadView const& v, DebtDirection prevStepDir) const
{
if (auto const res = qualityUpperBound(v, prevStepDir); res.first)
return {QualityFunction{*res.first, QualityFunction::CLOBLikeTag{}}, res.second};
else
return {std::nullopt, res.second};
}
/// @cond INTERNAL
using Strand = std::vector<std::unique_ptr<Step>>;
inline std::uint32_t
offersUsed(Strand const& strand)
{
std::uint32_t r = 0;
for (auto const& step : strand)
{
if (step)
r += step->offersUsed();
}
return r;
}
/// @endcond
/// @cond INTERNAL
inline bool
operator==(Strand const& lhs, Strand const& rhs)
{
if (lhs.size() != rhs.size())
return false;
for (size_t i = 0, e = lhs.size(); i != e; ++i)
if (*lhs[i] != *rhs[i])
return false;
return true;
}
/// @endcond
/*
Normalize a path by inserting implied accounts and offers
@param src Account that is sending assets
@param dst Account that is receiving assets
@param deliver Asset the dst account will receive
(if issuer of deliver == dst, then accept any issuer)
@param sendMax Optional asset to send.
@param path Liquidity sources to use for this strand of the payment. The path
contains an ordered collection of the offer books to use and
accounts to ripple through.
@return error code and normalized path
*/
std::pair<TER, STPath>
normalizePath(
AccountID const& src,
AccountID const& dst,
Issue const& deliver,
std::optional<Issue> const& sendMaxIssue,
STPath const& path);
/**
Create a Strand for the specified path
@param sb view for trust lines, balances, and attributes like auth and freeze
@param src Account that is sending assets
@param dst Account that is receiving assets
@param deliver Asset the dst account will receive
(if issuer of deliver == dst, then accept any issuer)
@param limitQuality Offer crossing BookSteps use this value in an
optimization. If, during direct offer crossing, the
quality of the tip of the book drops below this value,
then evaluating the strand can stop.
@param sendMaxIssue Optional asset to send.
@param path Liquidity sources to use for this strand of the payment. The path
contains an ordered collection of the offer books to use and
accounts to ripple through.
@param ownerPaysTransferFee false -> charge sender; true -> charge offer
owner
@param offerCrossing false -> payment; true -> offer crossing
@param ammContext counts iterations with AMM offers
@param domainID the domain that order books will use
@param j Journal for logging messages
@return Error code and constructed Strand
*/
std::pair<TER, Strand>
toStrand(
ReadView const& sb,
AccountID const& src,
AccountID const& dst,
Issue const& deliver,
std::optional<Quality> const& limitQuality,
std::optional<Issue> const& sendMaxIssue,
STPath const& path,
bool ownerPaysTransferFee,
OfferCrossing offerCrossing,
AMMContext& ammContext,
std::optional<uint256> const& domainID,
beast::Journal j);
/**
Create a Strand for each specified path (including the default path, if
indicated)
@param sb View for trust lines, balances, and attributes like auth and freeze
@param src Account that is sending assets
@param dst Account that is receiving assets
@param deliver Asset the dst account will receive
(if issuer of deliver == dst, then accept any issuer)
@param limitQuality Offer crossing BookSteps use this value in an
optimization. If, during direct offer crossing, the
quality of the tip of the book drops below this value,
then evaluating the strand can stop.
@param sendMax Optional asset to send.
@param paths Paths to use to fulfill the payment. Each path in the pathset
contains an ordered collection of the offer books to use and
accounts to ripple through.
@param addDefaultPath Determines if the default path should be included
@param ownerPaysTransferFee false -> charge sender; true -> charge offer
owner
@param offerCrossing false -> payment; true -> offer crossing
@param ammContext counts iterations with AMM offers
@param domainID the domain that order books will use
@param j Journal for logging messages
@return error code and collection of strands
*/
std::pair<TER, std::vector<Strand>>
toStrands(
ReadView const& sb,
AccountID const& src,
AccountID const& dst,
Issue const& deliver,
std::optional<Quality> const& limitQuality,
std::optional<Issue> const& sendMax,
STPathSet const& paths,
bool addDefaultPath,
bool ownerPaysTransferFee,
OfferCrossing offerCrossing,
AMMContext& ammContext,
std::optional<uint256> const& domainID,
beast::Journal j);
/// @cond INTERNAL
template <class TIn, class TOut, class TDerived>
struct StepImp : public Step
{
explicit StepImp() = default;
std::pair<EitherAmount, EitherAmount>
rev(PaymentSandbox& sb,
ApplyView& afView,
boost::container::flat_set<uint256>& ofrsToRm,
EitherAmount const& out) override
{
auto const r = static_cast<TDerived*>(this)->revImp(sb, afView, ofrsToRm, get<TOut>(out));
return {EitherAmount(r.first), EitherAmount(r.second)};
}
// Given the requested amount to consume, compute the amount produced.
// Return the consumed/produced
std::pair<EitherAmount, EitherAmount>
fwd(PaymentSandbox& sb,
ApplyView& afView,
boost::container::flat_set<uint256>& ofrsToRm,
EitherAmount const& in) override
{
auto const r = static_cast<TDerived*>(this)->fwdImp(sb, afView, ofrsToRm, get<TIn>(in));
return {EitherAmount(r.first), EitherAmount(r.second)};
}
bool
isZero(EitherAmount const& out) const override
{
return get<TOut>(out) == beast::zero;
}
bool
equalOut(EitherAmount const& lhs, EitherAmount const& rhs) const override
{
return get<TOut>(lhs) == get<TOut>(rhs);
}
bool
equalIn(EitherAmount const& lhs, EitherAmount const& rhs) const override
{
return get<TIn>(lhs) == get<TIn>(rhs);
}
};
/// @endcond
/// @cond INTERNAL
// Thrown when unexpected errors occur
class FlowException : public std::runtime_error
{
public:
TER ter;
FlowException(TER t, std::string const& msg) : std::runtime_error(msg), ter(t)
{
}
explicit FlowException(TER t) : std::runtime_error(transHuman(t)), ter(t)
{
}
};
/// @endcond
/// @cond INTERNAL
// Check equal with tolerance
bool
checkNear(IOUAmount const& expected, IOUAmount const& actual);
bool
checkNear(XRPAmount const& expected, XRPAmount const& actual);
/// @endcond
/**
Context needed to build Strand Steps and for error checking
*/
struct StrandContext
{
ReadView const& view; ///< Current ReadView
AccountID const strandSrc; ///< Strand source account
AccountID const strandDst; ///< Strand destination account
Issue const strandDeliver; ///< Issue strand delivers
std::optional<Quality> const limitQuality; ///< Worst accepted quality
bool const isFirst; ///< true if Step is first in Strand
bool const isLast = false; ///< true if Step is last in Strand
bool const ownerPaysTransferFee; ///< true if owner, not sender, pays fee
OfferCrossing const offerCrossing; ///< Yes/Sell if offer crossing, not payment
bool const isDefaultPath; ///< true if Strand is default path
size_t const strandSize; ///< Length of Strand
/** The previous step in the strand. Needed to check the no ripple
constraint
*/
Step const* const prevStep = nullptr;
/** A strand may not include the same account node more than once
in the same currency. In a direct step, an account will show up
at most twice: once as a src and once as a dst (hence the two element
array). The strandSrc and strandDst will only show up once each.
*/
std::array<boost::container::flat_set<Issue>, 2>& seenDirectIssues;
/** A strand may not include an offer that output the same issue more
than once
*/
boost::container::flat_set<Issue>& seenBookOuts;
AMMContext& ammContext;
std::optional<uint256> domainID; // the domain the order book will use
beast::Journal const j;
/** StrandContext constructor. */
StrandContext(
ReadView const& view_,
std::vector<std::unique_ptr<Step>> const& strand_,
// A strand may not include an inner node that
// replicates the source or destination.
AccountID const& strandSrc_,
AccountID const& strandDst_,
Issue const& strandDeliver_,
std::optional<Quality> const& limitQuality_,
bool isLast_,
bool ownerPaysTransferFee_,
OfferCrossing offerCrossing_,
bool isDefaultPath_,
std::array<boost::container::flat_set<Issue>, 2>& seenDirectIssues_, ///< For detecting currency loops
boost::container::flat_set<Issue>& seenBookOuts_, ///< For detecting book loops
AMMContext& ammContext_,
std::optional<uint256> const& domainID,
beast::Journal j_); ///< Journal for logging
};
/// @cond INTERNAL
namespace test {
// Needed for testing
bool
directStepEqual(Step const& step, AccountID const& src, AccountID const& dst, Currency const& currency);
bool
xrpEndpointStepEqual(Step const& step, AccountID const& acc);
bool
bookStepEqual(Step const& step, xrpl::Book const& book);
} // namespace test
std::pair<TER, std::unique_ptr<Step>>
make_DirectStepI(StrandContext const& ctx, AccountID const& src, AccountID const& dst, Currency const& c);
std::pair<TER, std::unique_ptr<Step>>
make_BookStepII(StrandContext const& ctx, Issue const& in, Issue const& out);
std::pair<TER, std::unique_ptr<Step>>
make_BookStepIX(StrandContext const& ctx, Issue const& in);
std::pair<TER, std::unique_ptr<Step>>
make_BookStepXI(StrandContext const& ctx, Issue const& out);
std::pair<TER, std::unique_ptr<Step>>
make_XRPEndpointStep(StrandContext const& ctx, AccountID const& acc);
template <class InAmt, class OutAmt>
bool
isDirectXrpToXrp(Strand const& strand);
/// @endcond
} // namespace xrpl

View File

@@ -0,0 +1,784 @@
#pragma once
#include <xrpl/basics/Log.h>
#include <xrpl/ledger/Credit.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/IOUAmount.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/paths/Flow.h>
#include <xrpl/tx/paths/detail/AmountSpec.h>
#include <xrpl/tx/paths/detail/FlatSets.h>
#include <xrpl/tx/paths/detail/FlowDebugInfo.h>
#include <xrpl/tx/paths/detail/Steps.h>
#include <xrpl/tx/transactors/AMM/AMMContext.h>
#include <xrpl/tx/transactors/AMM/AMMHelpers.h>
#include <boost/container/flat_set.hpp>
#include <algorithm>
#include <iterator>
#include <numeric>
namespace xrpl {
/** Result of flow() execution of a single Strand. */
template <class TInAmt, class TOutAmt>
struct StrandResult
{
bool success; ///< Strand succeeded
TInAmt in = beast::zero; ///< Currency amount in
TOutAmt out = beast::zero; ///< Currency amount out
std::optional<PaymentSandbox> sandbox; ///< Resulting Sandbox state
boost::container::flat_set<uint256> ofrsToRm; ///< Offers to remove
// Num offers consumed or partially consumed (includes expired and unfunded
// offers)
std::uint32_t ofrsUsed = 0;
// strand can be inactive if there is no more liquidity or too many offers
// have been consumed
bool inactive = false; ///< Strand should not considered as a further
///< source of liquidity (dry)
/** Strand result constructor */
StrandResult() = default;
StrandResult(
Strand const& strand,
TInAmt const& in_,
TOutAmt const& out_,
PaymentSandbox&& sandbox_,
boost::container::flat_set<uint256> ofrsToRm_,
bool inactive_)
: success(true)
, in(in_)
, out(out_)
, sandbox(std::move(sandbox_))
, ofrsToRm(std::move(ofrsToRm_))
, ofrsUsed(offersUsed(strand))
, inactive(inactive_)
{
}
StrandResult(Strand const& strand, boost::container::flat_set<uint256> ofrsToRm_)
: success(false), ofrsToRm(std::move(ofrsToRm_)), ofrsUsed(offersUsed(strand))
{
}
};
/**
Request `out` amount from a strand
@param baseView Trust lines and balances
@param strand Steps of Accounts to ripple through and offer books to use
@param maxIn Max amount of input allowed
@param out Amount of output requested from the strand
@param j Journal to write log messages to
@return Actual amount in and out from the strand, errors, offers to remove,
and payment sandbox
*/
template <class TInAmt, class TOutAmt>
StrandResult<TInAmt, TOutAmt>
flow(
PaymentSandbox const& baseView,
Strand const& strand,
std::optional<TInAmt> const& maxIn,
TOutAmt const& out,
beast::Journal j)
{
using Result = StrandResult<TInAmt, TOutAmt>;
if (strand.empty())
{
JLOG(j.warn()) << "Empty strand passed to Liquidity";
return {};
}
boost::container::flat_set<uint256> ofrsToRm;
if (isDirectXrpToXrp<TInAmt, TOutAmt>(strand))
{
return Result{strand, std::move(ofrsToRm)};
}
try
{
std::size_t const s = strand.size();
std::size_t limitingStep = strand.size();
std::optional<PaymentSandbox> sb(&baseView);
// The "all funds" view determines if an offer becomes unfunded or is
// found unfunded
// These are the account balances before the strand executes
std::optional<PaymentSandbox> afView(&baseView);
EitherAmount limitStepOut;
{
EitherAmount stepOut(out);
for (auto i = s; i--;)
{
auto r = strand[i]->rev(*sb, *afView, ofrsToRm, stepOut);
if (strand[i]->isZero(r.second))
{
JLOG(j.trace()) << "Strand found dry in rev";
return Result{strand, std::move(ofrsToRm)};
}
if (i == 0 && maxIn && *maxIn < get<TInAmt>(r.first))
{
// limiting - exceeded maxIn
// Throw out previous results
sb.emplace(&baseView);
limitingStep = i;
// re-execute the limiting step
r = strand[i]->fwd(*sb, *afView, ofrsToRm, EitherAmount(*maxIn));
limitStepOut = r.second;
if (strand[i]->isZero(r.second))
{
JLOG(j.trace()) << "First step found dry";
return Result{strand, std::move(ofrsToRm)};
}
if (get<TInAmt>(r.first) != *maxIn)
{
// Something is very wrong
// throwing out the sandbox can only increase liquidity
// yet the limiting is still limiting
// LCOV_EXCL_START
JLOG(j.fatal()) << "Re-executed limiting step failed. r.first: "
<< to_string(get<TInAmt>(r.first)) << " maxIn: " << to_string(*maxIn);
UNREACHABLE(
"xrpl::flow : first step re-executing the "
"limiting step failed");
return Result{strand, std::move(ofrsToRm)};
// LCOV_EXCL_STOP
}
}
else if (!strand[i]->equalOut(r.second, stepOut))
{
// limiting
// Throw out previous results
sb.emplace(&baseView);
afView.emplace(&baseView);
limitingStep = i;
// re-execute the limiting step
stepOut = r.second;
r = strand[i]->rev(*sb, *afView, ofrsToRm, stepOut);
limitStepOut = r.second;
if (strand[i]->isZero(r.second))
{
// A tiny input amount can cause this step to output
// zero. I.e. 10^-80 IOU into an IOU -> XRP offer.
JLOG(j.trace()) << "Limiting step found dry";
return Result{strand, std::move(ofrsToRm)};
}
if (!strand[i]->equalOut(r.second, stepOut))
{
// Something is very wrong
// throwing out the sandbox can only increase liquidity
// yet the limiting is still limiting
// LCOV_EXCL_START
#ifndef NDEBUG
JLOG(j.fatal()) << "Re-executed limiting step failed. r.second: " << r.second
<< " stepOut: " << stepOut;
#else
JLOG(j.fatal()) << "Re-executed limiting step failed";
#endif
UNREACHABLE(
"xrpl::flow : limiting step re-executing the "
"limiting step failed");
return Result{strand, std::move(ofrsToRm)};
// LCOV_EXCL_STOP
}
}
// prev node needs to produce what this node wants to consume
stepOut = r.first;
}
}
{
EitherAmount stepIn(limitStepOut);
for (auto i = limitingStep + 1; i < s; ++i)
{
auto const r = strand[i]->fwd(*sb, *afView, ofrsToRm, stepIn);
if (strand[i]->isZero(r.second))
{
// A tiny input amount can cause this step to output zero.
// I.e. 10^-80 IOU into an IOU -> XRP offer.
JLOG(j.trace()) << "Non-limiting step found dry";
return Result{strand, std::move(ofrsToRm)};
}
if (!strand[i]->equalIn(r.first, stepIn))
{
// The limits should already have been found, so executing a
// strand forward from the limiting step should not find a
// new limit
// LCOV_EXCL_START
#ifndef NDEBUG
JLOG(j.fatal()) << "Re-executed forward pass failed. r.first: " << r.first << " stepIn: " << stepIn;
#else
JLOG(j.fatal()) << "Re-executed forward pass failed";
#endif
UNREACHABLE(
"xrpl::flow : non-limiting step re-executing the "
"forward pass failed");
return Result{strand, std::move(ofrsToRm)};
// LCOV_EXCL_STOP
}
stepIn = r.second;
}
}
auto const strandIn = *strand.front()->cachedIn();
auto const strandOut = *strand.back()->cachedOut();
#ifndef NDEBUG
{
// Check that the strand will execute as intended
// Re-executing the strand will change the cached values
PaymentSandbox checkSB(&baseView);
PaymentSandbox checkAfView(&baseView);
EitherAmount stepIn(*strand[0]->cachedIn());
for (auto i = 0; i < s; ++i)
{
bool valid;
std::tie(valid, stepIn) = strand[i]->validFwd(checkSB, checkAfView, stepIn);
if (!valid)
{
JLOG(j.warn()) << "Strand re-execute check failed. Step: " << i;
break;
}
}
}
#endif
bool const inactive = std::any_of(
strand.begin(), strand.end(), [](std::unique_ptr<Step> const& step) { return step->inactive(); });
return Result(
strand, get<TInAmt>(strandIn), get<TOutAmt>(strandOut), std::move(*sb), std::move(ofrsToRm), inactive);
}
catch (FlowException const&)
{
return Result{strand, std::move(ofrsToRm)};
}
}
/// @cond INTERNAL
template <class TInAmt, class TOutAmt>
struct FlowResult
{
TInAmt in = beast::zero;
TOutAmt out = beast::zero;
std::optional<PaymentSandbox> sandbox;
boost::container::flat_set<uint256> removableOffers;
TER ter = temUNKNOWN;
FlowResult() = default;
FlowResult(
TInAmt const& in_,
TOutAmt const& out_,
PaymentSandbox&& sandbox_,
boost::container::flat_set<uint256> ofrsToRm)
: in(in_), out(out_), sandbox(std::move(sandbox_)), removableOffers(std::move(ofrsToRm)), ter(tesSUCCESS)
{
}
FlowResult(TER ter_, boost::container::flat_set<uint256> ofrsToRm) : removableOffers(std::move(ofrsToRm)), ter(ter_)
{
}
FlowResult(TER ter_, TInAmt const& in_, TOutAmt const& out_, boost::container::flat_set<uint256> ofrsToRm)
: in(in_), out(out_), removableOffers(std::move(ofrsToRm)), ter(ter_)
{
}
};
/// @endcond
/// @cond INTERNAL
inline std::optional<Quality>
qualityUpperBound(ReadView const& v, Strand const& strand)
{
Quality q{STAmount::uRateOne};
std::optional<Quality> stepQ;
DebtDirection dir = DebtDirection::issues;
for (auto const& step : strand)
{
if (std::tie(stepQ, dir) = step->qualityUpperBound(v, dir); stepQ)
q = composed_quality(q, *stepQ);
else
return std::nullopt;
}
return q;
};
/// @endcond
/// @cond INTERNAL
/** Limit remaining out only if one strand and limitQuality is included.
* Targets one path payment with AMM where the average quality is linear
* and instant quality is quadratic function of output. Calculating quality
* function for the whole strand enables figuring out required output
* to produce requested strand's limitQuality. Reducing the output,
* increases quality of AMM steps, increasing the strand's composite
* quality as the result.
*/
template <typename TOutAmt>
inline TOutAmt
limitOut(ReadView const& v, Strand const& strand, TOutAmt const& remainingOut, Quality const& limitQuality)
{
std::optional<QualityFunction> stepQualityFunc;
std::optional<QualityFunction> qf;
DebtDirection dir = DebtDirection::issues;
for (auto const& step : strand)
{
if (std::tie(stepQualityFunc, dir) = step->getQualityFunc(v, dir); stepQualityFunc)
{
if (!qf)
qf = stepQualityFunc;
else
qf->combine(*stepQualityFunc);
}
else
return remainingOut;
}
// QualityFunction is constant
if (!qf || qf->isConst())
return remainingOut;
auto const out = [&]() {
if (auto const out = qf->outFromAvgQ(limitQuality); !out)
return remainingOut;
else if constexpr (std::is_same_v<TOutAmt, XRPAmount>)
return XRPAmount{*out};
else if constexpr (std::is_same_v<TOutAmt, IOUAmount>)
return IOUAmount{*out};
else
return STAmount{remainingOut.issue(), out->mantissa(), out->exponent()};
}();
// A tiny difference could be due to the round off
if (withinRelativeDistance(out, remainingOut, Number(1, -9)))
return remainingOut;
return std::min(out, remainingOut);
};
/// @endcond
/// @cond INTERNAL
/* Track the non-dry strands
flow will search the non-dry strands (stored in `cur_`) for the best
available liquidity If flow doesn't use all the liquidity of a strand, that
strand is added to `next_`. The strands in `next_` are searched after the
current best liquidity is used.
*/
class ActiveStrands
{
private:
// Strands to be explored for liquidity
std::vector<Strand const*> cur_;
// Strands that may be explored for liquidity on the next iteration
std::vector<Strand const*> next_;
public:
ActiveStrands(std::vector<Strand> const& strands)
{
cur_.reserve(strands.size());
next_.reserve(strands.size());
for (auto& strand : strands)
next_.push_back(&strand);
}
// Start a new iteration in the search for liquidity
// Set the current strands to the strands in `next_`
void
activateNext(ReadView const& v, std::optional<Quality> const& limitQuality)
{
// add the strands in `next_` to `cur_`, sorted by theoretical quality.
// Best quality first.
cur_.clear();
if (!next_.empty())
{
std::vector<std::pair<Quality, Strand const*>> strandQualities;
strandQualities.reserve(next_.size());
if (next_.size() > 1) // no need to sort one strand
{
for (Strand const* strand : next_)
{
if (!strand)
{
// should not happen
continue;
}
if (auto const qual = qualityUpperBound(v, *strand))
{
if (limitQuality && *qual < *limitQuality)
{
// If a strand's quality is ever over limitQuality
// it is no longer part of the candidate set. Note
// that when transfer fees are charged, and an
// account goes from redeeming to issuing then
// strand quality _can_ increase; However, this is
// an unusual corner case.
continue;
}
strandQualities.push_back({*qual, strand});
}
}
// must stable sort for deterministic order across different c++
// standard library implementations
std::stable_sort(strandQualities.begin(), strandQualities.end(), [](auto const& lhs, auto const& rhs) {
// higher qualities first
return std::get<Quality>(lhs) > std::get<Quality>(rhs);
});
next_.clear();
next_.reserve(strandQualities.size());
for (auto const& sq : strandQualities)
{
next_.push_back(std::get<Strand const*>(sq));
}
}
}
std::swap(cur_, next_);
}
Strand const*
get(size_t i) const
{
if (i >= cur_.size())
{
// LCOV_EXCL_START
UNREACHABLE("xrpl::ActiveStrands::get : input out of range");
return nullptr;
// LCOV_EXCL_STOP
}
return cur_[i];
}
void
push(Strand const* s)
{
next_.push_back(s);
}
// Push the strands from index i to the end of cur_ to next_
void
pushRemainingCurToNext(size_t i)
{
if (i >= cur_.size())
return;
next_.insert(next_.end(), std::next(cur_.begin(), i), cur_.end());
}
auto
size() const
{
return cur_.size();
}
void
removeIndex(std::size_t i)
{
if (i >= next_.size())
return;
next_.erase(next_.begin() + i);
}
};
/// @endcond
/**
Request `out` amount from a collection of strands
Attempt to fulfill the payment by using liquidity from the strands in order
from least expensive to most expensive
@param baseView Trust lines and balances
@param strands Each strand contains the steps of accounts to ripple through
and offer books to use
@param outReq Amount of output requested from the strand
@param partialPayment If true allow less than the full payment
@param offerCrossing If true offer crossing, not handling a standard payment
@param limitQuality If present, the minimum quality for any strand taken
@param sendMaxST If present, the maximum STAmount to send
@param j Journal to write journal messages to
@param ammContext counts iterations with AMM offers
@param flowDebugInfo If pointer is non-null, write flow debug info here
@return Actual amount in and out from the strands, errors, and payment
sandbox
*/
template <class TInAmt, class TOutAmt>
FlowResult<TInAmt, TOutAmt>
flow(
PaymentSandbox const& baseView,
std::vector<Strand> const& strands,
TOutAmt const& outReq,
bool partialPayment,
OfferCrossing offerCrossing,
std::optional<Quality> const& limitQuality,
std::optional<STAmount> const& sendMaxST,
beast::Journal j,
AMMContext& ammContext,
path::detail::FlowDebugInfo* flowDebugInfo = nullptr)
{
// Used to track the strand that offers the best quality (output/input
// ratio)
struct BestStrand
{
TInAmt in;
TOutAmt out;
PaymentSandbox sb;
Strand const& strand;
Quality quality;
BestStrand(
TInAmt const& in_,
TOutAmt const& out_,
PaymentSandbox&& sb_,
Strand const& strand_,
Quality const& quality_)
: in(in_), out(out_), sb(std::move(sb_)), strand(strand_), quality(quality_)
{
}
};
std::size_t const maxTries = 1000;
std::size_t curTry = 0;
std::uint32_t maxOffersToConsider = 1500;
std::uint32_t offersConsidered = 0;
// There is a bug in gcc that incorrectly warns about using uninitialized
// values if `remainingIn` is initialized through a copy constructor. We can
// get similar warnings for `sendMax` if it is initialized in the most
// natural way. Using `make_optional`, allows us to work around this bug.
TInAmt const sendMaxInit = sendMaxST ? toAmount<TInAmt>(*sendMaxST) : TInAmt{beast::zero};
std::optional<TInAmt> const sendMax =
(sendMaxST && sendMaxInit >= beast::zero) ? std::make_optional(sendMaxInit) : std::nullopt;
std::optional<TInAmt> remainingIn = !!sendMax ? std::make_optional(sendMaxInit) : std::nullopt;
// std::optional<TInAmt> remainingIn{sendMax};
TOutAmt remainingOut(outReq);
PaymentSandbox sb(&baseView);
// non-dry strands
ActiveStrands activeStrands(strands);
// Keeping a running sum of the amount in the order they are processed
// will not give the best precision. Keep a collection so they may be summed
// from smallest to largest
boost::container::flat_multiset<TInAmt> savedIns;
savedIns.reserve(maxTries);
boost::container::flat_multiset<TOutAmt> savedOuts;
savedOuts.reserve(maxTries);
auto sum = [](auto const& col) {
using TResult = std::decay_t<decltype(*col.begin())>;
if (col.empty())
return TResult{beast::zero};
return std::accumulate(col.begin() + 1, col.end(), *col.begin());
};
// These offers only need to be removed if the payment is not
// successful
boost::container::flat_set<uint256> ofrsToRmOnFail;
while (remainingOut > beast::zero && (!remainingIn || *remainingIn > beast::zero))
{
++curTry;
if (curTry >= maxTries)
{
return {telFAILED_PROCESSING, std::move(ofrsToRmOnFail)};
}
activeStrands.activateNext(sb, limitQuality);
ammContext.setMultiPath(activeStrands.size() > 1);
// Limit only if one strand and limitQuality
auto const limitRemainingOut = [&]() {
if (activeStrands.size() == 1 && limitQuality)
if (auto const strand = activeStrands.get(0))
return limitOut(sb, *strand, remainingOut, *limitQuality);
return remainingOut;
}();
auto const adjustedRemOut = limitRemainingOut != remainingOut;
boost::container::flat_set<uint256> ofrsToRm;
std::optional<BestStrand> best;
if (flowDebugInfo)
flowDebugInfo->newLiquidityPass();
// Index of strand to mark as inactive (remove from the active list) if
// the liquidity is used. This is used for strands that consume too many
// offers Constructed as `false,0` to workaround a gcc warning about
// uninitialized variables
std::optional<std::size_t> markInactiveOnUse;
for (size_t strandIndex = 0, sie = activeStrands.size(); strandIndex != sie; ++strandIndex)
{
Strand const* strand = activeStrands.get(strandIndex);
if (!strand)
{
// should not happen
continue;
}
// Clear AMM liquidity used flag. The flag might still be set if
// the previous strand execution failed. It has to be reset
// since this strand might not have AMM liquidity.
ammContext.clear();
if (offerCrossing && limitQuality)
{
auto const strandQ = qualityUpperBound(sb, *strand);
if (!strandQ || *strandQ < *limitQuality)
continue;
}
auto f = flow<TInAmt, TOutAmt>(sb, *strand, remainingIn, limitRemainingOut, j);
// rm bad offers even if the strand fails
SetUnion(ofrsToRm, f.ofrsToRm);
offersConsidered += f.ofrsUsed;
if (!f.success || f.out == beast::zero)
continue;
if (flowDebugInfo)
flowDebugInfo->pushLiquiditySrc(EitherAmount(f.in), EitherAmount(f.out));
XRPL_ASSERT(
f.out <= remainingOut && f.sandbox && (!remainingIn || f.in <= *remainingIn),
"xrpl::flow : remaining constraints");
Quality const q(f.out, f.in);
JLOG(j.trace()) << "New flow iter (iter, in, out): " << curTry - 1 << " " << to_string(f.in) << " "
<< to_string(f.out);
// limitOut() finds output to generate exact requested
// limitQuality. But the actual limit quality might be slightly
// off due to the round off.
if (limitQuality && q < *limitQuality &&
(!adjustedRemOut || !withinRelativeDistance(q, *limitQuality, Number(1, -7))))
{
JLOG(j.trace()) << "Path rejected by limitQuality"
<< " limit: " << *limitQuality << " path q: " << q;
continue;
}
XRPL_ASSERT(!best, "xrpl::flow : best is unset");
if (!f.inactive)
activeStrands.push(strand);
best.emplace(f.in, f.out, std::move(*f.sandbox), *strand, q);
activeStrands.pushRemainingCurToNext(strandIndex + 1);
break;
}
bool const shouldBreak = !best || offersConsidered >= maxOffersToConsider;
if (best)
{
if (markInactiveOnUse)
{
activeStrands.removeIndex(*markInactiveOnUse);
markInactiveOnUse.reset();
}
savedIns.insert(best->in);
savedOuts.insert(best->out);
remainingOut = outReq - sum(savedOuts);
if (sendMax)
remainingIn = *sendMax - sum(savedIns);
if (flowDebugInfo)
flowDebugInfo->pushPass(EitherAmount(best->in), EitherAmount(best->out), activeStrands.size());
JLOG(j.trace()) << "Best path: in: " << to_string(best->in) << " out: " << to_string(best->out)
<< " remainingOut: " << to_string(remainingOut);
best->sb.apply(sb);
ammContext.update();
}
else
{
JLOG(j.trace()) << "All strands dry.";
}
best.reset(); // view in best must be destroyed before modifying base
// view
if (!ofrsToRm.empty())
{
SetUnion(ofrsToRmOnFail, ofrsToRm);
for (auto const& o : ofrsToRm)
{
if (auto ok = sb.peek(keylet::offer(o)))
offerDelete(sb, ok, j);
}
}
if (shouldBreak)
break;
}
auto const actualOut = sum(savedOuts);
auto const actualIn = sum(savedIns);
JLOG(j.trace()) << "Total flow: in: " << to_string(actualIn) << " out: " << to_string(actualOut);
/* flowCross doesn't handle offer crossing with tfFillOrKill flag correctly.
* 1. If tfFillOrKill is set then the owner must receive the full
* TakerPays. We reverse pays and gets because during crossing
* we are taking, therefore the owner must deliver the full TakerPays and
* the entire TakerGets doesn't have to be spent.
* Pre-fixFillOrKill amendment code fails if the entire TakerGets
* is not spent. fixFillOrKill addresses this issue.
* 2. If tfSell is also set then the owner must spend the entire TakerGets
* even if it means obtaining more than TakerPays. Since the pays and gets
* are reversed, the owner must send the entire TakerGets.
*/
bool const fillOrKillEnabled = baseView.rules().enabled(fixFillOrKill);
if (actualOut != outReq)
{
if (actualOut > outReq)
{
// Rounding in the payment engine is causing this assert to
// sometimes fire with "dust" amounts. This is causing issues when
// running debug builds of rippled. While this issue still needs to
// be resolved, the assert is causing more harm than good at this
// point.
// UNREACHABLE("xrpl::flow : rounding error");
return {tefEXCEPTION, std::move(ofrsToRmOnFail)};
}
if (!partialPayment)
{
// If we're offerCrossing a !partialPayment, then we're
// handling tfFillOrKill.
// Pre-fixFillOrKill amendment:
// That case is handled below; not here.
// fixFillOrKill amendment:
// That case is handled here if tfSell is also not set; i.e,
// case 1.
if (!offerCrossing || (fillOrKillEnabled && offerCrossing != OfferCrossing::sell))
return {tecPATH_PARTIAL, actualIn, actualOut, std::move(ofrsToRmOnFail)};
}
else if (actualOut == beast::zero)
{
return {tecPATH_DRY, std::move(ofrsToRmOnFail)};
}
}
if (offerCrossing && (!partialPayment && (!fillOrKillEnabled || offerCrossing == OfferCrossing::sell)))
{
// If we're offer crossing and partialPayment is *not* true, then
// we're handling a FillOrKill offer. In this case remainingIn must
// be zero (all funds must be consumed) or else we kill the offer.
// Pre-fixFillOrKill amendment:
// Handles both cases 1. and 2.
// fixFillOrKill amendment:
// Handles 2. 1. is handled above and falls through for tfSell.
XRPL_ASSERT(remainingIn, "xrpl::flow : nonzero remainingIn");
if (remainingIn && *remainingIn != beast::zero)
return {tecPATH_PARTIAL, actualIn, actualOut, std::move(ofrsToRmOnFail)};
}
return {actualIn, actualOut, std::move(sb), std::move(ofrsToRmOnFail)};
}
} // namespace xrpl

View File

@@ -0,0 +1,67 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
/** AMMBid implements AMM bid Transactor.
* This is a mechanism for an AMM instance to auction-off
* the trading advantages to users (arbitrageurs) at a discounted
* TradingFee for a 24 hour slot. Any account that owns corresponding
* LPTokens can bid for the auction slot of that AMM instance.
* Part of the proceeds from the auction, i.e. LPTokens are refunded
* to the current slot-holder computed on a pro rata basis.
* Remaining part of the proceeds - in the units of LPTokens- is burnt,
* thus effectively increasing the LPs shares.
* Total slot time of 24 hours is divided into 20 equal intervals.
* The auction slot can be in any of the following states at any time:
* - Empty - no account currently holds the slot.
* - Occupied - an account owns the slot with at least 5% of the remaining
* slot time (in one of 1-19 intervals).
* - Tailing - an account owns the slot with less than 5% of the remaining time.
* The slot-holder owns the slot privileges when in state Occupied or Tailing.
* If x is the fraction of used slot time for the current slot holder
* and X is the price at which the slot can be bought specified in LPTokens
* then: The minimum bid price for the slot in first interval is
* f(x) = X * 1.05 + min_slot_price
* The bid price of slot any time is
* f(x) = X * 1.05 * (1 - x^60) + min_slot_price, where min_slot_price
* is a constant fraction of the total LPTokens.
* The revenue from a successful bid is split between the current slot-holder
* and the pool. The current slot holder is always refunded the remaining slot
* value f(x) = (1 - x) * X.
* The remaining LPTokens are burnt.
* The auction information is maintained in AuctionSlot of ltAMM object.
* AuctionSlot contains:
* Account - account id, which owns the slot.
* Expiration - slot expiration time
* DiscountedFee - trading fee charged to the account, default is 0.
* Price - price paid for the slot in LPTokens.
* AuthAccounts - up to four accounts authorized to trade at
* the discounted fee.
* @see [XLS30d:Continuous Auction
* Mechanism](https://github.com/XRPLF/XRPL-Standards/discussions/78)
*/
class AMMBid : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit AMMBid(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,56 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class Sandbox;
class AMMClawback : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit AMMClawback(ApplyContext& ctx) : Transactor(ctx)
{
}
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
private:
TER
applyGuts(Sandbox& view);
/** Withdraw both assets by providing maximum amount of asset1,
* asset2's amount will be calculated according to the current proportion.
* Since it is two-asset withdrawal, tfee is omitted.
* @param view
* @param ammAccount current AMM account
* @param amountBalance current AMM asset1 balance
* @param amount2Balance current AMM asset2 balance
* @param lptAMMBalance current AMM LPT balance
* @param amount asset1 withdraw amount
* @return
*/
std::tuple<TER, STAmount, STAmount, std::optional<STAmount>>
equalWithdrawMatchingOneAmount(
Sandbox& view,
SLE const& ammSle,
AccountID const& holder,
AccountID const& ammAccount,
STAmount const& amountBalance,
STAmount const& amount2Balance,
STAmount const& lptAMMBalance,
STAmount const& holdLPtokens,
STAmount const& amount);
};
} // namespace xrpl

View File

@@ -0,0 +1,96 @@
#pragma once
#include <xrpl/protocol/AccountID.h>
#include <cstdint>
namespace xrpl {
/** Maintains AMM info per overall payment engine execution and
* individual iteration.
* Only one instance of this class is created in Flow.cpp::flow().
* The reference is percolated through calls to AMMLiquidity class,
* which handles AMM offer generation.
*/
class AMMContext
{
public:
// Restrict number of AMM offers. If this restriction is removed
// then need to restrict in some other way because AMM offers are
// not counted in the BookStep offer counter.
constexpr static std::uint8_t MaxIterations = 30;
private:
// Tx account owner is required to get the AMM trading fee in BookStep
AccountID account_;
// true if payment has multiple paths
bool multiPath_{false};
// Is true if AMM offer is consumed during a payment engine iteration.
bool ammUsed_{false};
// Counter of payment engine iterations with consumed AMM
std::uint16_t ammIters_{0};
public:
AMMContext(AccountID const& account, bool multiPath) : account_(account), multiPath_(multiPath)
{
}
~AMMContext() = default;
AMMContext(AMMContext const&) = delete;
AMMContext&
operator=(AMMContext const&) = delete;
bool
multiPath() const
{
return multiPath_;
}
void
setMultiPath(bool fs)
{
multiPath_ = fs;
}
void
setAMMUsed()
{
ammUsed_ = true;
}
void
update()
{
if (ammUsed_)
++ammIters_;
ammUsed_ = false;
}
bool
maxItersReached() const
{
return ammIters_ >= MaxIterations;
}
std::uint16_t
curIters() const
{
return ammIters_;
}
AccountID
account() const
{
return account_;
}
/** Strand execution may fail. Reset the flag at the start
* of each payment engine iteration.
*/
void
clear()
{
ammUsed_ = false;
}
};
} // namespace xrpl

View File

@@ -0,0 +1,63 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
/** AMMCreate implements Automatic Market Maker(AMM) creation Transactor.
* It creates a new AMM instance with two tokens. Any trader, or Liquidity
* Provider (LP), can create the AMM instance and receive in return shares
* of the AMM pool in the form of LPTokens. The number of tokens that LP gets
* are determined by LPTokens = sqrt(A * B), where A and B is the current
* composition of the AMM pool. LP can add (AMMDeposit) or withdraw
* (AMMWithdraw) tokens from AMM and
* AMM can be used transparently in the payment or offer crossing transactions.
* Trading fee is charged to the traders for the trades executed against
* AMM instance. The fee is added to the AMM pool and distributed to the LPs
* in proportion to the LPTokens upon liquidity removal. The fee can be voted
* on by LP's (AMMVote). LP's can continuously bid (AMMBid) for the 24 hour
* auction slot, which enables LP's to trade at zero trading fee.
* AMM instance creates AccountRoot object with disabled master key
* for book-keeping of XRP balance if one of the tokens
* is XRP, a trustline for each IOU token, a trustline to keep track
* of LPTokens, and ltAMM ledger object. AccountRoot ID is generated
* internally from the parent's hash. ltAMM's object ID is
* hash{token1.currency, token1.issuer, token2.currency, token2.issuer}, where
* issue1 < issue2. ltAMM object provides mapping from the hash to AccountRoot
* ID and contains: AMMAccount - AMM AccountRoot ID. TradingFee - AMM voted
* TradingFee. VoteSlots - Array of VoteEntry, contains fee vote information.
* AuctionSlot - Auction slot, contains discounted fee bid information.
* LPTokenBalance - LPTokens outstanding balance.
* AMMToken - currency/issuer information for AMM tokens.
* AMMDeposit, AMMWithdraw, AMMVote, and AMMBid transactions use the hash
* to access AMM instance.
* @see [XLS30d:Creating AMM instance on
* XRPL](https://github.com/XRPLF/XRPL-Standards/discussions/78)
*/
class AMMCreate : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit AMMCreate(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);
static TER
preclaim(PreclaimContext const& ctx);
/** Attempt to create the AMM instance. */
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,35 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
/** AMMDelete implements AMM delete transactor. This is a mechanism to
* delete AMM in an empty state when the number of LP tokens is 0.
* AMMDelete deletes the trustlines up to configured maximum. If all
* trustlines are deleted then AMM ltAMM and root account are deleted.
* Otherwise AMMDelete should be called again.
*/
class AMMDelete : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit AMMDelete(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,231 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class Sandbox;
/** AMMDeposit implements AMM deposit Transactor.
* The deposit transaction is used to add liquidity to the AMM instance pool,
* thus obtaining some share of the instance's pools in the form of LPTokens.
* If the trader deposits proportional values of both assets without changing
* their relative price, then no trading fee is charged on the transaction.
* The trader can specify different combination of the fields in the deposit.
* LPTokens - transaction assumes proportional deposit of pools assets in
* exchange for the specified amount of LPTokens of the AMM instance.
* Asset1In - transaction assumes single asset deposit of the amount of asset
* specified by Asset1In. This is essentially a swap and an equal asset
* deposit.
* Asset1In and Asset2In - transaction assumes proportional deposit of pool
* assets with the constraints on the maximum amount of each asset that
* the trader is willing to deposit.
* Asset1In and LPTokens - transaction assumes that a single asset asset1
* is deposited to obtain some share of the AMM instance's pools
* represented by amount of LPTokens.
* Asset1In and EPrice - transaction assumes single asset deposit with
* the following two constraints:
* a. amount of asset1 if specified (not 0) in Asset1In specifies the
* maximum amount of asset1 that the trader is willing to deposit b. The
* effective-price of the LPTokens traded out does not exceed the specified
* EPrice. Following updates after a successful AMMDeposit transaction: The
* deposited asset, if XRP, is transferred from the account that initiated the
* transaction to the AMM instance account, thus changing the Balance field of
* each account. The deposited asset, if tokens, are balanced between the AMM
* account and the issuer account trustline. The LPTokens are issued by the AMM
* instance account to the account that initiated the transaction and a new
* trustline is created, if there does not exist one. The pool composition is
* updated.
* @see [XLS30d:AMMDeposit
* transaction](https://github.com/XRPLF/XRPL-Standards/discussions/78)
*/
class AMMDeposit : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit AMMDeposit(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
private:
std::pair<TER, bool>
applyGuts(Sandbox& view);
/** Deposit requested assets and token amount into LP account.
* Return new total LPToken balance.
* @param view
* @param ammAccount
* @param amountBalance current AMM asset1 balance
* @param amountDeposit
* @param amount2Deposit
* @param lptAMMBalance current AMM LPT balance
* @param lpTokensDeposit amount of tokens to deposit
* @param depositMin minimum accepted amount deposit
* @param deposit2Min minimum accepted amount2 deposit
* @param lpTokensDepositMin minimum accepted LPTokens deposit
* @param tfee trading fee in basis points
* @return
*/
std::pair<TER, STAmount>
deposit(
Sandbox& view,
AccountID const& ammAccount,
STAmount const& amountBalance,
STAmount const& amountDeposit,
std::optional<STAmount> const& amount2Deposit,
STAmount const& lptAMMBalance,
STAmount const& lpTokensDeposit,
std::optional<STAmount> const& depositMin,
std::optional<STAmount> const& deposit2Min,
std::optional<STAmount> const& lpTokensDepositMin,
std::uint16_t tfee);
/** Equal asset deposit (LPTokens) for the specified share of
* the AMM instance pools. The trading fee is not charged.
* @param view
* @param ammAccount
* @param amountBalance current AMM asset1 balance
* @param amount2Balance current AMM asset2 balance
* @param lptAMMBalance current AMM LPT balance
* @param lpTokensDeposit amount of tokens to deposit
* @param depositMin minimum accepted amount deposit
* @param deposit2Min minimum accepted amount2 deposit
* @param tfee trading fee in basis points
* @return
*/
std::pair<TER, STAmount>
equalDepositTokens(
Sandbox& view,
AccountID const& ammAccount,
STAmount const& amountBalance,
STAmount const& amount2Balance,
STAmount const& lptAMMBalance,
STAmount const& lpTokensDeposit,
std::optional<STAmount> const& depositMin,
std::optional<STAmount> const& deposit2Min,
std::uint16_t tfee);
/** Equal asset deposit (Asset1In, Asset2In) with the constraint on
* the maximum amount of both assets that the trader is willing to deposit.
* The trading fee is not charged.
* @param view
* @param ammAccount
* @param amountBalance current AMM asset1 balance
* @param amount2Balance current AMM asset2 balance
* @param lptAMMBalance current AMM LPT balance
* @param amount maximum asset1 deposit amount
* @param amount2 maximum asset2 deposit amount
* @param lpTokensDepositMin minimum accepted LPTokens deposit
* @param tfee trading fee in basis points
* @return
*/
std::pair<TER, STAmount>
equalDepositLimit(
Sandbox& view,
AccountID const& ammAccount,
STAmount const& amountBalance,
STAmount const& amount2Balance,
STAmount const& lptAMMBalance,
STAmount const& amount,
STAmount const& amount2,
std::optional<STAmount> const& lpTokensDepositMin,
std::uint16_t tfee);
/** Single asset deposit (Asset1In) by the amount.
* The trading fee is charged.
* @param view
* @param ammAccount
* @param amountBalance current AMM asset1 balance
* @param lptAMMBalance current AMM LPT balance
* @param amount requested asset1 deposit amount
* @param lpTokensDepositMin minimum accepted LPTokens deposit
* @param tfee trading fee in basis points
* @return
*/
std::pair<TER, STAmount>
singleDeposit(
Sandbox& view,
AccountID const& ammAccount,
STAmount const& amountBalance,
STAmount const& lptAMMBalance,
STAmount const& amount,
std::optional<STAmount> const& lpTokensDepositMin,
std::uint16_t tfee);
/** Single asset deposit (Asset1In, LPTokens) by the tokens.
* The trading fee is charged.
* @param view
* @param ammAccount
* @param amountBalance current AMM asset1 balance
* @param amount max asset1 to deposit
* @param lptAMMBalance current AMM LPT balance
* @param lpTokensDeposit amount of tokens to deposit
* @param tfee trading fee in basis points
* @return
*/
std::pair<TER, STAmount>
singleDepositTokens(
Sandbox& view,
AccountID const& ammAccount,
STAmount const& amountBalance,
STAmount const& amount,
STAmount const& lptAMMBalance,
STAmount const& lpTokensDeposit,
std::uint16_t tfee);
/** Single asset deposit (Asset1In, EPrice) with two constraints.
* The trading fee is charged.
* @param view
* @param ammAccount
* @param amountBalance current AMM asset1 balance
* @param amount requested asset1 deposit amount
* @param lptAMMBalance current AMM LPT balance
* @param ePrice maximum effective price
* @param tfee
* @return
*/
std::pair<TER, STAmount>
singleDepositEPrice(
Sandbox& view,
AccountID const& ammAccount,
STAmount const& amountBalance,
STAmount const& amount,
STAmount const& lptAMMBalance,
STAmount const& ePrice,
std::uint16_t tfee);
/** Equal deposit in empty AMM state (LP tokens balance is 0)
* @param view
* @param ammAccount
* @param amount requested asset1 deposit amount
* @param amount2 requested asset2 deposit amount
* @param tfee
* @return
*/
std::pair<TER, STAmount>
equalDepositInEmptyState(
Sandbox& view,
AccountID const& ammAccount,
STAmount const& amount,
STAmount const& amount2,
Issue const& lptIssue,
std::uint16_t tfee);
};
} // namespace xrpl

View File

@@ -0,0 +1,689 @@
#pragma once
#include <xrpl/basics/Log.h>
#include <xrpl/basics/Number.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/protocol/AMMCore.h>
#include <xrpl/protocol/AmountConversions.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/IOUAmount.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/Quality.h>
#include <xrpl/protocol/Rules.h>
#include <xrpl/protocol/STAmount.h>
namespace xrpl {
namespace detail {
Number
reduceOffer(auto const& amount)
{
static Number const reducedOfferPct(9999, -4);
// Make sure the result is always less than amount or zero.
NumberRoundModeGuard mg(Number::towards_zero);
return amount * reducedOfferPct;
}
} // namespace detail
enum class IsDeposit : bool { No = false, Yes = true };
/** Calculate LP Tokens given AMM pool reserves.
* @param asset1 AMM one side of the pool reserve
* @param asset2 AMM another side of the pool reserve
* @return LP Tokens as IOU
*/
STAmount
ammLPTokens(STAmount const& asset1, STAmount const& asset2, Issue const& lptIssue);
/** Calculate LP Tokens given asset's deposit amount.
* @param asset1Balance current AMM asset1 balance
* @param asset1Deposit requested asset1 deposit amount
* @param lptAMMBalance AMM LPT balance
* @param tfee trading fee in basis points
* @return tokens
*/
STAmount
lpTokensOut(
STAmount const& asset1Balance,
STAmount const& asset1Deposit,
STAmount const& lptAMMBalance,
std::uint16_t tfee);
/** Calculate asset deposit given LP Tokens.
* @param asset1Balance current AMM asset1 balance
* @param lpTokens LP Tokens
* @param lptAMMBalance AMM LPT balance
* @param tfee trading fee in basis points
* @return
*/
STAmount
ammAssetIn(STAmount const& asset1Balance, STAmount const& lptAMMBalance, STAmount const& lpTokens, std::uint16_t tfee);
/** Calculate LP Tokens given asset's withdraw amount. Return 0
* if can't calculate.
* @param asset1Balance current AMM asset1 balance
* @param asset1Withdraw requested asset1 withdraw amount
* @param lptAMMBalance AMM LPT balance
* @param tfee trading fee in basis points
* @return tokens out amount
*/
STAmount
lpTokensIn(
STAmount const& asset1Balance,
STAmount const& asset1Withdraw,
STAmount const& lptAMMBalance,
std::uint16_t tfee);
/** Calculate asset withdrawal by tokens
* @param assetBalance balance of the asset being withdrawn
* @param lptAMMBalance total AMM Tokens balance
* @param lpTokens LP Tokens balance
* @param tfee trading fee in basis points
* @return calculated asset amount
*/
STAmount
ammAssetOut(STAmount const& assetBalance, STAmount const& lptAMMBalance, STAmount const& lpTokens, std::uint16_t tfee);
/** Check if the relative distance between the qualities
* is within the requested distance.
* @param calcQuality calculated quality
* @param reqQuality requested quality
* @param dist requested relative distance
* @return true if within dist, false otherwise
*/
inline bool
withinRelativeDistance(Quality const& calcQuality, Quality const& reqQuality, Number const& dist)
{
if (calcQuality == reqQuality)
return true;
auto const [min, max] = std::minmax(calcQuality, reqQuality);
// Relative distance is (max - min)/max. Can't use basic operations
// on Quality. Have to use Quality::rate() instead, which
// is inverse of quality: (1/max.rate - 1/min.rate)/(1/max.rate)
return ((min.rate() - max.rate()) / min.rate()) < dist;
}
/** Check if the relative distance between the amounts
* is within the requested distance.
* @param calc calculated amount
* @param req requested amount
* @param dist requested relative distance
* @return true if within dist, false otherwise
*/
// clang-format off
template <typename Amt>
requires(
std::is_same_v<Amt, STAmount> || std::is_same_v<Amt, IOUAmount> ||
std::is_same_v<Amt, XRPAmount> || std::is_same_v<Amt, Number>)
bool
withinRelativeDistance(Amt const& calc, Amt const& req, Number const& dist)
{
if (calc == req)
return true;
auto const [min, max] = std::minmax(calc, req);
return ((max - min) / max) < dist;
}
// clang-format on
/** Solve quadratic equation to find takerGets or takerPays. Round
* to minimize the amount in order to maximize the quality.
*/
std::optional<Number>
solveQuadraticEqSmallest(Number const& a, Number const& b, Number const& c);
/** Generate AMM offer starting with takerGets when AMM pool
* from the payment perspective is IOU(in)/XRP(out)
* Equations:
* Spot Price Quality after the offer is consumed:
* Qsp = (O - o) / (I + i) -- equation (1)
* where O is poolPays, I is poolGets, o is takerGets, i is takerPays
* Swap out:
* i = (I * o) / (O - o) * f -- equation (2)
* where f is (1 - tfee/100000), tfee is in basis points
* Effective price targetQuality:
* Qep = o / i -- equation (3)
* There are two scenarios to consider
* A) Qsp = Qep. Substitute i in (1) with (2) and solve for o
* and Qsp = targetQuality(Qt):
* o**2 + o * (I * Qt * (1 - 1 / f) - 2 * O) + O**2 - Qt * I * O = 0
* B) Qep = Qsp. Substitute i in (3) with (2) and solve for o
* and Qep = targetQuality(Qt):
* o = O - I * Qt / f
* Since the scenario is not known a priori, both A and B are solved and
* the lowest value of o is takerGets. takerPays is calculated with
* swap out eq (2). If o is less or equal to 0 then the offer can't
* be generated.
*/
template <typename TIn, typename TOut>
std::optional<TAmounts<TIn, TOut>>
getAMMOfferStartWithTakerGets(TAmounts<TIn, TOut> const& pool, Quality const& targetQuality, std::uint16_t const& tfee)
{
if (targetQuality.rate() == beast::zero)
return std::nullopt;
NumberRoundModeGuard mg(Number::to_nearest);
auto const f = feeMult(tfee);
auto const a = 1;
auto const b = pool.in * (1 - 1 / f) / targetQuality.rate() - 2 * pool.out;
auto const c = pool.out * pool.out - (pool.in * pool.out) / targetQuality.rate();
auto nTakerGets = solveQuadraticEqSmallest(a, b, c);
if (!nTakerGets || *nTakerGets <= 0)
return std::nullopt; // LCOV_EXCL_LINE
auto const nTakerGetsConstraint = pool.out - pool.in / (targetQuality.rate() * f);
if (nTakerGetsConstraint <= 0)
return std::nullopt;
// Select the smallest to maximize the quality
if (nTakerGetsConstraint < *nTakerGets)
nTakerGets = nTakerGetsConstraint;
auto getAmounts = [&pool, &tfee](Number const& nTakerGetsProposed) {
// Round downward to minimize the offer and to maximize the quality.
// This has the most impact when takerGets is XRP.
auto const takerGets = toAmount<TOut>(getIssue(pool.out), nTakerGetsProposed, Number::downward);
return TAmounts<TIn, TOut>{swapAssetOut(pool, takerGets, tfee), takerGets};
};
// Try to reduce the offer size to improve the quality.
// The quality might still not match the targetQuality for a tiny offer.
if (auto const amounts = getAmounts(*nTakerGets); Quality{amounts} < targetQuality)
return getAmounts(detail::reduceOffer(amounts.out));
else
return amounts;
}
/** Generate AMM offer starting with takerPays when AMM pool
* from the payment perspective is XRP(in)/IOU(out) or IOU(in)/IOU(out).
* Equations:
* Spot Price Quality after the offer is consumed:
* Qsp = (O - o) / (I + i) -- equation (1)
* where O is poolPays, I is poolGets, o is takerGets, i is takerPays
* Swap in:
* o = (O * i * f) / (I + i * f) -- equation (2)
* where f is (1 - tfee/100000), tfee is in basis points
* Effective price quality:
* Qep = o / i -- equation (3)
* There are two scenarios to consider
* A) Qsp = Qep. Substitute o in (1) with (2) and solve for i
* and Qsp = targetQuality(Qt):
* i**2 * f + i * I * (1 + f) + I**2 - I * O / Qt = 0
* B) Qep = Qsp. Substitute i in (3) with (2) and solve for i
* and Qep = targetQuality(Qt):
* i = O / Qt - I / f
* Since the scenario is not known a priori, both A and B are solved and
* the lowest value of i is takerPays. takerGets is calculated with
* swap in eq (2). If i is less or equal to 0 then the offer can't
* be generated.
*/
template <typename TIn, typename TOut>
std::optional<TAmounts<TIn, TOut>>
getAMMOfferStartWithTakerPays(TAmounts<TIn, TOut> const& pool, Quality const& targetQuality, std::uint16_t tfee)
{
if (targetQuality.rate() == beast::zero)
return std::nullopt;
NumberRoundModeGuard mg(Number::to_nearest);
auto const f = feeMult(tfee);
auto const& a = f;
auto const b = pool.in * (1 + f);
auto const c = pool.in * pool.in - pool.in * pool.out * targetQuality.rate();
auto nTakerPays = solveQuadraticEqSmallest(a, b, c);
if (!nTakerPays || nTakerPays <= 0)
return std::nullopt; // LCOV_EXCL_LINE
auto const nTakerPaysConstraint = pool.out * targetQuality.rate() - pool.in / f;
if (nTakerPaysConstraint <= 0)
return std::nullopt;
// Select the smallest to maximize the quality
if (nTakerPaysConstraint < *nTakerPays)
nTakerPays = nTakerPaysConstraint;
auto getAmounts = [&pool, &tfee](Number const& nTakerPaysProposed) {
// Round downward to minimize the offer and to maximize the quality.
// This has the most impact when takerPays is XRP.
auto const takerPays = toAmount<TIn>(getIssue(pool.in), nTakerPaysProposed, Number::downward);
return TAmounts<TIn, TOut>{takerPays, swapAssetIn(pool, takerPays, tfee)};
};
// Try to reduce the offer size to improve the quality.
// The quality might still not match the targetQuality for a tiny offer.
if (auto const amounts = getAmounts(*nTakerPays); Quality{amounts} < targetQuality)
return getAmounts(detail::reduceOffer(amounts.in));
else
return amounts;
}
/** Generate AMM offer so that either updated Spot Price Quality (SPQ)
* is equal to LOB quality (in this case AMM offer quality is
* better than LOB quality) or AMM offer is equal to LOB quality
* (in this case SPQ is better than LOB quality).
* Pre-amendment code calculates takerPays first. If takerGets is XRP,
* it is rounded down, which results in worse offer quality than
* LOB quality, and the offer might fail to generate.
* Post-amendment code calculates the XRP offer side first. The result
* is rounded down, which makes the offer quality better.
* It might not be possible to match either SPQ or AMM offer to LOB
* quality. This generally happens at higher fees.
* @param pool AMM pool balances
* @param quality requested quality
* @param tfee trading fee in basis points
* @return seated in/out amounts if the quality can be changed
*/
template <typename TIn, typename TOut>
std::optional<TAmounts<TIn, TOut>>
changeSpotPriceQuality(
TAmounts<TIn, TOut> const& pool,
Quality const& quality,
std::uint16_t tfee,
Rules const& rules,
beast::Journal j)
{
if (!rules.enabled(fixAMMv1_1))
{
// Finds takerPays (i) and takerGets (o) such that given pool
// composition poolGets(I) and poolPays(O): (O - o) / (I + i) = quality.
// Where takerGets is calculated as the swapAssetIn (see below).
// The above equation produces the quadratic equation:
// i^2*(1-fee) + i*I*(2-fee) + I^2 - I*O/quality,
// which is solved for i, and o is found with swapAssetIn().
auto const f = feeMult(tfee); // 1 - fee
auto const& a = f;
auto const b = pool.in * (1 + f);
Number const c = pool.in * pool.in - pool.in * pool.out * quality.rate();
if (auto const res = b * b - 4 * a * c; res < 0)
return std::nullopt; // LCOV_EXCL_LINE
else if (auto const nTakerPaysPropose = (-b + root2(res)) / (2 * a); nTakerPaysPropose > 0)
{
auto const nTakerPays = [&]() {
// The fee might make the AMM offer quality less than CLOB
// quality. Therefore, AMM offer has to satisfy this constraint:
// o / i >= q. Substituting o with swapAssetIn() gives: i <= O /
// q - I / (1 - fee).
auto const nTakerPaysConstraint = pool.out * quality.rate() - pool.in / f;
if (nTakerPaysPropose > nTakerPaysConstraint)
return nTakerPaysConstraint;
return nTakerPaysPropose;
}();
if (nTakerPays <= 0)
{
JLOG(j.trace()) << "changeSpotPriceQuality calc failed: " << to_string(pool.in) << " "
<< to_string(pool.out) << " " << quality << " " << tfee;
return std::nullopt;
}
auto const takerPays = toAmount<TIn>(getIssue(pool.in), nTakerPays, Number::upward);
// should not fail
if (auto const amounts = TAmounts<TIn, TOut>{takerPays, swapAssetIn(pool, takerPays, tfee)};
Quality{amounts} < quality && !withinRelativeDistance(Quality{amounts}, quality, Number(1, -7)))
{
JLOG(j.error()) << "changeSpotPriceQuality failed: " << to_string(pool.in) << " " << to_string(pool.out)
<< " "
<< " " << quality << " " << tfee << " " << to_string(amounts.in) << " "
<< to_string(amounts.out);
Throw<std::runtime_error>("changeSpotPriceQuality failed");
}
else
{
JLOG(j.trace()) << "changeSpotPriceQuality succeeded: " << to_string(pool.in) << " "
<< to_string(pool.out) << " "
<< " " << quality << " " << tfee << " " << to_string(amounts.in) << " "
<< to_string(amounts.out);
return amounts;
}
}
JLOG(j.trace()) << "changeSpotPriceQuality calc failed: " << to_string(pool.in) << " " << to_string(pool.out)
<< " " << quality << " " << tfee;
return std::nullopt;
}
// Generate the offer starting with XRP side. Return seated offer amounts
// if the offer can be generated, otherwise nullopt.
auto const amounts = [&]() {
if (isXRP(getIssue(pool.out)))
return getAMMOfferStartWithTakerGets(pool, quality, tfee);
return getAMMOfferStartWithTakerPays(pool, quality, tfee);
}();
if (!amounts)
{
JLOG(j.trace()) << "changeSpotPrice calc failed: " << to_string(pool.in) << " " << to_string(pool.out) << " "
<< quality << " " << tfee << std::endl;
return std::nullopt;
}
if (Quality{*amounts} < quality)
{
JLOG(j.error()) << "changeSpotPriceQuality failed: " << to_string(pool.in) << " " << to_string(pool.out) << " "
<< quality << " " << tfee << " " << to_string(amounts->in) << " " << to_string(amounts->out);
return std::nullopt;
}
JLOG(j.trace()) << "changeSpotPriceQuality succeeded: " << to_string(pool.in) << " " << to_string(pool.out) << " "
<< " " << quality << " " << tfee << " " << to_string(amounts->in) << " " << to_string(amounts->out);
return amounts;
}
/** AMM pool invariant - the product (A * B) after swap in/out has to remain
* at least the same: (A + in) * (B - out) >= A * B
* XRP round-off may result in a smaller product after swap in/out.
* To address this:
* - if on swapIn the out is XRP then the amount is round-off
* downward, making the product slightly larger since out
* value is reduced.
* - if on swapOut the in is XRP then the amount is round-off
* upward, making the product slightly larger since in
* value is increased.
*/
/** Swap assetIn into the pool and swap out a proportional amount
* of the other asset. Implements AMM Swap in.
* @see [XLS30d:AMM
* Swap](https://github.com/XRPLF/XRPL-Standards/discussions/78)
* @param pool current AMM pool balances
* @param assetIn amount to swap in
* @param tfee trading fee in basis points
* @return
*/
template <typename TIn, typename TOut>
TOut
swapAssetIn(TAmounts<TIn, TOut> const& pool, TIn const& assetIn, std::uint16_t tfee)
{
if (auto const& rules = getCurrentTransactionRules(); rules && rules->enabled(fixAMMv1_1))
{
// set rounding to always favor the amm. Clip to zero.
// calculate:
// pool.out -
// (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
// and explicitly set the rounding modes
// Favoring the amm means we should:
// minimize:
// pool.out -
// (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
// maximize:
// (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
// (pool.in * pool.out)
// minimize:
// (pool.in + assetIn * feeMult(tfee)),
// minimize:
// assetIn * feeMult(tfee)
// feeMult is: (1-fee), fee is tfee/100000
// minimize:
// 1-fee
// maximize:
// fee
saveNumberRoundMode _{Number::getround()};
Number::setround(Number::upward);
auto const numerator = pool.in * pool.out;
auto const fee = getFee(tfee);
Number::setround(Number::downward);
auto const denom = pool.in + assetIn * (1 - fee);
if (denom.signum() <= 0)
return toAmount<TOut>(getIssue(pool.out), 0);
Number::setround(Number::upward);
auto const ratio = numerator / denom;
Number::setround(Number::downward);
auto const swapOut = pool.out - ratio;
if (swapOut.signum() < 0)
return toAmount<TOut>(getIssue(pool.out), 0);
return toAmount<TOut>(getIssue(pool.out), swapOut, Number::downward);
}
else
{
return toAmount<TOut>(
getIssue(pool.out),
pool.out - (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
Number::downward);
}
}
/** Swap assetOut out of the pool and swap in a proportional amount
* of the other asset. Implements AMM Swap out.
* @see [XLS30d:AMM
* Swap](https://github.com/XRPLF/XRPL-Standards/discussions/78)
* @param pool current AMM pool balances
* @param assetOut amount to swap out
* @param tfee trading fee in basis points
* @return
*/
template <typename TIn, typename TOut>
TIn
swapAssetOut(TAmounts<TIn, TOut> const& pool, TOut const& assetOut, std::uint16_t tfee)
{
if (auto const& rules = getCurrentTransactionRules(); rules && rules->enabled(fixAMMv1_1))
{
// set rounding to always favor the amm. Clip to zero.
// calculate:
// ((pool.in * pool.out) / (pool.out - assetOut) - pool.in) /
// (1-tfee/100000)
// maximize:
// ((pool.in * pool.out) / (pool.out - assetOut) - pool.in)
// maximize:
// (pool.in * pool.out) / (pool.out - assetOut)
// maximize:
// (pool.in * pool.out)
// minimize
// (pool.out - assetOut)
// minimize:
// (1-tfee/100000)
// maximize:
// tfee/100000
saveNumberRoundMode _{Number::getround()};
Number::setround(Number::upward);
auto const numerator = pool.in * pool.out;
Number::setround(Number::downward);
auto const denom = pool.out - assetOut;
if (denom.signum() <= 0)
{
return toMaxAmount<TIn>(getIssue(pool.in));
}
Number::setround(Number::upward);
auto const ratio = numerator / denom;
auto const numerator2 = ratio - pool.in;
auto const fee = getFee(tfee);
Number::setround(Number::downward);
auto const feeMult = 1 - fee;
Number::setround(Number::upward);
auto const swapIn = numerator2 / feeMult;
if (swapIn.signum() < 0)
return toAmount<TIn>(getIssue(pool.in), 0);
return toAmount<TIn>(getIssue(pool.in), swapIn, Number::upward);
}
else
{
return toAmount<TIn>(
getIssue(pool.in),
((pool.in * pool.out) / (pool.out - assetOut) - pool.in) / feeMult(tfee),
Number::upward);
}
}
/** Return square of n.
*/
Number
square(Number const& n);
/** Adjust LP tokens to deposit/withdraw.
* Amount type keeps 16 digits. Maintaining the LP balance by adding
* deposited tokens or subtracting withdrawn LP tokens from LP balance
* results in losing precision in LP balance. I.e. the resulting LP balance
* is less than the actual sum of LP tokens. To adjust for this, subtract
* old tokens balance from the new one for deposit or vice versa for
* withdraw to cancel out the precision loss.
* @param lptAMMBalance LPT AMM Balance
* @param lpTokens LP tokens to deposit or withdraw
* @param isDeposit Yes if deposit, No if withdraw
*/
STAmount
adjustLPTokens(STAmount const& lptAMMBalance, STAmount const& lpTokens, IsDeposit isDeposit);
/** Calls adjustLPTokens() and adjusts deposit or withdraw amounts if
* the adjusted LP tokens are less than the provided LP tokens.
* @param amountBalance asset1 pool balance
* @param amount asset1 to deposit or withdraw
* @param amount2 asset2 to deposit or withdraw
* @param lptAMMBalance LPT AMM Balance
* @param lpTokens LP tokens to deposit or withdraw
* @param tfee trading fee in basis points
* @param isDeposit Yes if deposit, No if withdraw
* @return
*/
std::tuple<STAmount, std::optional<STAmount>, STAmount>
adjustAmountsByLPTokens(
STAmount const& amountBalance,
STAmount const& amount,
std::optional<STAmount> const& amount2,
STAmount const& lptAMMBalance,
STAmount const& lpTokens,
std::uint16_t tfee,
IsDeposit isDeposit);
/** Positive solution for quadratic equation:
* x = (-b + sqrt(b**2 + 4*a*c))/(2*a)
*/
Number
solveQuadraticEq(Number const& a, Number const& b, Number const& c);
STAmount
multiply(STAmount const& amount, Number const& frac, Number::rounding_mode rm);
namespace detail {
inline Number::rounding_mode
getLPTokenRounding(IsDeposit isDeposit)
{
// Minimize on deposit, maximize on withdraw to ensure
// AMM invariant sqrt(poolAsset1 * poolAsset2) >= LPTokensBalance
return isDeposit == IsDeposit::Yes ? Number::downward : Number::upward;
}
inline Number::rounding_mode
getAssetRounding(IsDeposit isDeposit)
{
// Maximize on deposit, minimize on withdraw to ensure
// AMM invariant sqrt(poolAsset1 * poolAsset2) >= LPTokensBalance
return isDeposit == IsDeposit::Yes ? Number::upward : Number::downward;
}
} // namespace detail
/** Round AMM equal deposit/withdrawal amount. Deposit/withdrawal formulas
* calculate the amount as a fractional value of the pool balance. The rounding
* takes place on the last step of multiplying the balance by the fraction if
* AMMv1_3 is enabled.
*/
template <typename A>
STAmount
getRoundedAsset(Rules const& rules, STAmount const& balance, A const& frac, IsDeposit isDeposit)
{
if (!rules.enabled(fixAMMv1_3))
{
if constexpr (std::is_same_v<A, STAmount>)
return multiply(balance, frac, balance.issue());
else
return toSTAmount(balance.issue(), balance * frac);
}
auto const rm = detail::getAssetRounding(isDeposit);
return multiply(balance, frac, rm);
}
/** Round AMM single deposit/withdrawal amount.
* The lambda's are used to delay evaluation until the function
* is executed so that the calculation is not done twice. noRoundCb() is
* called if AMMv1_3 is disabled. Otherwise, the rounding is set and
* the amount is:
* isDeposit is Yes - the balance multiplied by productCb()
* isDeposit is No - the result of productCb(). The rounding is
* the same for all calculations in productCb()
*/
STAmount
getRoundedAsset(
Rules const& rules,
std::function<Number()>&& noRoundCb,
STAmount const& balance,
std::function<Number()>&& productCb,
IsDeposit isDeposit);
/** Round AMM deposit/withdrawal LPToken amount. Deposit/withdrawal formulas
* calculate the lptokens as a fractional value of the AMM total lptokens.
* The rounding takes place on the last step of multiplying the balance by
* the fraction if AMMv1_3 is enabled. The tokens are then
* adjusted to factor in the loss in precision (we only keep 16 significant
* digits) when adding the lptokens to the balance.
*/
STAmount
getRoundedLPTokens(Rules const& rules, STAmount const& balance, Number const& frac, IsDeposit isDeposit);
/** Round AMM single deposit/withdrawal LPToken amount.
* The lambda's are used to delay evaluation until the function is executed
* so that the calculations are not done twice.
* noRoundCb() is called if AMMv1_3 is disabled. Otherwise, the rounding is set
* and the lptokens are:
* if isDeposit is Yes - the result of productCb(). The rounding is
* the same for all calculations in productCb()
* if isDeposit is No - the balance multiplied by productCb()
* The lptokens are then adjusted to factor in the loss in precision
* (we only keep 16 significant digits) when adding the lptokens to the balance.
*/
STAmount
getRoundedLPTokens(
Rules const& rules,
std::function<Number()>&& noRoundCb,
STAmount const& lptAMMBalance,
std::function<Number()>&& productCb,
IsDeposit isDeposit);
/* Next two functions adjust asset in/out amount to factor in the adjusted
* lptokens. The lptokens are calculated from the asset in/out. The lptokens are
* then adjusted to factor in the loss in precision. The adjusted lptokens might
* be less than the initially calculated tokens. Therefore, the asset in/out
* must be adjusted. The rounding might result in the adjusted amount being
* greater than the original asset in/out amount. If this happens,
* then the original amount is reduced by the difference in the adjusted amount
* and the original amount. The actual tokens and the actual adjusted amount
* are then recalculated. The minimum of the original and the actual
* adjusted amount is returned.
*/
std::pair<STAmount, STAmount>
adjustAssetInByTokens(
Rules const& rules,
STAmount const& balance,
STAmount const& amount,
STAmount const& lptAMMBalance,
STAmount const& tokens,
std::uint16_t tfee);
std::pair<STAmount, STAmount>
adjustAssetOutByTokens(
Rules const& rules,
STAmount const& balance,
STAmount const& amount,
STAmount const& lptAMMBalance,
STAmount const& tokens,
std::uint16_t tfee);
/** Find a fraction of tokens after the tokens are adjusted. The fraction
* is used to adjust equal deposit/withdraw amount.
*/
Number
adjustFracByTokens(Rules const& rules, STAmount const& lptAMMBalance, STAmount const& tokens, Number const& frac);
} // namespace xrpl

View File

@@ -0,0 +1,101 @@
#pragma once
#include <xrpl/basics/Expected.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/View.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/TER.h>
namespace xrpl {
class ReadView;
class ApplyView;
class Sandbox;
class NetClock;
/** Get AMM pool balances.
*/
std::pair<STAmount, STAmount>
ammPoolHolds(
ReadView const& view,
AccountID const& ammAccountID,
Issue const& issue1,
Issue const& issue2,
FreezeHandling freezeHandling,
beast::Journal const j);
/** Get AMM pool and LP token balances. If both optIssue are
* provided then they are used as the AMM token pair issues.
* Otherwise the missing issues are fetched from ammSle.
*/
Expected<std::tuple<STAmount, STAmount, STAmount>, TER>
ammHolds(
ReadView const& view,
SLE const& ammSle,
std::optional<Issue> const& optIssue1,
std::optional<Issue> const& optIssue2,
FreezeHandling freezeHandling,
beast::Journal const j);
/** Get the balance of LP tokens.
*/
STAmount
ammLPHolds(
ReadView const& view,
Currency const& cur1,
Currency const& cur2,
AccountID const& ammAccount,
AccountID const& lpAccount,
beast::Journal const j);
STAmount
ammLPHolds(ReadView const& view, SLE const& ammSle, AccountID const& lpAccount, beast::Journal const j);
/** Get AMM trading fee for the given account. The fee is discounted
* if the account is the auction slot owner or one of the slot's authorized
* accounts.
*/
std::uint16_t
getTradingFee(ReadView const& view, SLE const& ammSle, AccountID const& account);
/** Returns total amount held by AMM for the given token.
*/
STAmount
ammAccountHolds(ReadView const& view, AccountID const& ammAccountID, Issue const& issue);
/** Delete trustlines to AMM. If all trustlines are deleted then
* AMM object and account are deleted. Otherwise tecIMPCOMPLETE is returned.
*/
TER
deleteAMMAccount(Sandbox& view, Issue const& asset, Issue const& asset2, beast::Journal j);
/** Initialize Auction and Voting slots and set the trading/discounted fee.
*/
void
initializeFeeAuctionVote(
ApplyView& view,
std::shared_ptr<SLE>& ammSle,
AccountID const& account,
Issue const& lptIssue,
std::uint16_t tfee);
/** Return true if the Liquidity Provider is the only AMM provider, false
* otherwise. Return tecINTERNAL if encountered an unexpected condition,
* for instance Liquidity Provider has more than one LPToken trustline.
*/
Expected<bool, TER>
isOnlyLiquidityProvider(ReadView const& view, Issue const& ammIssue, AccountID const& lpAccount);
/** Due to rounding, the LPTokenBalance of the last LP might
* not match the LP's trustline balance. If it's within the tolerance,
* update LPTokenBalance to match the LP's trustline balance.
*/
Expected<bool, TER>
verifyAndAdjustLPTokenBalance(
Sandbox& sb,
STAmount const& lpTokens,
std::shared_ptr<SLE>& ammSle,
AccountID const& account);
} // namespace xrpl

View File

@@ -0,0 +1,52 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
/** AMMVote implements AMM vote Transactor.
* This transactor allows for the TradingFee of the AMM instance be a votable
* parameter. Any account (LP) that holds the corresponding LPTokens can cast
* a vote using the new AMMVote transaction. VoteSlots array in ltAMM object
* keeps track of upto eight active votes (VoteEntry) for the instance.
* VoteEntry contains:
* Account - account id that cast the vote.
* FeeVal - proposed fee in basis points.
* VoteWeight - LPTokens owned by the account in basis points.
* TradingFee is calculated as sum(VoteWeight_i * fee_i)/sum(VoteWeight_i).
* Every time AMMVote transaction is submitted, the transactor
* - Fails the transaction if the account doesn't hold LPTokens
* - Removes VoteEntry for accounts that don't hold LPTokens
* - If there are fewer than eight VoteEntry objects then add new VoteEntry
* object for the account.
* - If all eight VoteEntry slots are full, then remove VoteEntry that
* holds less LPTokens than the account. If all accounts hold more
* LPTokens then fail transaction.
* - If the account already holds a vote, then update VoteEntry.
* - Calculate and update TradingFee.
* @see [XLS30d:Governance: Trading Fee Voting
* Mechanism](https://github.com/XRPLF/XRPL-Standards/discussions/78)
*/
class AMMVote : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit AMMVote(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,294 @@
#pragma once
#include <xrpl/ledger/View.h>
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class Sandbox;
/** AMMWithdraw implements AMM withdraw Transactor.
* The withdraw transaction is used to remove liquidity from the AMM instance
* pool, thus redeeming some share of the pools that one owns in the form
* of LPTokens. If the trader withdraws proportional values of both assets
* without changing their relative pricing, no trading fee is charged on
* the transaction. The trader can specify different combination of
* the fields in the withdrawal.
* LPTokens - transaction assumes proportional withdrawal of pool assets
* for the amount of LPTokens.
* Asset1Out - transaction assumes withdrawal of single asset equivalent
* to the amount specified in Asset1Out.
* Asset1Out and Asset2Out - transaction assumes all assets withdrawal
* with the constraints on the maximum amount of each asset that
* the trader is willing to withdraw.
* Asset1Out and LPTokens - transaction assumes withdrawal of single
* asset specified in Asset1Out proportional to the share represented
* by the amount of LPTokens.
* Asset1Out and EPrice - transaction assumes withdrawal of single
* asset with the following constraints:
* a. Amount of asset1 if specified (not 0) in Asset1Out specifies
* the minimum amount of asset1 that the trader is willing
* to withdraw.
* b. The effective price of asset traded out does not exceed
* the amount specified in EPrice.
* Following updates after a successful transaction:
* The withdrawn asset, if XRP, is transferred from AMM instance account
* to the account that initiated the transaction, thus changing
* the Balance field of each account.
* The withdrawn asset, if token, is balanced between the AMM instance
* account and the issuer account.
* The LPTokens ~ are balanced between the AMM instance account and
* the account that initiated the transaction.
* The pool composition is updated.
* @see [XLS30d:AMMWithdraw
* transaction](https://github.com/XRPLF/XRPL-Standards/discussions/78)
*/
enum class WithdrawAll : bool { No = false, Yes };
class AMMWithdraw : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit AMMWithdraw(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
/** Equal-asset withdrawal (LPTokens) of some AMM instance pools
* shares represented by the number of LPTokens .
* The trading fee is not charged.
* @param view
* @param ammAccount
* @param amountBalance current LP asset1 balance
* @param amount2Balance current LP asset2 balance
* @param lptAMMBalance current AMM LPT balance
* @param lpTokens current LPT balance
* @param lpTokensWithdraw amount of tokens to withdraw
* @param tfee trading fee in basis points
* @param withdrawAll if withdrawing all lptokens
* @param priorBalance balance before fees
* @return
*/
static std::tuple<TER, STAmount, STAmount, std::optional<STAmount>>
equalWithdrawTokens(
Sandbox& view,
SLE const& ammSle,
AccountID const account,
AccountID const& ammAccount,
STAmount const& amountBalance,
STAmount const& amount2Balance,
STAmount const& lptAMMBalance,
STAmount const& lpTokens,
STAmount const& lpTokensWithdraw,
std::uint16_t tfee,
FreezeHandling freezeHanding,
WithdrawAll withdrawAll,
XRPAmount const& priorBalance,
beast::Journal const& journal);
/** Withdraw requested assets and token from AMM into LP account.
* Return new total LPToken balance and the withdrawn amounts for both
* assets.
* @param view
* @param ammSle AMM ledger entry
* @param ammAccount AMM account
* @param amountBalance current LP asset1 balance
* @param amountWithdraw asset1 withdraw amount
* @param amount2Withdraw asset2 withdraw amount
* @param lpTokensAMMBalance current AMM LPT balance
* @param lpTokensWithdraw amount of lptokens to withdraw
* @param tfee trading fee in basis points
* @param withdrawAll if withdraw all lptokens
* @param priorBalance balance before fees
* @return
*/
static std::tuple<TER, STAmount, STAmount, std::optional<STAmount>>
withdraw(
Sandbox& view,
SLE const& ammSle,
AccountID const& ammAccount,
AccountID const& account,
STAmount const& amountBalance,
STAmount const& amountWithdraw,
std::optional<STAmount> const& amount2Withdraw,
STAmount const& lpTokensAMMBalance,
STAmount const& lpTokensWithdraw,
std::uint16_t tfee,
FreezeHandling freezeHandling,
WithdrawAll withdrawAll,
XRPAmount const& priorBalance,
beast::Journal const& journal);
static std::pair<TER, bool>
deleteAMMAccountIfEmpty(
Sandbox& sb,
std::shared_ptr<SLE> const ammSle,
STAmount const& lpTokenBalance,
Issue const& issue1,
Issue const& issue2,
beast::Journal const& journal);
private:
std::pair<TER, bool>
applyGuts(Sandbox& view);
/** Withdraw requested assets and token from AMM into LP account.
* Return new total LPToken balance.
* @param view
* @param ammSle AMM ledger entry
* @param ammAccount AMM account
* @param amountBalance current LP asset1 balance
* @param amountWithdraw asset1 withdraw amount
* @param amount2Withdraw asset2 withdraw amount
* @param lpTokensAMMBalance current AMM LPT balance
* @param lpTokensWithdraw amount of lptokens to withdraw
* @return
*/
std::pair<TER, STAmount>
withdraw(
Sandbox& view,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
STAmount const& amountWithdraw,
std::optional<STAmount> const& amount2Withdraw,
STAmount const& lpTokensAMMBalance,
STAmount const& lpTokensWithdraw,
std::uint16_t tfee);
/** Equal-asset withdrawal (LPTokens) of some AMM instance pools
* shares represented by the number of LPTokens .
* The trading fee is not charged.
* @param view
* @param ammAccount
* @param amountBalance current LP asset1 balance
* @param amount2Balance current LP asset2 balance
* @param lptAMMBalance current AMM LPT balance
* @param lpTokens current LPT balance
* @param lpTokensWithdraw amount of tokens to withdraw
* @param tfee trading fee in basis points
* @return
*/
std::pair<TER, STAmount>
equalWithdrawTokens(
Sandbox& view,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
STAmount const& amount2Balance,
STAmount const& lptAMMBalance,
STAmount const& lpTokens,
STAmount const& lpTokensWithdraw,
std::uint16_t tfee);
/** Withdraw both assets (Asset1Out, Asset2Out) with the constraints
* on the maximum amount of each asset that the trader is willing
* to withdraw. The trading fee is not charged.
* @param view
* @param ammAccount
* @param amountBalance current AMM asset1 balance
* @param amount2Balance current AMM asset2 balance
* @param lptAMMBalance current AMM LPT balance
* @param amount asset1 withdraw amount
* @param amount2 max asset2 withdraw amount
* @param tfee trading fee in basis points
* @return
*/
std::pair<TER, STAmount>
equalWithdrawLimit(
Sandbox& view,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
STAmount const& amount2Balance,
STAmount const& lptAMMBalance,
STAmount const& amount,
STAmount const& amount2,
std::uint16_t tfee);
/** Single asset withdrawal (Asset1Out) equivalent to the amount specified
* in Asset1Out. The trading fee is charged.
* @param view
* @param ammAccount
* @param amountBalance current AMM asset1 balance
* @param lptAMMBalance current AMM LPT balance
* @param amount asset1 withdraw amount
* @param tfee trading fee in basis points
* @return
*/
std::pair<TER, STAmount>
singleWithdraw(
Sandbox& view,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
STAmount const& lptAMMBalance,
STAmount const& amount,
std::uint16_t tfee);
/** Single asset withdrawal (Asset1Out, LPTokens) proportional
* to the share specified by tokens. The trading fee is charged.
* @param view
* @param ammAccount
* @param amountBalance current AMM asset1 balance
* @param lptAMMBalance current AMM LPT balance
* @param amount asset1 withdraw amount
* @param lpTokensWithdraw amount of tokens to withdraw
* @param tfee trading fee in basis points
* @return
*/
std::pair<TER, STAmount>
singleWithdrawTokens(
Sandbox& view,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
STAmount const& lptAMMBalance,
STAmount const& amount,
STAmount const& lpTokensWithdraw,
std::uint16_t tfee);
/** Withdraw single asset (Asset1Out, EPrice) with two constraints.
* The trading fee is charged.
* @param view
* @param ammAccount
* @param amountBalance current AMM asset1 balance
* @param lptAMMBalance current AMM LPT balance
* @param amount asset1 withdraw amount
* @param ePrice maximum asset1 effective price
* @param tfee trading fee in basis points
* @return
*/
std::pair<TER, STAmount>
singleWithdrawEPrice(
Sandbox& view,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
STAmount const& lptAMMBalance,
STAmount const& amount,
STAmount const& ePrice,
std::uint16_t tfee);
/** Check from the flags if it's withdraw all */
static WithdrawAll
isWithdrawAll(STTx const& tx);
};
} // namespace xrpl

View File

@@ -0,0 +1,55 @@
#pragma once
#include <xrpl/basics/Log.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class Batch : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit Batch(ApplyContext& ctx) : Transactor(ctx)
{
}
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static NotTEC
preflightSigValidated(PreflightContext const& ctx);
static NotTEC
checkSign(PreclaimContext const& ctx);
TER
doApply() override;
static constexpr auto disabledTxTypes = std::to_array<TxType>({
ttVAULT_CREATE,
ttVAULT_SET,
ttVAULT_DELETE,
ttVAULT_DEPOSIT,
ttVAULT_WITHDRAW,
ttVAULT_CLAWBACK,
ttLOAN_BROKER_SET,
ttLOAN_BROKER_DELETE,
ttLOAN_BROKER_COVER_DEPOSIT,
ttLOAN_BROKER_COVER_WITHDRAW,
ttLOAN_BROKER_COVER_CLAWBACK,
ttLOAN_SET,
ttLOAN_DELETE,
ttLOAN_MANAGE,
ttLOAN_PAY,
});
};
} // namespace xrpl

View File

@@ -0,0 +1,45 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class Change : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit Change(ApplyContext& ctx) : Transactor(ctx)
{
}
TER
doApply() override;
void
preCompute() override;
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx)
{
return XRPAmount{0};
}
static TER
preclaim(PreclaimContext const& ctx);
private:
TER
applyAmendment();
TER
applyFee();
TER
applyUNLModify();
};
using EnableAmendment = Change;
using SetFee = Change;
using UNLModify = Change;
} // namespace xrpl

View File

@@ -0,0 +1,28 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class CancelCheck : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit CancelCheck(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
using CheckCancel = CancelCheck;
} // namespace xrpl

View File

@@ -0,0 +1,28 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class CashCheck : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit CashCheck(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
using CheckCash = CashCheck;
} // namespace xrpl

View File

@@ -0,0 +1,28 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class CreateCheck : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit CreateCheck(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
using CheckCreate = CreateCheck;
} // namespace xrpl

View File

@@ -0,0 +1,29 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class Clawback : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit Clawback(ApplyContext& ctx) : Transactor(ctx)
{
}
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,66 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class CreateTicket : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Custom};
constexpr static std::uint32_t minValidCount = 1;
// A note on how the maxValidCount was determined. The goal is for
// a single TicketCreate transaction to not use more compute power than
// a single compute-intensive Payment.
//
// Timing was performed using a MacBook Pro laptop and a release build
// with asserts off. 20 measurements were taken of each of the Payment
// and TicketCreate transactions and averaged to get timings.
//
// For the example compute-intensive Payment a Discrepancy unit test
// unit test Payment with 3 paths was chosen. With all the latest
// amendments enabled, that Payment::doApply() operation took, on
// average, 1.25 ms.
//
// Using that same test set up creating 250 Tickets in a single
// CreateTicket::doApply() in a unit test took, on average, 1.21 ms.
//
// So, for the moment, a single transaction creating 250 Tickets takes
// about the same compute time as a single compute-intensive payment.
//
// October 2018.
constexpr static std::uint32_t maxValidCount = 250;
// The maximum number of Tickets an account may hold. If a
// TicketCreate would cause an account to own more than this many
// tickets, then the TicketCreate will fail.
//
// The number was chosen arbitrarily and is an effort toward avoiding
// ledger-stuffing with Tickets.
constexpr static std::uint32_t maxTicketThreshold = 250;
explicit CreateTicket(ApplyContext& ctx) : Transactor(ctx)
{
}
static TxConsequences
makeTxConsequences(PreflightContext const& ctx);
/** Enforce constraints beyond those of the Transactor base class. */
static NotTEC
preflight(PreflightContext const& ctx);
/** Enforce constraints beyond those of the Transactor base class. */
static TER
preclaim(PreclaimContext const& ctx);
/** Precondition: fee collection is likely. Attempt to create ticket(s). */
TER
doApply() override;
};
using TicketCreate = CreateTicket;
} // namespace xrpl

View File

@@ -0,0 +1,77 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class CredentialCreate : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit CredentialCreate(ApplyContext& ctx) : Transactor(ctx)
{
}
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
//------------------------------------------------------------------------------
class CredentialDelete : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit CredentialDelete(ApplyContext& ctx) : Transactor(ctx)
{
}
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
//------------------------------------------------------------------------------
class CredentialAccept : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit CredentialAccept(ApplyContext& ctx) : Transactor(ctx)
{
}
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,47 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class DIDSet : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit DIDSet(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
TER
doApply() override;
};
//------------------------------------------------------------------------------
class DIDDelete : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit DIDDelete(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
deleteSLE(ApplyContext& ctx, Keylet sleKeylet, AccountID const owner);
static TER
deleteSLE(ApplyView& view, std::shared_ptr<SLE> sle, AccountID const owner, beast::Journal j);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,30 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class DelegateSet : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit DelegateSet(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
// Interface used by DeleteAccount
static TER
deleteDelegate(ApplyView& view, std::shared_ptr<SLE> const& sle, AccountID const& account, beast::Journal j);
};
} // namespace xrpl

View File

@@ -0,0 +1,35 @@
#pragma once
#include <xrpl/protocol/Permissions.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
namespace xrpl {
/**
* Check if the delegate account has permission to execute the transaction.
* @param delegate The delegate account.
* @param tx The transaction that the delegate account intends to execute.
* @return tesSUCCESS if the transaction is allowed, terNO_DELEGATE_PERMISSION
* if not.
*/
NotTEC
checkTxPermission(std::shared_ptr<SLE const> const& delegate, STTx const& tx);
/**
* Load the granular permissions granted to the delegate account for the
* specified transaction type
* @param delegate The delegate account.
* @param type Used to determine which granted granular permissions to load,
* based on the transaction type.
* @param granularPermissions Granted granular permissions tied to the
* transaction type.
*/
void
loadGranularPermission(
std::shared_ptr<SLE const> const& delegate,
TxType const& type,
std::unordered_set<GranularPermissionType>& granularPermissions);
} // namespace xrpl

View File

@@ -0,0 +1,34 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class DeleteAccount : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Blocker};
explicit DeleteAccount(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
using AccountDelete = DeleteAccount;
} // namespace xrpl

View File

@@ -0,0 +1,40 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
/**
Price Oracle is a system that acts as a bridge between
a blockchain network and the external world, providing off-chain price data
to decentralized applications (dApps) on the blockchain. This implementation
conforms to the requirements specified in the XLS-47d.
The DeleteOracle transactor implements the deletion of Oracle objects.
*/
class DeleteOracle : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit DeleteOracle(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
static TER
deleteOracle(ApplyView& view, std::shared_ptr<SLE> const& sle, AccountID const& account, beast::Journal j);
};
using OracleDelete = DeleteOracle;
} // namespace xrpl

View File

@@ -0,0 +1,33 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class DepositPreauth : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit DepositPreauth(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
// Interface used by DeleteAccount
static TER
removeFromLedger(ApplyView& view, uint256 const& delIndex, beast::Journal j);
};
} // namespace xrpl

View File

@@ -0,0 +1,80 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class EscrowCreate : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Custom};
explicit EscrowCreate(ApplyContext& ctx) : Transactor(ctx)
{
}
static TxConsequences
makeTxConsequences(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
//------------------------------------------------------------------------------
class EscrowFinish : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit EscrowFinish(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static NotTEC
preflightSigValidated(PreflightContext const& ctx);
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
//------------------------------------------------------------------------------
class EscrowCancel : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit EscrowCancel(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,33 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class LedgerStateFix : public Transactor
{
public:
enum FixType : std::uint16_t {
nfTokenPageLink = 1,
};
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit LedgerStateFix(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,467 @@
#pragma once
#include <xrpl/ledger/View.h>
#include <xrpl/protocol/st.h>
namespace xrpl {
struct PreflightContext;
// Lending protocol has dependencies, so capture them here.
bool
checkLendingProtocolDependencies(PreflightContext const& ctx);
static constexpr std::uint32_t secondsInYear = 365 * 24 * 60 * 60;
Number
loanPeriodicRate(TenthBips32 interestRate, std::uint32_t paymentInterval);
/// Ensure the periodic payment is always rounded consistently
inline Number
roundPeriodicPayment(Asset const& asset, Number const& periodicPayment, std::int32_t scale)
{
return roundToAsset(asset, periodicPayment, scale, Number::upward);
}
/* Represents the breakdown of amounts to be paid and changes applied to the
* Loan object while processing a loan payment.
*
* This structure is returned after processing a loan payment transaction and
* captures the amounts that need to be paid. The actual ledger entry changes
* are made in LoanPay based on this structure values.
*
* The sum of principalPaid, interestPaid, and feePaid represents the total
* amount to be deducted from the borrower's account. The valueChange field
* tracks whether the loan's total value increased or decreased beyond normal
* amortization.
*
* This structure is explained in the XLS-66 spec, section 3.2.4.2 (Payment
* Processing).
*/
struct LoanPaymentParts
{
// The amount of principal paid that reduces the loan balance.
// This amount is subtracted from sfPrincipalOutstanding in the Loan object
// and paid to the Vault
Number principalPaid = numZero;
// The total amount of interest paid to the Vault.
// This includes:
// - Tracked interest from the amortization schedule
// - Untracked interest (e.g., late payment penalty interest)
// This value is always non-negative.
Number interestPaid = numZero;
// The change in the loan's total value outstanding.
// - If valueChange < 0: Loan value decreased
// - If valueChange > 0: Loan value increased
// - If valueChange = 0: No value adjustment
//
// For regular on-time payments, this is always 0. Non-zero values occur
// when:
// - Overpayments reduce the loan balance beyond the scheduled amount
// - Late payments add penalty interest to the loan value
// - Early full payment may increase or decrease the loan value based on
// terms
Number valueChange = numZero;
/* The total amount of fees paid to the Broker.
* This includes:
* - Tracked management fees from the amortization schedule
* - Untracked fees (e.g., late payment fees, service fees, origination
* fees) This value is always non-negative.
*/
Number feePaid = numZero;
LoanPaymentParts&
operator+=(LoanPaymentParts const& other);
bool
operator==(LoanPaymentParts const& other) const;
};
/** This structure captures the parts of a loan state.
*
* Whether the values are theoretical (unrounded) or rounded will depend on how
* it was computed.
*
* Many of the fields can be derived from each other, but they're all provided
* here to reduce code duplication and possible mistakes.
* e.g.
* * interestOutstanding = valueOutstanding - principalOutstanding
* * interestDue = interestOutstanding - managementFeeDue
*/
struct LoanState
{
// Total value still due to be paid by the borrower.
Number valueOutstanding;
// Principal still due to be paid by the borrower.
Number principalOutstanding;
// Interest still due to be paid to the Vault.
// This is a portion of interestOutstanding
Number interestDue;
// Management fee still due to be paid to the broker.
// This is a portion of interestOutstanding
Number managementFeeDue;
// Interest still due to be paid by the borrower.
Number
interestOutstanding() const
{
XRPL_ASSERT_PARTS(
interestDue + managementFeeDue == valueOutstanding - principalOutstanding,
"xrpl::LoanState::interestOutstanding",
"other values add up correctly");
return interestDue + managementFeeDue;
}
};
/* Describes the initial computed properties of a loan.
*
* This structure contains the fundamental calculated values that define a
* loan's payment structure and amortization schedule. These properties are
* computed:
* - At loan creation (LoanSet transaction)
* - When loan terms change (e.g., after an overpayment that reduces the loan
* balance)
*/
struct LoanProperties
{
// The unrounded amount to be paid at each regular payment period.
// Calculated using the standard amortization formula based on principal,
// interest rate, and number of payments.
// The actual amount paid in the LoanPay transaction must be rounded up to
// the precision of the asset and loan.
Number periodicPayment;
// The loan's current state, with all values rounded to the loan's scale.
LoanState loanState;
// The scale (decimal places) used for rounding all loan amounts.
// This is the maximum of:
// - The asset's native scale
// - A minimum scale required to represent the periodic payment accurately
// All loan state values (principal, interest, fees) are rounded to this
// scale.
std::int32_t loanScale;
// The principal portion of the first payment.
Number firstPaymentPrincipal;
};
// Some values get re-rounded to the vault scale any time they are adjusted. In
// addition, they are prevented from ever going below zero. This helps avoid
// accumulated rounding errors and leftover dust amounts.
template <class NumberProxy>
void
adjustImpreciseNumber(NumberProxy value, Number const& adjustment, Asset const& asset, int vaultScale)
{
value = roundToAsset(asset, value + adjustment, vaultScale);
if (*value < beast::zero)
value = 0;
}
inline int
getAssetsTotalScale(SLE::const_ref vaultSle)
{
if (!vaultSle)
return Number::minExponent - 1; // LCOV_EXCL_LINE
return STAmount{vaultSle->at(sfAsset), vaultSle->at(sfAssetsTotal)}.exponent();
}
TER
checkLoanGuards(
Asset const& vaultAsset,
Number const& principalRequested,
bool expectInterest,
std::uint32_t paymentTotal,
LoanProperties const& properties,
beast::Journal j);
LoanState
computeTheoreticalLoanState(
Number const& periodicPayment,
Number const& periodicRate,
std::uint32_t const paymentRemaining,
TenthBips32 const managementFeeRate);
// Constructs a valid LoanState object from arbitrary inputs
LoanState
constructLoanState(
Number const& totalValueOutstanding,
Number const& principalOutstanding,
Number const& managementFeeOutstanding);
// Constructs a valid LoanState object from a Loan object, which always has
// rounded values
LoanState
constructRoundedLoanState(SLE::const_ref loan);
Number
computeManagementFee(Asset const& asset, Number const& interest, TenthBips32 managementFeeRate, std::int32_t scale);
Number
computeFullPaymentInterest(
Number const& theoreticalPrincipalOutstanding,
Number const& periodicRate,
NetClock::time_point parentCloseTime,
std::uint32_t paymentInterval,
std::uint32_t prevPaymentDate,
std::uint32_t startDate,
TenthBips32 closeInterestRate);
namespace detail {
// These classes and functions should only be accessed by LendingHelper
// functions and unit tests
enum class PaymentSpecialCase { none, final, extra };
/* Represents a single loan payment component parts.
* This structure captures the "delta" (change) values that will be applied to
* the tracked fields in the Loan ledger object when a payment is processed.
*
* These are called "deltas" because they represent the amount by which each
* corresponding field in the Loan object will be reduced.
* They are "tracked" as they change tracked loan values.
*/
struct PaymentComponents
{
// The change in total value outstanding for this payment.
// This amount will be subtracted from sfTotalValueOutstanding in the Loan
// object. Equal to the sum of trackedPrincipalDelta,
// trackedInterestPart(), and trackedManagementFeeDelta.
Number trackedValueDelta;
// The change in principal outstanding for this payment.
// This amount will be subtracted from sfPrincipalOutstanding in the Loan
// object, representing the portion of the payment that reduces the
// original loan amount.
Number trackedPrincipalDelta;
// The change in management fee outstanding for this payment.
// This amount will be subtracted from sfManagementFeeOutstanding in the
// Loan object. This represents only the tracked management fees from the
// amortization schedule and does not include additional untracked fees
// (such as late payment fees) that go directly to the broker.
Number trackedManagementFeeDelta;
// Indicates if this payment has special handling requirements.
// - none: Regular scheduled payment
// - final: The last payment that closes out the loan
// - extra: An additional payment beyond the regular schedule (overpayment)
PaymentSpecialCase specialCase = PaymentSpecialCase::none;
// Calculates the tracked interest portion of this payment.
// This is derived from the other components as:
// trackedValueDelta - trackedPrincipalDelta - trackedManagementFeeDelta
//
// @return The amount of tracked interest included in this payment that
// will be paid to the vault.
Number
trackedInterestPart() const;
};
/* Extends PaymentComponents with untracked payment amounts.
*
* This structure adds untracked fees and interest to the base
* PaymentComponents, representing amounts that don't affect the Loan object's
* tracked state but are still part of the total payment due from the borrower.
*
* Untracked amounts include:
* - Late payment fees that go directly to the Broker
* - Late payment penalty interest that goes directly to the Vault
* - Service fees
*
* The key distinction is that tracked amounts reduce the Loan object's state
* (sfTotalValueOutstanding, sfPrincipalOutstanding,
* sfManagementFeeOutstanding), while untracked amounts are paid directly to the
* recipient without affecting the loan's amortization schedule.
*/
struct ExtendedPaymentComponents : public PaymentComponents
{
// Additional management fees that go directly to the Broker.
// This includes fees not part of the standard amortization schedule
// (e.g., late fees, service fees, origination fees).
// This value may be negative, though the final value returned in
// LoanPaymentParts.feePaid will never be negative.
Number untrackedManagementFee;
// Additional interest that goes directly to the Vault.
// This includes interest not part of the standard amortization schedule
// (e.g., late payment penalty interest).
// This value may be negative, though the final value returned in
// LoanPaymentParts.interestPaid will never be negative.
Number untrackedInterest;
// The complete amount due from the borrower for this payment.
// Calculated as: trackedValueDelta + untrackedInterest +
// untrackedManagementFee
//
// This value is used to validate that the payment amount provided by the
// borrower is sufficient to cover all components of the payment.
Number totalDue;
ExtendedPaymentComponents(PaymentComponents const& p, Number fee, Number interest = numZero)
: PaymentComponents(p)
, untrackedManagementFee(fee)
, untrackedInterest(interest)
, totalDue(trackedValueDelta + untrackedInterest + untrackedManagementFee)
{
}
};
/* Represents the differences between two loan states.
*
* This structure is used to capture the change in each component of a loan's
* state, typically when computing the difference between two LoanState objects
* (e.g., before and after a payment). It is a convenient way to capture changes
* in each component. How that difference is used depends on the context.
*/
struct LoanStateDeltas
{
// The difference in principal outstanding between two loan states.
Number principal;
// The difference in interest due between two loan states.
Number interest;
// The difference in management fee outstanding between two loan states.
Number managementFee;
/* Calculates the total change across all components.
* @return The sum of principal, interest, and management fee deltas.
*/
Number
total() const
{
return principal + interest + managementFee;
}
// Ensures all delta values are non-negative.
void
nonNegative();
};
Expected<std::pair<LoanPaymentParts, LoanProperties>, TER>
tryOverpayment(
Asset const& asset,
std::int32_t loanScale,
ExtendedPaymentComponents const& overpaymentComponents,
LoanState const& roundedLoanState,
Number const& periodicPayment,
Number const& periodicRate,
std::uint32_t paymentRemaining,
TenthBips16 const managementFeeRate,
beast::Journal j);
Number
computeRaisedRate(Number const& periodicRate, std::uint32_t paymentsRemaining);
Number
computePaymentFactor(Number const& periodicRate, std::uint32_t paymentsRemaining);
std::pair<Number, Number>
computeInterestAndFeeParts(
Asset const& asset,
Number const& interest,
TenthBips16 managementFeeRate,
std::int32_t loanScale);
Number
loanPeriodicPayment(Number const& principalOutstanding, Number const& periodicRate, std::uint32_t paymentsRemaining);
Number
loanPrincipalFromPeriodicPayment(
Number const& periodicPayment,
Number const& periodicRate,
std::uint32_t paymentsRemaining);
Number
loanLatePaymentInterest(
Number const& principalOutstanding,
TenthBips32 lateInterestRate,
NetClock::time_point parentCloseTime,
std::uint32_t nextPaymentDueDate);
Number
loanAccruedInterest(
Number const& principalOutstanding,
Number const& periodicRate,
NetClock::time_point parentCloseTime,
std::uint32_t startDate,
std::uint32_t prevPaymentDate,
std::uint32_t paymentInterval);
ExtendedPaymentComponents
computeOverpaymentComponents(
Asset const& asset,
int32_t const loanScale,
Number const& overpayment,
TenthBips32 const overpaymentInterestRate,
TenthBips32 const overpaymentFeeRate,
TenthBips16 const managementFeeRate);
PaymentComponents
computePaymentComponents(
Asset const& asset,
std::int32_t scale,
Number const& totalValueOutstanding,
Number const& principalOutstanding,
Number const& managementFeeOutstanding,
Number const& periodicPayment,
Number const& periodicRate,
std::uint32_t paymentRemaining,
TenthBips16 managementFeeRate);
} // namespace detail
detail::LoanStateDeltas
operator-(LoanState const& lhs, LoanState const& rhs);
LoanState
operator-(LoanState const& lhs, detail::LoanStateDeltas const& rhs);
LoanState
operator+(LoanState const& lhs, detail::LoanStateDeltas const& rhs);
LoanProperties
computeLoanProperties(
Asset const& asset,
Number const& principalOutstanding,
TenthBips32 interestRate,
std::uint32_t paymentInterval,
std::uint32_t paymentsRemaining,
TenthBips32 managementFeeRate,
std::int32_t minimumScale);
LoanProperties
computeLoanProperties(
Asset const& asset,
Number const& principalOutstanding,
Number const& periodicRate,
std::uint32_t paymentsRemaining,
TenthBips32 managementFeeRate,
std::int32_t minimumScale);
bool
isRounded(Asset const& asset, Number const& value, std::int32_t scale);
// Indicates what type of payment is being made.
// regular, late, and full are mutually exclusive.
// overpayment is an "add on" to a regular payment, and follows that path with
// potential extra work at the end.
enum class LoanPaymentType { regular = 0, late, full, overpayment };
Expected<LoanPaymentParts, TER>
loanMakePayment(
Asset const& asset,
ApplyView& view,
SLE::ref loan,
SLE::const_ref brokerSle,
STAmount const& amount,
LoanPaymentType const paymentType,
beast::Journal j);
} // namespace xrpl

View File

@@ -0,0 +1,31 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class LoanBrokerCoverClawback : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit LoanBrokerCoverClawback(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
//------------------------------------------------------------------------------
} // namespace xrpl

View File

@@ -0,0 +1,31 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class LoanBrokerCoverDeposit : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit LoanBrokerCoverDeposit(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
//------------------------------------------------------------------------------
} // namespace xrpl

View File

@@ -0,0 +1,31 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class LoanBrokerCoverWithdraw : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit LoanBrokerCoverWithdraw(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
//------------------------------------------------------------------------------
} // namespace xrpl

View File

@@ -0,0 +1,31 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class LoanBrokerDelete : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit LoanBrokerDelete(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
//------------------------------------------------------------------------------
} // namespace xrpl

View File

@@ -0,0 +1,34 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class LoanBrokerSet : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit LoanBrokerSet(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static std::vector<OptionaledField<STNumber>> const&
getValueFields();
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
//------------------------------------------------------------------------------
} // namespace xrpl

View File

@@ -0,0 +1,31 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class LoanDelete : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit LoanDelete(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
//------------------------------------------------------------------------------
} // namespace xrpl

View File

@@ -0,0 +1,55 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class LoanManage : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit LoanManage(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
/** Helper function that might be needed by other transactors
*/
static TER
defaultLoan(
ApplyView& view,
SLE::ref loanSle,
SLE::ref brokerSle,
SLE::ref vaultSle,
Asset const& vaultAsset,
beast::Journal j);
/** Helper function that might be needed by other transactors
*/
static TER
impairLoan(ApplyView& view, SLE::ref loanSle, SLE::ref vaultSle, Asset const& vaultAsset, beast::Journal j);
/** Helper function that might be needed by other transactors
*/
[[nodiscard]] static TER
unimpairLoan(ApplyView& view, SLE::ref loanSle, SLE::ref vaultSle, Asset const& vaultAsset, beast::Journal j);
TER
doApply() override;
};
//------------------------------------------------------------------------------
} // namespace xrpl

View File

@@ -0,0 +1,37 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class LoanPay : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit LoanPay(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
//------------------------------------------------------------------------------
} // namespace xrpl

Some files were not shown because too many files have changed in this diff Show More