mirror of
https://github.com/XRPLF/clio.git
synced 2025-11-19 11:15:50 +00:00
first half of support for books stream
This commit is contained in:
@@ -151,7 +151,7 @@ BackendInterface::fetchBookOffers(
|
||||
page.offers.push_back({keys[i], objs[i]});
|
||||
}
|
||||
auto end = std::chrono::system_clock::now();
|
||||
BOOST_LOG_TRIVIAL(info)
|
||||
BOOST_LOG_TRIVIAL(debug)
|
||||
<< __func__ << " "
|
||||
<< "Fetching " << std::to_string(keys.size()) << " keys took "
|
||||
<< std::to_string(getMillis(mid - begin))
|
||||
|
||||
@@ -3,14 +3,13 @@
|
||||
|
||||
#include <rpc/RPC.h>
|
||||
|
||||
namespace RPC
|
||||
{
|
||||
/*
|
||||
* This file just contains declarations for all of the handlers
|
||||
*/
|
||||
namespace RPC {
|
||||
/*
|
||||
* This file just contains declarations for all of the handlers
|
||||
*/
|
||||
|
||||
// account state methods
|
||||
Result
|
||||
Result
|
||||
doAccountInfo(Context const& context);
|
||||
|
||||
Result
|
||||
@@ -22,7 +21,7 @@ doAccountCurrencies(Context const& context);
|
||||
Result
|
||||
doAccountLines(Context const& context);
|
||||
|
||||
Result
|
||||
Result
|
||||
doAccountObjects(Context const& context);
|
||||
|
||||
Result
|
||||
@@ -30,7 +29,7 @@ doAccountOffers(Context const& context);
|
||||
|
||||
// channels methods
|
||||
|
||||
Result
|
||||
Result
|
||||
doChannelAuthorize(Context const& context);
|
||||
|
||||
Result
|
||||
@@ -41,7 +40,7 @@ Result
|
||||
doBookOffers(Context const& context);
|
||||
|
||||
// ledger methods
|
||||
Result
|
||||
Result
|
||||
doLedger(Context const& context);
|
||||
|
||||
Result
|
||||
@@ -54,18 +53,22 @@ Result
|
||||
doLedgerRange(Context const& context);
|
||||
|
||||
// transaction methods
|
||||
Result
|
||||
Result
|
||||
doTx(Context const& context);
|
||||
|
||||
Result
|
||||
doAccountTx(Context const& context);
|
||||
|
||||
// subscriptions
|
||||
Result
|
||||
Result
|
||||
doSubscribe(Context const& context);
|
||||
|
||||
Result
|
||||
doUnsubscribe(Context const& context);
|
||||
|
||||
} // namespace RPC
|
||||
// server methods
|
||||
Result
|
||||
doServerInfo(Context const& context);
|
||||
|
||||
} // namespace RPC
|
||||
#endif
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
#include <unordered_map>
|
||||
#include <etl/ETLSource.h>
|
||||
#include <rpc/Handlers.h>
|
||||
namespace RPC
|
||||
{
|
||||
#include <unordered_map>
|
||||
namespace RPC {
|
||||
|
||||
std::optional<Context>
|
||||
make_WsContext(
|
||||
@@ -15,19 +14,11 @@ make_WsContext(
|
||||
{
|
||||
if (!request.contains("command"))
|
||||
return {};
|
||||
|
||||
|
||||
std::string command = request.at("command").as_string().c_str();
|
||||
|
||||
return Context{
|
||||
command,
|
||||
1,
|
||||
request,
|
||||
backend,
|
||||
subscriptions,
|
||||
balancer,
|
||||
session,
|
||||
range
|
||||
};
|
||||
command, 1, request, backend, subscriptions, balancer, session, range};
|
||||
}
|
||||
|
||||
std::optional<Context>
|
||||
@@ -53,10 +44,10 @@ make_HttpContext(
|
||||
|
||||
if (array.size() != 1)
|
||||
return {};
|
||||
|
||||
|
||||
if (!array.at(0).is_object())
|
||||
return {};
|
||||
|
||||
|
||||
return Context{
|
||||
command,
|
||||
1,
|
||||
@@ -65,11 +56,8 @@ make_HttpContext(
|
||||
subscriptions,
|
||||
balancer,
|
||||
nullptr,
|
||||
range
|
||||
};
|
||||
range};
|
||||
}
|
||||
|
||||
|
||||
|
||||
void
|
||||
inject_error(Error err, boost::json::object& json)
|
||||
@@ -126,6 +114,7 @@ static std::unordered_map<std::string, std::function<Result(Context const&)>>
|
||||
{"ledger_range", &doLedgerRange},
|
||||
{"ledger_data", &doLedgerData},
|
||||
{"subscribe", &doSubscribe},
|
||||
{"server_info", &doServerInfo},
|
||||
{"unsubscribe", &doUnsubscribe},
|
||||
{"tx", &doTx},
|
||||
};
|
||||
@@ -172,4 +161,4 @@ buildResponse(Context const& ctx)
|
||||
|
||||
return method(ctx);
|
||||
}
|
||||
}
|
||||
} // namespace RPC
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <rpc/RPCHelpers.h>
|
||||
namespace RPC {
|
||||
|
||||
std::optional<ripple::STAmount>
|
||||
getDeliveredAmount(
|
||||
@@ -211,8 +212,8 @@ toJson(ripple::LedgerInfo const& lgrInfo)
|
||||
return header;
|
||||
}
|
||||
|
||||
std::variant<RPC::Status, ripple::LedgerInfo>
|
||||
ledgerInfoFromRequest(RPC::Context const& ctx)
|
||||
std::variant<Status, ripple::LedgerInfo>
|
||||
ledgerInfoFromRequest(Context const& ctx)
|
||||
{
|
||||
auto indexValue = ctx.params.contains("ledger_index")
|
||||
? ctx.params.at("ledger_index")
|
||||
@@ -226,13 +227,11 @@ ledgerInfoFromRequest(RPC::Context const& ctx)
|
||||
if (!hashValue.is_null())
|
||||
{
|
||||
if (!hashValue.is_string())
|
||||
return RPC::Status{
|
||||
RPC::Error::rpcINVALID_PARAMS, "ledgerHashNotString"};
|
||||
return Status{Error::rpcINVALID_PARAMS, "ledgerHashNotString"};
|
||||
|
||||
ripple::uint256 ledgerHash;
|
||||
if (!ledgerHash.parseHex(hashValue.as_string().c_str()))
|
||||
return RPC::Status{
|
||||
RPC::Error::rpcINVALID_PARAMS, "ledgerHashMalformed"};
|
||||
return Status{Error::rpcINVALID_PARAMS, "ledgerHashMalformed"};
|
||||
|
||||
lgrInfo = ctx.backend->fetchLedgerByHash(ledgerHash);
|
||||
}
|
||||
@@ -244,8 +243,7 @@ ledgerInfoFromRequest(RPC::Context const& ctx)
|
||||
else if (!indexValue.is_string() && indexValue.is_int64())
|
||||
ledgerSequence = indexValue.as_int64();
|
||||
else
|
||||
return RPC::Status{
|
||||
RPC::Error::rpcINVALID_PARAMS, "ledgerIndexMalformed"};
|
||||
return Status{Error::rpcINVALID_PARAMS, "ledgerIndexMalformed"};
|
||||
|
||||
lgrInfo = ctx.backend->fetchLedgerBySequence(ledgerSequence);
|
||||
}
|
||||
@@ -255,7 +253,7 @@ ledgerInfoFromRequest(RPC::Context const& ctx)
|
||||
}
|
||||
|
||||
if (!lgrInfo)
|
||||
return RPC::Status{RPC::Error::rpcLGR_NOT_FOUND, "ledgerNotFound"};
|
||||
return Status{Error::rpcLGR_NOT_FOUND, "ledgerNotFound"};
|
||||
|
||||
return *lgrInfo;
|
||||
}
|
||||
@@ -362,7 +360,7 @@ parseRippleLibSeed(boost::json::value const& value)
|
||||
return {};
|
||||
}
|
||||
|
||||
std::variant<RPC::Status, std::pair<ripple::PublicKey, ripple::SecretKey>>
|
||||
std::variant<Status, std::pair<ripple::PublicKey, ripple::SecretKey>>
|
||||
keypairFromRequst(boost::json::object const& request)
|
||||
{
|
||||
bool const has_key_type = request.contains("key_type");
|
||||
@@ -385,13 +383,12 @@ keypairFromRequst(boost::json::object const& request)
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
return RPC::Status{
|
||||
RPC::Error::rpcINVALID_PARAMS, "missing field secret"};
|
||||
return Status{Error::rpcINVALID_PARAMS, "missing field secret"};
|
||||
|
||||
if (count > 1)
|
||||
{
|
||||
return RPC::Status{
|
||||
RPC::Error::rpcINVALID_PARAMS,
|
||||
return Status{
|
||||
Error::rpcINVALID_PARAMS,
|
||||
"Exactly one of the following must be specified: "
|
||||
" passphrase, secret, seed, or seed_hex"};
|
||||
}
|
||||
@@ -402,19 +399,17 @@ keypairFromRequst(boost::json::object const& request)
|
||||
if (has_key_type)
|
||||
{
|
||||
if (!request.at("key_type").is_string())
|
||||
return RPC::Status{
|
||||
RPC::Error::rpcINVALID_PARAMS, "keyTypeNotString"};
|
||||
return Status{Error::rpcINVALID_PARAMS, "keyTypeNotString"};
|
||||
|
||||
std::string key_type = request.at("key_type").as_string().c_str();
|
||||
keyType = ripple::keyTypeFromString(key_type);
|
||||
|
||||
if (!keyType)
|
||||
return RPC::Status{
|
||||
RPC::Error::rpcINVALID_PARAMS, "invalidFieldKeyType"};
|
||||
return Status{Error::rpcINVALID_PARAMS, "invalidFieldKeyType"};
|
||||
|
||||
if (secretType == "secret")
|
||||
return RPC::Status{
|
||||
RPC::Error::rpcINVALID_PARAMS,
|
||||
return Status{
|
||||
Error::rpcINVALID_PARAMS,
|
||||
"The secret field is not allowed if key_type is used."};
|
||||
}
|
||||
|
||||
@@ -431,8 +426,8 @@ keypairFromRequst(boost::json::object const& request)
|
||||
// requested another key type, return an error.
|
||||
if (keyType.value_or(ripple::KeyType::ed25519) !=
|
||||
ripple::KeyType::ed25519)
|
||||
return RPC::Status{
|
||||
RPC::Error::rpcINVALID_PARAMS,
|
||||
return Status{
|
||||
Error::rpcINVALID_PARAMS,
|
||||
"Specified seed is for an Ed25519 wallet."};
|
||||
|
||||
keyType = ripple::KeyType::ed25519;
|
||||
@@ -447,9 +442,8 @@ keypairFromRequst(boost::json::object const& request)
|
||||
if (has_key_type)
|
||||
{
|
||||
if (!request.at(secretType).is_string())
|
||||
return RPC::Status{
|
||||
RPC::Error::rpcINVALID_PARAMS,
|
||||
"secret value must be string"};
|
||||
return Status{
|
||||
Error::rpcINVALID_PARAMS, "secret value must be string"};
|
||||
|
||||
std::string key = request.at(secretType).as_string().c_str();
|
||||
|
||||
@@ -467,8 +461,8 @@ keypairFromRequst(boost::json::object const& request)
|
||||
else
|
||||
{
|
||||
if (!request.at("secret").is_string())
|
||||
return RPC::Status{
|
||||
RPC::Error::rpcINVALID_PARAMS,
|
||||
return Status{
|
||||
Error::rpcINVALID_PARAMS,
|
||||
"field secret should be a string"};
|
||||
|
||||
std::string secret = request.at("secret").as_string().c_str();
|
||||
@@ -477,15 +471,13 @@ keypairFromRequst(boost::json::object const& request)
|
||||
}
|
||||
|
||||
if (!seed)
|
||||
return RPC::Status{
|
||||
RPC::Error::rpcBAD_SEED,
|
||||
"Bad Seed: invalid field message secretType"};
|
||||
return Status{
|
||||
Error::rpcBAD_SEED, "Bad Seed: invalid field message secretType"};
|
||||
|
||||
if (keyType != ripple::KeyType::secp256k1 &&
|
||||
keyType != ripple::KeyType::ed25519)
|
||||
return RPC::Status{
|
||||
RPC::Error::rpcINVALID_PARAMS,
|
||||
"keypairForSignature: invalid key type"};
|
||||
return Status{
|
||||
Error::rpcINVALID_PARAMS, "keypairForSignature: invalid key type"};
|
||||
|
||||
return generateKeyPair(*keyType, *seed);
|
||||
}
|
||||
@@ -672,3 +664,130 @@ transferRate(
|
||||
|
||||
return ripple::parityRate;
|
||||
}
|
||||
|
||||
std::variant<Status, ripple::Book>
|
||||
parseBook(boost::json::object const& request)
|
||||
{
|
||||
if (!request.contains("taker_pays"))
|
||||
return Status{Error::rpcINVALID_PARAMS, "missingTakerPays"};
|
||||
|
||||
if (!request.contains("taker_gets"))
|
||||
return Status{Error::rpcINVALID_PARAMS, "missingTakerGets"};
|
||||
|
||||
if (!request.at("taker_pays").is_object())
|
||||
return Status{Error::rpcINVALID_PARAMS, "takerPaysNotObject"};
|
||||
|
||||
if (!request.at("taker_gets").is_object())
|
||||
return Status{Error::rpcINVALID_PARAMS, "takerGetsNotObject"};
|
||||
|
||||
auto taker_pays = request.at("taker_pays").as_object();
|
||||
if (!taker_pays.contains("currency"))
|
||||
return Status{Error::rpcINVALID_PARAMS, "missingTakerPaysCurrency"};
|
||||
|
||||
if (!taker_pays.at("currency").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "takerPaysCurrencyNotString"};
|
||||
|
||||
auto taker_gets = request.at("taker_gets").as_object();
|
||||
if (!taker_gets.contains("currency"))
|
||||
return Status{Error::rpcINVALID_PARAMS, "missingTakerGetsCurrency"};
|
||||
|
||||
if (!taker_gets.at("currency").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "takerGetsCurrencyNotString"};
|
||||
|
||||
ripple::Currency pay_currency;
|
||||
if (!ripple::to_currency(
|
||||
pay_currency, taker_pays.at("currency").as_string().c_str()))
|
||||
return Status{Error::rpcINVALID_PARAMS, "badTakerPaysCurrency"};
|
||||
|
||||
ripple::Currency get_currency;
|
||||
if (!ripple::to_currency(
|
||||
get_currency, taker_gets["currency"].as_string().c_str()))
|
||||
return Status{Error::rpcINVALID_PARAMS, "badTakerGetsCurrency"};
|
||||
|
||||
ripple::AccountID pay_issuer;
|
||||
if (taker_pays.contains("issuer"))
|
||||
{
|
||||
if (!taker_pays.at("issuer").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "takerPaysIssuerNotString"};
|
||||
|
||||
if (!ripple::to_issuer(
|
||||
pay_issuer, taker_pays.at("issuer").as_string().c_str()))
|
||||
return Status{Error::rpcINVALID_PARAMS, "badTakerPaysIssuer"};
|
||||
|
||||
if (pay_issuer == ripple::noAccount())
|
||||
return Status{
|
||||
Error::rpcINVALID_PARAMS, "badTakerPaysIssuerAccountOne"};
|
||||
}
|
||||
else
|
||||
{
|
||||
pay_issuer = ripple::xrpAccount();
|
||||
}
|
||||
|
||||
if (isXRP(pay_currency) && !isXRP(pay_issuer))
|
||||
return Status{
|
||||
Error::rpcINVALID_PARAMS,
|
||||
"Unneeded field 'taker_pays.issuer' for XRP currency "
|
||||
"specification."};
|
||||
|
||||
if (!isXRP(pay_currency) && isXRP(pay_issuer))
|
||||
return Status{
|
||||
Error::rpcINVALID_PARAMS,
|
||||
"Invalid field 'taker_pays.issuer', expected non-XRP "
|
||||
"issuer."};
|
||||
|
||||
ripple::AccountID get_issuer;
|
||||
|
||||
if (taker_gets.contains("issuer"))
|
||||
{
|
||||
if (!taker_gets["issuer"].is_string())
|
||||
return Status{
|
||||
Error::rpcINVALID_PARAMS, "taker_gets.issuer should be string"};
|
||||
|
||||
if (!ripple::to_issuer(
|
||||
get_issuer, taker_gets.at("issuer").as_string().c_str()))
|
||||
return Status{
|
||||
Error::rpcINVALID_PARAMS,
|
||||
"Invalid field 'taker_gets.issuer', bad issuer."};
|
||||
|
||||
if (get_issuer == ripple::noAccount())
|
||||
return Status{
|
||||
Error::rpcINVALID_PARAMS,
|
||||
"Invalid field 'taker_gets.issuer', bad issuer account "
|
||||
"one."};
|
||||
}
|
||||
else
|
||||
{
|
||||
get_issuer = ripple::xrpAccount();
|
||||
}
|
||||
|
||||
if (ripple::isXRP(get_currency) && !ripple::isXRP(get_issuer))
|
||||
return Status{
|
||||
Error::rpcINVALID_PARAMS,
|
||||
"Unneeded field 'taker_gets.issuer' for XRP currency "
|
||||
"specification."};
|
||||
|
||||
if (!ripple::isXRP(get_currency) && ripple::isXRP(get_issuer))
|
||||
return Status{
|
||||
Error::rpcINVALID_PARAMS,
|
||||
"Invalid field 'taker_gets.issuer', expected non-XRP issuer."};
|
||||
|
||||
if (pay_currency == get_currency && pay_issuer == get_issuer)
|
||||
return Status{Error::rpcINVALID_PARAMS, "badMarket"};
|
||||
|
||||
return ripple::Book{{pay_currency, pay_issuer}, {get_currency, get_issuer}};
|
||||
}
|
||||
std::variant<Status, ripple::AccountID>
|
||||
parseTaker(boost::json::value const& taker)
|
||||
{
|
||||
std::optional<ripple::AccountID> takerID = {};
|
||||
if (!taker.is_string())
|
||||
return {Status{Error::rpcINVALID_PARAMS, "takerNotString"}};
|
||||
|
||||
takerID = accountFromStringStrict(taker.as_string().c_str());
|
||||
|
||||
if (!takerID)
|
||||
return Status{Error::rpcINVALID_PARAMS, "invalidTakerAccount"};
|
||||
return *takerID;
|
||||
}
|
||||
|
||||
} // namespace RPC
|
||||
|
||||
@@ -14,14 +14,17 @@
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <rpc/RPC.h>
|
||||
|
||||
namespace RPC {
|
||||
std::optional<ripple::AccountID>
|
||||
accountFromStringStrict(std::string const& account);
|
||||
|
||||
// TODO this function should probably be in a different file and namespace
|
||||
std::pair<
|
||||
std::shared_ptr<ripple::STTx const>,
|
||||
std::shared_ptr<ripple::STObject const>>
|
||||
deserializeTxPlusMeta(Backend::TransactionAndMetadata const& blobs);
|
||||
|
||||
// TODO this function should probably be in a different file and namespace
|
||||
std::pair<
|
||||
std::shared_ptr<ripple::STTx const>,
|
||||
std::shared_ptr<ripple::TxMeta const>>
|
||||
@@ -48,15 +51,15 @@ using RippledJson = Json::Value;
|
||||
boost::json::value
|
||||
toBoostJson(RippledJson const& value);
|
||||
|
||||
|
||||
boost::json::object
|
||||
generatePubLedgerMessage(ripple::LedgerInfo const& lgrInfo,
|
||||
ripple::Fees const& fees,
|
||||
std::string const& ledgerRange,
|
||||
uint32_t txnCount);
|
||||
generatePubLedgerMessage(
|
||||
ripple::LedgerInfo const& lgrInfo,
|
||||
ripple::Fees const& fees,
|
||||
std::string const& ledgerRange,
|
||||
uint32_t txnCount);
|
||||
|
||||
std::variant<RPC::Status, ripple::LedgerInfo>
|
||||
ledgerInfoFromRequest(RPC::Context const& ctx);
|
||||
std::variant<Status, ripple::LedgerInfo>
|
||||
ledgerInfoFromRequest(Context const& ctx);
|
||||
|
||||
std::optional<ripple::uint256>
|
||||
traverseOwnedNodes(
|
||||
@@ -66,7 +69,7 @@ traverseOwnedNodes(
|
||||
ripple::uint256 const& cursor,
|
||||
std::function<bool(ripple::SLE)> atOwnedNode);
|
||||
|
||||
std::variant<RPC::Status, std::pair<ripple::PublicKey, ripple::SecretKey>>
|
||||
std::variant<Status, std::pair<ripple::PublicKey, ripple::SecretKey>>
|
||||
keypairFromRequst(boost::json::object const& request);
|
||||
|
||||
std::vector<ripple::AccountID>
|
||||
@@ -109,4 +112,11 @@ xrpLiquid(
|
||||
std::uint32_t sequence,
|
||||
ripple::AccountID const& id);
|
||||
|
||||
std::variant<Status, ripple::Book>
|
||||
parseBook(boost::json::object const& request);
|
||||
|
||||
std::variant<Status, ripple::AccountID>
|
||||
parseTaker(boost::json::value const& request);
|
||||
|
||||
} // namespace RPC
|
||||
#endif
|
||||
|
||||
@@ -12,8 +12,7 @@
|
||||
#include <backend/DBHelpers.h>
|
||||
#include <backend/Pg.h>
|
||||
|
||||
namespace RPC
|
||||
{
|
||||
namespace RPC {
|
||||
|
||||
Result
|
||||
doBookOffers(Context const& context)
|
||||
@@ -39,113 +38,20 @@ doBookOffers(Context const& context)
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!request.contains("taker_pays"))
|
||||
return Status{Error::rpcINVALID_PARAMS, "missingTakerPays"};
|
||||
|
||||
if (!request.contains("taker_gets"))
|
||||
return Status{Error::rpcINVALID_PARAMS, "missingTakerGets"};
|
||||
|
||||
if (!request.at("taker_pays").is_object())
|
||||
return Status{Error::rpcINVALID_PARAMS, "takerPaysNotObject"};
|
||||
|
||||
if (!request.at("taker_gets").is_object())
|
||||
return Status{Error::rpcINVALID_PARAMS, "takerGetsNotObject"};
|
||||
|
||||
auto taker_pays = request.at("taker_pays").as_object();
|
||||
if (!taker_pays.contains("currency"))
|
||||
return Status{Error::rpcINVALID_PARAMS, "missingTakerPaysCurrency"};
|
||||
|
||||
if (!taker_pays.at("currency").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "takerPaysCurrencyNotString"};
|
||||
|
||||
auto taker_gets = request.at("taker_gets").as_object();
|
||||
if (!taker_gets.contains("currency"))
|
||||
return Status{Error::rpcINVALID_PARAMS, "missingTakerGetsCurrency"};
|
||||
|
||||
if (!taker_gets.at("currency").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "takerGetsCurrencyNotString"};
|
||||
|
||||
ripple::Currency pay_currency;
|
||||
if (!ripple::to_currency(
|
||||
pay_currency, taker_pays.at("currency").as_string().c_str()))
|
||||
return Status{Error::rpcINVALID_PARAMS, "badTakerPaysCurrency"};
|
||||
|
||||
ripple::Currency get_currency;
|
||||
if (!ripple::to_currency(
|
||||
get_currency, taker_gets["currency"].as_string().c_str()))
|
||||
return Status{Error::rpcINVALID_PARAMS, "badTakerGetsCurrency"};
|
||||
|
||||
ripple::AccountID pay_issuer;
|
||||
if (taker_pays.contains("issuer"))
|
||||
{
|
||||
if (!taker_pays.at("issuer").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "takerPaysIssuerNotString"};
|
||||
|
||||
if (!ripple::to_issuer(
|
||||
pay_issuer, taker_pays.at("issuer").as_string().c_str()))
|
||||
return Status{Error::rpcINVALID_PARAMS, "badTakerPaysIssuer"};
|
||||
|
||||
if (pay_issuer == ripple::noAccount())
|
||||
return Status{Error::rpcINVALID_PARAMS, "badTakerPaysIssuerAccountOne"};
|
||||
}
|
||||
auto parsed = parseBook(request);
|
||||
if (auto status = std::get_if<Status>(&parsed))
|
||||
return *status;
|
||||
else
|
||||
{
|
||||
pay_issuer = ripple::xrpAccount();
|
||||
book = std::get<ripple::Book>(parsed);
|
||||
bookBase = getBookBase(book);
|
||||
}
|
||||
|
||||
if (isXRP(pay_currency) && !isXRP(pay_issuer))
|
||||
return Status{Error::rpcINVALID_PARAMS,
|
||||
"Unneeded field 'taker_pays.issuer' for XRP currency "
|
||||
"specification."};
|
||||
|
||||
if (!isXRP(pay_currency) && isXRP(pay_issuer))
|
||||
return Status{Error::rpcINVALID_PARAMS,
|
||||
"Invalid field 'taker_pays.issuer', expected non-XRP "
|
||||
"issuer."};
|
||||
|
||||
ripple::AccountID get_issuer;
|
||||
|
||||
if (taker_gets.contains("issuer"))
|
||||
{
|
||||
if (!taker_gets["issuer"].is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS,
|
||||
"taker_gets.issuer should be string"};
|
||||
|
||||
if (!ripple::to_issuer(
|
||||
get_issuer, taker_gets.at("issuer").as_string().c_str()))
|
||||
return Status{Error::rpcINVALID_PARAMS,
|
||||
"Invalid field 'taker_gets.issuer', bad issuer."};
|
||||
|
||||
if (get_issuer == ripple::noAccount())
|
||||
return Status{Error::rpcINVALID_PARAMS,
|
||||
"Invalid field 'taker_gets.issuer', bad issuer account "
|
||||
"one."};
|
||||
}
|
||||
else
|
||||
{
|
||||
get_issuer = ripple::xrpAccount();
|
||||
}
|
||||
|
||||
if (ripple::isXRP(get_currency) && !ripple::isXRP(get_issuer))
|
||||
return Status{Error::rpcINVALID_PARAMS,
|
||||
"Unneeded field 'taker_gets.issuer' for XRP currency "
|
||||
"specification."};
|
||||
|
||||
if (!ripple::isXRP(get_currency) && ripple::isXRP(get_issuer))
|
||||
return Status{Error::rpcINVALID_PARAMS,
|
||||
"Invalid field 'taker_gets.issuer', expected non-XRP issuer."};
|
||||
|
||||
if (pay_currency == get_currency && pay_issuer == get_issuer)
|
||||
return Status{Error::rpcINVALID_PARAMS, "badMarket"};
|
||||
|
||||
book = {{pay_currency, pay_issuer}, {get_currency, get_issuer}};
|
||||
bookBase = getBookBase(book);
|
||||
}
|
||||
|
||||
std::uint32_t limit = 200;
|
||||
if (request.contains("limit"))
|
||||
{
|
||||
if(!request.at("limit").is_int64())
|
||||
if (!request.at("limit").is_int64())
|
||||
return Status{Error::rpcINVALID_PARAMS, "limitNotInt"};
|
||||
|
||||
limit = request.at("limit").as_int64();
|
||||
@@ -153,24 +59,22 @@ doBookOffers(Context const& context)
|
||||
return Status{Error::rpcINVALID_PARAMS, "limitNotPositive"};
|
||||
}
|
||||
|
||||
|
||||
std::optional<ripple::AccountID> takerID = {};
|
||||
if (request.contains("taker"))
|
||||
{
|
||||
if (!request.at("taker").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "takerNotString"};
|
||||
|
||||
takerID =
|
||||
accountFromStringStrict(request.at("taker").as_string().c_str());
|
||||
|
||||
if (!takerID)
|
||||
return Status{Error::rpcINVALID_PARAMS, "invalidTakerAccount"};
|
||||
auto parsed = parseTaker(request["taker"]);
|
||||
if (auto status = std::get_if<Status>(&parsed))
|
||||
return *status;
|
||||
else
|
||||
{
|
||||
takerID = std::get<ripple::AccountID>(parsed);
|
||||
}
|
||||
}
|
||||
|
||||
ripple::uint256 cursor = beast::zero;
|
||||
if (request.contains("cursor"))
|
||||
{
|
||||
if(!request.at("cursor").is_string())
|
||||
if (!request.at("cursor").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "cursorNotString"};
|
||||
|
||||
if (!cursor.parseHex(request.at("cursor").as_string().c_str()))
|
||||
@@ -182,8 +86,8 @@ doBookOffers(Context const& context)
|
||||
context.backend->fetchBookOffers(bookBase, lgrInfo.seq, limit, cursor);
|
||||
auto end = std::chrono::system_clock::now();
|
||||
|
||||
BOOST_LOG_TRIVIAL(warning) << "Time loading books: "
|
||||
<< ((end - start).count() / 1000000000.0);
|
||||
BOOST_LOG_TRIVIAL(warning)
|
||||
<< "Time loading books: " << ((end - start).count() / 1000000000.0);
|
||||
|
||||
response["ledger_hash"] = ripple::strHex(lgrInfo.hash);
|
||||
response["ledger_index"] = lgrInfo.seq;
|
||||
@@ -193,7 +97,7 @@ doBookOffers(Context const& context)
|
||||
|
||||
std::map<ripple::AccountID, ripple::STAmount> umBalance;
|
||||
|
||||
bool globalFreeze =
|
||||
bool globalFreeze =
|
||||
isGlobalFrozen(*context.backend, lgrInfo.seq, book.out.account) ||
|
||||
isGlobalFrozen(*context.backend, lgrInfo.seq, book.out.account);
|
||||
|
||||
@@ -209,7 +113,8 @@ doBookOffers(Context const& context)
|
||||
{
|
||||
ripple::SerialIter it{obj.blob.data(), obj.blob.size()};
|
||||
ripple::SLE offer{it, obj.key};
|
||||
ripple::uint256 bookDir = offer.getFieldH256(ripple::sfBookDirectory);
|
||||
ripple::uint256 bookDir =
|
||||
offer.getFieldH256(ripple::sfBookDirectory);
|
||||
|
||||
auto const uOfferOwnerID = offer.getAccountID(ripple::sfAccount);
|
||||
auto const& saTakerGets = offer.getFieldAmount(ripple::sfTakerGets);
|
||||
@@ -239,7 +144,8 @@ doBookOffers(Context const& context)
|
||||
saOwnerFunds = umBalanceEntry->second;
|
||||
firstOwnerOffer = false;
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
saOwnerFunds = accountHolds(
|
||||
*context.backend,
|
||||
lgrInfo.seq,
|
||||
@@ -281,19 +187,20 @@ doBookOffers(Context const& context)
|
||||
{
|
||||
saTakerGetsFunded = saOwnerFundsLimit;
|
||||
offerJson["taker_gets_funded"] = saTakerGetsFunded.getText();
|
||||
offerJson["taker_pays_funded"] = toBoostJson(std::min(
|
||||
saTakerPays,
|
||||
ripple::multiply(
|
||||
saTakerGetsFunded, dirRate, saTakerPays.issue()))
|
||||
.getJson(ripple::JsonOptions::none));
|
||||
offerJson["taker_pays_funded"] = toBoostJson(
|
||||
std::min(
|
||||
saTakerPays,
|
||||
ripple::multiply(
|
||||
saTakerGetsFunded, dirRate, saTakerPays.issue()))
|
||||
.getJson(ripple::JsonOptions::none));
|
||||
}
|
||||
|
||||
ripple::STAmount saOwnerPays = (ripple::parityRate == offerRate)
|
||||
? saTakerGetsFunded
|
||||
: std::min(
|
||||
saOwnerFunds,
|
||||
ripple::multiply(saTakerGetsFunded, offerRate));
|
||||
|
||||
saOwnerFunds,
|
||||
ripple::multiply(saTakerGetsFunded, offerRate));
|
||||
|
||||
umBalance[uOfferOwnerID] = saOwnerFunds - saOwnerPays;
|
||||
|
||||
if (firstOwnerOffer)
|
||||
@@ -303,7 +210,9 @@ doBookOffers(Context const& context)
|
||||
|
||||
jsonOffers.push_back(offerJson);
|
||||
}
|
||||
catch (std::exception const& e) {}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
end = std::chrono::system_clock::now();
|
||||
@@ -322,4 +231,4 @@ doBookOffers(Context const& context)
|
||||
return response;
|
||||
}
|
||||
|
||||
} // namespace RPC
|
||||
} // namespace RPC
|
||||
|
||||
26
src/rpc/handlers/ServerInfo.cpp
Normal file
26
src/rpc/handlers/ServerInfo.cpp
Normal file
@@ -0,0 +1,26 @@
|
||||
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <rpc/RPCHelpers.h>
|
||||
|
||||
namespace RPC {
|
||||
|
||||
Result
|
||||
doServerInfo(Context const& context)
|
||||
{
|
||||
boost::json::object response = {};
|
||||
|
||||
auto range = context.backend->fetchLedgerRange();
|
||||
if (!range)
|
||||
{
|
||||
return Status{Error::rpcNOT_READY, "rangeNotFound"};
|
||||
}
|
||||
else
|
||||
{
|
||||
response["info"] = boost::json::object{};
|
||||
response["info"].as_object()["complete_ledgers"] =
|
||||
std::to_string(range->minSequence) + " - " +
|
||||
std::to_string(range->maxSequence);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
} // namespace RPC
|
||||
@@ -1,16 +1,16 @@
|
||||
#include <boost/json.hpp>
|
||||
#include <webserver/WsBase.h>
|
||||
#include <webserver/SubscriptionManager.h>
|
||||
#include <webserver/WsBase.h>
|
||||
|
||||
#include <rpc/RPCHelpers.h>
|
||||
|
||||
namespace RPC
|
||||
{
|
||||
namespace RPC {
|
||||
|
||||
static std::unordered_set<std::string> validStreams {
|
||||
// these are the streams that take no arguments
|
||||
static std::unordered_set<std::string> validCommonStreams{
|
||||
"ledger",
|
||||
"transactions",
|
||||
"transactions_proposed" };
|
||||
"transactions_proposed"};
|
||||
|
||||
Status
|
||||
validateStreams(boost::json::object const& request)
|
||||
@@ -24,7 +24,7 @@ validateStreams(boost::json::object const& request)
|
||||
|
||||
std::string s = stream.as_string().c_str();
|
||||
|
||||
if (validStreams.find(s) == validStreams.end())
|
||||
if (validCommonStreams.find(s) == validCommonStreams.end())
|
||||
return Status{Error::rpcINVALID_PARAMS, "invalidStream" + s};
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ subscribeToAccounts(
|
||||
|
||||
auto accountID = accountFromStringStrict(s);
|
||||
|
||||
if(!accountID)
|
||||
if (!accountID)
|
||||
{
|
||||
assert(false);
|
||||
continue;
|
||||
@@ -133,7 +133,7 @@ unsubscribeToAccounts(
|
||||
|
||||
auto accountID = accountFromStringStrict(s);
|
||||
|
||||
if(!accountID)
|
||||
if (!accountID)
|
||||
{
|
||||
assert(false);
|
||||
continue;
|
||||
@@ -149,7 +149,8 @@ subscribeToAccountsProposed(
|
||||
std::shared_ptr<WsBase> session,
|
||||
SubscriptionManager& manager)
|
||||
{
|
||||
boost::json::array const& accounts = request.at("accounts_proposed").as_array();
|
||||
boost::json::array const& accounts =
|
||||
request.at("accounts_proposed").as_array();
|
||||
|
||||
for (auto const& account : accounts)
|
||||
{
|
||||
@@ -157,7 +158,7 @@ subscribeToAccountsProposed(
|
||||
|
||||
auto accountID = ripple::parseBase58<ripple::AccountID>(s);
|
||||
|
||||
if(!accountID)
|
||||
if (!accountID)
|
||||
{
|
||||
assert(false);
|
||||
continue;
|
||||
@@ -173,7 +174,8 @@ unsubscribeToAccountsProposed(
|
||||
std::shared_ptr<WsBase> session,
|
||||
SubscriptionManager& manager)
|
||||
{
|
||||
boost::json::array const& accounts = request.at("accounts_proposed").as_array();
|
||||
boost::json::array const& accounts =
|
||||
request.at("accounts_proposed").as_array();
|
||||
|
||||
for (auto const& account : accounts)
|
||||
{
|
||||
@@ -181,7 +183,7 @@ unsubscribeToAccountsProposed(
|
||||
|
||||
auto accountID = ripple::parseBase58<ripple::AccountID>(s);
|
||||
|
||||
if(!accountID)
|
||||
if (!accountID)
|
||||
{
|
||||
assert(false);
|
||||
continue;
|
||||
@@ -191,7 +193,40 @@ unsubscribeToAccountsProposed(
|
||||
}
|
||||
}
|
||||
|
||||
std::variant<Status, std::vector<ripple::Book>>
|
||||
validateAndGetBooks(boost::json::object const& request)
|
||||
{
|
||||
if (!request.at("books").is_array())
|
||||
return Status{Error::rpcINVALID_PARAMS, "booksNotArray"};
|
||||
boost::json::array const& books = request.at("books").as_array();
|
||||
|
||||
std::vector<ripple::Book> booksToSub;
|
||||
for (auto const& book : books)
|
||||
{
|
||||
auto parsed = parseBook(book.as_object());
|
||||
if (auto status = std::get_if<Status>(&parsed))
|
||||
return *status;
|
||||
else
|
||||
{
|
||||
auto b = std::get<ripple::Book>(parsed);
|
||||
booksToSub.push_back(b);
|
||||
if (book.as_object().contains("both"))
|
||||
booksToSub.push_back(ripple::reversed(b));
|
||||
}
|
||||
}
|
||||
return booksToSub;
|
||||
}
|
||||
void
|
||||
subscribeToBooks(
|
||||
std::vector<ripple::Book> const& books,
|
||||
std::shared_ptr<WsBase> session,
|
||||
SubscriptionManager& manager)
|
||||
{
|
||||
for (auto const book : books)
|
||||
{
|
||||
manager.subBook(book, session);
|
||||
}
|
||||
}
|
||||
Result
|
||||
doSubscribe(Context const& context)
|
||||
{
|
||||
@@ -210,7 +245,6 @@ doSubscribe(Context const& context)
|
||||
|
||||
if (request.contains("accounts"))
|
||||
{
|
||||
|
||||
if (!request.at("accounts").is_array())
|
||||
return Status{Error::rpcINVALID_PARAMS, "accountsNotArray"};
|
||||
|
||||
@@ -226,12 +260,21 @@ doSubscribe(Context const& context)
|
||||
if (!request.at("accounts_proposed").is_array())
|
||||
return Status{Error::rpcINVALID_PARAMS, "accountsProposedNotArray"};
|
||||
|
||||
boost::json::array accounts = request.at("accounts_proposed").as_array();
|
||||
boost::json::array accounts =
|
||||
request.at("accounts_proposed").as_array();
|
||||
auto status = validateAccounts(accounts);
|
||||
|
||||
if(status)
|
||||
if (status)
|
||||
return status;
|
||||
}
|
||||
std::vector<ripple::Book> books;
|
||||
if (request.contains("books"))
|
||||
{
|
||||
auto parsed = validateAndGetBooks(request);
|
||||
if (auto status = std::get_if<Status>(&parsed))
|
||||
return *status;
|
||||
books = std::get<std::vector<ripple::Book>>(parsed);
|
||||
}
|
||||
|
||||
if (request.contains("streams"))
|
||||
subscribeToStreams(request, context.session, *context.subscriptions);
|
||||
@@ -240,7 +283,11 @@ doSubscribe(Context const& context)
|
||||
subscribeToAccounts(request, context.session, *context.subscriptions);
|
||||
|
||||
if (request.contains("accounts_proposed"))
|
||||
subscribeToAccountsProposed(request, context.session, *context.subscriptions);
|
||||
subscribeToAccountsProposed(
|
||||
request, context.session, *context.subscriptions);
|
||||
|
||||
if (request.contains("books"))
|
||||
subscribeToBooks(books, context.session, *context.subscriptions);
|
||||
|
||||
boost::json::object response = {{"status", "success"}};
|
||||
return response;
|
||||
@@ -251,8 +298,7 @@ doUnsubscribe(Context const& context)
|
||||
{
|
||||
auto request = context.params;
|
||||
|
||||
|
||||
if (request.contains("streams"))
|
||||
if (request.contains("streams"))
|
||||
{
|
||||
if (!request.at("streams").is_array())
|
||||
return Status{Error::rpcINVALID_PARAMS, "streamsNotArray"};
|
||||
@@ -265,7 +311,6 @@ doUnsubscribe(Context const& context)
|
||||
|
||||
if (request.contains("accounts"))
|
||||
{
|
||||
|
||||
if (!request.at("accounts").is_array())
|
||||
return Status{Error::rpcINVALID_PARAMS, "accountsNotArray"};
|
||||
|
||||
@@ -281,10 +326,11 @@ doUnsubscribe(Context const& context)
|
||||
if (!request.at("accounts_proposed").is_array())
|
||||
return Status{Error::rpcINVALID_PARAMS, "accountsProposedNotArray"};
|
||||
|
||||
boost::json::array accounts = request.at("accounts_proposed").as_array();
|
||||
boost::json::array accounts =
|
||||
request.at("accounts_proposed").as_array();
|
||||
auto status = validateAccounts(accounts);
|
||||
|
||||
if(status)
|
||||
if (status)
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -295,10 +341,11 @@ doUnsubscribe(Context const& context)
|
||||
unsubscribeToAccounts(request, context.session, *context.subscriptions);
|
||||
|
||||
if (request.contains("accounts_proposed"))
|
||||
unsubscribeToAccountsProposed(request, context.session, *context.subscriptions);
|
||||
unsubscribeToAccountsProposed(
|
||||
request, context.session, *context.subscriptions);
|
||||
|
||||
boost::json::object response = {{"status", "success"}};
|
||||
return response;
|
||||
}
|
||||
|
||||
} // namespace RPC
|
||||
} // namespace RPC
|
||||
|
||||
@@ -30,16 +30,17 @@ SubscriptionManager::pubLedger(
|
||||
pubMsg["ledger_hash"] = to_string(lgrInfo.hash);
|
||||
pubMsg["ledger_time"] = lgrInfo.closeTime.time_since_epoch().count();
|
||||
|
||||
pubMsg["fee_ref"] = toBoostJson(fees.units.jsonClipped());
|
||||
pubMsg["fee_base"] = toBoostJson(fees.base.jsonClipped());
|
||||
pubMsg["reserve_base"] = toBoostJson(fees.accountReserve(0).jsonClipped());
|
||||
pubMsg["reserve_inc"] = toBoostJson(fees.increment.jsonClipped());
|
||||
pubMsg["fee_ref"] = RPC::toBoostJson(fees.units.jsonClipped());
|
||||
pubMsg["fee_base"] = RPC::toBoostJson(fees.base.jsonClipped());
|
||||
pubMsg["reserve_base"] =
|
||||
RPC::toBoostJson(fees.accountReserve(0).jsonClipped());
|
||||
pubMsg["reserve_inc"] = RPC::toBoostJson(fees.increment.jsonClipped());
|
||||
|
||||
pubMsg["validated_ledgers"] = ledgerRange;
|
||||
pubMsg["txn_count"] = txnCount;
|
||||
|
||||
std::lock_guard lk(m_);
|
||||
for (auto const& session: streamSubscribers_[Ledgers])
|
||||
for (auto const& session : streamSubscribers_[Ledgers])
|
||||
session->send(boost::json::serialize(pubMsg));
|
||||
}
|
||||
|
||||
@@ -75,18 +76,35 @@ SubscriptionManager::unsubAccount(
|
||||
accountSubscribers_[account].erase(session);
|
||||
}
|
||||
|
||||
void
|
||||
SubscriptionManager::subBook(
|
||||
ripple::Book const& book,
|
||||
std::shared_ptr<WsBase>& session)
|
||||
{
|
||||
std::lock_guard lk(m_);
|
||||
bookSubscribers_[book].emplace(std::move(session));
|
||||
}
|
||||
|
||||
void
|
||||
SubscriptionManager::unsubBook(
|
||||
ripple::Book const& book,
|
||||
std::shared_ptr<WsBase>& session)
|
||||
{
|
||||
std::lock_guard lk(m_);
|
||||
bookSubscribers_[book].erase(session);
|
||||
}
|
||||
|
||||
void
|
||||
SubscriptionManager::pubTransaction(
|
||||
Backend::TransactionAndMetadata const& blob,
|
||||
Backend::TransactionAndMetadata const& blobs,
|
||||
std::uint32_t seq)
|
||||
{
|
||||
std::lock_guard lk(m_);
|
||||
|
||||
auto [tx, meta] = deserializeTxPlusMeta(blob, seq);
|
||||
|
||||
auto [tx, meta] = RPC::deserializeTxPlusMeta(blobs, seq);
|
||||
boost::json::object pubMsg;
|
||||
pubMsg["transaction"] = toJson(*tx);
|
||||
pubMsg["meta"] = toJson(*meta);
|
||||
pubMsg["transaction"] = RPC::toJson(*tx);
|
||||
pubMsg["meta"] = RPC::toJson(*meta);
|
||||
|
||||
for (auto const& session : streamSubscribers_[Transactions])
|
||||
session->send(boost::json::serialize(pubMsg));
|
||||
@@ -108,7 +126,7 @@ SubscriptionManager::forwardProposedTransaction(
|
||||
session->send(boost::json::serialize(response));
|
||||
|
||||
auto transaction = response.at("transaction").as_object();
|
||||
auto accounts = getAccountsFromTransaction(transaction);
|
||||
auto accounts = RPC::getAccountsFromTransaction(transaction);
|
||||
|
||||
for (ripple::AccountID const& account : accounts)
|
||||
for (auto const& session : accountProposedSubscribers_[account])
|
||||
@@ -153,17 +171,17 @@ SubscriptionManager::clearSession(WsBase* s)
|
||||
std::lock_guard lk(m_);
|
||||
|
||||
// need the == operator. No-op delete
|
||||
std::shared_ptr<WsBase> targetSession(s, [](WsBase*){});
|
||||
for(auto& stream : streamSubscribers_)
|
||||
std::shared_ptr<WsBase> targetSession(s, [](WsBase*) {});
|
||||
for (auto& stream : streamSubscribers_)
|
||||
stream.erase(targetSession);
|
||||
|
||||
for(auto& [account, subscribers] : accountSubscribers_)
|
||||
for (auto& [account, subscribers] : accountSubscribers_)
|
||||
{
|
||||
if (subscribers.find(targetSession) != subscribers.end())
|
||||
accountSubscribers_[account].erase(targetSession);
|
||||
}
|
||||
|
||||
for(auto& [account, subscribers] : accountProposedSubscribers_)
|
||||
for (auto& [account, subscribers] : accountProposedSubscribers_)
|
||||
{
|
||||
if (subscribers.find(targetSession) != subscribers.end())
|
||||
accountProposedSubscribers_[account].erase(targetSession);
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <memory>
|
||||
#include <backend/BackendInterface.h>
|
||||
|
||||
class WsBase;
|
||||
|
||||
@@ -37,12 +36,12 @@ class SubscriptionManager
|
||||
|
||||
finalEntry
|
||||
};
|
||||
|
||||
std::mutex m_;
|
||||
std::array<subscriptions, finalEntry> streamSubscribers_;
|
||||
std::unordered_map<ripple::AccountID, subscriptions> accountSubscribers_;
|
||||
std::unordered_map<ripple::AccountID, subscriptions>
|
||||
accountProposedSubscribers_;
|
||||
std::unordered_map<ripple::Book, subscriptions> bookSubscribers_;
|
||||
|
||||
public:
|
||||
static std::shared_ptr<SubscriptionManager>
|
||||
@@ -72,7 +71,7 @@ public:
|
||||
|
||||
void
|
||||
pubTransaction(
|
||||
Backend::TransactionAndMetadata const& blob,
|
||||
Backend::TransactionAndMetadata const& blobs,
|
||||
std::uint32_t seq);
|
||||
|
||||
void
|
||||
@@ -85,6 +84,12 @@ public:
|
||||
ripple::AccountID const& account,
|
||||
std::shared_ptr<WsBase>& session);
|
||||
|
||||
void
|
||||
subBook(ripple::Book const& book, std::shared_ptr<WsBase>& session);
|
||||
|
||||
void
|
||||
unsubBook(ripple::Book const& book, std::shared_ptr<WsBase>& session);
|
||||
|
||||
void
|
||||
forwardProposedTransaction(boost::json::object const& response);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user