first half of support for books stream

This commit is contained in:
CJ Cobb
2021-08-23 14:44:46 -04:00
parent fd20ab77f7
commit d65bbfc841
12 changed files with 396 additions and 255 deletions

View File

@@ -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))

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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

View File

@@ -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

View File

@@ -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);

View File

@@ -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);