Compare commits

...

5 Commits

Author SHA1 Message Date
tequ
89890e40d9 Merge branch 'sync-2.0.1-conan' into sync-2.1.1-conan 2025-06-04 20:10:10 +09:00
tequ
0a1a289bd4 Merge commit '827e6fe617808139556e4eb1513f468941f20912' into sync-2.0.1-conan 2025-06-04 20:10:00 +09:00
Mayukha Vadari
827e6fe617 feat(rpc): add server_definitions method (#4703)
Add a new RPC / WS call for `server_definitions`, which returns an
SDK-compatible `definitions.json` (binary enum definitions) generated by
the server. This enables clients/libraries to dynamically work with new
fields and features, such as ones that may become available on side
chains. Clients query `server_definitions` on a node from the network
they want to work with, and immediately know how to speak that node's
binary "language", even if new features are added to it in the future
(as long as there are no new serialized types that the software doesn't
know how to serialize/deserialize).

Example:

```js
> {"command": "server_definitions"}
< {
    "result": {
        "FIELDS": [
            [
                "Generic",
                {
                    "isSerialized": false,
                    "isSigningField": false,
                    "isVLEncoded": false,
                    "nth": 0,
                    "type": "Unknown"
                }
            ],
            [
                "Invalid",
                {
                    "isSerialized": false,
                    "isSigningField": false,
                    "isVLEncoded": false,
                    "nth": -1,
                    "type": "Unknown"
                }
            ],
            [
                "ObjectEndMarker",
                {
                    "isSerialized": false,
                    "isSigningField": true,
                    "isVLEncoded": false,
                    "nth": 1,
                    "type": "STObject"
                }
            ],
        ...
```

Close #3657

---------

Co-authored-by: Richard Holland <richard.holland@starstone.co.nz>
2025-06-04 20:07:53 +09:00
tequ
99ee7814e1 Merge branch 'sync-2.1.0-conan' into sync-2.1.1-conan 2025-06-04 18:45:47 +09:00
Gregory Tsipenyuk
60e80c0427 fix: improper handling of large synthetic AMM offers:
A large synthetic offer was not handled correctly in the payment engine.
This patch fixes that issue and introduces a new invariant check while
processing synthetic offers.
2025-05-06 15:14:35 +09:00
18 changed files with 323 additions and 83 deletions

View File

@@ -168,6 +168,7 @@ printHelp(const po::options_description& desc)
" peer_reservations_list\n"
" ripple ...\n"
" ripple_path_find <json> [<ledger>]\n"
" server_definitions [<hash>]\n"
" server_info [counters]\n"
" server_state [counters]\n"
" sign <private_key> <tx_json> [offline]\n"

View File

@@ -134,7 +134,7 @@ withinRelativeDistance(
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, XRPAmount> || std::is_same_v<Amt, Number>)
bool
withinRelativeDistance(Amt const& calc, Amt const& req, Number const& dist)
{

View File

@@ -137,10 +137,17 @@ private:
TAmounts<TIn, TOut>
generateFibSeqOffer(TAmounts<TIn, TOut> const& balances) const;
/** Generate max offer
/** Generate max offer.
* If `fixAMMOverflowOffer` is active, the offer is generated as:
* takerGets = 99% * balances.out takerPays = swapOut(takerGets).
* Return nullopt if takerGets is 0 or takerGets == balances.out.
*
* If `fixAMMOverflowOffer` is not active, the offer is generated as:
* takerPays = max input amount;
* takerGets = swapIn(takerPays).
*/
AMMOffer<TIn, TOut>
maxOffer(TAmounts<TIn, TOut> const& balances) const;
std::optional<AMMOffer<TIn, TOut>>
maxOffer(TAmounts<TIn, TOut> const& balances, Rules const& rules) const;
};
} // namespace ripple

View File

@@ -50,9 +50,8 @@ private:
// the swap out of the entire side of the pool, in which case
// the swap in amount is infinite.
TAmounts<TIn, TOut> const amounts_;
// If seated then current pool balances. Used in one-path limiting steps
// to swap in/out.
std::optional<TAmounts<TIn, TOut>> const balances_;
// Current pool balances.
TAmounts<TIn, TOut> const balances_;
// The Spot Price quality if balances != amounts
// else the amounts quality
Quality const quality_;
@@ -63,7 +62,7 @@ public:
AMMOffer(
AMMLiquidity<TIn, TOut> const& ammLiquidity,
TAmounts<TIn, TOut> const& amounts,
std::optional<TAmounts<TIn, TOut>> const& balances,
TAmounts<TIn, TOut> const& balances,
Quality const& quality);
Quality
@@ -142,6 +141,12 @@ public:
// AMM doesn't pay transfer fee on Payment tx
return {ofrInRate, QUALITY_ONE};
}
/** Check the new pool product is greater or equal to the old pool
* product or if decreases then within some threshold.
*/
bool
checkInvariant(TAmounts<TIn, TOut> const& consumed, beast::Journal j) const;
};
} // namespace ripple

View File

@@ -93,6 +93,7 @@ AMMLiquidity<TIn, TOut>::generateFibSeqOffer(
return cur;
}
namespace {
template <typename T>
constexpr T
maxAmount()
@@ -105,16 +106,41 @@ maxAmount()
return STAmount(STAmount::cMaxValue / 2, STAmount::cMaxOffset);
}
template <typename TIn, typename TOut>
AMMOffer<TIn, TOut>
AMMLiquidity<TIn, TOut>::maxOffer(TAmounts<TIn, TOut> const& balances) const
template <typename T>
T
maxOut(T const& out, Issue const& iss)
{
return AMMOffer<TIn, TOut>(
*this,
{maxAmount<TIn>(),
swapAssetIn(balances, maxAmount<TIn>(), tradingFee_)},
balances,
Quality{balances});
Number const res = out * Number{99, -2};
return toAmount<T>(iss, res, Number::rounding_mode::downward);
}
} // namespace
template <typename TIn, typename TOut>
std::optional<AMMOffer<TIn, TOut>>
AMMLiquidity<TIn, TOut>::maxOffer(
TAmounts<TIn, TOut> const& balances,
Rules const& rules) const
{
if (!rules.enabled(fixAMMOverflowOffer))
{
return AMMOffer<TIn, TOut>(
*this,
{maxAmount<TIn>(),
swapAssetIn(balances, maxAmount<TIn>(), tradingFee_)},
balances,
Quality{balances});
}
else
{
auto const out = maxOut<TOut>(balances.out, issueOut());
if (out <= TOut{0} || out >= balances.out)
return std::nullopt;
return AMMOffer<TIn, TOut>(
*this,
{swapAssetOut(balances, out, tradingFee_), out},
balances,
Quality{balances});
}
}
template <typename TIn, typename TOut>
@@ -167,15 +193,16 @@ AMMLiquidity<TIn, TOut>::getOffer(
if (clobQuality && Quality{amounts} < clobQuality)
return std::nullopt;
return AMMOffer<TIn, TOut>(
*this, amounts, std::nullopt, Quality{amounts});
*this, amounts, balances, Quality{amounts});
}
else if (!clobQuality)
{
// If there is no CLOB to compare against, return the largest
// amount, which doesn't overflow. The size is going to be
// changed in BookStep per either deliver amount limit, or
// sendmax, or available output or input funds.
return maxOffer(balances);
// sendmax, or available output or input funds. Might return
// nullopt if the pool is small.
return maxOffer(balances, view.rules());
}
else if (
auto const amounts =
@@ -188,7 +215,10 @@ AMMLiquidity<TIn, TOut>::getOffer(
catch (std::overflow_error const& e)
{
JLOG(j_.error()) << "AMMLiquidity::getOffer overflow " << e.what();
return maxOffer(balances);
if (!view.rules().enabled(fixAMMOverflowOffer))
return maxOffer(balances, view.rules());
else
return std::nullopt;
}
catch (std::exception const& e)
{

View File

@@ -27,7 +27,7 @@ template <typename TIn, typename TOut>
AMMOffer<TIn, TOut>::AMMOffer(
AMMLiquidity<TIn, TOut> const& ammLiquidity,
TAmounts<TIn, TOut> const& amounts,
std::optional<TAmounts<TIn, TOut>> const& balances,
TAmounts<TIn, TOut> const& balances,
Quality const& quality)
: ammLiquidity_(ammLiquidity)
, amounts_(amounts)
@@ -110,7 +110,7 @@ AMMOffer<TIn, TOut>::limitOut(
// Change the offer size according to the conservation function. The offer
// quality is increased in this case, but it doesn't matter since there is
// only one path.
return {swapAssetOut(*balances_, limit, ammLiquidity_.tradingFee()), limit};
return {swapAssetOut(balances_, limit, ammLiquidity_.tradingFee()), limit};
}
template <typename TIn, typename TOut>
@@ -122,7 +122,7 @@ AMMOffer<TIn, TOut>::limitIn(
// See the comments above in limitOut().
if (ammLiquidity_.multiPath())
return quality().ceil_in(offrAmt, limit);
return {limit, swapAssetIn(*balances_, limit, ammLiquidity_.tradingFee())};
return {limit, swapAssetIn(balances_, limit, ammLiquidity_.tradingFee())};
}
template <typename TIn, typename TOut>
@@ -132,7 +132,45 @@ AMMOffer<TIn, TOut>::getQualityFunc() const
if (ammLiquidity_.multiPath())
return QualityFunction{quality(), QualityFunction::CLOBLikeTag{}};
return QualityFunction{
*balances_, ammLiquidity_.tradingFee(), QualityFunction::AMMTag{}};
balances_, ammLiquidity_.tradingFee(), QualityFunction::AMMTag{}};
}
template <typename TIn, typename TOut>
bool
AMMOffer<TIn, TOut>::checkInvariant(
TAmounts<TIn, TOut> const& consumed,
beast::Journal j) const
{
if (consumed.in > amounts_.in || consumed.out > amounts_.out)
{
JLOG(j.error()) << "AMMOffer::checkInvariant failed: consumed "
<< to_string(consumed.in) << " "
<< to_string(consumed.out) << " amounts "
<< to_string(amounts_.in) << " "
<< to_string(amounts_.out);
return false;
}
Number const product = balances_.in * balances_.out;
auto const newBalances = TAmounts<TIn, TOut>{
balances_.in + consumed.in, balances_.out - consumed.out};
Number const newProduct = newBalances.in * newBalances.out;
if (newProduct >= product ||
withinRelativeDistance(product, newProduct, Number{1, -7}))
return true;
JLOG(j.error()) << "AMMOffer::checkInvariant failed: balances "
<< to_string(balances_.in) << " "
<< to_string(balances_.out) << " new balances "
<< to_string(newBalances.in) << " "
<< to_string(newBalances.out) << " product/newProduct "
<< product << " " << newProduct << " diff "
<< (product != Number{0}
? to_string((product - newProduct) / product)
: "undefined");
return false;
}
template class AMMOffer<STAmount, STAmount>;

View File

@@ -793,6 +793,17 @@ BookStep<TIn, TOut, TDerived>::consumeOffer(
TAmounts<TIn, TOut> const& stepAmt,
TOut const& ownerGives) const
{
if (!offer.checkInvariant(ofrAmt, j_))
{
// purposely written as separate if statements so we get logging even
// when the amendment isn't active.
if (sb.rules().enabled(fixAMMOverflowOffer))
{
Throw<FlowException>(
tecINVARIANT_FAILED, "AMM pool product invariant failed.");
}
}
// The offer owner gets the ofrAmt. The difference between ofrAmt and
// stepAmt is a transfer fee that goes to book_.in.account
{

View File

@@ -163,6 +163,15 @@ public:
// 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&, beast::Journal j) const
{
return true;
}
};
using Offer = TOffer<>;

View File

@@ -1340,6 +1340,20 @@ private:
return jvRequest;
}
// server_definitions [hash]
Json::Value
parseServerDefinitions(Json::Value const& jvParams)
{
Json::Value jvRequest{Json::objectValue};
if (jvParams.size() == 1)
{
jvRequest[jss::hash] = jvParams[0u].asString();
}
return jvRequest;
}
// server_info [counters]
Json::Value
parseServerInfo(Json::Value const& jvParams)
@@ -1406,6 +1420,7 @@ public:
{"channel_verify", &RPCParser::parseChannelVerify, 4, 4},
{"connect", &RPCParser::parseConnect, 1, 2},
{"consensus_info", &RPCParser::parseAsIs, 0, 0},
{"crawl_shards", &RPCParser::parseAsIs, 0, 2},
{"deposit_authorized", &RPCParser::parseDepositAuthorized, 2, 3},
{"download_shard", &RPCParser::parseDownloadShard, 2, -1},
{"feature", &RPCParser::parseFeature, 0, 2},
@@ -1444,14 +1459,14 @@ public:
1},
{"peer_reservations_list", &RPCParser::parseAsIs, 0, 0},
{"ripple_path_find", &RPCParser::parseRipplePathFind, 1, 2},
{"sign", &RPCParser::parseSignSubmit, 2, 3},
{"sign_for", &RPCParser::parseSignFor, 3, 4},
{"submit", &RPCParser::parseSignSubmit, 1, 3},
{"submit_multisigned", &RPCParser::parseSubmitMultiSigned, 1, 1},
{"server_definitions", &RPCParser::parseServerDefinitions, 0, 1},
{"server_info", &RPCParser::parseServerInfo, 0, 1},
{"server_state", &RPCParser::parseServerInfo, 0, 1},
{"crawl_shards", &RPCParser::parseAsIs, 0, 2},
{"sign", &RPCParser::parseSignSubmit, 2, 3},
{"sign_for", &RPCParser::parseSignFor, 3, 4},
{"stop", &RPCParser::parseAsIs, 0, 0},
{"submit", &RPCParser::parseSignSubmit, 1, 3},
{"submit_multisigned", &RPCParser::parseSubmitMultiSigned, 1, 1},
{"transaction_entry", &RPCParser::parseTransactionEntry, 2, 2},
{"tx", &RPCParser::parseTx, 1, 4},
{"tx_history", &RPCParser::parseTxHistory, 1, 1},

View File

@@ -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 = 90;
static constexpr std::size_t numFeatures = 91;
/** Amendments that this server supports and the default voting behavior.
Whether they are enabled depends on the Rules defined in the validated
@@ -378,6 +378,7 @@ extern uint256 const featureDID;
extern uint256 const fixFillOrKill;
extern uint256 const fixNFTokenReserve;
extern uint256 const fixInnerObjTemplate;
extern uint256 const fixAMMOverflowOffer;
} // namespace ripple

View File

@@ -53,43 +53,65 @@ class STXChainBridge;
class STVector256;
class Definitions;
enum SerializedTypeID {
// special types
STI_UNKNOWN = -2,
STI_NOTPRESENT = 0,
#pragma push_macro("XMACRO")
#undef XMACRO
// // types (common)
STI_UINT16 = 1,
STI_UINT32 = 2,
STI_UINT64 = 3,
STI_UINT128 = 4,
STI_UINT256 = 5,
STI_AMOUNT = 6,
STI_VL = 7,
STI_ACCOUNT = 8,
// 9-13 are reserved
STI_OBJECT = 14,
STI_ARRAY = 15,
#define XMACRO(STYPE) \
/* special types */ \
STYPE(STI_UNKNOWN, -2) \
STYPE(STI_NOTPRESENT, 0) \
STYPE(STI_UINT16, 1) \
\
/* types (common) */ \
STYPE(STI_UINT32, 2) \
STYPE(STI_UINT64, 3) \
STYPE(STI_UINT128, 4) \
STYPE(STI_UINT256, 5) \
STYPE(STI_AMOUNT, 6) \
STYPE(STI_VL, 7) \
STYPE(STI_ACCOUNT, 8) \
\
/* 9-13 are reserved */ \
STYPE(STI_OBJECT, 14) \
STYPE(STI_ARRAY, 15) \
\
/* types (uncommon) */ \
STYPE(STI_UINT8, 16) \
STYPE(STI_UINT160, 17) \
STYPE(STI_PATHSET, 18) \
STYPE(STI_VECTOR256, 19) \
STYPE(STI_UINT96, 20) \
STYPE(STI_UINT192, 21) \
STYPE(STI_UINT384, 22) \
STYPE(STI_UINT512, 23) \
STYPE(STI_ISSUE, 24) \
STYPE(STI_XCHAIN_BRIDGE, 25) \
\
/* high-level types */ \
/* cannot be serialized inside other types */ \
STYPE(STI_TRANSACTION, 10001) \
STYPE(STI_LEDGERENTRY, 10002) \
STYPE(STI_VALIDATION, 10003) \
STYPE(STI_METADATA, 10004)
// types (uncommon)
STI_UINT8 = 16,
STI_UINT160 = 17,
STI_PATHSET = 18,
STI_VECTOR256 = 19,
STI_UINT96 = 20,
STI_UINT192 = 21,
STI_UINT384 = 22,
STI_UINT512 = 23,
STI_ISSUE = 24,
STI_XCHAIN_BRIDGE = 25,
#pragma push_macro("TO_ENUM")
#undef TO_ENUM
#pragma push_macro("TO_MAP")
#undef TO_MAP
// high level types
// cannot be serialized inside other types
STI_TRANSACTION = 10001,
STI_LEDGERENTRY = 10002,
STI_VALIDATION = 10003,
STI_METADATA = 10004,
};
#define TO_ENUM(name, value) name = value,
#define TO_MAP(name, value) {#name, value},
enum SerializedTypeID { XMACRO(TO_ENUM) };
static std::map<std::string, int> const sTypeMap = {XMACRO(TO_MAP)};
#undef XMACRO
#undef TO_ENUM
#pragma pop_macro("XMACRO")
#pragma pop_macro("TO_ENUM")
#pragma pop_macro("TO_MAP")
// constexpr
inline int

View File

@@ -26,6 +26,7 @@
#include <optional>
#include <ostream>
#include <string>
#include <unordered_map>
namespace ripple {
@@ -668,6 +669,11 @@ isTecClaim(TER x)
return ((x) >= tecCLAIM);
}
std::unordered_map<
TERUnderlyingType,
std::pair<char const* const, char const* const>> const&
transResults();
bool
transResultInfo(TER code, std::string& token, std::string& text);

View File

@@ -483,7 +483,8 @@ REGISTER_FIX (fixDisallowIncomingV1, Supported::yes, VoteBehavior::De
REGISTER_FEATURE(DID, Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FIX(fixFillOrKill, Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FIX (fixNFTokenReserve, Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FIX(fixInnerObjTemplate, Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FIX (fixInnerObjTemplate, Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FIX (fixAMMOverflowOffer, Supported::yes, VoteBehavior::DefaultYes);
// The following amendments are obsolete, but must remain supported
// because they could potentially get enabled.

View File

@@ -20,13 +20,10 @@
#include <ripple/protocol/TER.h>
#include <boost/range/adaptor/transformed.hpp>
#include <type_traits>
#include <unordered_map>
namespace ripple {
namespace detail {
static std::unordered_map<
std::unordered_map<
TERUnderlyingType,
std::pair<char const* const, char const* const>> const&
transResults()
@@ -239,12 +236,10 @@ transResults()
return results;
}
} // namespace detail
bool
transResultInfo(TER code, std::string& token, std::string& text)
{
auto& results = detail::transResults();
auto& results = transResults();
auto const r = results.find(TERtoInt(code));
@@ -278,7 +273,7 @@ std::optional<TER>
transCode(std::string const& token)
{
static auto const results = [] {
auto& byTer = detail::transResults();
auto& byTer = transResults();
auto range = boost::make_iterator_range(byTer.begin(), byTer.end());
auto tRange = boost::adaptors::transform(range, [](auto const& r) {
return std::make_pair(r.second.first, r.first);

View File

@@ -110,8 +110,11 @@ JSS(HookParameterValue); // field
JSS(HookParameter); // field
JSS(HookGrant); // field
JSS(isSerialized); // out: RPC server_definitions
// matches definitions.json format
JSS(isSigningField); // out: RPC server_definitions
// matches definitions.json format
JSS(isVLEncoded); // out: RPC server_definitions
// matches definitions.json format
JSS(Import);
JSS(ImportVLSequence);
JSS(Invalid); //
@@ -788,8 +791,11 @@ JSS(type); // in: AccountObjects
// out: NetworkOPs RPC server_definitions
// OverlayImpl, Logic
JSS(TRANSACTION_RESULTS); // out: RPC server_definitions
// matches definitions.json format
JSS(TRANSACTION_TYPES); // out: RPC server_definitions
// matches definitions.json format
JSS(TYPES); // out: RPC server_definitions
// matches definitions.json format
JSS(TRANSACTION_FLAGS); // out: RPC server_definitions
JSS(TRANSACTION_FLAGS_INDICES); // out: RPC server_definitions
JSS(type_hex); // out: STPathSet

View File

@@ -20,10 +20,21 @@
#include <ripple/app/main/Application.h>
#include <ripple/app/misc/NetworkOPs.h>
#include <ripple/app/reporting/P2pProxy.h>
#include <ripple/json/json_value.h>
#include <ripple/net/RPCErr.h>
#include <ripple/protocol/LedgerFormats.h>
#include <ripple/protocol/SField.h>
#include <ripple/protocol/TER.h>
#include <ripple/protocol/TxFormats.h>
#include <ripple/protocol/digest.h>
#include <ripple/protocol/jss.h>
#include <ripple/rpc/Context.h>
#include <ripple/rpc/Role.h>
#include <boost/algorithm/string.hpp>
#include <unordered_map>
namespace ripple {
Json::Value

View File

@@ -100,6 +100,7 @@ Handler const handlerArray[]{
{"channel_verify", byRef(&doChannelVerify), Role::USER, NO_CONDITION},
{"connect", byRef(&doConnect), Role::ADMIN, NO_CONDITION},
{"consensus_info", byRef(&doConsensusInfo), Role::ADMIN, NO_CONDITION},
{"crawl_shards", byRef(&doCrawlShards), Role::ADMIN, NO_CONDITION},
{"deposit_authorized",
byRef(&doDepositAuthorized),
Role::USER,
@@ -114,6 +115,7 @@ Handler const handlerArray[]{
{"feature", byRef(&doFeature), Role::ADMIN, NO_CONDITION},
{"fee", byRef(&doFee), Role::USER, NEEDS_CURRENT_LEDGER},
{"fetch_info", byRef(&doFetchInfo), Role::ADMIN, NO_CONDITION},
{"inject", byRef(&doInject), Role::ADMIN, NEEDS_CURRENT_LEDGER},
{"ledger_accept",
byRef(&doLedgerAccept),
Role::ADMIN,
@@ -159,22 +161,20 @@ Handler const handlerArray[]{
Role::ADMIN,
NO_CONDITION},
{"ripple_path_find", byRef(&doRipplePathFind), Role::USER, NO_CONDITION},
{"sign", byRef(&doSign), Role::USER, NO_CONDITION},
{"sign_for", byRef(&doSignFor), Role::USER, NO_CONDITION},
{"inject", byRef(&doInject), Role::ADMIN, NEEDS_CURRENT_LEDGER},
{"submit", byRef(&doSubmit), Role::USER, NEEDS_CURRENT_LEDGER},
{"submit_multisigned",
byRef(&doSubmitMultiSigned),
Role::USER,
NEEDS_CURRENT_LEDGER},
{"server_definitions",
byRef(&doServerDefinitions),
Role::USER,
NO_CONDITION},
{"server_info", byRef(&doServerInfo), Role::USER, NO_CONDITION},
{"server_state", byRef(&doServerState), Role::USER, NO_CONDITION},
{"crawl_shards", byRef(&doCrawlShards), Role::ADMIN, NO_CONDITION},
{"sign", byRef(&doSign), Role::USER, NO_CONDITION},
{"sign_for", byRef(&doSignFor), Role::USER, NO_CONDITION},
{"stop", byRef(&doStop), Role::ADMIN, NO_CONDITION},
{"submit", byRef(&doSubmit), Role::USER, NEEDS_CURRENT_LEDGER},
{"submit_multisigned",
byRef(&doSubmitMultiSigned),
Role::USER,
NEEDS_CURRENT_LEDGER},
{"transaction_entry", byRef(&doTransactionEntry), Role::USER, NO_CONDITION},
{"tx", byRef(&doTxJson), Role::USER, NEEDS_NETWORK_CONNECTION},
{"tx_history", byRef(&doTxHistory), Role::USER, NO_CONDITION, 1, 1},

View File

@@ -80,6 +80,8 @@ admin = 127.0.0.1
void
testServerInfo()
{
testcase("server_info");
using namespace test::jtx;
{
@@ -152,6 +154,8 @@ admin = 127.0.0.1
void
testServerDefinitions()
{
testcase("server_definitions");
using namespace test::jtx;
{
@@ -159,6 +163,84 @@ admin = 127.0.0.1
auto const result = env.rpc("server_definitions");
BEAST_EXPECT(!result[jss::result].isMember(jss::error));
BEAST_EXPECT(result[jss::result][jss::status] == "success");
BEAST_EXPECT(result[jss::result].isMember(jss::FIELDS));
BEAST_EXPECT(result[jss::result].isMember(jss::LEDGER_ENTRY_TYPES));
BEAST_EXPECT(
result[jss::result].isMember(jss::TRANSACTION_RESULTS));
BEAST_EXPECT(result[jss::result].isMember(jss::TRANSACTION_TYPES));
BEAST_EXPECT(result[jss::result].isMember(jss::TYPES));
BEAST_EXPECT(result[jss::result].isMember(jss::hash));
// test a random element of each result
// (testing the whole output would be difficult to maintain)
{
auto const firstField = result[jss::result][jss::FIELDS][0u];
BEAST_EXPECT(firstField[0u].asString() == "Generic");
BEAST_EXPECT(
firstField[1][jss::isSerialized].asBool() == false);
BEAST_EXPECT(
firstField[1][jss::isSigningField].asBool() == false);
BEAST_EXPECT(firstField[1][jss::isVLEncoded].asBool() == false);
BEAST_EXPECT(firstField[1][jss::nth].asUInt() == 0);
BEAST_EXPECT(firstField[1][jss::type].asString() == "Unknown");
}
BEAST_EXPECT(
result[jss::result][jss::LEDGER_ENTRY_TYPES]["AccountRoot"]
.asUInt() == 97);
BEAST_EXPECT(
result[jss::result][jss::TRANSACTION_RESULTS]["tecDIR_FULL"]
.asUInt() == 121);
BEAST_EXPECT(
result[jss::result][jss::TRANSACTION_TYPES]["Payment"]
.asUInt() == 0);
BEAST_EXPECT(
result[jss::result][jss::TYPES]["AccountID"].asUInt() == 8);
}
// test providing the same hash
{
Env env(*this);
auto const firstResult = env.rpc("server_definitions");
auto const hash = firstResult[jss::result][jss::hash].asString();
auto const hashParam =
std::string("{ ") + "\"hash\": \"" + hash + "\"}";
auto const result =
env.rpc("json", "server_definitions", hashParam);
BEAST_EXPECT(!result[jss::result].isMember(jss::error));
BEAST_EXPECT(result[jss::result][jss::status] == "success");
BEAST_EXPECT(!result[jss::result].isMember(jss::FIELDS));
BEAST_EXPECT(
!result[jss::result].isMember(jss::LEDGER_ENTRY_TYPES));
BEAST_EXPECT(
!result[jss::result].isMember(jss::TRANSACTION_RESULTS));
BEAST_EXPECT(!result[jss::result].isMember(jss::TRANSACTION_TYPES));
BEAST_EXPECT(!result[jss::result].isMember(jss::TYPES));
BEAST_EXPECT(result[jss::result].isMember(jss::hash));
}
// test providing a different hash
{
Env env(*this);
std::string const hash =
"54296160385A27154BFA70A239DD8E8FD4CC2DB7BA32D970BA3A5B132CF749"
"D1";
auto const hashParam =
std::string("{ ") + "\"hash\": \"" + hash + "\"}";
auto const result =
env.rpc("json", "server_definitions", hashParam);
BEAST_EXPECT(!result[jss::result].isMember(jss::error));
BEAST_EXPECT(result[jss::result][jss::status] == "success");
BEAST_EXPECT(result[jss::result].isMember(jss::FIELDS));
BEAST_EXPECT(result[jss::result].isMember(jss::LEDGER_ENTRY_TYPES));
BEAST_EXPECT(
result[jss::result].isMember(jss::TRANSACTION_RESULTS));
BEAST_EXPECT(result[jss::result].isMember(jss::TRANSACTION_TYPES));
BEAST_EXPECT(result[jss::result].isMember(jss::TYPES));
BEAST_EXPECT(result[jss::result].isMember(jss::hash));
}
}