mirror of
https://github.com/Xahau/xahaud.git
synced 2026-01-24 08:35:16 +00:00
Compare commits
8 Commits
service_fe
...
reduced-im
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b4f7b91216 | ||
|
|
cba05af058 | ||
|
|
8e88a49d26 | ||
|
|
3879c529c1 | ||
|
|
61753d39bb | ||
|
|
0033b3ae4c | ||
|
|
f636f8158e | ||
|
|
a05d58a6e9 |
@@ -392,7 +392,6 @@ target_sources (rippled PRIVATE
|
||||
src/ripple/app/misc/NegativeUNLVote.cpp
|
||||
src/ripple/app/misc/NetworkOPs.cpp
|
||||
src/ripple/app/misc/SHAMapStoreImp.cpp
|
||||
src/ripple/app/misc/StateAccounting.cpp
|
||||
src/ripple/app/misc/detail/impl/WorkSSL.cpp
|
||||
src/ripple/app/misc/impl/AccountTxPaging.cpp
|
||||
src/ripple/app/misc/impl/AmendmentTable.cpp
|
||||
@@ -749,7 +748,6 @@ if (tests)
|
||||
src/test/app/Regression_test.cpp
|
||||
src/test/app/Remit_test.cpp
|
||||
src/test/app/SHAMapStore_test.cpp
|
||||
src/test/app/ServiceFee_test.cpp
|
||||
src/test/app/SetAuth_test.cpp
|
||||
src/test/app/SetRegularKey_test.cpp
|
||||
src/test/app/SetTrust_test.cpp
|
||||
|
||||
@@ -92,7 +92,7 @@ pwd &&
|
||||
tar -xzf cmake-3.23.1-linux-x86_64.tar.gz -C /hbb/ &&
|
||||
echo "-- Install Boost 1.86.0 --" &&
|
||||
pwd &&
|
||||
( wget -nc -q https://archives.boost.io/release/1.86.0/source/boost_1_86_0.tar.gz; echo "" ) &&
|
||||
( wget -nc -q https://boostorg.jfrog.io/artifactory/main/release/1.86.0/source/boost_1_86_0.tar.gz; echo "" ) &&
|
||||
tar -xzf boost_1_86_0.tar.gz &&
|
||||
cd boost_1_86_0 && ./bootstrap.sh && ./b2 link=static -j$3 && ./b2 install &&
|
||||
cd ../ &&
|
||||
|
||||
@@ -1217,10 +1217,9 @@ hook::apply(
|
||||
.hookParamOverrides = hookParamOverrides,
|
||||
.hookParams = hookParams,
|
||||
.hookSkips = {},
|
||||
.exitType = applyCtx.view().rules().enabled(fixXahauV3)
|
||||
? hook_api::ExitType::UNSET
|
||||
: hook_api::ExitType::ROLLBACK, // default is to rollback
|
||||
// unless hook calls accept()
|
||||
.exitType =
|
||||
hook_api::ExitType::ROLLBACK, // default is to rollback unless
|
||||
// hook calls accept()
|
||||
.exitReason = std::string(""),
|
||||
.exitCode = -1,
|
||||
.hasCallback = hasCallback,
|
||||
@@ -4791,7 +4790,7 @@ DEFINE_HOOK_FUNCTION(
|
||||
|
||||
if (float1 == 0)
|
||||
{
|
||||
j.trace() << "HookTrace[" << HC_ACC() << "]: "
|
||||
j.trace() << "HookTrace[" << HC_ACC() << "]:"
|
||||
<< (read_len == 0
|
||||
? ""
|
||||
: std::string_view(
|
||||
|
||||
@@ -152,9 +152,6 @@ public:
|
||||
std::string
|
||||
getCompleteLedgers();
|
||||
|
||||
RangeSet<std::uint32_t>
|
||||
getCompleteLedgersRangeSet();
|
||||
|
||||
/** Apply held transactions to the open ledger
|
||||
This is normally called as we close the ledger.
|
||||
The open ledger remains open to handle new transactions
|
||||
|
||||
@@ -1714,13 +1714,6 @@ LedgerMaster::getCompleteLedgers()
|
||||
return to_string(mCompleteLedgers);
|
||||
}
|
||||
|
||||
RangeSet<std::uint32_t>
|
||||
LedgerMaster::getCompleteLedgersRangeSet()
|
||||
{
|
||||
std::lock_guard sl(mCompleteLock);
|
||||
return mCompleteLedgers;
|
||||
}
|
||||
|
||||
std::optional<NetClock::time_point>
|
||||
LedgerMaster::getCloseTimeBySeq(LedgerIndex ledgerIndex)
|
||||
{
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
#include <ripple/app/main/NodeStoreScheduler.h>
|
||||
#include <ripple/app/main/Tuning.h>
|
||||
#include <ripple/app/misc/AmendmentTable.h>
|
||||
#include <ripple/app/misc/DatagramMonitor.h>
|
||||
#include <ripple/app/misc/HashRouter.h>
|
||||
#include <ripple/app/misc/LoadFeeTrack.h>
|
||||
#include <ripple/app/misc/NetworkOPs.h>
|
||||
@@ -168,8 +167,6 @@ public:
|
||||
std::unique_ptr<Logs> logs_;
|
||||
std::unique_ptr<TimeKeeper> timeKeeper_;
|
||||
|
||||
std::unique_ptr<DatagramMonitor> datagram_monitor_;
|
||||
|
||||
std::uint64_t const instanceCookie_;
|
||||
|
||||
beast::Journal m_journal;
|
||||
@@ -1526,14 +1523,6 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline)
|
||||
if (reportingETL_)
|
||||
reportingETL_->start();
|
||||
|
||||
// Datagram monitor if applicable
|
||||
if (!config_->standalone() && !config_->DATAGRAM_MONITOR.empty())
|
||||
{
|
||||
datagram_monitor_ = std::make_unique<DatagramMonitor>(*this);
|
||||
if (datagram_monitor_)
|
||||
datagram_monitor_->start();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -33,7 +33,6 @@
|
||||
#include <ripple/app/misc/HashRouter.h>
|
||||
#include <ripple/app/misc/LoadFeeTrack.h>
|
||||
#include <ripple/app/misc/NetworkOPs.h>
|
||||
#include <ripple/app/misc/StateAccounting.h>
|
||||
#include <ripple/app/misc/Transaction.h>
|
||||
#include <ripple/app/misc/TxQ.h>
|
||||
#include <ripple/app/misc/ValidatorKeys.h>
|
||||
@@ -68,9 +67,9 @@
|
||||
#include <ripple/rpc/CTID.h>
|
||||
#include <ripple/rpc/DeliveredAmount.h>
|
||||
#include <ripple/rpc/impl/RPCHelpers.h>
|
||||
#include <ripple/rpc/impl/UDPInfoSub.h>
|
||||
#include <boost/asio/ip/host_name.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
|
||||
#include <exception>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
@@ -117,6 +116,81 @@ class NetworkOPsImp final : public NetworkOPs
|
||||
running,
|
||||
};
|
||||
|
||||
static std::array<char const*, 5> const states_;
|
||||
|
||||
/**
|
||||
* State accounting records two attributes for each possible server state:
|
||||
* 1) Amount of time spent in each state (in microseconds). This value is
|
||||
* updated upon each state transition.
|
||||
* 2) Number of transitions to each state.
|
||||
*
|
||||
* This data can be polled through server_info and represented by
|
||||
* monitoring systems similarly to how bandwidth, CPU, and other
|
||||
* counter-based metrics are managed.
|
||||
*
|
||||
* State accounting is more accurate than periodic sampling of server
|
||||
* state. With periodic sampling, it is very likely that state transitions
|
||||
* are missed, and accuracy of time spent in each state is very rough.
|
||||
*/
|
||||
class StateAccounting
|
||||
{
|
||||
struct Counters
|
||||
{
|
||||
explicit Counters() = default;
|
||||
|
||||
std::uint64_t transitions = 0;
|
||||
std::chrono::microseconds dur = std::chrono::microseconds(0);
|
||||
};
|
||||
|
||||
OperatingMode mode_ = OperatingMode::DISCONNECTED;
|
||||
std::array<Counters, 5> counters_;
|
||||
mutable std::mutex mutex_;
|
||||
std::chrono::steady_clock::time_point start_ =
|
||||
std::chrono::steady_clock::now();
|
||||
std::chrono::steady_clock::time_point const processStart_ = start_;
|
||||
std::uint64_t initialSyncUs_{0};
|
||||
static std::array<Json::StaticString const, 5> const states_;
|
||||
|
||||
public:
|
||||
explicit StateAccounting()
|
||||
{
|
||||
counters_[static_cast<std::size_t>(OperatingMode::DISCONNECTED)]
|
||||
.transitions = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record state transition. Update duration spent in previous
|
||||
* state.
|
||||
*
|
||||
* @param om New state.
|
||||
*/
|
||||
void
|
||||
mode(OperatingMode om);
|
||||
|
||||
/**
|
||||
* Output state counters in JSON format.
|
||||
*
|
||||
* @obj Json object to which to add state accounting data.
|
||||
*/
|
||||
void
|
||||
json(Json::Value& obj) const;
|
||||
|
||||
struct CounterData
|
||||
{
|
||||
decltype(counters_) counters;
|
||||
decltype(mode_) mode;
|
||||
decltype(start_) start;
|
||||
decltype(initialSyncUs_) initialSyncUs;
|
||||
};
|
||||
|
||||
CounterData
|
||||
getCounterData() const
|
||||
{
|
||||
std::lock_guard lock(mutex_);
|
||||
return {counters_, mode_, start_, initialSyncUs_};
|
||||
}
|
||||
};
|
||||
|
||||
//! Server fees published on `server` subscription
|
||||
struct ServerFeeSummary
|
||||
{
|
||||
@@ -198,9 +272,6 @@ public:
|
||||
std::string
|
||||
strOperatingMode(bool const admin = false) const override;
|
||||
|
||||
StateAccounting::CounterData
|
||||
getStateAccountingData();
|
||||
|
||||
//
|
||||
// Transaction operations.
|
||||
//
|
||||
@@ -705,17 +776,11 @@ private:
|
||||
DispatchState mDispatchState = DispatchState::none;
|
||||
std::vector<TransactionStatus> mTransactions;
|
||||
|
||||
StateAccounting accounting_;
|
||||
StateAccounting accounting_{};
|
||||
|
||||
std::set<uint256> pendingValidations_;
|
||||
std::mutex validationsMutex_;
|
||||
|
||||
RCLConsensus&
|
||||
getConsensus();
|
||||
|
||||
LedgerMaster&
|
||||
getLedgerMaster();
|
||||
|
||||
private:
|
||||
struct Stats
|
||||
{
|
||||
@@ -778,6 +843,19 @@ private:
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
static std::array<char const*, 5> const stateNames{
|
||||
{"disconnected", "connected", "syncing", "tracking", "full"}};
|
||||
|
||||
std::array<char const*, 5> const NetworkOPsImp::states_ = stateNames;
|
||||
|
||||
std::array<Json::StaticString const, 5> const
|
||||
NetworkOPsImp::StateAccounting::states_ = {
|
||||
{Json::StaticString(stateNames[0]),
|
||||
Json::StaticString(stateNames[1]),
|
||||
Json::StaticString(stateNames[2]),
|
||||
Json::StaticString(stateNames[3]),
|
||||
Json::StaticString(stateNames[4])}};
|
||||
|
||||
static auto const genesisAccountId = calcAccountID(
|
||||
generateKeyPair(KeyType::secp256k1, generateSeed("masterpassphrase"))
|
||||
.first);
|
||||
@@ -1052,7 +1130,7 @@ NetworkOPsImp::strOperatingMode(OperatingMode const mode, bool const admin)
|
||||
}
|
||||
}
|
||||
|
||||
return {StateAccounting::states_[static_cast<std::size_t>(mode)].c_str()};
|
||||
return states_[static_cast<std::size_t>(mode)];
|
||||
}
|
||||
|
||||
void
|
||||
@@ -2318,19 +2396,6 @@ NetworkOPsImp::getConsensusInfo()
|
||||
return mConsensus.getJson(true);
|
||||
}
|
||||
|
||||
// RHTODO: not threadsafe?
|
||||
RCLConsensus&
|
||||
NetworkOPsImp::getConsensus()
|
||||
{
|
||||
return mConsensus;
|
||||
}
|
||||
|
||||
LedgerMaster&
|
||||
NetworkOPsImp::getLedgerMaster()
|
||||
{
|
||||
return m_ledgerMaster;
|
||||
}
|
||||
|
||||
Json::Value
|
||||
NetworkOPsImp::getServerInfo(bool human, bool admin, bool counters)
|
||||
{
|
||||
@@ -4128,12 +4193,6 @@ NetworkOPsImp::stateAccounting(Json::Value& obj)
|
||||
accounting_.json(obj);
|
||||
}
|
||||
|
||||
StateAccounting::CounterData
|
||||
NetworkOPsImp::getStateAccountingData()
|
||||
{
|
||||
return accounting_.getCounterData();
|
||||
}
|
||||
|
||||
// <-- bool: true=erased, false=was not there
|
||||
bool
|
||||
NetworkOPsImp::unsubValidations(std::uint64_t uSeq)
|
||||
@@ -4604,6 +4663,50 @@ NetworkOPsImp::collect_metrics()
|
||||
counters[static_cast<std::size_t>(OperatingMode::FULL)].transitions);
|
||||
}
|
||||
|
||||
void
|
||||
NetworkOPsImp::StateAccounting::mode(OperatingMode om)
|
||||
{
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
std::lock_guard lock(mutex_);
|
||||
++counters_[static_cast<std::size_t>(om)].transitions;
|
||||
if (om == OperatingMode::FULL &&
|
||||
counters_[static_cast<std::size_t>(om)].transitions == 1)
|
||||
{
|
||||
initialSyncUs_ = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
now - processStart_)
|
||||
.count();
|
||||
}
|
||||
counters_[static_cast<std::size_t>(mode_)].dur +=
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(now - start_);
|
||||
|
||||
mode_ = om;
|
||||
start_ = now;
|
||||
}
|
||||
|
||||
void
|
||||
NetworkOPsImp::StateAccounting::json(Json::Value& obj) const
|
||||
{
|
||||
auto [counters, mode, start, initialSync] = getCounterData();
|
||||
auto const current = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::steady_clock::now() - start);
|
||||
counters[static_cast<std::size_t>(mode)].dur += current;
|
||||
|
||||
obj[jss::state_accounting] = Json::objectValue;
|
||||
for (std::size_t i = static_cast<std::size_t>(OperatingMode::DISCONNECTED);
|
||||
i <= static_cast<std::size_t>(OperatingMode::FULL);
|
||||
++i)
|
||||
{
|
||||
obj[jss::state_accounting][states_[i]] = Json::objectValue;
|
||||
auto& state = obj[jss::state_accounting][states_[i]];
|
||||
state[jss::transitions] = std::to_string(counters[i].transitions);
|
||||
state[jss::duration_us] = std::to_string(counters[i].dur.count());
|
||||
}
|
||||
obj[jss::server_state_duration_us] = std::to_string(current.count());
|
||||
if (initialSync)
|
||||
obj[jss::initial_sync_duration_us] = std::to_string(initialSync);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
std::unique_ptr<NetworkOPs>
|
||||
|
||||
@@ -20,10 +20,8 @@
|
||||
#ifndef RIPPLE_APP_MISC_NETWORKOPS_H_INCLUDED
|
||||
#define RIPPLE_APP_MISC_NETWORKOPS_H_INCLUDED
|
||||
|
||||
#include <ripple/app/consensus/RCLConsensus.h>
|
||||
#include <ripple/app/consensus/RCLCxPeerPos.h>
|
||||
#include <ripple/app/ledger/Ledger.h>
|
||||
#include <ripple/app/misc/StateAccounting.h>
|
||||
#include <ripple/core/JobQueue.h>
|
||||
#include <ripple/ledger/ReadView.h>
|
||||
#include <ripple/net/InfoSub.h>
|
||||
@@ -44,6 +42,35 @@ class LedgerMaster;
|
||||
class Transaction;
|
||||
class ValidatorKeys;
|
||||
|
||||
// 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 (synched, unsynched,
|
||||
// etectera) and defer to the correct means of processing. The current
|
||||
// code assumes this node is synched (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
|
||||
@@ -194,13 +221,6 @@ public:
|
||||
|
||||
virtual Json::Value
|
||||
getConsensusInfo() = 0;
|
||||
|
||||
virtual RCLConsensus&
|
||||
getConsensus() = 0;
|
||||
|
||||
virtual LedgerMaster&
|
||||
getLedgerMaster() = 0;
|
||||
|
||||
virtual Json::Value
|
||||
getServerInfo(bool human, bool admin, bool counters) = 0;
|
||||
virtual void
|
||||
@@ -208,9 +228,6 @@ public:
|
||||
virtual Json::Value
|
||||
getLedgerFetchInfo() = 0;
|
||||
|
||||
virtual StateAccounting::CounterData
|
||||
getStateAccountingData() = 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
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
#include <ripple/app/misc/StateAccounting.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
void
|
||||
StateAccounting::mode(OperatingMode om)
|
||||
{
|
||||
std::lock_guard lock(mutex_);
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
++counters_[static_cast<std::size_t>(om)].transitions;
|
||||
if (om == OperatingMode::FULL &&
|
||||
counters_[static_cast<std::size_t>(om)].transitions == 1)
|
||||
{
|
||||
initialSyncUs_ = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
now - processStart_)
|
||||
.count();
|
||||
}
|
||||
counters_[static_cast<std::size_t>(mode_)].dur +=
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(now - start_);
|
||||
|
||||
mode_ = om;
|
||||
start_ = now;
|
||||
}
|
||||
|
||||
void
|
||||
StateAccounting::json(Json::Value& obj)
|
||||
{
|
||||
auto [counters, mode, start, initialSync] = getCounterData();
|
||||
auto const current = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::steady_clock::now() - start);
|
||||
counters[static_cast<std::size_t>(mode)].dur += current;
|
||||
|
||||
obj[jss::state_accounting] = Json::objectValue;
|
||||
for (std::size_t i = static_cast<std::size_t>(OperatingMode::DISCONNECTED);
|
||||
i <= static_cast<std::size_t>(OperatingMode::FULL);
|
||||
++i)
|
||||
{
|
||||
obj[jss::state_accounting][states_[i]] = Json::objectValue;
|
||||
auto& state = obj[jss::state_accounting][states_[i]];
|
||||
state[jss::transitions] = std::to_string(counters[i].transitions);
|
||||
state[jss::duration_us] = std::to_string(counters[i].dur.count());
|
||||
}
|
||||
obj[jss::server_state_duration_us] = std::to_string(current.count());
|
||||
if (initialSync)
|
||||
obj[jss::initial_sync_duration_us] = std::to_string(initialSync);
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
@@ -1,99 +0,0 @@
|
||||
#ifndef RIPPLE_APP_MAIN_STATEACCOUNTING_H_INCLUDED
|
||||
#define RIPPLE_APP_MAIN_STATEACCOUNTING_H_INCLUDED
|
||||
|
||||
#include <ripple/basics/chrono.h>
|
||||
#include <ripple/beast/utility/Journal.h>
|
||||
#include <ripple/json/json_value.h>
|
||||
#include <ripple/protocol/jss.h>
|
||||
#include <array>
|
||||
#include <mutex>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
// 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 (synched, unsynched,
|
||||
// etectera) and defer to the correct means of processing. The current
|
||||
// code assumes this node is synched (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
|
||||
};
|
||||
|
||||
class StateAccounting
|
||||
{
|
||||
public:
|
||||
constexpr static std::array<Json::StaticString const, 5> const states_ = {
|
||||
{Json::StaticString("disconnected"),
|
||||
Json::StaticString("connected"),
|
||||
Json::StaticString("syncing"),
|
||||
Json::StaticString("tracking"),
|
||||
Json::StaticString("full")}};
|
||||
|
||||
struct Counters
|
||||
{
|
||||
explicit Counters() = default;
|
||||
|
||||
std::uint64_t transitions = 0;
|
||||
std::chrono::microseconds dur = std::chrono::microseconds(0);
|
||||
};
|
||||
|
||||
private:
|
||||
OperatingMode mode_ = OperatingMode::DISCONNECTED;
|
||||
std::array<Counters, 5> counters_;
|
||||
mutable std::mutex mutex_;
|
||||
std::chrono::steady_clock::time_point start_ =
|
||||
std::chrono::steady_clock::now();
|
||||
std::chrono::steady_clock::time_point const processStart_ = start_;
|
||||
std::uint64_t initialSyncUs_{0};
|
||||
|
||||
public:
|
||||
explicit StateAccounting()
|
||||
{
|
||||
counters_[static_cast<std::size_t>(OperatingMode::DISCONNECTED)]
|
||||
.transitions = 1;
|
||||
}
|
||||
|
||||
//! Record state transition. Update duration spent in previous state.
|
||||
void
|
||||
mode(OperatingMode om);
|
||||
|
||||
//! Output state counters in JSON format.
|
||||
void
|
||||
json(Json::Value& obj);
|
||||
|
||||
using CounterData = std::tuple<
|
||||
decltype(counters_),
|
||||
decltype(mode_),
|
||||
decltype(start_),
|
||||
decltype(initialSyncUs_)>;
|
||||
|
||||
CounterData
|
||||
getCounterData()
|
||||
{
|
||||
return {counters_, mode_, start_, initialSyncUs_};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -88,69 +88,6 @@ preflight0(PreflightContext const& ctx)
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
checkServiceFee(PreflightContext const& ctx)
|
||||
{
|
||||
if (!ctx.tx.isFieldPresent(sfServiceFee))
|
||||
return tesSUCCESS;
|
||||
|
||||
if (!ctx.rules.enabled(featureServiceFee))
|
||||
{
|
||||
JLOG(ctx.j.debug())
|
||||
<< "ServiceFee: Service fee feature is not enabled.";
|
||||
return temDISABLED;
|
||||
}
|
||||
|
||||
STObject const& obj = const_cast<ripple::STTx&>(ctx.tx)
|
||||
.getField(sfServiceFee)
|
||||
.downcast<STObject>();
|
||||
|
||||
// This should be enforced by template but doesn't hurt to
|
||||
// defensively check it here.
|
||||
if (!obj.isFieldPresent(sfDestination) || !obj.isFieldPresent(sfAmount) ||
|
||||
obj.getCount() != 2)
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "ServiceFee: Malformed: Destination and Amount "
|
||||
"fields are required.";
|
||||
return temINVALID;
|
||||
}
|
||||
|
||||
if (ctx.tx.getAccountID(sfAccount) == obj.getAccountID(sfDestination))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "ServiceFee: Malformed: Destination may not be "
|
||||
"the same as the source account.";
|
||||
return temDST_IS_SRC;
|
||||
}
|
||||
|
||||
auto const amount = obj.getFieldAmount(sfAmount);
|
||||
|
||||
if (!isXRP(amount))
|
||||
{
|
||||
if (!isLegalNet(amount))
|
||||
{
|
||||
JLOG(ctx.j.debug())
|
||||
<< "ServiceFee: Malformed: Amount must be a valid net amount.";
|
||||
return temBAD_AMOUNT;
|
||||
}
|
||||
|
||||
if (isBadCurrency(amount))
|
||||
{
|
||||
JLOG(ctx.j.debug())
|
||||
<< "ServiceFee: Malformed: Currency is not allowed.";
|
||||
return temBAD_CURRENCY;
|
||||
}
|
||||
}
|
||||
|
||||
if (amount <= beast::zero)
|
||||
{
|
||||
JLOG(ctx.j.debug())
|
||||
<< "ServiceFee: Malformed: Amount must be a positive value.";
|
||||
return temBAD_AMOUNT;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
/** Performs early sanity checks on the account and fee fields */
|
||||
NotTEC
|
||||
preflight1(PreflightContext const& ctx)
|
||||
@@ -228,9 +165,6 @@ preflight1(PreflightContext const& ctx)
|
||||
ctx.tx.isFieldPresent(sfAccountTxnID))
|
||||
return temINVALID;
|
||||
|
||||
if (auto const ret = checkServiceFee(ctx); !isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -1336,18 +1270,10 @@ Transactor::executeHookChain(
|
||||
if (results.back().exitType == hook_api::ExitType::WASM_ERROR)
|
||||
{
|
||||
JLOG(j_.warn()) << "HookError[" << account << "-"
|
||||
<< ctx_.tx.getAccountID(sfAccount)
|
||||
<< ctx_.tx.getAccountID(sfAccount) << "]: "
|
||||
<< "]: Execution failure (graceful) "
|
||||
<< "HookHash: " << hookHash;
|
||||
}
|
||||
if (results.back().exitType == hook_api::ExitType::UNSET)
|
||||
{
|
||||
JLOG(j_.warn())
|
||||
<< "HookError[" << account << "-"
|
||||
<< ctx_.tx.getAccountID(sfAccount)
|
||||
<< "]: Execution failure (no exit type specified) "
|
||||
<< "HookHash: " << hookHash;
|
||||
}
|
||||
return tecHOOK_REJECTED;
|
||||
}
|
||||
|
||||
@@ -1372,7 +1298,7 @@ Transactor::executeHookChain(
|
||||
{
|
||||
JLOG(j_.warn())
|
||||
<< "HookError[" << account << "-"
|
||||
<< ctx_.tx.getAccountID(sfAccount)
|
||||
<< ctx_.tx.getAccountID(sfAccount) << "]: "
|
||||
<< "]: Execution failure (exceptional) "
|
||||
<< "Exception: " << e.what() << " HookHash: " << hookHash;
|
||||
|
||||
@@ -1500,13 +1426,13 @@ Transactor::doHookCallback(
|
||||
finalizeHookResult(callbackResult, ctx_, success);
|
||||
|
||||
JLOG(j_.trace()) << "HookInfo[" << callbackAccountID << "-"
|
||||
<< ctx_.tx.getAccountID(sfAccount)
|
||||
<< "]: Callback finalizeHookResult = " << result;
|
||||
<< ctx_.tx.getAccountID(sfAccount) << "]: "
|
||||
<< "Callback finalizeHookResult = " << result;
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
JLOG(j_.fatal()) << "HookError[" << callbackAccountID << "-"
|
||||
<< ctx_.tx.getAccountID(sfAccount)
|
||||
<< ctx_.tx.getAccountID(sfAccount) << "]: "
|
||||
<< "]: Callback failure " << e.what();
|
||||
}
|
||||
}
|
||||
@@ -1752,13 +1678,13 @@ Transactor::doAgainAsWeak(
|
||||
results.push_back(aawResult);
|
||||
|
||||
JLOG(j_.trace()) << "HookInfo[" << hookAccountID << "-"
|
||||
<< ctx_.tx.getAccountID(sfAccount)
|
||||
<< "]: aaw Hook ExitCode = " << aawResult.exitCode;
|
||||
<< ctx_.tx.getAccountID(sfAccount) << "]: "
|
||||
<< " aaw Hook ExitCode = " << aawResult.exitCode;
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
JLOG(j_.fatal()) << "HookError[" << hookAccountID << "-"
|
||||
<< ctx_.tx.getAccountID(sfAccount)
|
||||
<< ctx_.tx.getAccountID(sfAccount) << "]: "
|
||||
<< "]: aaw failure " << e.what();
|
||||
}
|
||||
}
|
||||
@@ -1960,178 +1886,6 @@ Transactor::operator()()
|
||||
applied = isTecClaim(result);
|
||||
}
|
||||
|
||||
if (applied && view().rules().enabled(featureServiceFee) &&
|
||||
ctx_.tx.isFieldPresent(sfServiceFee))
|
||||
do
|
||||
{
|
||||
// Service fee is processed on a best-effort basis without affecting
|
||||
// tx application. The reason is that the client completely controls
|
||||
// the service fee that it submits with the user's txn, and
|
||||
// therefore is already completely aware of the user's capacity to
|
||||
// pay the fee and therefore enforcement logic is unnecessary
|
||||
// chain-side.
|
||||
|
||||
STObject const& obj = const_cast<ripple::STTx&>(ctx_.tx)
|
||||
.getField(sfServiceFee)
|
||||
.downcast<STObject>();
|
||||
auto const src = ctx_.tx.getAccountID(sfAccount);
|
||||
auto const dst = obj.getAccountID(sfDestination);
|
||||
auto const amt = obj.getFieldAmount(sfAmount);
|
||||
|
||||
// check if the source exists
|
||||
auto const& sleSrc = view().read(keylet::account(src));
|
||||
if (!sleSrc)
|
||||
{
|
||||
// this can happen if the account was just deleted
|
||||
JLOG(j_.debug()) << "service fee not applied because source "
|
||||
<< src << " does not exist.";
|
||||
break;
|
||||
}
|
||||
|
||||
// check if the destination exists
|
||||
// service fee cannot be used to create accounts.
|
||||
if (!view().exists(keylet::account(dst)))
|
||||
{
|
||||
JLOG(j_.debug())
|
||||
<< "service fee not applied because destination " << dst
|
||||
<< " does not exist.";
|
||||
break;
|
||||
}
|
||||
|
||||
if (isXRP(amt))
|
||||
{
|
||||
// check if there's enough left in the sender's account
|
||||
auto srcBal = sleSrc->getFieldAmount(sfBalance);
|
||||
|
||||
// service fee will only be delivered if the account
|
||||
// contains adequate balance to cover reserves, otherwise
|
||||
// it is disregarded
|
||||
auto after = srcBal - amt - fee;
|
||||
if (after < view().fees().accountReserve(
|
||||
sleSrc->getFieldU32(sfOwnerCount)))
|
||||
{
|
||||
JLOG(j_.debug())
|
||||
<< "service fee not applied because source " << src
|
||||
<< " cannot pay it (native).";
|
||||
break;
|
||||
}
|
||||
|
||||
PaymentSandbox pv(&view());
|
||||
auto res = accountSend(pv, src, dst, amt, j_, false);
|
||||
if (isTesSuccess(res))
|
||||
{
|
||||
pv.apply(ctx_.rawView());
|
||||
break;
|
||||
}
|
||||
|
||||
JLOG(j_.warn()) << "service fee (native) not applied because "
|
||||
"accountSend failed.";
|
||||
break;
|
||||
}
|
||||
|
||||
// issued currency
|
||||
|
||||
// service fee cannot be used to create trustlines,
|
||||
// so a line must already exist and the currency must
|
||||
// be able to be xfer'd to it
|
||||
|
||||
auto const issuer = amt.getIssuer();
|
||||
if (issuer != src && !view().exists(keylet::line(src, amt.issue())))
|
||||
{
|
||||
JLOG(j_.debug())
|
||||
<< "service fee not applied because source " << src
|
||||
<< " has no trustline for currency: " << amt.getCurrency()
|
||||
<< " issued by: " << toBase58(issuer) << ".";
|
||||
break;
|
||||
}
|
||||
|
||||
if (issuer != dst && !view().exists(keylet::line(dst, amt.issue())))
|
||||
{
|
||||
JLOG(j_.debug())
|
||||
<< "service fee not applied because destination " << dst
|
||||
<< " has no trustline for currency: " << amt.getCurrency()
|
||||
<< " issued by: " << toBase58(issuer) << ".";
|
||||
break;
|
||||
}
|
||||
|
||||
if (dst != issuer && src != issuer)
|
||||
{
|
||||
TER const res =
|
||||
trustTransferAllowed(view(), {src, dst}, amt.issue(), j_);
|
||||
if (!isTesSuccess(res))
|
||||
{
|
||||
JLOG(j_.debug())
|
||||
<< "service fee not applied because destination " << dst
|
||||
<< " trust transfer not allowed for currency: "
|
||||
<< amt.getCurrency()
|
||||
<< " issued by: " << toBase58(issuer) << ".";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (src != issuer)
|
||||
{
|
||||
Keylet const srcLine =
|
||||
keylet::line(src, issuer, amt.getCurrency());
|
||||
auto const sleSrcLine = view().read(srcLine);
|
||||
STAmount srcBalance = src < amt.issue().account
|
||||
? (*sleSrcLine)[sfBalance]
|
||||
: -(*sleSrcLine)[sfBalance];
|
||||
// TODO: xferRate
|
||||
if (srcBalance < amt)
|
||||
{
|
||||
JLOG(j_.debug())
|
||||
<< "service fee not applied because source " << src
|
||||
<< " has insufficient funds for currency: "
|
||||
<< amt.getCurrency()
|
||||
<< " issued by: " << toBase58(issuer) << ".";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (dst != issuer)
|
||||
{
|
||||
Keylet const dstLine =
|
||||
keylet::line(dst, issuer, amt.getCurrency());
|
||||
auto const sleDstLine = view().read(dstLine);
|
||||
STAmount dstLimit = dst < amt.issue().account
|
||||
? (*sleDstLine)[sfLowLimit]
|
||||
: (*sleDstLine)[sfHighLimit];
|
||||
if (accountFunds(view(), dst, amt, fhZERO_IF_FROZEN, j_) + amt >
|
||||
dstLimit)
|
||||
{
|
||||
JLOG(j_.debug())
|
||||
<< "service fee not applied because destination " << dst
|
||||
<< " has insufficient trustline limit for "
|
||||
"currency: "
|
||||
<< amt.getCurrency()
|
||||
<< " issued by: " << toBase58(issuer) << ".";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// action the transfer
|
||||
{
|
||||
PaymentSandbox pv(&view());
|
||||
|
||||
STAmount saActual;
|
||||
auto res = accountSend(pv, src, dst, amt, j_, false);
|
||||
if (isTesSuccess(res))
|
||||
{
|
||||
pv.apply(ctx_.rawView());
|
||||
break;
|
||||
}
|
||||
|
||||
JLOG(j_.warn())
|
||||
<< "service fee not sent from " << src << " to " << dst
|
||||
<< " for " << amt.getCurrency() << " issued by "
|
||||
<< toBase58(issuer) << " because "
|
||||
<< "accountSend() failed with code " << res << ".";
|
||||
break;
|
||||
}
|
||||
|
||||
} while (0);
|
||||
|
||||
if (applied)
|
||||
{
|
||||
// Check invariants: if `tecINVARIANT_FAILED` is not returned, we can
|
||||
@@ -2280,12 +2034,12 @@ Transactor::operator()()
|
||||
|
||||
if (applied)
|
||||
{
|
||||
// Transaction succeeded fully or (retries are not allowed and
|
||||
// the transaction could claim a fee)
|
||||
// Transaction succeeded fully or (retries are not allowed and the
|
||||
// transaction could claim a fee)
|
||||
|
||||
// The transactor and invariant checkers guarantee that this
|
||||
// will *never* trigger but if it, somehow, happens, don't allow
|
||||
// a tx that charges a negative fee.
|
||||
// The transactor and invariant checkers guarantee that this will
|
||||
// *never* trigger but if it, somehow, happens, don't allow a tx
|
||||
// that charges a negative fee.
|
||||
if (fee < beast::zero)
|
||||
Throw<std::logic_error>("fee charged is negative!");
|
||||
|
||||
@@ -2296,8 +2050,7 @@ Transactor::operator()()
|
||||
if (!view().open() && fee != beast::zero)
|
||||
ctx_.destroyXRP(fee);
|
||||
|
||||
// Once we call apply, we will no longer be able to look at
|
||||
// view()
|
||||
// Once we call apply, we will no longer be able to look at view()
|
||||
ctx_.apply(result);
|
||||
}
|
||||
|
||||
|
||||
@@ -155,8 +155,6 @@ public:
|
||||
std::map<std::string, PublicKey>
|
||||
IMPORT_VL_KEYS; // hex string -> class PublicKey (for caching purposes)
|
||||
|
||||
std::vector<std::string> DATAGRAM_MONITOR;
|
||||
|
||||
enum StartUpType {
|
||||
FRESH,
|
||||
NORMAL,
|
||||
|
||||
@@ -101,7 +101,6 @@ struct ConfigSection
|
||||
#define SECTION_SWEEP_INTERVAL "sweep_interval"
|
||||
#define SECTION_NETWORK_ID "network_id"
|
||||
#define SECTION_IMPORT_VL_KEYS "import_vl_keys"
|
||||
#define SECTION_DATAGRAM_MONITOR "datagram_monitor"
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
|
||||
@@ -281,9 +281,6 @@ Config::setupControl(bool bQuiet, bool bSilent, bool bStandalone)
|
||||
// RAM and CPU resources. We default to "tiny" for standalone mode.
|
||||
if (!bStandalone)
|
||||
{
|
||||
NODE_SIZE = 4;
|
||||
return;
|
||||
|
||||
// First, check against 'minimum' RAM requirements per node size:
|
||||
auto const& threshold =
|
||||
sizedItems[std::underlying_type_t<SizedItem>(SizedItem::ramSizeGB)];
|
||||
@@ -468,24 +465,26 @@ Config::loadFromString(std::string const& fileContents)
|
||||
SNTP_SERVERS = *s;
|
||||
|
||||
// if the user has specified ip:port then replace : with a space.
|
||||
auto replaceColons = [](std::vector<std::string>& strVec) {
|
||||
const static std::regex e(":([0-9]+)$");
|
||||
for (auto& line : strVec)
|
||||
{
|
||||
// skip anything that might be an ipv6 address
|
||||
if (std::count(line.begin(), line.end(), ':') != 1)
|
||||
continue;
|
||||
{
|
||||
auto replaceColons = [](std::vector<std::string>& strVec) {
|
||||
const static std::regex e(":([0-9]+)$");
|
||||
for (auto& line : strVec)
|
||||
{
|
||||
// skip anything that might be an ipv6 address
|
||||
if (std::count(line.begin(), line.end(), ':') != 1)
|
||||
continue;
|
||||
|
||||
std::string result = std::regex_replace(line, e, " $1");
|
||||
// sanity check the result of the replace, should be same length
|
||||
// as input
|
||||
if (result.size() == line.size())
|
||||
line = result;
|
||||
}
|
||||
};
|
||||
std::string result = std::regex_replace(line, e, " $1");
|
||||
// sanity check the result of the replace, should be same length
|
||||
// as input
|
||||
if (result.size() == line.size())
|
||||
line = result;
|
||||
}
|
||||
};
|
||||
|
||||
replaceColons(IPS_FIXED);
|
||||
replaceColons(IPS);
|
||||
replaceColons(IPS_FIXED);
|
||||
replaceColons(IPS);
|
||||
}
|
||||
|
||||
{
|
||||
std::string dbPath;
|
||||
@@ -510,12 +509,6 @@ Config::loadFromString(std::string const& fileContents)
|
||||
NETWORK_ID = beast::lexicalCastThrow<uint32_t>(strTemp);
|
||||
}
|
||||
|
||||
if (auto s = getIniFileSection(secConfig, SECTION_DATAGRAM_MONITOR))
|
||||
{
|
||||
DATAGRAM_MONITOR = *s;
|
||||
replaceColons(DATAGRAM_MONITOR);
|
||||
}
|
||||
|
||||
if (getSingleSection(secConfig, SECTION_PEER_PRIVATE, strTemp, j_))
|
||||
PEER_PRIVATE = beast::lexicalCastThrow<bool>(strTemp);
|
||||
|
||||
|
||||
@@ -422,13 +422,6 @@ transferXRP(
|
||||
STAmount const& amount,
|
||||
beast::Journal j);
|
||||
|
||||
/** Check if the account lacks required authorization.
|
||||
* Return tecNO_AUTH or tecNO_LINE if it does
|
||||
* and tesSUCCESS otherwise.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
requireAuth(ReadView const& view, Issue const& issue, AccountID const& account);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
//
|
||||
|
||||
@@ -1583,24 +1583,4 @@ transferXRP(
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
requireAuth(ReadView const& view, Issue const& issue, AccountID const& account)
|
||||
{
|
||||
if (isXRP(issue) || issue.account == account)
|
||||
return tesSUCCESS;
|
||||
if (auto const issuerAccount = view.read(keylet::account(issue.account));
|
||||
issuerAccount && (*issuerAccount)[sfFlags] & lsfRequireAuth)
|
||||
{
|
||||
if (auto const trustLine =
|
||||
view.read(keylet::line(account, issue.account, issue.currency)))
|
||||
return ((*trustLine)[sfFlags] &
|
||||
((account > issue.account) ? lsfLowAuth : lsfHighAuth))
|
||||
? tesSUCCESS
|
||||
: TER{tecNO_AUTH};
|
||||
return TER{tecNO_LINE};
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -74,7 +74,7 @@ namespace detail {
|
||||
// Feature.cpp. Because it's only used to reserve storage, and determine how
|
||||
// large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than
|
||||
// the actual number of amendments. A LogicError on startup will verify this.
|
||||
static constexpr std::size_t numFeatures = 77;
|
||||
static constexpr std::size_t numFeatures = 75;
|
||||
|
||||
/** Amendments that this server supports and the default voting behavior.
|
||||
Whether they are enabled depends on the Rules defined in the validated
|
||||
@@ -363,8 +363,6 @@ extern uint256 const fixPageCap;
|
||||
extern uint256 const fix240911;
|
||||
extern uint256 const fixFloatDivide;
|
||||
extern uint256 const fixReduceImport;
|
||||
extern uint256 const fixXahauV3;
|
||||
extern uint256 const featureServiceFee;
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
|
||||
@@ -608,7 +608,6 @@ extern SField const sfMemos;
|
||||
extern SField const sfNFTokens;
|
||||
extern SField const sfHooks;
|
||||
extern SField const sfGenesisMint;
|
||||
extern SField const sfServiceFee;
|
||||
|
||||
// array of objects (uncommon)
|
||||
extern SField const sfMajorities;
|
||||
|
||||
@@ -469,8 +469,6 @@ REGISTER_FIX (fixPageCap, Supported::yes, VoteBehavior::De
|
||||
REGISTER_FIX (fix240911, Supported::yes, VoteBehavior::DefaultYes);
|
||||
REGISTER_FIX (fixFloatDivide, Supported::yes, VoteBehavior::DefaultYes);
|
||||
REGISTER_FIX (fixReduceImport, Supported::yes, VoteBehavior::DefaultYes);
|
||||
REGISTER_FIX (fixXahauV3, Supported::yes, VoteBehavior::DefaultNo);
|
||||
REGISTER_FEATURE(ServiceFee, Supported::yes, VoteBehavior::DefaultNo);
|
||||
|
||||
// The following amendments are obsolete, but must remain supported
|
||||
// because they could potentially get enabled.
|
||||
|
||||
@@ -157,13 +157,6 @@ InnerObjectFormats::InnerObjectFormats()
|
||||
{sfDigest, soeOPTIONAL},
|
||||
{sfFlags, soeOPTIONAL},
|
||||
});
|
||||
|
||||
add(sfServiceFee.jsonName.c_str(),
|
||||
sfServiceFee.getCode(),
|
||||
{
|
||||
{sfAmount, soeREQUIRED},
|
||||
{sfDestination, soeREQUIRED},
|
||||
});
|
||||
}
|
||||
|
||||
InnerObjectFormats const&
|
||||
|
||||
@@ -350,7 +350,6 @@ CONSTRUCT_UNTYPED_SFIELD(sfImportVLKey, "ImportVLKey", OBJECT,
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfHookEmission, "HookEmission", OBJECT, 93);
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfMintURIToken, "MintURIToken", OBJECT, 92);
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfAmountEntry, "AmountEntry", OBJECT, 91);
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfServiceFee, "ServiceFee", OBJECT, 90);
|
||||
|
||||
// array of objects
|
||||
// ARRAY/1 is reserved for end of array
|
||||
|
||||
@@ -44,7 +44,6 @@ TxFormats::TxFormats()
|
||||
{sfNetworkID, soeOPTIONAL},
|
||||
{sfHookParameters, soeOPTIONAL},
|
||||
{sfOperationLimit, soeOPTIONAL},
|
||||
{sfServiceFee, soeOPTIONAL},
|
||||
};
|
||||
|
||||
add(jss::AccountSet,
|
||||
|
||||
@@ -126,7 +126,6 @@ JSS(UNLReport); // transaction type.
|
||||
JSS(SettleDelay); // in: TransactionSign
|
||||
JSS(SendMax); // in: TransactionSign
|
||||
JSS(Sequence); // in/out: TransactionSign; field.
|
||||
JSS(ServiceFee); // field.
|
||||
JSS(SetFlag); // field.
|
||||
JSS(SetRegularKey); // transaction type.
|
||||
JSS(SetHook); // transaction type.
|
||||
|
||||
@@ -75,7 +75,7 @@ doAccountInfo(RPC::JsonContext& context)
|
||||
auto const accountID{std::move(id.value())};
|
||||
|
||||
static constexpr std::
|
||||
array<std::pair<std::string_view, LedgerSpecificFlags>, 11>
|
||||
array<std::pair<std::string_view, LedgerSpecificFlags>, 10>
|
||||
lsFlags{
|
||||
{{"defaultRipple", lsfDefaultRipple},
|
||||
{"depositAuth", lsfDepositAuth},
|
||||
@@ -86,8 +86,7 @@ doAccountInfo(RPC::JsonContext& context)
|
||||
{"passwordSpent", lsfPasswordSpent},
|
||||
{"requireAuthorization", lsfRequireAuth},
|
||||
{"tshCollect", lsfTshCollect},
|
||||
{"requireDestinationTag", lsfRequireDestTag},
|
||||
{"uriTokenIssuer", lsfURITokenIssuer}}};
|
||||
{"requireDestinationTag", lsfRequireDestTag}}};
|
||||
|
||||
static constexpr std::
|
||||
array<std::pair<std::string_view, LedgerSpecificFlags>, 5>
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
#include <ripple/rpc/Context.h>
|
||||
#include <ripple/rpc/Role.h>
|
||||
#include <ripple/rpc/impl/RPCHelpers.h>
|
||||
#include <ripple/rpc/impl/UDPInfoSub.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
@@ -43,7 +42,7 @@ doSubscribe(RPC::JsonContext& context)
|
||||
if (!context.infoSub && !context.params.isMember(jss::url))
|
||||
{
|
||||
// Must be a JSON-RPC call.
|
||||
JLOG(context.j.warn()) << "doSubscribe: RPC subscribe requires a url";
|
||||
JLOG(context.j.info()) << "doSubscribe: RPC subscribe requires a url";
|
||||
return rpcError(rpcINVALID_PARAMS);
|
||||
}
|
||||
|
||||
@@ -374,13 +373,6 @@ doSubscribe(RPC::JsonContext& context)
|
||||
}
|
||||
}
|
||||
|
||||
if (ispSub)
|
||||
{
|
||||
if (std::shared_ptr<UDPInfoSub> udp =
|
||||
std::dynamic_pointer_cast<UDPInfoSub>(ispSub))
|
||||
udp->increment();
|
||||
}
|
||||
|
||||
return jvResult;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#include <ripple/rpc/Context.h>
|
||||
#include <ripple/rpc/Role.h>
|
||||
#include <ripple/rpc/impl/RPCHelpers.h>
|
||||
#include <ripple/rpc/impl/UDPInfoSub.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
@@ -246,12 +245,6 @@ doUnsubscribe(RPC::JsonContext& context)
|
||||
context.netOps.tryRemoveRpcSub(context.params[jss::url].asString());
|
||||
}
|
||||
|
||||
if (ispSub)
|
||||
{
|
||||
if (auto udp = std::dynamic_pointer_cast<UDPInfoSub>(ispSub))
|
||||
udp->destroy();
|
||||
}
|
||||
|
||||
return jvResult;
|
||||
}
|
||||
|
||||
|
||||
@@ -361,67 +361,6 @@ ServerHandlerImp::onWSMessage(
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ServerHandlerImp::onUDPMessage(
|
||||
std::string const& message,
|
||||
boost::asio::ip::tcp::endpoint const& remoteEndpoint,
|
||||
std::function<void(std::string const&)> sendResponse)
|
||||
{
|
||||
Json::Value jv;
|
||||
if (message.size() > RPC::Tuning::maxRequestSize ||
|
||||
!Json::Reader{}.parse(message, jv) || !jv.isObject())
|
||||
{
|
||||
Json::Value jvResult(Json::objectValue);
|
||||
jvResult[jss::type] = jss::error;
|
||||
jvResult[jss::error] = "jsonInvalid";
|
||||
jvResult[jss::value] = message;
|
||||
|
||||
std::string const response = to_string(jvResult);
|
||||
JLOG(m_journal.trace())
|
||||
<< "UDP sending error response: '" << jvResult << "'";
|
||||
sendResponse(response);
|
||||
return;
|
||||
}
|
||||
|
||||
JLOG(m_journal.trace())
|
||||
<< "UDP received '" << jv << "' from " << remoteEndpoint;
|
||||
|
||||
auto const postResult = m_jobQueue.postCoro(
|
||||
jtCLIENT_RPC, // Using RPC job type since this is admin RPC
|
||||
"UDP-RPC",
|
||||
[this,
|
||||
remoteEndpoint,
|
||||
jv = std::move(jv),
|
||||
sendResponse = std::move(sendResponse)](
|
||||
std::shared_ptr<JobQueue::Coro> const& coro) {
|
||||
// Process the request similar to WebSocket but with UDP context
|
||||
Role const role = Role::ADMIN; // UDP-RPC is admin-only
|
||||
auto const jr =
|
||||
this->processUDP(jv, role, coro, sendResponse, remoteEndpoint);
|
||||
|
||||
std::string const response = to_string(jr);
|
||||
JLOG(m_journal.trace())
|
||||
<< "UDP sending '" << jr << "' to " << remoteEndpoint;
|
||||
|
||||
// Send response back via UDP
|
||||
sendResponse(response);
|
||||
});
|
||||
|
||||
if (postResult == nullptr)
|
||||
{
|
||||
// Request rejected, probably shutting down
|
||||
Json::Value jvResult(Json::objectValue);
|
||||
jvResult[jss::type] = jss::error;
|
||||
jvResult[jss::error] = "serverShuttingDown";
|
||||
jvResult[jss::value] = "Server is shutting down";
|
||||
|
||||
std::string const response = to_string(jvResult);
|
||||
JLOG(m_journal.trace())
|
||||
<< "UDP sending shutdown response to " << remoteEndpoint;
|
||||
sendResponse(response);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ServerHandlerImp::onClose(Session& session, boost::system::error_code const&)
|
||||
{
|
||||
@@ -458,145 +397,6 @@ logDuration(
|
||||
<< " microseconds. request = " << request;
|
||||
}
|
||||
|
||||
Json::Value
|
||||
ServerHandlerImp::processUDP(
|
||||
Json::Value const& jv,
|
||||
Role const& role,
|
||||
std::shared_ptr<JobQueue::Coro> const& coro,
|
||||
std::optional<std::function<void(std::string const&)>>
|
||||
sendResponse /* used for subscriptions */,
|
||||
boost::asio::ip::tcp::endpoint const& remoteEndpoint)
|
||||
{
|
||||
std::shared_ptr<InfoSub> is;
|
||||
// Requests without "command" are invalid.
|
||||
Json::Value jr(Json::objectValue);
|
||||
try
|
||||
{
|
||||
auto apiVersion =
|
||||
RPC::getAPIVersionNumber(jv, app_.config().BETA_RPC_API);
|
||||
if (apiVersion == RPC::apiInvalidVersion ||
|
||||
(!jv.isMember(jss::command) && !jv.isMember(jss::method)) ||
|
||||
(jv.isMember(jss::command) && !jv[jss::command].isString()) ||
|
||||
(jv.isMember(jss::method) && !jv[jss::method].isString()) ||
|
||||
(jv.isMember(jss::command) && jv.isMember(jss::method) &&
|
||||
jv[jss::command].asString() != jv[jss::method].asString()))
|
||||
{
|
||||
jr[jss::type] = jss::response;
|
||||
jr[jss::status] = jss::error;
|
||||
jr[jss::error] = apiVersion == RPC::apiInvalidVersion
|
||||
? jss::invalid_API_version
|
||||
: jss::missingCommand;
|
||||
jr[jss::request] = jv;
|
||||
if (jv.isMember(jss::id))
|
||||
jr[jss::id] = jv[jss::id];
|
||||
if (jv.isMember(jss::jsonrpc))
|
||||
jr[jss::jsonrpc] = jv[jss::jsonrpc];
|
||||
if (jv.isMember(jss::ripplerpc))
|
||||
jr[jss::ripplerpc] = jv[jss::ripplerpc];
|
||||
if (jv.isMember(jss::api_version))
|
||||
jr[jss::api_version] = jv[jss::api_version];
|
||||
|
||||
return jr;
|
||||
}
|
||||
|
||||
auto required = RPC::roleRequired(
|
||||
apiVersion,
|
||||
app_.config().BETA_RPC_API,
|
||||
jv.isMember(jss::command) ? jv[jss::command].asString()
|
||||
: jv[jss::method].asString());
|
||||
if (Role::FORBID == role)
|
||||
{
|
||||
jr[jss::result] = rpcError(rpcFORBIDDEN);
|
||||
}
|
||||
else
|
||||
{
|
||||
Resource::Consumer c;
|
||||
Resource::Charge loadType = Resource::feeReferenceRPC;
|
||||
|
||||
if (sendResponse.has_value())
|
||||
is = UDPInfoSub::getInfoSub(
|
||||
m_networkOPs, *sendResponse, remoteEndpoint);
|
||||
|
||||
RPC::JsonContext context{
|
||||
{app_.journal("RPCHandler"),
|
||||
app_,
|
||||
loadType,
|
||||
app_.getOPs(),
|
||||
app_.getLedgerMaster(),
|
||||
c,
|
||||
role,
|
||||
coro,
|
||||
is,
|
||||
apiVersion},
|
||||
jv};
|
||||
|
||||
auto start = std::chrono::system_clock::now();
|
||||
RPC::doCommand(context, jr[jss::result]);
|
||||
auto end = std::chrono::system_clock::now();
|
||||
logDuration(jv, end - start, m_journal);
|
||||
}
|
||||
}
|
||||
catch (std::exception const& ex)
|
||||
{
|
||||
jr[jss::result] = RPC::make_error(rpcINTERNAL);
|
||||
JLOG(m_journal.error())
|
||||
<< "Exception while processing WS: " << ex.what() << "\n"
|
||||
<< "Input JSON: " << Json::Compact{Json::Value{jv}};
|
||||
}
|
||||
|
||||
if (is)
|
||||
{
|
||||
if (auto udp = std::dynamic_pointer_cast<UDPInfoSub>(is))
|
||||
udp->destroy();
|
||||
}
|
||||
|
||||
// Currently we will simply unwrap errors returned by the RPC
|
||||
// API, in the future maybe we can make the responses
|
||||
// consistent.
|
||||
//
|
||||
// Regularize result. This is duplicate code.
|
||||
if (jr[jss::result].isMember(jss::error))
|
||||
{
|
||||
jr = jr[jss::result];
|
||||
jr[jss::status] = jss::error;
|
||||
|
||||
auto rq = jv;
|
||||
|
||||
if (rq.isObject())
|
||||
{
|
||||
if (rq.isMember(jss::passphrase.c_str()))
|
||||
rq[jss::passphrase.c_str()] = "<masked>";
|
||||
if (rq.isMember(jss::secret.c_str()))
|
||||
rq[jss::secret.c_str()] = "<masked>";
|
||||
if (rq.isMember(jss::seed.c_str()))
|
||||
rq[jss::seed.c_str()] = "<masked>";
|
||||
if (rq.isMember(jss::seed_hex.c_str()))
|
||||
rq[jss::seed_hex.c_str()] = "<masked>";
|
||||
}
|
||||
|
||||
jr[jss::request] = rq;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (jr[jss::result].isMember("forwarded") &&
|
||||
jr[jss::result]["forwarded"])
|
||||
jr = jr[jss::result];
|
||||
jr[jss::status] = jss::success;
|
||||
}
|
||||
|
||||
if (jv.isMember(jss::id))
|
||||
jr[jss::id] = jv[jss::id];
|
||||
if (jv.isMember(jss::jsonrpc))
|
||||
jr[jss::jsonrpc] = jv[jss::jsonrpc];
|
||||
if (jv.isMember(jss::ripplerpc))
|
||||
jr[jss::ripplerpc] = jv[jss::ripplerpc];
|
||||
if (jv.isMember(jss::api_version))
|
||||
jr[jss::api_version] = jv[jss::api_version];
|
||||
|
||||
jr[jss::type] = jss::response;
|
||||
return jr;
|
||||
}
|
||||
|
||||
Json::Value
|
||||
ServerHandlerImp::processSession(
|
||||
std::shared_ptr<WSSession> const& session,
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include <ripple/core/JobQueue.h>
|
||||
#include <ripple/json/Output.h>
|
||||
#include <ripple/rpc/RPCHandler.h>
|
||||
#include <ripple/rpc/impl/UDPInfoSub.h>
|
||||
#include <ripple/rpc/impl/WSInfoSub.h>
|
||||
#include <ripple/server/Server.h>
|
||||
#include <ripple/server/Session.h>
|
||||
@@ -165,12 +164,6 @@ public:
|
||||
std::shared_ptr<WSSession> session,
|
||||
std::vector<boost::asio::const_buffer> const& buffers);
|
||||
|
||||
void
|
||||
onUDPMessage(
|
||||
std::string const& message,
|
||||
boost::asio::ip::tcp::endpoint const& remoteEndpoint,
|
||||
std::function<void(std::string const&)> sendResponse);
|
||||
|
||||
void
|
||||
onClose(Session& session, boost::system::error_code const&);
|
||||
|
||||
@@ -184,14 +177,6 @@ private:
|
||||
std::shared_ptr<JobQueue::Coro> const& coro,
|
||||
Json::Value const& jv);
|
||||
|
||||
Json::Value
|
||||
processUDP(
|
||||
Json::Value const& jv,
|
||||
Role const& role,
|
||||
std::shared_ptr<JobQueue::Coro> const& coro,
|
||||
std::optional<std::function<void(std::string const&)>> sendResponse,
|
||||
boost::asio::ip::tcp::endpoint const& remoteEndpoint);
|
||||
|
||||
void
|
||||
processSession(
|
||||
std::shared_ptr<Session> const&,
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_RPC_UDPINFOSUB_H
|
||||
#define RIPPLE_RPC_UDPINFOSUB_H
|
||||
|
||||
#include <ripple/beast/net/IPAddressConversion.h>
|
||||
#include <ripple/json/json_writer.h>
|
||||
#include <ripple/json/to_string.h>
|
||||
#include <ripple/net/InfoSub.h>
|
||||
#include <ripple/rpc/Role.h>
|
||||
#include <ripple/server/WSSession.h>
|
||||
#include <boost/utility/string_view.hpp>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace ripple {
|
||||
class UDPInfoSub : public InfoSub
|
||||
{
|
||||
std::function<void(std::string const&)> send_;
|
||||
boost::asio::ip::tcp::endpoint endpoint_;
|
||||
|
||||
UDPInfoSub(
|
||||
Source& source,
|
||||
std::function<void(std::string const&)>& sendResponse,
|
||||
boost::asio::ip::tcp::endpoint const& remoteEndpoint)
|
||||
: InfoSub(source), send_(sendResponse), endpoint_(remoteEndpoint)
|
||||
{
|
||||
}
|
||||
|
||||
struct RefCountedSub
|
||||
{
|
||||
std::shared_ptr<UDPInfoSub> sub;
|
||||
size_t refCount;
|
||||
|
||||
RefCountedSub(std::shared_ptr<UDPInfoSub> s)
|
||||
: sub(std::move(s)), refCount(1)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
static inline std::mutex mtx_;
|
||||
static inline std::map<boost::asio::ip::tcp::endpoint, RefCountedSub> map_;
|
||||
|
||||
public:
|
||||
static std::shared_ptr<UDPInfoSub>
|
||||
getInfoSub(
|
||||
Source& source,
|
||||
std::function<void(std::string const&)>& sendResponse,
|
||||
boost::asio::ip::tcp::endpoint const& remoteEndpoint)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mtx_);
|
||||
|
||||
auto it = map_.find(remoteEndpoint);
|
||||
if (it != map_.end())
|
||||
{
|
||||
it->second.refCount++;
|
||||
return it->second.sub;
|
||||
}
|
||||
|
||||
auto sub = std::shared_ptr<UDPInfoSub>(
|
||||
new UDPInfoSub(source, sendResponse, remoteEndpoint));
|
||||
map_.emplace(remoteEndpoint, RefCountedSub(sub));
|
||||
return sub;
|
||||
}
|
||||
|
||||
static bool
|
||||
increment(boost::asio::ip::tcp::endpoint const& remoteEndpoint)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mtx_);
|
||||
|
||||
auto it = map_.find(remoteEndpoint);
|
||||
if (it != map_.end())
|
||||
{
|
||||
it->second.refCount++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
increment()
|
||||
{
|
||||
return increment(endpoint_);
|
||||
}
|
||||
|
||||
static bool
|
||||
destroy(boost::asio::ip::tcp::endpoint const& remoteEndpoint)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mtx_);
|
||||
|
||||
auto it = map_.find(remoteEndpoint);
|
||||
if (it != map_.end())
|
||||
{
|
||||
if (--it->second.refCount == 0)
|
||||
{
|
||||
map_.erase(it);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
destroy()
|
||||
{
|
||||
return destroy(endpoint_);
|
||||
}
|
||||
|
||||
void
|
||||
send(Json::Value const& jv, bool) override
|
||||
{
|
||||
std::string const str = to_string(jv);
|
||||
send_(str);
|
||||
}
|
||||
|
||||
boost::asio::ip::tcp::endpoint const&
|
||||
endpoint() const
|
||||
{
|
||||
return endpoint_;
|
||||
}
|
||||
};
|
||||
} // namespace ripple
|
||||
#endif
|
||||
@@ -86,15 +86,6 @@ struct Port
|
||||
// Returns a string containing the list of protocols
|
||||
std::string
|
||||
protocols() const;
|
||||
|
||||
bool
|
||||
has_udp() const
|
||||
{
|
||||
return protocol.count("udp") > 0;
|
||||
}
|
||||
|
||||
// Maximum UDP packet size (default 64KB)
|
||||
std::size_t udp_packet_size = 65536;
|
||||
};
|
||||
|
||||
std::ostream&
|
||||
|
||||
@@ -244,13 +244,6 @@ parse_Port(ParsedPort& port, Section const& section, std::ostream& log)
|
||||
optResult->begin(), optResult->end()))
|
||||
port.protocol.insert(s);
|
||||
}
|
||||
|
||||
if (port.protocol.count("udp") > 0 && port.protocol.size() > 1)
|
||||
{
|
||||
log << "Port " << section.name()
|
||||
<< " cannot mix UDP with other protocols";
|
||||
Throw<std::exception>();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include <ripple/beast/core/List.h>
|
||||
#include <ripple/server/Server.h>
|
||||
#include <ripple/server/impl/Door.h>
|
||||
#include <ripple/server/impl/UDPDoor.h>
|
||||
#include <ripple/server/impl/io_list.h>
|
||||
#include <boost/asio.hpp>
|
||||
#include <array>
|
||||
@@ -163,35 +162,18 @@ ServerImpl<Handler>::ports(std::vector<Port> const& ports)
|
||||
{
|
||||
if (closed())
|
||||
Throw<std::logic_error>("ports() on closed Server");
|
||||
|
||||
ports_.reserve(ports.size());
|
||||
Endpoints eps;
|
||||
eps.reserve(ports.size());
|
||||
|
||||
for (auto const& port : ports)
|
||||
{
|
||||
ports_.push_back(port);
|
||||
|
||||
if (port.has_udp())
|
||||
if (auto sp = ios_.emplace<Door<Handler>>(
|
||||
handler_, io_service_, ports_.back(), j_))
|
||||
{
|
||||
// UDP-RPC door
|
||||
if (auto sp = ios_.emplace<UDPDoor<Handler>>(
|
||||
handler_, io_service_, ports_.back(), j_))
|
||||
{
|
||||
eps.push_back(sp->get_endpoint());
|
||||
sp->run();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Standard TCP door
|
||||
if (auto sp = ios_.emplace<Door<Handler>>(
|
||||
handler_, io_service_, ports_.back(), j_))
|
||||
{
|
||||
list_.push_back(sp);
|
||||
eps.push_back(sp->get_endpoint());
|
||||
sp->run();
|
||||
}
|
||||
list_.push_back(sp);
|
||||
eps.push_back(sp->get_endpoint());
|
||||
sp->run();
|
||||
}
|
||||
}
|
||||
return eps;
|
||||
|
||||
@@ -1,284 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_SERVER_UDPDOOR_H_INCLUDED
|
||||
#define RIPPLE_SERVER_UDPDOOR_H_INCLUDED
|
||||
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/basics/contract.h>
|
||||
#include <ripple/server/impl/PlainHTTPPeer.h>
|
||||
#include <ripple/server/impl/SSLHTTPPeer.h>
|
||||
#include <ripple/server/impl/io_list.h>
|
||||
#include <boost/asio/basic_waitable_timer.hpp>
|
||||
#include <boost/asio/buffer.hpp>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/beast/core/detect_ssl.hpp>
|
||||
#include <boost/beast/core/multi_buffer.hpp>
|
||||
#include <boost/beast/core/tcp_stream.hpp>
|
||||
#include <boost/container/flat_map.hpp>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
template <class Handler>
|
||||
class UDPDoor : public io_list::work,
|
||||
public std::enable_shared_from_this<UDPDoor<Handler>>
|
||||
{
|
||||
private:
|
||||
using error_code = boost::system::error_code;
|
||||
using endpoint_type = boost::asio::ip::tcp::endpoint;
|
||||
using udp_socket = boost::asio::ip::udp::socket;
|
||||
|
||||
beast::Journal const j_;
|
||||
Port const& port_;
|
||||
Handler& handler_;
|
||||
boost::asio::io_context& ioc_;
|
||||
boost::asio::strand<boost::asio::io_context::executor_type> strand_;
|
||||
udp_socket socket_;
|
||||
std::vector<char> recv_buffer_;
|
||||
endpoint_type local_endpoint_; // Store TCP-style endpoint
|
||||
|
||||
public:
|
||||
UDPDoor(
|
||||
Handler& handler,
|
||||
boost::asio::io_context& io_context,
|
||||
Port const& port,
|
||||
beast::Journal j)
|
||||
: j_(j)
|
||||
, port_(port)
|
||||
, handler_(handler)
|
||||
, ioc_(io_context)
|
||||
, strand_(io_context.get_executor())
|
||||
, socket_(io_context)
|
||||
, recv_buffer_(port.udp_packet_size)
|
||||
, local_endpoint_(port.ip, port.port) // Store as TCP endpoint
|
||||
{
|
||||
error_code ec;
|
||||
|
||||
// Create UDP endpoint from port configuration
|
||||
auto const addr = port_.ip.to_v4();
|
||||
boost::asio::ip::udp::endpoint udp_endpoint(addr, port_.port);
|
||||
|
||||
socket_.open(boost::asio::ip::udp::v4(), ec);
|
||||
if (ec)
|
||||
{
|
||||
JLOG(j_.error()) << "UDP socket open failed: " << ec.message();
|
||||
return;
|
||||
}
|
||||
|
||||
// Set socket options
|
||||
socket_.set_option(boost::asio::socket_base::reuse_address(true), ec);
|
||||
if (ec)
|
||||
{
|
||||
JLOG(j_.error())
|
||||
<< "UDP set reuse_address failed: " << ec.message();
|
||||
return;
|
||||
}
|
||||
|
||||
socket_.bind(udp_endpoint, ec);
|
||||
if (ec)
|
||||
{
|
||||
JLOG(j_.error()) << "UDP socket bind failed: " << ec.message();
|
||||
return;
|
||||
}
|
||||
|
||||
JLOG(j_.info()) << "UDP-RPC listening on " << udp_endpoint;
|
||||
}
|
||||
|
||||
endpoint_type
|
||||
get_endpoint() const
|
||||
{
|
||||
return local_endpoint_;
|
||||
}
|
||||
|
||||
void
|
||||
run()
|
||||
{
|
||||
if (!socket_.is_open())
|
||||
return;
|
||||
|
||||
do_receive();
|
||||
}
|
||||
|
||||
void
|
||||
close() override
|
||||
{
|
||||
error_code ec;
|
||||
socket_.close(ec);
|
||||
}
|
||||
|
||||
private:
|
||||
void
|
||||
do_receive()
|
||||
{
|
||||
if (!socket_.is_open())
|
||||
return;
|
||||
|
||||
socket_.async_receive_from(
|
||||
boost::asio::buffer(recv_buffer_),
|
||||
sender_endpoint_,
|
||||
boost::asio::bind_executor(
|
||||
strand_,
|
||||
std::bind(
|
||||
&UDPDoor::on_receive,
|
||||
this->shared_from_this(),
|
||||
std::placeholders::_1,
|
||||
std::placeholders::_2)));
|
||||
}
|
||||
|
||||
void
|
||||
on_receive(error_code ec, std::size_t bytes_transferred)
|
||||
{
|
||||
if (ec)
|
||||
{
|
||||
if (ec != boost::asio::error::operation_aborted)
|
||||
{
|
||||
JLOG(j_.error()) << "UDP receive failed: " << ec.message();
|
||||
do_receive();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert UDP endpoint to TCP endpoint for compatibility
|
||||
endpoint_type tcp_endpoint(
|
||||
sender_endpoint_.address(), sender_endpoint_.port());
|
||||
|
||||
// Handle the received UDP message
|
||||
handler_.onUDPMessage(
|
||||
std::string(recv_buffer_.data(), bytes_transferred),
|
||||
tcp_endpoint,
|
||||
[this, tcp_endpoint](std::string const& response) {
|
||||
do_send(response, tcp_endpoint);
|
||||
});
|
||||
|
||||
do_receive();
|
||||
}
|
||||
|
||||
void
|
||||
do_send(std::string const& response, endpoint_type const& tcp_endpoint)
|
||||
{
|
||||
if (!socket_.is_open())
|
||||
{
|
||||
std::cout << "UDP SOCKET NOT OPEN WHEN SENDING\n\n";
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t HEADER_SIZE = 16;
|
||||
const size_t MAX_DATAGRAM_SIZE =
|
||||
65487; // Allow for ipv6 header 40 bytes + 8 bytes of udp header
|
||||
const size_t MAX_PAYLOAD_SIZE = MAX_DATAGRAM_SIZE - HEADER_SIZE;
|
||||
|
||||
// Convert TCP endpoint back to UDP for sending
|
||||
boost::asio::ip::udp::endpoint udp_endpoint(
|
||||
tcp_endpoint.address(), tcp_endpoint.port());
|
||||
|
||||
// If message fits in single datagram, send normally
|
||||
if (response.length() <= MAX_DATAGRAM_SIZE)
|
||||
{
|
||||
socket_.async_send_to(
|
||||
boost::asio::buffer(response),
|
||||
udp_endpoint,
|
||||
boost::asio::bind_executor(
|
||||
strand_,
|
||||
[this, self = this->shared_from_this()](
|
||||
error_code ec, std::size_t bytes_transferred) {
|
||||
if (ec && ec != boost::asio::error::operation_aborted)
|
||||
{
|
||||
JLOG(j_.error())
|
||||
<< "UDP send failed: " << ec.message();
|
||||
}
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate number of packets needed
|
||||
const size_t payload_size = MAX_PAYLOAD_SIZE;
|
||||
const uint16_t total_packets =
|
||||
(response.length() + payload_size - 1) / payload_size;
|
||||
|
||||
// Get current timestamp in microseconds
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto micros = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
now.time_since_epoch())
|
||||
.count();
|
||||
uint64_t timestamp = static_cast<uint64_t>(micros);
|
||||
|
||||
// Send fragmented packets
|
||||
for (uint16_t packet_num = 0; packet_num < total_packets; packet_num++)
|
||||
{
|
||||
std::string fragment;
|
||||
fragment.reserve(MAX_DATAGRAM_SIZE);
|
||||
|
||||
// Add header - 4 bytes of zeros
|
||||
fragment.push_back(0);
|
||||
fragment.push_back(0);
|
||||
fragment.push_back(0);
|
||||
fragment.push_back(0);
|
||||
|
||||
// Add packet number (little endian)
|
||||
fragment.push_back(packet_num & 0xFF);
|
||||
fragment.push_back((packet_num >> 8) & 0xFF);
|
||||
|
||||
// Add total packets (little endian)
|
||||
fragment.push_back(total_packets & 0xFF);
|
||||
fragment.push_back((total_packets >> 8) & 0xFF);
|
||||
|
||||
// Add timestamp (8 bytes, little endian)
|
||||
fragment.push_back(timestamp & 0xFF);
|
||||
fragment.push_back((timestamp >> 8) & 0xFF);
|
||||
fragment.push_back((timestamp >> 16) & 0xFF);
|
||||
fragment.push_back((timestamp >> 24) & 0xFF);
|
||||
fragment.push_back((timestamp >> 32) & 0xFF);
|
||||
fragment.push_back((timestamp >> 40) & 0xFF);
|
||||
fragment.push_back((timestamp >> 48) & 0xFF);
|
||||
fragment.push_back((timestamp >> 56) & 0xFF);
|
||||
|
||||
// Calculate payload slice
|
||||
size_t start = packet_num * payload_size;
|
||||
size_t length = std::min(payload_size, response.length() - start);
|
||||
fragment.append(response.substr(start, length));
|
||||
|
||||
socket_.async_send_to(
|
||||
boost::asio::buffer(fragment),
|
||||
udp_endpoint,
|
||||
boost::asio::bind_executor(
|
||||
strand_,
|
||||
[this, self = this->shared_from_this()](
|
||||
error_code ec, std::size_t bytes_transferred) {
|
||||
if (ec && ec != boost::asio::error::operation_aborted)
|
||||
{
|
||||
JLOG(j_.error())
|
||||
<< "UDP send failed: " << ec.message();
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
boost::asio::ip::udp::endpoint sender_endpoint_;
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -1,617 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2025 XRPL-Labs
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <ripple/basics/chrono.h>
|
||||
#include <ripple/protocol/Feature.h>
|
||||
#include <ripple/protocol/jss.h>
|
||||
#include <test/jtx.h>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
struct ServiceFee_test : public beast::unit_test::suite
|
||||
{
|
||||
static STAmount
|
||||
lineBalance(
|
||||
jtx::Env const& env,
|
||||
jtx::Account const& account,
|
||||
jtx::Account const& gw,
|
||||
jtx::IOU const& iou)
|
||||
{
|
||||
auto const sle = env.le(keylet::line(account, gw, iou.currency));
|
||||
if (sle && sle->isFieldPresent(sfBalance))
|
||||
return (*sle)[sfBalance];
|
||||
return STAmount(iou, 0);
|
||||
}
|
||||
|
||||
void
|
||||
testEnabled(FeatureBitset features)
|
||||
{
|
||||
testcase("enabled");
|
||||
using namespace jtx;
|
||||
using namespace std::literals::chrono_literals;
|
||||
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
auto const carol = Account("carol");
|
||||
for (bool const withSFee : {true, false})
|
||||
{
|
||||
auto const amend =
|
||||
withSFee ? features : features - featureServiceFee;
|
||||
Env env{*this, amend};
|
||||
auto const feeDrops = env.current()->fees().base;
|
||||
env.fund(XRP(1000), alice, bob, carol);
|
||||
|
||||
auto const preAlice = env.balance(alice);
|
||||
auto const preBob = env.balance(bob);
|
||||
auto const preCarol = env.balance(carol);
|
||||
|
||||
auto const result = withSFee ? ter(tesSUCCESS) : ter(temDISABLED);
|
||||
env(pay(alice, bob, XRP(10)),
|
||||
fee(feeDrops),
|
||||
sfee(XRP(1), carol),
|
||||
result);
|
||||
env.close();
|
||||
|
||||
auto const postAlice =
|
||||
withSFee ? preAlice - feeDrops - XRP(10) - XRP(1) : preAlice;
|
||||
auto const postBob = withSFee ? preBob + XRP(10) : preBob;
|
||||
auto const postCarol = withSFee ? preCarol + XRP(1) : preCarol;
|
||||
BEAST_EXPECT(env.balance(alice) == postAlice);
|
||||
BEAST_EXPECT(env.balance(bob) == postBob);
|
||||
BEAST_EXPECT(env.balance(carol) == postCarol);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testInvalid(FeatureBitset features)
|
||||
{
|
||||
testcase("invalid");
|
||||
using namespace jtx;
|
||||
using namespace std::literals::chrono_literals;
|
||||
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
auto const carol = Account("carol");
|
||||
auto const gw = Account("gw");
|
||||
auto const USD = gw["USD"];
|
||||
|
||||
// malformed inner object. No Amount // TEMPLATE ERROR
|
||||
// malformed inner object. No Destination // TEMPLATE ERROR
|
||||
// skipping self service-fee
|
||||
{
|
||||
Env env{*this, features};
|
||||
env.fund(XRP(1000), alice, bob);
|
||||
env.close();
|
||||
|
||||
auto const amt = XRP(10);
|
||||
auto const sfeeAmt = XRP(1);
|
||||
env(pay(alice, bob, amt), sfee(sfeeAmt, alice), ter(temDST_IS_SRC));
|
||||
env.close();
|
||||
}
|
||||
// skipping non-positive service-fee
|
||||
{
|
||||
Env env{*this, features};
|
||||
env.fund(XRP(1000), alice, bob, carol);
|
||||
env.close();
|
||||
|
||||
auto const amt = XRP(10);
|
||||
auto const sfeeAmt = XRP(-1);
|
||||
env(pay(alice, bob, amt), sfee(sfeeAmt, carol), ter(temBAD_AMOUNT));
|
||||
env.close();
|
||||
}
|
||||
// source does not exist.
|
||||
{
|
||||
// TODO
|
||||
} // destination does not exist.
|
||||
{
|
||||
Env env{*this, features};
|
||||
auto const baseFee = env.current()->fees().base;
|
||||
env.fund(XRP(1000), alice, bob);
|
||||
env.close();
|
||||
|
||||
auto const preAlice = env.balance(alice);
|
||||
auto const amt = XRP(10);
|
||||
auto const sfeeAmt = XRP(1);
|
||||
env(pay(alice, bob, amt), sfee(sfeeAmt, carol));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.balance(alice) == preAlice - amt - baseFee);
|
||||
}
|
||||
// insufficient reserve
|
||||
{
|
||||
// TODO
|
||||
} // no trustline (source)
|
||||
{
|
||||
Env env{*this, features};
|
||||
env.fund(XRP(1000), alice, bob, carol, gw);
|
||||
env.close();
|
||||
env.trust(USD(100000), carol);
|
||||
env.close();
|
||||
env(pay(gw, carol, USD(10000)));
|
||||
env.close();
|
||||
|
||||
auto const preAlice = env.balance(alice, USD);
|
||||
auto const preCarol = env.balance(carol, USD);
|
||||
BEAST_EXPECT(preAlice == USD(0));
|
||||
|
||||
auto const sfeeAmt = USD(1);
|
||||
env(pay(alice, bob, XRP(100)), sfee(sfeeAmt, carol));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.balance(alice, USD) == preAlice);
|
||||
BEAST_EXPECT(env.balance(carol, USD) == preCarol);
|
||||
}
|
||||
// insufficient trustline balance (source)
|
||||
{
|
||||
Env env{*this, features};
|
||||
env.fund(XRP(1000), alice, bob, carol, gw);
|
||||
env.close();
|
||||
env.trust(USD(10), alice, carol);
|
||||
env.close();
|
||||
env(pay(gw, alice, USD(1)));
|
||||
env(pay(gw, carol, USD(1)));
|
||||
env.close();
|
||||
|
||||
auto const preAlice = env.balance(alice, USD);
|
||||
auto const preCarol = env.balance(carol, USD);
|
||||
auto const sfeeAmt = USD(10);
|
||||
env(pay(alice, bob, XRP(100)), sfee(sfeeAmt, carol));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.balance(alice, USD) == preAlice);
|
||||
BEAST_EXPECT(env.balance(carol, USD) == preCarol);
|
||||
}
|
||||
// no trustline (destination)
|
||||
{
|
||||
Env env{*this, features};
|
||||
env.fund(XRP(1000), alice, bob, carol, gw);
|
||||
env.close();
|
||||
env.trust(USD(100000), alice);
|
||||
env.close();
|
||||
env(pay(gw, alice, USD(10000)));
|
||||
env.close();
|
||||
|
||||
auto const preAlice = env.balance(alice, USD);
|
||||
auto const preCarol = env.balance(carol, USD);
|
||||
auto const sfeeAmt = USD(1);
|
||||
env(pay(alice, bob, XRP(100)), sfee(sfeeAmt, carol));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.balance(alice, USD) == preAlice);
|
||||
BEAST_EXPECT(env.balance(carol, USD) == preCarol);
|
||||
}
|
||||
// insufficient trustline limit (destination)
|
||||
{
|
||||
Env env{*this, features};
|
||||
env.fund(XRP(1000), alice, bob, carol, gw);
|
||||
env.close();
|
||||
env.trust(USD(10), alice);
|
||||
env.trust(USD(1), carol);
|
||||
env.close();
|
||||
env(pay(gw, alice, USD(10)));
|
||||
env.close();
|
||||
|
||||
auto const preAlice = env.balance(alice, USD);
|
||||
auto const preCarol = env.balance(carol, USD);
|
||||
auto const sfeeAmt = USD(10);
|
||||
env(pay(alice, bob, XRP(100)), sfee(sfeeAmt, carol));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.balance(alice, USD) == preAlice);
|
||||
BEAST_EXPECT(env.balance(carol, USD) == preCarol);
|
||||
}
|
||||
// accountSend() failed // INTERNAL ERROR
|
||||
}
|
||||
|
||||
void
|
||||
testRippleState(FeatureBitset features)
|
||||
{
|
||||
testcase("ripple_state");
|
||||
using namespace test::jtx;
|
||||
using namespace std::literals;
|
||||
|
||||
struct TestAccountData
|
||||
{
|
||||
Account src;
|
||||
Account dst;
|
||||
Account gw;
|
||||
bool hasTrustline;
|
||||
bool negative;
|
||||
};
|
||||
|
||||
std::array<TestAccountData, 8> tests = {{
|
||||
// src > dst && src > issuer && dst no trustline
|
||||
{Account("alice2"), Account("bob0"), Account{"gw0"}, false, true},
|
||||
// src < dst && src < issuer && dst no trustline
|
||||
{Account("carol0"), Account("dan1"), Account{"gw1"}, false, false},
|
||||
// dst > src && dst > issuer && dst no trustline
|
||||
{Account("dan1"), Account("alice2"), Account{"gw0"}, false, true},
|
||||
// dst < src && dst < issuer && dst no trustline
|
||||
{Account("bob0"), Account("carol0"), Account{"gw1"}, false, false},
|
||||
// src > dst && src > issuer && dst has trustline
|
||||
{Account("alice2"), Account("bob0"), Account{"gw0"}, true, true},
|
||||
// src < dst && src < issuer && dst has trustline
|
||||
{Account("carol0"), Account("dan1"), Account{"gw1"}, true, false},
|
||||
// dst > src && dst > issuer && dst has trustline
|
||||
{Account("dan1"), Account("alice2"), Account{"gw0"}, true, true},
|
||||
// dst < src && dst < issuer && dst has trustline
|
||||
{Account("bob0"), Account("carol0"), Account{"gw1"}, true, false},
|
||||
}};
|
||||
|
||||
for (auto const& t : tests)
|
||||
{
|
||||
Env env{*this, features};
|
||||
auto const carol = Account("carol");
|
||||
auto const USD = t.gw["USD"];
|
||||
env.fund(XRP(5000), t.src, t.dst, t.gw, carol);
|
||||
env.close();
|
||||
if (t.hasTrustline)
|
||||
env.trust(USD(100000), t.src, t.dst, carol);
|
||||
else
|
||||
env.trust(USD(100000), t.src, t.dst);
|
||||
env.close();
|
||||
|
||||
env(pay(t.gw, t.src, USD(10000)));
|
||||
env(pay(t.gw, t.dst, USD(10000)));
|
||||
if (t.hasTrustline)
|
||||
env(pay(t.gw, carol, USD(10000)));
|
||||
env.close();
|
||||
|
||||
auto const delta = USD(100);
|
||||
auto const sfeeAmt = USD(1);
|
||||
auto const preSrc = lineBalance(env, t.src, t.gw, USD);
|
||||
auto const preDst = lineBalance(env, t.dst, t.gw, USD);
|
||||
auto const preCarol = lineBalance(env, carol, t.gw, USD);
|
||||
env(pay(t.src, t.dst, delta), sfee(sfeeAmt, carol));
|
||||
env.close();
|
||||
|
||||
auto const appliedSFee = t.hasTrustline ? sfeeAmt : USD(0);
|
||||
auto const postSrc = t.negative ? (preSrc + delta + appliedSFee)
|
||||
: (preSrc - delta - appliedSFee);
|
||||
auto const postDst =
|
||||
t.negative ? (preDst - delta) : (preDst + delta);
|
||||
auto const postCarol = t.negative ? (preCarol - appliedSFee)
|
||||
: (preCarol + appliedSFee);
|
||||
BEAST_EXPECT(lineBalance(env, t.src, t.gw, USD) == postSrc);
|
||||
BEAST_EXPECT(lineBalance(env, t.dst, t.gw, USD) == postDst);
|
||||
BEAST_EXPECT(lineBalance(env, carol, t.gw, USD) == postCarol);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testGateway(FeatureBitset features)
|
||||
{
|
||||
testcase("gateway");
|
||||
using namespace test::jtx;
|
||||
using namespace std::literals;
|
||||
|
||||
struct TestAccountData
|
||||
{
|
||||
Account acct;
|
||||
Account gw;
|
||||
bool hasTrustline;
|
||||
bool negative;
|
||||
};
|
||||
|
||||
std::array<TestAccountData, 4> tests = {{
|
||||
// acct no trustline
|
||||
// acct > issuer
|
||||
{Account("alice2"), Account{"gw0"}, false, true},
|
||||
// acct < issuer
|
||||
{Account("carol0"), Account{"gw1"}, false, false},
|
||||
|
||||
// acct has trustline
|
||||
// acct > issuer
|
||||
{Account("alice2"), Account{"gw0"}, true, true},
|
||||
// acct < issuer
|
||||
{Account("carol0"), Account{"gw1"}, true, false},
|
||||
}};
|
||||
|
||||
// test gateway is source
|
||||
for (auto const& t : tests)
|
||||
{
|
||||
Env env{*this, features};
|
||||
auto const carol = Account("carol");
|
||||
auto const USD = t.gw["USD"];
|
||||
env.fund(XRP(5000), t.acct, t.gw, carol);
|
||||
env.trust(USD(100000), carol);
|
||||
env(pay(t.gw, carol, USD(10000)));
|
||||
env.close();
|
||||
|
||||
if (t.hasTrustline)
|
||||
{
|
||||
env.trust(USD(100000), t.acct);
|
||||
env(pay(t.gw, t.acct, USD(10000)));
|
||||
env.close();
|
||||
}
|
||||
|
||||
auto const preAcct = lineBalance(env, t.acct, t.gw, USD);
|
||||
auto const preCarol = lineBalance(env, carol, t.gw, USD);
|
||||
|
||||
auto const delta = USD(100);
|
||||
auto const sfeeAmt = USD(1);
|
||||
env(pay(t.gw, carol, delta), sfee(sfeeAmt, t.acct));
|
||||
env.close();
|
||||
|
||||
auto const appliedSFee = t.hasTrustline ? sfeeAmt : USD(0);
|
||||
// Receiver of service fee
|
||||
auto const postAcct =
|
||||
t.negative ? (preAcct - appliedSFee) : (preAcct + appliedSFee);
|
||||
// Receiver of payment
|
||||
auto const postCarol =
|
||||
t.negative ? (preCarol - delta) : (preCarol + delta);
|
||||
BEAST_EXPECT(lineBalance(env, t.acct, t.gw, USD) == postAcct);
|
||||
BEAST_EXPECT(lineBalance(env, carol, t.gw, USD) == postCarol);
|
||||
BEAST_EXPECT(lineBalance(env, t.gw, t.acct, USD) == postAcct);
|
||||
BEAST_EXPECT(lineBalance(env, t.gw, carol, USD) == postCarol);
|
||||
}
|
||||
|
||||
// test gateway is destination
|
||||
for (auto const& t : tests)
|
||||
{
|
||||
Env env{*this, features};
|
||||
auto const USD = t.gw["USD"];
|
||||
env.fund(XRP(5000), t.acct, t.gw);
|
||||
env.trust(USD(100000), t.acct);
|
||||
env(pay(t.gw, t.acct, USD(10000)));
|
||||
env.close();
|
||||
|
||||
auto const preAcct = lineBalance(env, t.acct, t.gw, USD);
|
||||
auto const delta = USD(100);
|
||||
auto const sfeeAmt = USD(1);
|
||||
env(pay(t.acct, t.gw, delta), sfee(sfeeAmt, t.gw));
|
||||
env.close();
|
||||
|
||||
// Sender of Payment & Fee
|
||||
auto const postAcct = t.negative ? (preAcct + delta + sfeeAmt)
|
||||
: (preAcct - delta - sfeeAmt);
|
||||
BEAST_EXPECT(lineBalance(env, t.acct, t.gw, USD) == postAcct);
|
||||
BEAST_EXPECT(lineBalance(env, t.gw, t.acct, USD) == postAcct);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testRequireAuth(FeatureBitset features)
|
||||
{
|
||||
testcase("require_auth");
|
||||
using namespace test::jtx;
|
||||
using namespace std::literals;
|
||||
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
auto const carol = Account("carol");
|
||||
auto const gw = Account{"gateway"};
|
||||
auto const USD = gw["USD"];
|
||||
|
||||
auto const aliceUSD = alice["USD"];
|
||||
auto const bobUSD = bob["USD"];
|
||||
auto const carolUSD = carol["USD"];
|
||||
|
||||
// test asfRequireAuth
|
||||
{
|
||||
Env env{*this, features};
|
||||
env.fund(XRP(1000), alice, bob, carol, gw);
|
||||
env(fset(gw, asfRequireAuth));
|
||||
env.close();
|
||||
env(trust(gw, carolUSD(10000)), txflags(tfSetfAuth));
|
||||
env(trust(carol, USD(10000)));
|
||||
env(trust(gw, bobUSD(10000)), txflags(tfSetfAuth));
|
||||
env(trust(bob, USD(10000)));
|
||||
env.close();
|
||||
env(pay(gw, carol, USD(1000)));
|
||||
env(pay(gw, bob, USD(1000)));
|
||||
env.close();
|
||||
|
||||
{
|
||||
// Alice does not receive service fee because she is not
|
||||
// authorized
|
||||
auto const preAlice = env.balance(alice, USD);
|
||||
auto const sfeeAmt = USD(1);
|
||||
env(pay(bob, carol, XRP(100)), sfee(sfeeAmt, alice));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(env.balance(alice, USD) == preAlice);
|
||||
}
|
||||
|
||||
{
|
||||
env(trust(gw, aliceUSD(10000)), txflags(tfSetfAuth));
|
||||
env(trust(alice, USD(10000)));
|
||||
env.close();
|
||||
env(pay(gw, alice, USD(1000)));
|
||||
env.close();
|
||||
|
||||
// Alice now receives service fee because she is authorized
|
||||
auto const preAlice = env.balance(alice, USD);
|
||||
auto const sfeeAmt = USD(1);
|
||||
env(pay(bob, carol, XRP(100)), sfee(sfeeAmt, alice));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(env.balance(alice, USD) == preAlice + sfeeAmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testFreeze(FeatureBitset features)
|
||||
{
|
||||
testcase("freeze");
|
||||
using namespace test::jtx;
|
||||
using namespace std::literals;
|
||||
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
auto const carol = Account("carol");
|
||||
auto const gw = Account{"gateway"};
|
||||
auto const USD = gw["USD"];
|
||||
// test Global Freeze
|
||||
{
|
||||
// setup env
|
||||
Env env{*this, features};
|
||||
env.fund(XRP(1000), alice, bob, carol, gw);
|
||||
env.close();
|
||||
env.trust(USD(100000), alice, bob, carol);
|
||||
env.close();
|
||||
env(pay(gw, alice, USD(1000)));
|
||||
env(pay(gw, bob, USD(1000)));
|
||||
env(pay(gw, carol, USD(1000)));
|
||||
env.close();
|
||||
env(fset(gw, asfGlobalFreeze));
|
||||
env.close();
|
||||
|
||||
{
|
||||
// carol cannot receive because of global freeze
|
||||
auto const preCarol = env.balance(carol, USD);
|
||||
auto const sfeeAmt = USD(1);
|
||||
env(pay(alice, bob, XRP(100)), sfee(sfeeAmt, carol));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.balance(carol, USD) == preCarol);
|
||||
}
|
||||
|
||||
{
|
||||
// clear global freeze
|
||||
env(fclear(gw, asfGlobalFreeze));
|
||||
env.close();
|
||||
|
||||
// carol can receive because global freeze is cleared
|
||||
auto const preCarol = env.balance(carol, USD);
|
||||
auto const sfeeAmt = USD(1);
|
||||
env(pay(alice, bob, XRP(100)), sfee(sfeeAmt, carol));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.balance(carol, USD) == preCarol + sfeeAmt);
|
||||
}
|
||||
}
|
||||
|
||||
// test Individual Freeze
|
||||
{
|
||||
// Env Setup
|
||||
Env env{*this, features};
|
||||
env.fund(XRP(1000), alice, bob, carol, gw);
|
||||
env.close();
|
||||
env.trust(USD(100000), alice, bob, carol);
|
||||
env.close();
|
||||
env(pay(gw, alice, USD(1000)));
|
||||
env(pay(gw, bob, USD(1000)));
|
||||
env(pay(gw, carol, USD(1000)));
|
||||
env.close();
|
||||
|
||||
// set freeze on carol trustline
|
||||
env(trust(gw, USD(10000), carol, tfSetFreeze));
|
||||
env.close();
|
||||
|
||||
{
|
||||
auto const preCarol = env.balance(carol, USD);
|
||||
auto const sfeeAmt = USD(1);
|
||||
env(pay(alice, bob, XRP(100)), sfee(sfeeAmt, carol));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.balance(carol, USD) == preCarol);
|
||||
}
|
||||
|
||||
{
|
||||
// clear freeze on carol trustline
|
||||
env(trust(gw, USD(10000), carol, tfClearFreeze));
|
||||
env.close();
|
||||
|
||||
auto const preCarol = env.balance(carol, USD);
|
||||
auto const sfeeAmt = USD(1);
|
||||
env(pay(alice, bob, XRP(100)), sfee(sfeeAmt, carol));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.balance(carol, USD) == preCarol + sfeeAmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testTransferRate(FeatureBitset features)
|
||||
{
|
||||
testcase("transfer_rate");
|
||||
using namespace test::jtx;
|
||||
using namespace std::literals;
|
||||
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
auto const carol = Account("carol");
|
||||
auto const gw = Account{"gateway"};
|
||||
auto const USD = gw["USD"];
|
||||
|
||||
// test rate
|
||||
{
|
||||
Env env{*this, features};
|
||||
env.fund(XRP(10000), alice, bob, carol, gw);
|
||||
env(rate(gw, 1.25));
|
||||
env.close();
|
||||
env.trust(USD(100000), alice, carol);
|
||||
env.close();
|
||||
env(pay(gw, alice, USD(10000)));
|
||||
env(pay(gw, carol, USD(10000)));
|
||||
env.close();
|
||||
|
||||
auto const preAlice = env.balance(alice, USD);
|
||||
auto const preCarol = env.balance(carol, USD);
|
||||
auto const sfeeAmt = USD(1);
|
||||
env(pay(alice, bob, XRP(100)), sfee(sfeeAmt, carol));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(env.balance(alice, USD) == preAlice - sfeeAmt);
|
||||
BEAST_EXPECT(
|
||||
env.balance(carol, USD) == preCarol + sfeeAmt - USD(0.20));
|
||||
}
|
||||
|
||||
// test issuer doesnt pay own rate
|
||||
{
|
||||
Env env{*this, features};
|
||||
env.fund(XRP(10000), alice, carol, gw);
|
||||
env(rate(gw, 1.25));
|
||||
env.close();
|
||||
env.trust(USD(100000), carol);
|
||||
env.close();
|
||||
env(pay(gw, carol, USD(10000)));
|
||||
env.close();
|
||||
|
||||
auto const preGwC = -lineBalance(env, carol, gw, USD);
|
||||
auto const preCarol = env.balance(carol, USD);
|
||||
auto const sfeeAmt = USD(1);
|
||||
env(pay(gw, alice, XRP(100)), sfee(sfeeAmt, carol));
|
||||
env.close();
|
||||
BEAST_EXPECT(-lineBalance(env, carol, gw, USD) == preGwC + sfeeAmt);
|
||||
BEAST_EXPECT(env.balance(carol, USD) == preCarol + sfeeAmt);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testWithFeats(FeatureBitset features)
|
||||
{
|
||||
testEnabled(features);
|
||||
testInvalid(features);
|
||||
testRippleState(features);
|
||||
testGateway(features);
|
||||
testRequireAuth(features);
|
||||
testFreeze(features);
|
||||
testTransferRate(features);
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
using namespace test::jtx;
|
||||
auto const sa = supported_amendments();
|
||||
testWithFeats(sa);
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(ServiceFee, app, ripple);
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
@@ -61,23 +61,6 @@ public:
|
||||
operator()(Env&, JTx& jt) const;
|
||||
};
|
||||
|
||||
/** Set the service fee on a JTx. */
|
||||
class sfee
|
||||
{
|
||||
private:
|
||||
STAmount amount_;
|
||||
Account dest_;
|
||||
|
||||
public:
|
||||
explicit sfee(STAmount const& amount, Account const& destination)
|
||||
: amount_(amount), dest_(destination)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(Env&, JTx& jtx) const;
|
||||
};
|
||||
|
||||
} // namespace jtx
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
|
||||
@@ -34,14 +34,6 @@ fee::operator()(Env&, JTx& jt) const
|
||||
jt[jss::Fee] = amount_->getJson(JsonOptions::none);
|
||||
}
|
||||
|
||||
void
|
||||
sfee::operator()(Env&, JTx& jt) const
|
||||
{
|
||||
jt.jv[jss::ServiceFee] = Json::objectValue;
|
||||
jt.jv[jss::ServiceFee][jss::Amount] = amount_.getJson(JsonOptions::none);
|
||||
jt.jv[jss::ServiceFee][jss::Destination] = dest_.human();
|
||||
}
|
||||
|
||||
} // namespace jtx
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
|
||||
@@ -144,14 +144,6 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
onUDPMessage(
|
||||
std::string const& message,
|
||||
boost::asio::ip::tcp::endpoint const& remoteEndpoint,
|
||||
std::function<void(std::string const&)> sendResponse)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
onClose(Session& session, boost::system::error_code const&)
|
||||
{
|
||||
@@ -357,14 +349,6 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
onUDPMessage(
|
||||
std::string const& message,
|
||||
boost::asio::ip::tcp::endpoint const& remoteEndpoint,
|
||||
std::function<void(std::string const&)> sendResponse)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
onClose(Session& session, boost::system::error_code const&)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user