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

@@ -98,7 +98,9 @@ target_sources(clio PRIVATE
src/rpc/handlers/ChannelAuthorize.cpp src/rpc/handlers/ChannelAuthorize.cpp
src/rpc/handlers/ChannelVerify.cpp src/rpc/handlers/ChannelVerify.cpp
# Subscribe # Subscribe
src/rpc/handlers/Subscribe.cpp) src/rpc/handlers/Subscribe.cpp
# Server
src/rpc/handlers/ServerInfo.cpp)
message(${Boost_LIBRARIES}) message(${Boost_LIBRARIES})

View File

@@ -151,7 +151,7 @@ BackendInterface::fetchBookOffers(
page.offers.push_back({keys[i], objs[i]}); page.offers.push_back({keys[i], objs[i]});
} }
auto end = std::chrono::system_clock::now(); auto end = std::chrono::system_clock::now();
BOOST_LOG_TRIVIAL(info) BOOST_LOG_TRIVIAL(debug)
<< __func__ << " " << __func__ << " "
<< "Fetching " << std::to_string(keys.size()) << " keys took " << "Fetching " << std::to_string(keys.size()) << " keys took "
<< std::to_string(getMillis(mid - begin)) << std::to_string(getMillis(mid - begin))

View File

@@ -3,14 +3,13 @@
#include <rpc/RPC.h> #include <rpc/RPC.h>
namespace RPC namespace RPC {
{ /*
/* * This file just contains declarations for all of the handlers
* This file just contains declarations for all of the handlers */
*/
// account state methods // account state methods
Result Result
doAccountInfo(Context const& context); doAccountInfo(Context const& context);
Result Result
@@ -22,7 +21,7 @@ doAccountCurrencies(Context const& context);
Result Result
doAccountLines(Context const& context); doAccountLines(Context const& context);
Result Result
doAccountObjects(Context const& context); doAccountObjects(Context const& context);
Result Result
@@ -30,7 +29,7 @@ doAccountOffers(Context const& context);
// channels methods // channels methods
Result Result
doChannelAuthorize(Context const& context); doChannelAuthorize(Context const& context);
Result Result
@@ -41,7 +40,7 @@ Result
doBookOffers(Context const& context); doBookOffers(Context const& context);
// ledger methods // ledger methods
Result Result
doLedger(Context const& context); doLedger(Context const& context);
Result Result
@@ -54,18 +53,22 @@ Result
doLedgerRange(Context const& context); doLedgerRange(Context const& context);
// transaction methods // transaction methods
Result Result
doTx(Context const& context); doTx(Context const& context);
Result Result
doAccountTx(Context const& context); doAccountTx(Context const& context);
// subscriptions // subscriptions
Result Result
doSubscribe(Context const& context); doSubscribe(Context const& context);
Result Result
doUnsubscribe(Context const& context); doUnsubscribe(Context const& context);
} // namespace RPC // server methods
Result
doServerInfo(Context const& context);
} // namespace RPC
#endif #endif

View File

@@ -1,8 +1,7 @@
#include <unordered_map>
#include <etl/ETLSource.h> #include <etl/ETLSource.h>
#include <rpc/Handlers.h> #include <rpc/Handlers.h>
namespace RPC #include <unordered_map>
{ namespace RPC {
std::optional<Context> std::optional<Context>
make_WsContext( make_WsContext(
@@ -15,19 +14,11 @@ make_WsContext(
{ {
if (!request.contains("command")) if (!request.contains("command"))
return {}; return {};
std::string command = request.at("command").as_string().c_str(); std::string command = request.at("command").as_string().c_str();
return Context{ return Context{
command, command, 1, request, backend, subscriptions, balancer, session, range};
1,
request,
backend,
subscriptions,
balancer,
session,
range
};
} }
std::optional<Context> std::optional<Context>
@@ -53,10 +44,10 @@ make_HttpContext(
if (array.size() != 1) if (array.size() != 1)
return {}; return {};
if (!array.at(0).is_object()) if (!array.at(0).is_object())
return {}; return {};
return Context{ return Context{
command, command,
1, 1,
@@ -65,11 +56,8 @@ make_HttpContext(
subscriptions, subscriptions,
balancer, balancer,
nullptr, nullptr,
range range};
};
} }
void void
inject_error(Error err, boost::json::object& json) 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_range", &doLedgerRange},
{"ledger_data", &doLedgerData}, {"ledger_data", &doLedgerData},
{"subscribe", &doSubscribe}, {"subscribe", &doSubscribe},
{"server_info", &doServerInfo},
{"unsubscribe", &doUnsubscribe}, {"unsubscribe", &doUnsubscribe},
{"tx", &doTx}, {"tx", &doTx},
}; };
@@ -172,4 +161,4 @@ buildResponse(Context const& ctx)
return method(ctx); return method(ctx);
} }
} } // namespace RPC

View File

@@ -1,5 +1,6 @@
#include <backend/BackendInterface.h> #include <backend/BackendInterface.h>
#include <rpc/RPCHelpers.h> #include <rpc/RPCHelpers.h>
namespace RPC {
std::optional<ripple::STAmount> std::optional<ripple::STAmount>
getDeliveredAmount( getDeliveredAmount(
@@ -211,8 +212,8 @@ toJson(ripple::LedgerInfo const& lgrInfo)
return header; return header;
} }
std::variant<RPC::Status, ripple::LedgerInfo> std::variant<Status, ripple::LedgerInfo>
ledgerInfoFromRequest(RPC::Context const& ctx) ledgerInfoFromRequest(Context const& ctx)
{ {
auto indexValue = ctx.params.contains("ledger_index") auto indexValue = ctx.params.contains("ledger_index")
? ctx.params.at("ledger_index") ? ctx.params.at("ledger_index")
@@ -226,13 +227,11 @@ ledgerInfoFromRequest(RPC::Context const& ctx)
if (!hashValue.is_null()) if (!hashValue.is_null())
{ {
if (!hashValue.is_string()) if (!hashValue.is_string())
return RPC::Status{ return Status{Error::rpcINVALID_PARAMS, "ledgerHashNotString"};
RPC::Error::rpcINVALID_PARAMS, "ledgerHashNotString"};
ripple::uint256 ledgerHash; ripple::uint256 ledgerHash;
if (!ledgerHash.parseHex(hashValue.as_string().c_str())) if (!ledgerHash.parseHex(hashValue.as_string().c_str()))
return RPC::Status{ return Status{Error::rpcINVALID_PARAMS, "ledgerHashMalformed"};
RPC::Error::rpcINVALID_PARAMS, "ledgerHashMalformed"};
lgrInfo = ctx.backend->fetchLedgerByHash(ledgerHash); lgrInfo = ctx.backend->fetchLedgerByHash(ledgerHash);
} }
@@ -244,8 +243,7 @@ ledgerInfoFromRequest(RPC::Context const& ctx)
else if (!indexValue.is_string() && indexValue.is_int64()) else if (!indexValue.is_string() && indexValue.is_int64())
ledgerSequence = indexValue.as_int64(); ledgerSequence = indexValue.as_int64();
else else
return RPC::Status{ return Status{Error::rpcINVALID_PARAMS, "ledgerIndexMalformed"};
RPC::Error::rpcINVALID_PARAMS, "ledgerIndexMalformed"};
lgrInfo = ctx.backend->fetchLedgerBySequence(ledgerSequence); lgrInfo = ctx.backend->fetchLedgerBySequence(ledgerSequence);
} }
@@ -255,7 +253,7 @@ ledgerInfoFromRequest(RPC::Context const& ctx)
} }
if (!lgrInfo) if (!lgrInfo)
return RPC::Status{RPC::Error::rpcLGR_NOT_FOUND, "ledgerNotFound"}; return Status{Error::rpcLGR_NOT_FOUND, "ledgerNotFound"};
return *lgrInfo; return *lgrInfo;
} }
@@ -362,7 +360,7 @@ parseRippleLibSeed(boost::json::value const& value)
return {}; 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) keypairFromRequst(boost::json::object const& request)
{ {
bool const has_key_type = request.contains("key_type"); bool const has_key_type = request.contains("key_type");
@@ -385,13 +383,12 @@ keypairFromRequst(boost::json::object const& request)
} }
if (count == 0) if (count == 0)
return RPC::Status{ return Status{Error::rpcINVALID_PARAMS, "missing field secret"};
RPC::Error::rpcINVALID_PARAMS, "missing field secret"};
if (count > 1) if (count > 1)
{ {
return RPC::Status{ return Status{
RPC::Error::rpcINVALID_PARAMS, Error::rpcINVALID_PARAMS,
"Exactly one of the following must be specified: " "Exactly one of the following must be specified: "
" passphrase, secret, seed, or seed_hex"}; " passphrase, secret, seed, or seed_hex"};
} }
@@ -402,19 +399,17 @@ keypairFromRequst(boost::json::object const& request)
if (has_key_type) if (has_key_type)
{ {
if (!request.at("key_type").is_string()) if (!request.at("key_type").is_string())
return RPC::Status{ return Status{Error::rpcINVALID_PARAMS, "keyTypeNotString"};
RPC::Error::rpcINVALID_PARAMS, "keyTypeNotString"};
std::string key_type = request.at("key_type").as_string().c_str(); std::string key_type = request.at("key_type").as_string().c_str();
keyType = ripple::keyTypeFromString(key_type); keyType = ripple::keyTypeFromString(key_type);
if (!keyType) if (!keyType)
return RPC::Status{ return Status{Error::rpcINVALID_PARAMS, "invalidFieldKeyType"};
RPC::Error::rpcINVALID_PARAMS, "invalidFieldKeyType"};
if (secretType == "secret") if (secretType == "secret")
return RPC::Status{ return Status{
RPC::Error::rpcINVALID_PARAMS, Error::rpcINVALID_PARAMS,
"The secret field is not allowed if key_type is used."}; "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. // requested another key type, return an error.
if (keyType.value_or(ripple::KeyType::ed25519) != if (keyType.value_or(ripple::KeyType::ed25519) !=
ripple::KeyType::ed25519) ripple::KeyType::ed25519)
return RPC::Status{ return Status{
RPC::Error::rpcINVALID_PARAMS, Error::rpcINVALID_PARAMS,
"Specified seed is for an Ed25519 wallet."}; "Specified seed is for an Ed25519 wallet."};
keyType = ripple::KeyType::ed25519; keyType = ripple::KeyType::ed25519;
@@ -447,9 +442,8 @@ keypairFromRequst(boost::json::object const& request)
if (has_key_type) if (has_key_type)
{ {
if (!request.at(secretType).is_string()) if (!request.at(secretType).is_string())
return RPC::Status{ return Status{
RPC::Error::rpcINVALID_PARAMS, Error::rpcINVALID_PARAMS, "secret value must be string"};
"secret value must be string"};
std::string key = request.at(secretType).as_string().c_str(); std::string key = request.at(secretType).as_string().c_str();
@@ -467,8 +461,8 @@ keypairFromRequst(boost::json::object const& request)
else else
{ {
if (!request.at("secret").is_string()) if (!request.at("secret").is_string())
return RPC::Status{ return Status{
RPC::Error::rpcINVALID_PARAMS, Error::rpcINVALID_PARAMS,
"field secret should be a string"}; "field secret should be a string"};
std::string secret = request.at("secret").as_string().c_str(); std::string secret = request.at("secret").as_string().c_str();
@@ -477,15 +471,13 @@ keypairFromRequst(boost::json::object const& request)
} }
if (!seed) if (!seed)
return RPC::Status{ return Status{
RPC::Error::rpcBAD_SEED, Error::rpcBAD_SEED, "Bad Seed: invalid field message secretType"};
"Bad Seed: invalid field message secretType"};
if (keyType != ripple::KeyType::secp256k1 && if (keyType != ripple::KeyType::secp256k1 &&
keyType != ripple::KeyType::ed25519) keyType != ripple::KeyType::ed25519)
return RPC::Status{ return Status{
RPC::Error::rpcINVALID_PARAMS, Error::rpcINVALID_PARAMS, "keypairForSignature: invalid key type"};
"keypairForSignature: invalid key type"};
return generateKeyPair(*keyType, *seed); return generateKeyPair(*keyType, *seed);
} }
@@ -672,3 +664,130 @@ transferRate(
return ripple::parityRate; 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 <backend/BackendInterface.h>
#include <rpc/RPC.h> #include <rpc/RPC.h>
namespace RPC {
std::optional<ripple::AccountID> std::optional<ripple::AccountID>
accountFromStringStrict(std::string const& account); accountFromStringStrict(std::string const& account);
// TODO this function should probably be in a different file and namespace
std::pair< std::pair<
std::shared_ptr<ripple::STTx const>, std::shared_ptr<ripple::STTx const>,
std::shared_ptr<ripple::STObject const>> std::shared_ptr<ripple::STObject const>>
deserializeTxPlusMeta(Backend::TransactionAndMetadata const& blobs); deserializeTxPlusMeta(Backend::TransactionAndMetadata const& blobs);
// TODO this function should probably be in a different file and namespace
std::pair< std::pair<
std::shared_ptr<ripple::STTx const>, std::shared_ptr<ripple::STTx const>,
std::shared_ptr<ripple::TxMeta const>> std::shared_ptr<ripple::TxMeta const>>
@@ -48,15 +51,15 @@ using RippledJson = Json::Value;
boost::json::value boost::json::value
toBoostJson(RippledJson const& value); toBoostJson(RippledJson const& value);
boost::json::object boost::json::object
generatePubLedgerMessage(ripple::LedgerInfo const& lgrInfo, generatePubLedgerMessage(
ripple::Fees const& fees, ripple::LedgerInfo const& lgrInfo,
std::string const& ledgerRange, ripple::Fees const& fees,
uint32_t txnCount); std::string const& ledgerRange,
uint32_t txnCount);
std::variant<RPC::Status, ripple::LedgerInfo> std::variant<Status, ripple::LedgerInfo>
ledgerInfoFromRequest(RPC::Context const& ctx); ledgerInfoFromRequest(Context const& ctx);
std::optional<ripple::uint256> std::optional<ripple::uint256>
traverseOwnedNodes( traverseOwnedNodes(
@@ -66,7 +69,7 @@ traverseOwnedNodes(
ripple::uint256 const& cursor, ripple::uint256 const& cursor,
std::function<bool(ripple::SLE)> atOwnedNode); 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); keypairFromRequst(boost::json::object const& request);
std::vector<ripple::AccountID> std::vector<ripple::AccountID>
@@ -109,4 +112,11 @@ xrpLiquid(
std::uint32_t sequence, std::uint32_t sequence,
ripple::AccountID const& id); 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 #endif

View File

@@ -12,8 +12,7 @@
#include <backend/DBHelpers.h> #include <backend/DBHelpers.h>
#include <backend/Pg.h> #include <backend/Pg.h>
namespace RPC namespace RPC {
{
Result Result
doBookOffers(Context const& context) doBookOffers(Context const& context)
@@ -39,113 +38,20 @@ doBookOffers(Context const& context)
} }
else else
{ {
if (!request.contains("taker_pays")) auto parsed = parseBook(request);
return Status{Error::rpcINVALID_PARAMS, "missingTakerPays"}; if (auto status = std::get_if<Status>(&parsed))
return *status;
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 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; std::uint32_t limit = 200;
if (request.contains("limit")) if (request.contains("limit"))
{ {
if(!request.at("limit").is_int64()) if (!request.at("limit").is_int64())
return Status{Error::rpcINVALID_PARAMS, "limitNotInt"}; return Status{Error::rpcINVALID_PARAMS, "limitNotInt"};
limit = request.at("limit").as_int64(); limit = request.at("limit").as_int64();
@@ -153,24 +59,22 @@ doBookOffers(Context const& context)
return Status{Error::rpcINVALID_PARAMS, "limitNotPositive"}; return Status{Error::rpcINVALID_PARAMS, "limitNotPositive"};
} }
std::optional<ripple::AccountID> takerID = {}; std::optional<ripple::AccountID> takerID = {};
if (request.contains("taker")) if (request.contains("taker"))
{ {
if (!request.at("taker").is_string()) auto parsed = parseTaker(request["taker"]);
return Status{Error::rpcINVALID_PARAMS, "takerNotString"}; if (auto status = std::get_if<Status>(&parsed))
return *status;
takerID = else
accountFromStringStrict(request.at("taker").as_string().c_str()); {
takerID = std::get<ripple::AccountID>(parsed);
if (!takerID) }
return Status{Error::rpcINVALID_PARAMS, "invalidTakerAccount"};
} }
ripple::uint256 cursor = beast::zero; ripple::uint256 cursor = beast::zero;
if (request.contains("cursor")) if (request.contains("cursor"))
{ {
if(!request.at("cursor").is_string()) if (!request.at("cursor").is_string())
return Status{Error::rpcINVALID_PARAMS, "cursorNotString"}; return Status{Error::rpcINVALID_PARAMS, "cursorNotString"};
if (!cursor.parseHex(request.at("cursor").as_string().c_str())) 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); context.backend->fetchBookOffers(bookBase, lgrInfo.seq, limit, cursor);
auto end = std::chrono::system_clock::now(); auto end = std::chrono::system_clock::now();
BOOST_LOG_TRIVIAL(warning) << "Time loading books: " BOOST_LOG_TRIVIAL(warning)
<< ((end - start).count() / 1000000000.0); << "Time loading books: " << ((end - start).count() / 1000000000.0);
response["ledger_hash"] = ripple::strHex(lgrInfo.hash); response["ledger_hash"] = ripple::strHex(lgrInfo.hash);
response["ledger_index"] = lgrInfo.seq; response["ledger_index"] = lgrInfo.seq;
@@ -193,7 +97,7 @@ doBookOffers(Context const& context)
std::map<ripple::AccountID, ripple::STAmount> umBalance; 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) ||
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::SerialIter it{obj.blob.data(), obj.blob.size()};
ripple::SLE offer{it, obj.key}; 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 uOfferOwnerID = offer.getAccountID(ripple::sfAccount);
auto const& saTakerGets = offer.getFieldAmount(ripple::sfTakerGets); auto const& saTakerGets = offer.getFieldAmount(ripple::sfTakerGets);
@@ -239,7 +144,8 @@ doBookOffers(Context const& context)
saOwnerFunds = umBalanceEntry->second; saOwnerFunds = umBalanceEntry->second;
firstOwnerOffer = false; firstOwnerOffer = false;
} }
else { else
{
saOwnerFunds = accountHolds( saOwnerFunds = accountHolds(
*context.backend, *context.backend,
lgrInfo.seq, lgrInfo.seq,
@@ -281,19 +187,20 @@ doBookOffers(Context const& context)
{ {
saTakerGetsFunded = saOwnerFundsLimit; saTakerGetsFunded = saOwnerFundsLimit;
offerJson["taker_gets_funded"] = saTakerGetsFunded.getText(); offerJson["taker_gets_funded"] = saTakerGetsFunded.getText();
offerJson["taker_pays_funded"] = toBoostJson(std::min( offerJson["taker_pays_funded"] = toBoostJson(
saTakerPays, std::min(
ripple::multiply( saTakerPays,
saTakerGetsFunded, dirRate, saTakerPays.issue())) ripple::multiply(
.getJson(ripple::JsonOptions::none)); saTakerGetsFunded, dirRate, saTakerPays.issue()))
.getJson(ripple::JsonOptions::none));
} }
ripple::STAmount saOwnerPays = (ripple::parityRate == offerRate) ripple::STAmount saOwnerPays = (ripple::parityRate == offerRate)
? saTakerGetsFunded ? saTakerGetsFunded
: std::min( : std::min(
saOwnerFunds, saOwnerFunds,
ripple::multiply(saTakerGetsFunded, offerRate)); ripple::multiply(saTakerGetsFunded, offerRate));
umBalance[uOfferOwnerID] = saOwnerFunds - saOwnerPays; umBalance[uOfferOwnerID] = saOwnerFunds - saOwnerPays;
if (firstOwnerOffer) if (firstOwnerOffer)
@@ -303,7 +210,9 @@ doBookOffers(Context const& context)
jsonOffers.push_back(offerJson); jsonOffers.push_back(offerJson);
} }
catch (std::exception const& e) {} catch (std::exception const& e)
{
}
} }
end = std::chrono::system_clock::now(); end = std::chrono::system_clock::now();
@@ -322,4 +231,4 @@ doBookOffers(Context const& context)
return response; 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 <boost/json.hpp>
#include <webserver/WsBase.h>
#include <webserver/SubscriptionManager.h> #include <webserver/SubscriptionManager.h>
#include <webserver/WsBase.h>
#include <rpc/RPCHelpers.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", "ledger",
"transactions", "transactions",
"transactions_proposed" }; "transactions_proposed"};
Status Status
validateStreams(boost::json::object const& request) validateStreams(boost::json::object const& request)
@@ -24,7 +24,7 @@ validateStreams(boost::json::object const& request)
std::string s = stream.as_string().c_str(); 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}; return Status{Error::rpcINVALID_PARAMS, "invalidStream" + s};
} }
@@ -109,7 +109,7 @@ subscribeToAccounts(
auto accountID = accountFromStringStrict(s); auto accountID = accountFromStringStrict(s);
if(!accountID) if (!accountID)
{ {
assert(false); assert(false);
continue; continue;
@@ -133,7 +133,7 @@ unsubscribeToAccounts(
auto accountID = accountFromStringStrict(s); auto accountID = accountFromStringStrict(s);
if(!accountID) if (!accountID)
{ {
assert(false); assert(false);
continue; continue;
@@ -149,7 +149,8 @@ subscribeToAccountsProposed(
std::shared_ptr<WsBase> session, std::shared_ptr<WsBase> session,
SubscriptionManager& manager) 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) for (auto const& account : accounts)
{ {
@@ -157,7 +158,7 @@ subscribeToAccountsProposed(
auto accountID = ripple::parseBase58<ripple::AccountID>(s); auto accountID = ripple::parseBase58<ripple::AccountID>(s);
if(!accountID) if (!accountID)
{ {
assert(false); assert(false);
continue; continue;
@@ -173,7 +174,8 @@ unsubscribeToAccountsProposed(
std::shared_ptr<WsBase> session, std::shared_ptr<WsBase> session,
SubscriptionManager& manager) 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) for (auto const& account : accounts)
{ {
@@ -181,7 +183,7 @@ unsubscribeToAccountsProposed(
auto accountID = ripple::parseBase58<ripple::AccountID>(s); auto accountID = ripple::parseBase58<ripple::AccountID>(s);
if(!accountID) if (!accountID)
{ {
assert(false); assert(false);
continue; 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 Result
doSubscribe(Context const& context) doSubscribe(Context const& context)
{ {
@@ -210,7 +245,6 @@ doSubscribe(Context const& context)
if (request.contains("accounts")) if (request.contains("accounts"))
{ {
if (!request.at("accounts").is_array()) if (!request.at("accounts").is_array())
return Status{Error::rpcINVALID_PARAMS, "accountsNotArray"}; return Status{Error::rpcINVALID_PARAMS, "accountsNotArray"};
@@ -226,12 +260,21 @@ doSubscribe(Context const& context)
if (!request.at("accounts_proposed").is_array()) if (!request.at("accounts_proposed").is_array())
return Status{Error::rpcINVALID_PARAMS, "accountsProposedNotArray"}; 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); auto status = validateAccounts(accounts);
if(status) if (status)
return 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")) if (request.contains("streams"))
subscribeToStreams(request, context.session, *context.subscriptions); subscribeToStreams(request, context.session, *context.subscriptions);
@@ -240,7 +283,11 @@ doSubscribe(Context const& context)
subscribeToAccounts(request, context.session, *context.subscriptions); subscribeToAccounts(request, context.session, *context.subscriptions);
if (request.contains("accounts_proposed")) 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"}}; boost::json::object response = {{"status", "success"}};
return response; return response;
@@ -251,8 +298,7 @@ doUnsubscribe(Context const& context)
{ {
auto request = context.params; auto request = context.params;
if (request.contains("streams"))
if (request.contains("streams"))
{ {
if (!request.at("streams").is_array()) if (!request.at("streams").is_array())
return Status{Error::rpcINVALID_PARAMS, "streamsNotArray"}; return Status{Error::rpcINVALID_PARAMS, "streamsNotArray"};
@@ -265,7 +311,6 @@ doUnsubscribe(Context const& context)
if (request.contains("accounts")) if (request.contains("accounts"))
{ {
if (!request.at("accounts").is_array()) if (!request.at("accounts").is_array())
return Status{Error::rpcINVALID_PARAMS, "accountsNotArray"}; return Status{Error::rpcINVALID_PARAMS, "accountsNotArray"};
@@ -281,10 +326,11 @@ doUnsubscribe(Context const& context)
if (!request.at("accounts_proposed").is_array()) if (!request.at("accounts_proposed").is_array())
return Status{Error::rpcINVALID_PARAMS, "accountsProposedNotArray"}; 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); auto status = validateAccounts(accounts);
if(status) if (status)
return status; return status;
} }
@@ -295,10 +341,11 @@ doUnsubscribe(Context const& context)
unsubscribeToAccounts(request, context.session, *context.subscriptions); unsubscribeToAccounts(request, context.session, *context.subscriptions);
if (request.contains("accounts_proposed")) if (request.contains("accounts_proposed"))
unsubscribeToAccountsProposed(request, context.session, *context.subscriptions); unsubscribeToAccountsProposed(
request, context.session, *context.subscriptions);
boost::json::object response = {{"status", "success"}}; boost::json::object response = {{"status", "success"}};
return response; return response;
} }
} // namespace RPC } // namespace RPC

View File

@@ -30,16 +30,17 @@ SubscriptionManager::pubLedger(
pubMsg["ledger_hash"] = to_string(lgrInfo.hash); pubMsg["ledger_hash"] = to_string(lgrInfo.hash);
pubMsg["ledger_time"] = lgrInfo.closeTime.time_since_epoch().count(); pubMsg["ledger_time"] = lgrInfo.closeTime.time_since_epoch().count();
pubMsg["fee_ref"] = toBoostJson(fees.units.jsonClipped()); pubMsg["fee_ref"] = RPC::toBoostJson(fees.units.jsonClipped());
pubMsg["fee_base"] = toBoostJson(fees.base.jsonClipped()); pubMsg["fee_base"] = RPC::toBoostJson(fees.base.jsonClipped());
pubMsg["reserve_base"] = toBoostJson(fees.accountReserve(0).jsonClipped()); pubMsg["reserve_base"] =
pubMsg["reserve_inc"] = toBoostJson(fees.increment.jsonClipped()); RPC::toBoostJson(fees.accountReserve(0).jsonClipped());
pubMsg["reserve_inc"] = RPC::toBoostJson(fees.increment.jsonClipped());
pubMsg["validated_ledgers"] = ledgerRange; pubMsg["validated_ledgers"] = ledgerRange;
pubMsg["txn_count"] = txnCount; pubMsg["txn_count"] = txnCount;
std::lock_guard lk(m_); std::lock_guard lk(m_);
for (auto const& session: streamSubscribers_[Ledgers]) for (auto const& session : streamSubscribers_[Ledgers])
session->send(boost::json::serialize(pubMsg)); session->send(boost::json::serialize(pubMsg));
} }
@@ -75,18 +76,35 @@ SubscriptionManager::unsubAccount(
accountSubscribers_[account].erase(session); 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 void
SubscriptionManager::pubTransaction( SubscriptionManager::pubTransaction(
Backend::TransactionAndMetadata const& blob, Backend::TransactionAndMetadata const& blobs,
std::uint32_t seq) std::uint32_t seq)
{ {
std::lock_guard lk(m_); std::lock_guard lk(m_);
auto [tx, meta] = deserializeTxPlusMeta(blob, seq); auto [tx, meta] = RPC::deserializeTxPlusMeta(blobs, seq);
boost::json::object pubMsg; boost::json::object pubMsg;
pubMsg["transaction"] = toJson(*tx); pubMsg["transaction"] = RPC::toJson(*tx);
pubMsg["meta"] = toJson(*meta); pubMsg["meta"] = RPC::toJson(*meta);
for (auto const& session : streamSubscribers_[Transactions]) for (auto const& session : streamSubscribers_[Transactions])
session->send(boost::json::serialize(pubMsg)); session->send(boost::json::serialize(pubMsg));
@@ -108,7 +126,7 @@ SubscriptionManager::forwardProposedTransaction(
session->send(boost::json::serialize(response)); session->send(boost::json::serialize(response));
auto transaction = response.at("transaction").as_object(); auto transaction = response.at("transaction").as_object();
auto accounts = getAccountsFromTransaction(transaction); auto accounts = RPC::getAccountsFromTransaction(transaction);
for (ripple::AccountID const& account : accounts) for (ripple::AccountID const& account : accounts)
for (auto const& session : accountProposedSubscribers_[account]) for (auto const& session : accountProposedSubscribers_[account])
@@ -153,17 +171,17 @@ SubscriptionManager::clearSession(WsBase* s)
std::lock_guard lk(m_); std::lock_guard lk(m_);
// need the == operator. No-op delete // need the == operator. No-op delete
std::shared_ptr<WsBase> targetSession(s, [](WsBase*){}); std::shared_ptr<WsBase> targetSession(s, [](WsBase*) {});
for(auto& stream : streamSubscribers_) for (auto& stream : streamSubscribers_)
stream.erase(targetSession); stream.erase(targetSession);
for(auto& [account, subscribers] : accountSubscribers_) for (auto& [account, subscribers] : accountSubscribers_)
{ {
if (subscribers.find(targetSession) != subscribers.end()) if (subscribers.find(targetSession) != subscribers.end())
accountSubscribers_[account].erase(targetSession); accountSubscribers_[account].erase(targetSession);
} }
for(auto& [account, subscribers] : accountProposedSubscribers_) for (auto& [account, subscribers] : accountProposedSubscribers_)
{ {
if (subscribers.find(targetSession) != subscribers.end()) if (subscribers.find(targetSession) != subscribers.end())
accountProposedSubscribers_[account].erase(targetSession); accountProposedSubscribers_[account].erase(targetSession);

View File

@@ -22,7 +22,6 @@
#include <backend/BackendInterface.h> #include <backend/BackendInterface.h>
#include <memory> #include <memory>
#include <backend/BackendInterface.h>
class WsBase; class WsBase;
@@ -37,12 +36,12 @@ class SubscriptionManager
finalEntry finalEntry
}; };
std::mutex m_; std::mutex m_;
std::array<subscriptions, finalEntry> streamSubscribers_; std::array<subscriptions, finalEntry> streamSubscribers_;
std::unordered_map<ripple::AccountID, subscriptions> accountSubscribers_; std::unordered_map<ripple::AccountID, subscriptions> accountSubscribers_;
std::unordered_map<ripple::AccountID, subscriptions> std::unordered_map<ripple::AccountID, subscriptions>
accountProposedSubscribers_; accountProposedSubscribers_;
std::unordered_map<ripple::Book, subscriptions> bookSubscribers_;
public: public:
static std::shared_ptr<SubscriptionManager> static std::shared_ptr<SubscriptionManager>
@@ -72,7 +71,7 @@ public:
void void
pubTransaction( pubTransaction(
Backend::TransactionAndMetadata const& blob, Backend::TransactionAndMetadata const& blobs,
std::uint32_t seq); std::uint32_t seq);
void void
@@ -85,6 +84,12 @@ public:
ripple::AccountID const& account, ripple::AccountID const& account,
std::shared_ptr<WsBase>& session); 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 void
forwardProposedTransaction(boost::json::object const& response); forwardProposedTransaction(boost::json::object const& response);

View File

@@ -73,7 +73,7 @@ TEST(BackendTest, Basic)
return uint.fromVoid((void const*)bin.data()); return uint.fromVoid((void const*)bin.data());
}; };
auto ledgerInfoToBinaryString = [](auto const& info) { auto ledgerInfoToBinaryString = [](auto const& info) {
auto blob = ledgerInfoToBlob(info); auto blob = RPC::ledgerInfoToBlob(info);
std::string strBlob; std::string strBlob;
for (auto c : blob) for (auto c : blob)
{ {
@@ -105,7 +105,8 @@ TEST(BackendTest, Basic)
auto retLgr = backend->fetchLedgerBySequence(lgrInfo.seq); auto retLgr = backend->fetchLedgerBySequence(lgrInfo.seq);
EXPECT_TRUE(retLgr.has_value()); EXPECT_TRUE(retLgr.has_value());
EXPECT_EQ(retLgr->seq, lgrInfo.seq); EXPECT_EQ(retLgr->seq, lgrInfo.seq);
EXPECT_EQ(ledgerInfoToBlob(lgrInfo), ledgerInfoToBlob(*retLgr)); EXPECT_EQ(
RPC::ledgerInfoToBlob(lgrInfo), RPC::ledgerInfoToBlob(*retLgr));
} }
EXPECT_FALSE( EXPECT_FALSE(
@@ -138,12 +139,20 @@ TEST(BackendTest, Basic)
auto retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq); auto retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq);
EXPECT_TRUE(retLgr.has_value()); EXPECT_TRUE(retLgr.has_value());
EXPECT_EQ(retLgr->seq, lgrInfoNext.seq); EXPECT_EQ(retLgr->seq, lgrInfoNext.seq);
EXPECT_EQ(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfoNext)); EXPECT_EQ(
EXPECT_NE(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfoOld)); RPC::ledgerInfoToBlob(*retLgr),
RPC::ledgerInfoToBlob(lgrInfoNext));
EXPECT_NE(
RPC::ledgerInfoToBlob(*retLgr),
RPC::ledgerInfoToBlob(lgrInfoOld));
retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq - 1); retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq - 1);
EXPECT_EQ(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfoOld)); EXPECT_EQ(
RPC::ledgerInfoToBlob(*retLgr),
RPC::ledgerInfoToBlob(lgrInfoOld));
EXPECT_NE(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfoNext)); EXPECT_NE(
RPC::ledgerInfoToBlob(*retLgr),
RPC::ledgerInfoToBlob(lgrInfoNext));
retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq - 2); retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq - 2);
EXPECT_FALSE(backend->fetchLedgerBySequence(lgrInfoNext.seq - 2) EXPECT_FALSE(backend->fetchLedgerBySequence(lgrInfoNext.seq - 2)
.has_value()); .has_value());
@@ -265,7 +274,9 @@ TEST(BackendTest, Basic)
EXPECT_EQ(rng->maxSequence, lgrInfoNext.seq); EXPECT_EQ(rng->maxSequence, lgrInfoNext.seq);
auto retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq); auto retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq);
EXPECT_TRUE(retLgr); EXPECT_TRUE(retLgr);
EXPECT_EQ(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfoNext)); EXPECT_EQ(
RPC::ledgerInfoToBlob(*retLgr),
RPC::ledgerInfoToBlob(lgrInfoNext));
auto txns = backend->fetchAllTransactionsInLedger(lgrInfoNext.seq); auto txns = backend->fetchAllTransactionsInLedger(lgrInfoNext.seq);
EXPECT_EQ(txns.size(), 1); EXPECT_EQ(txns.size(), 1);
EXPECT_STREQ( EXPECT_STREQ(
@@ -332,7 +343,9 @@ TEST(BackendTest, Basic)
EXPECT_EQ(rng->maxSequence, lgrInfoNext.seq); EXPECT_EQ(rng->maxSequence, lgrInfoNext.seq);
auto retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq); auto retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq);
EXPECT_TRUE(retLgr); EXPECT_TRUE(retLgr);
EXPECT_EQ(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfoNext)); EXPECT_EQ(
RPC::ledgerInfoToBlob(*retLgr),
RPC::ledgerInfoToBlob(lgrInfoNext));
auto txns = backend->fetchAllTransactionsInLedger(lgrInfoNext.seq); auto txns = backend->fetchAllTransactionsInLedger(lgrInfoNext.seq);
EXPECT_EQ(txns.size(), 0); EXPECT_EQ(txns.size(), 0);
@@ -466,9 +479,7 @@ TEST(BackendTest, Basic)
if (isOffer(obj.data())) if (isOffer(obj.data()))
bookDir = getBook(obj); bookDir = getBook(obj);
backend->writeLedgerObject( backend->writeLedgerObject(
std::move(key), std::move(key), lgrInfo.seq, std::move(obj));
lgrInfo.seq,
std::move(obj));
} }
backend->writeAccountTransactions(std::move(accountTx)); backend->writeAccountTransactions(std::move(accountTx));
@@ -486,10 +497,12 @@ TEST(BackendTest, Basic)
EXPECT_GE(rng->maxSequence, seq); EXPECT_GE(rng->maxSequence, seq);
auto retLgr = backend->fetchLedgerBySequence(seq); auto retLgr = backend->fetchLedgerBySequence(seq);
EXPECT_TRUE(retLgr); EXPECT_TRUE(retLgr);
EXPECT_EQ(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfo)); EXPECT_EQ(
RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfo));
// retLgr = backend->fetchLedgerByHash(lgrInfo.hash); // retLgr = backend->fetchLedgerByHash(lgrInfo.hash);
// EXPECT_TRUE(retLgr); // EXPECT_TRUE(retLgr);
// EXPECT_EQ(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfo)); // EXPECT_EQ(RPC::ledgerInfoToBlob(*retLgr),
// RPC::ledgerInfoToBlob(lgrInfo));
auto retTxns = backend->fetchAllTransactionsInLedger(seq); auto retTxns = backend->fetchAllTransactionsInLedger(seq);
for (auto [hash, txn, meta] : txns) for (auto [hash, txn, meta] : txns)
{ {