Add NFT RPC infrastructure

This commit is contained in:
Devon White
2022-02-24 15:22:14 -06:00
committed by CJ Cobb
parent a72aa73afe
commit 9939f6e6f4
30 changed files with 1095 additions and 734 deletions

View File

@@ -62,6 +62,8 @@ target_sources(clio PRIVATE
src/rpc/handlers/AccountTx.cpp src/rpc/handlers/AccountTx.cpp
# Dex # Dex
src/rpc/handlers/BookOffers.cpp src/rpc/handlers/BookOffers.cpp
# NFT
src/rpc/handlers/NFTOffers.cpp
# Payment Channel # Payment Channel
src/rpc/handlers/ChannelAuthorize.cpp src/rpc/handlers/ChannelAuthorize.cpp
src/rpc/handlers/ChannelVerify.cpp src/rpc/handlers/ChannelVerify.cpp

View File

@@ -259,7 +259,8 @@ BackendInterface::fetchLedgerPage(
ripple::uint256 const& curCursor = keys.size() ? keys.back() ripple::uint256 const& curCursor = keys.size() ? keys.back()
: cursor ? *cursor : cursor ? *cursor
: firstKey; : firstKey;
uint32_t seq = outOfOrder ? range->maxSequence : ledgerSequence; std::uint32_t const seq =
outOfOrder ? range->maxSequence : ledgerSequence;
auto succ = fetchSuccessorKey(curCursor, seq, yield); auto succ = fetchSuccessorKey(curCursor, seq, yield);
if (!succ) if (!succ)
reachedEnd = true; reachedEnd = true;

View File

@@ -21,6 +21,9 @@ doAccountCurrencies(Context const& context);
Result Result
doAccountLines(Context const& context); doAccountLines(Context const& context);
Result
doAccountNFTs(Context const& context);
Result Result
doAccountObjects(Context const& context); doAccountObjects(Context const& context);
@@ -45,6 +48,13 @@ doChannelVerify(Context const& context);
Result Result
doBookOffers(Context const& context); doBookOffers(Context const& context);
// NFT methods
Result
doNFTBuyOffers(Context const& context);
Result
doNFTSellOffers(Context const& context);
// ledger methods // ledger methods
Result Result
doLedger(Context const& context); doLedger(Context const& context);

View File

@@ -124,6 +124,7 @@ static std::unordered_map<std::string, std::function<Result(Context const&)>>
{"account_currencies", &doAccountCurrencies}, {"account_currencies", &doAccountCurrencies},
{"account_info", &doAccountInfo}, {"account_info", &doAccountInfo},
{"account_lines", &doAccountLines}, {"account_lines", &doAccountLines},
{"account_nfts", &doAccountNFTs},
{"account_objects", &doAccountObjects}, {"account_objects", &doAccountObjects},
{"account_offers", &doAccountOffers}, {"account_offers", &doAccountOffers},
{"account_tx", &doAccountTx}, {"account_tx", &doAccountTx},
@@ -137,6 +138,8 @@ static std::unordered_map<std::string, std::function<Result(Context const&)>>
{"ledger_entry", &doLedgerEntry}, {"ledger_entry", &doLedgerEntry},
{"ledger_range", &doLedgerRange}, {"ledger_range", &doLedgerRange},
{"ledger_data", &doLedgerData}, {"ledger_data", &doLedgerData},
{"nft_buy_offers", &doNFTBuyOffers},
{"nft_sell_offers", &doNFTSellOffers},
{"subscribe", &doSubscribe}, {"subscribe", &doSubscribe},
{"server_info", &doServerInfo}, {"server_info", &doServerInfo},
{"unsubscribe", &doUnsubscribe}, {"unsubscribe", &doUnsubscribe},

View File

@@ -107,6 +107,7 @@ struct Status
: error(error_), message(message_) : error(error_), message(message_)
{ {
} }
Status(Error error_, std::string strCode_, std::string message_) Status(Error error_, std::string strCode_, std::string message_)
: error(error_), strCode(strCode_), message(message_) : error(error_), strCode(strCode_), message(message_)
{ {

View File

@@ -13,6 +13,7 @@ getBool(boost::json::object const& request, std::string const& field)
else else
throw InvalidParamsError("Invalid field " + field + ", not bool."); throw InvalidParamsError("Invalid field " + field + ", not bool.");
} }
bool bool
getBool( getBool(
boost::json::object const& request, boost::json::object const& request,
@@ -24,6 +25,7 @@ getBool(
else else
return dfault; return dfault;
} }
bool bool
getRequiredBool(boost::json::object const& request, std::string const& field) getRequiredBool(boost::json::object const& request, std::string const& field)
{ {
@@ -152,6 +154,7 @@ getString(boost::json::object const& request, std::string const& field)
else else
throw InvalidParamsError("Invalid field " + field + ", not string."); throw InvalidParamsError("Invalid field " + field + ", not string.");
} }
std::string std::string
getRequiredString(boost::json::object const& request, std::string const& field) getRequiredString(boost::json::object const& request, std::string const& field)
{ {
@@ -160,6 +163,7 @@ getRequiredString(boost::json::object const& request, std::string const& field)
else else
throw InvalidParamsError("Missing field " + field); throw InvalidParamsError("Missing field " + field);
} }
std::string std::string
getString( getString(
boost::json::object const& request, boost::json::object const& request,
@@ -172,6 +176,112 @@ getString(
return dfault; return dfault;
} }
Status
getHexMarker(boost::json::object const& request, ripple::uint256& marker)
{
if (request.contains(JS(marker)))
{
if (!request.at(JS(marker)).is_string())
return Status{Error::rpcINVALID_PARAMS, "markerNotString"};
if (!marker.parseHex(request.at(JS(marker)).as_string().c_str()))
return Status{Error::rpcINVALID_PARAMS, "malformedMarker"};
}
return {};
}
Status
getLimit(boost::json::object const& request, std::uint32_t& limit)
{
if (request.contains(JS(limit)))
{
if (!request.at(JS(limit)).is_int64())
return Status{Error::rpcINVALID_PARAMS, "limitNotInt"};
limit = request.at(JS(limit)).as_int64();
if (limit <= 0)
return Status{Error::rpcINVALID_PARAMS, "limitNotPositive"};
}
return {};
}
Status
getAccount(
boost::json::object const& request,
ripple::AccountID& account,
boost::string_view const& field,
bool required)
{
if (!request.contains(field))
{
if (required)
return Status{
Error::rpcINVALID_PARAMS, field.to_string() + "Missing"};
return {};
}
if (!request.at(field).is_string())
return Status{
Error::rpcINVALID_PARAMS, field.to_string() + "NotString"};
if (auto a = accountFromStringStrict(request.at(field).as_string().c_str());
a)
{
account = a.value();
return {};
}
return Status{Error::rpcINVALID_PARAMS, field.to_string() + "Malformed"};
}
Status
getAccount(boost::json::object const& request, ripple::AccountID& accountId)
{
return getAccount(request, accountId, JS(account), true);
}
Status
getAccount(
boost::json::object const& request,
ripple::AccountID& destAccount,
boost::string_view const& field)
{
return getAccount(request, destAccount, field, false);
}
Status
getTaker(boost::json::object const& request, ripple::AccountID& takerID)
{
if (request.contains(JS(taker)))
{
auto parsed = parseTaker(request.at(JS(taker)));
if (auto status = std::get_if<Status>(&parsed))
return *status;
else
takerID = std::get<ripple::AccountID>(parsed);
}
return {};
}
Status
getChannelId(boost::json::object const& request, ripple::uint256& channelId)
{
if (!request.contains(JS(channel_id)))
return Status{Error::rpcINVALID_PARAMS, "missingChannelID"};
if (!request.at(JS(channel_id)).is_string())
return Status{Error::rpcINVALID_PARAMS, "channelIDNotString"};
if (!channelId.parseHex(request.at(JS(channel_id)).as_string().c_str()))
return Status{Error::rpcCHANNEL_MALFORMED, "malformedChannelID"};
return {};
}
std::optional<ripple::STAmount> std::optional<ripple::STAmount>
getDeliveredAmount( getDeliveredAmount(
std::shared_ptr<ripple::STTx const> const& txn, std::shared_ptr<ripple::STTx const> const& txn,
@@ -537,11 +647,35 @@ traverseOwnedNodes(
if (!parsedCursor) if (!parsedCursor)
return Status(ripple::rpcINVALID_PARAMS, "Malformed cursor"); return Status(ripple::rpcINVALID_PARAMS, "Malformed cursor");
auto cursor = AccountCursor({beast::zero, 0});
auto [hexCursor, startHint] = *parsedCursor; auto [hexCursor, startHint] = *parsedCursor;
auto const rootIndex = ripple::keylet::ownerDir(accountID); return traverseOwnedNodes(
backend,
ripple::keylet::ownerDir(accountID),
hexCursor,
startHint,
sequence,
limit,
jsonCursor,
yield,
atOwnedNode);
}
std::variant<Status, AccountCursor>
traverseOwnedNodes(
BackendInterface const& backend,
ripple::Keylet const& owner,
ripple::uint256 const& hexMarker,
std::uint32_t const startHint,
std::uint32_t sequence,
std::uint32_t limit,
std::optional<std::string> jsonCursor,
boost::asio::yield_context& yield,
std::function<void(ripple::SLE)> atOwnedNode)
{
auto cursor = AccountCursor({beast::zero, 0});
auto const rootIndex = owner;
auto currentIndex = rootIndex; auto currentIndex = rootIndex;
std::vector<ripple::uint256> keys; std::vector<ripple::uint256> keys;
@@ -550,7 +684,7 @@ traverseOwnedNodes(
auto start = std::chrono::system_clock::now(); auto start = std::chrono::system_clock::now();
// If startAfter is not zero try jumping to that page using the hint // If startAfter is not zero try jumping to that page using the hint
if (hexCursor.isNonZero()) if (hexMarker.isNonZero())
{ {
auto const hintIndex = ripple::keylet::page(rootIndex, startHint); auto const hintIndex = ripple::keylet::page(rootIndex, startHint);
auto hintDir = auto hintDir =
@@ -563,7 +697,7 @@ traverseOwnedNodes(
for (auto const& key : sle.getFieldV256(ripple::sfIndexes)) for (auto const& key : sle.getFieldV256(ripple::sfIndexes))
{ {
if (key == hexCursor) if (key == hexMarker)
{ {
// We found the hint, we can start here // We found the hint, we can start here
currentIndex = hintIndex; currentIndex = hintIndex;
@@ -589,7 +723,7 @@ traverseOwnedNodes(
{ {
if (!found) if (!found)
{ {
if (key == hexCursor) if (key == hexMarker)
found = true; found = true;
} }
else else
@@ -678,6 +812,23 @@ traverseOwnedNodes(
return AccountCursor({beast::zero, 0}); return AccountCursor({beast::zero, 0});
} }
std::shared_ptr<ripple::SLE const>
read(
ripple::Keylet const& keylet,
ripple::LedgerInfo const& lgrInfo,
Context const& context)
{
if (auto const blob = context.backend->fetchLedgerObject(
keylet.key, lgrInfo.seq, context.yield);
blob)
{
return std::make_shared<ripple::SLE const>(
ripple::SerialIter{blob->data(), blob->size()}, keylet.key);
}
return nullptr;
}
std::optional<ripple::Seed> std::optional<ripple::Seed>
parseRippleLibSeed(boost::json::value const& value) parseRippleLibSeed(boost::json::value const& value)
{ {
@@ -1280,6 +1431,7 @@ parseBook(boost::json::object const& request)
return ripple::Book{{pay_currency, pay_issuer}, {get_currency, get_issuer}}; return ripple::Book{{pay_currency, pay_issuer}, {get_currency, get_issuer}};
} }
std::variant<Status, ripple::AccountID> std::variant<Status, ripple::AccountID>
parseTaker(boost::json::value const& taker) parseTaker(boost::json::value const& taker)
{ {

View File

@@ -14,6 +14,13 @@
#include <backend/BackendInterface.h> #include <backend/BackendInterface.h>
#include <rpc/RPC.h> #include <rpc/RPC.h>
// Useful macro for borrowing from ripple::jss
// static strings. (J)son (S)trings
#define JS(x) ripple::jss::x.c_str()
// Access (SF)ield name (S)trings
#define SFS(x) ripple::x.jsonName.c_str()
namespace RPC { namespace RPC {
std::optional<ripple::AccountID> std::optional<ripple::AccountID>
accountFromStringStrict(std::string const& account); accountFromStringStrict(std::string const& account);
@@ -93,6 +100,24 @@ traverseOwnedNodes(
boost::asio::yield_context& yield, boost::asio::yield_context& yield,
std::function<void(ripple::SLE)> atOwnedNode); std::function<void(ripple::SLE)> atOwnedNode);
std::variant<Status, AccountCursor>
traverseOwnedNodes(
BackendInterface const& backend,
ripple::Keylet const& owner,
ripple::uint256 const& hexMarker,
std::uint32_t const startHint,
std::uint32_t sequence,
std::uint32_t limit,
std::optional<std::string> jsonCursor,
boost::asio::yield_context& yield,
std::function<void(ripple::SLE)> atOwnedNode);
std::shared_ptr<ripple::SLE const>
read(
ripple::Keylet const& keylet,
ripple::LedgerInfo const& lgrInfo,
Context const& context);
std::variant<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);
@@ -200,5 +225,27 @@ getString(
boost::json::object const& request, boost::json::object const& request,
std::string const& field, std::string const& field,
std::string dfault); std::string dfault);
Status
getHexMarker(boost::json::object const& request, ripple::uint256& marker);
Status
getLimit(boost::json::object const& request, std::uint32_t& limit);
Status
getAccount(boost::json::object const& request, ripple::AccountID& accountId);
Status
getAccount(
boost::json::object const& request,
ripple::AccountID& destAccount,
boost::string_view const& field);
Status
getTaker(boost::json::object const& request, ripple::AccountID& takerID);
Status
getChannelId(boost::json::object const& request, ripple::uint256& channelId);
} // namespace RPC } // namespace RPC
#endif #endif

View File

@@ -17,27 +17,27 @@ void
addChannel(boost::json::array& jsonLines, ripple::SLE const& line) addChannel(boost::json::array& jsonLines, ripple::SLE const& line)
{ {
boost::json::object jDst; boost::json::object jDst;
jDst["channel_id"] = ripple::to_string(line.key()); jDst[JS(channel_id)] = ripple::to_string(line.key());
jDst["account"] = ripple::to_string(line.getAccountID(ripple::sfAccount)); jDst[JS(account)] = ripple::to_string(line.getAccountID(ripple::sfAccount));
jDst["destination_account"] = jDst[JS(destination_account)] =
ripple::to_string(line.getAccountID(ripple::sfDestination)); ripple::to_string(line.getAccountID(ripple::sfDestination));
jDst["amount"] = line[ripple::sfAmount].getText(); jDst[JS(amount)] = line[ripple::sfAmount].getText();
jDst["balance"] = line[ripple::sfBalance].getText(); jDst[JS(balance)] = line[ripple::sfBalance].getText();
if (publicKeyType(line[ripple::sfPublicKey])) if (publicKeyType(line[ripple::sfPublicKey]))
{ {
ripple::PublicKey const pk(line[ripple::sfPublicKey]); ripple::PublicKey const pk(line[ripple::sfPublicKey]);
jDst["public_key"] = toBase58(ripple::TokenType::AccountPublic, pk); jDst[JS(public_key)] = toBase58(ripple::TokenType::AccountPublic, pk);
jDst["public_key_hex"] = strHex(pk); jDst[JS(public_key_hex)] = strHex(pk);
} }
jDst["settle_delay"] = line[ripple::sfSettleDelay]; jDst[JS(settle_delay)] = line[ripple::sfSettleDelay];
if (auto const& v = line[~ripple::sfExpiration]) if (auto const& v = line[~ripple::sfExpiration])
jDst["expiration"] = *v; jDst[JS(expiration)] = *v;
if (auto const& v = line[~ripple::sfCancelAfter]) if (auto const& v = line[~ripple::sfCancelAfter])
jDst["cancel_after"] = *v; jDst[JS(cancel_after)] = *v;
if (auto const& v = line[~ripple::sfSourceTag]) if (auto const& v = line[~ripple::sfSourceTag])
jDst["source_tag"] = *v; jDst[JS(source_tag)] = *v;
if (auto const& v = line[~ripple::sfDestinationTag]) if (auto const& v = line[~ripple::sfDestinationTag])
jDst["destination_tag"] = *v; jDst[JS(destination_tag)] = *v;
jsonLines.push_back(jDst); jsonLines.push_back(jDst);
} }
@@ -54,60 +54,38 @@ doAccountChannels(Context const& context)
auto lgrInfo = std::get<ripple::LedgerInfo>(v); auto lgrInfo = std::get<ripple::LedgerInfo>(v);
if (!request.contains("account")) ripple::AccountID accountID;
return Status{Error::rpcINVALID_PARAMS, "missingAccount"}; if (auto const status = getAccount(request, accountID); status)
return status;
if (!request.at("account").is_string()) ripple::AccountID destAccount;
return Status{Error::rpcINVALID_PARAMS, "accountNotString"}; if (auto const status =
getAccount(request, destAccount, JS(destination_account));
auto accountID = status)
accountFromStringStrict(request.at("account").as_string().c_str()); return status;
if (!accountID)
return Status{Error::rpcINVALID_PARAMS, "malformedAccount"};
std::optional<ripple::AccountID> destAccount = {};
if (request.contains("destination_account"))
{
if (!request.at("destination_account").is_string())
return Status{Error::rpcINVALID_PARAMS, "destinationNotString"};
destAccount = accountFromStringStrict(
request.at("destination_account").as_string().c_str());
if (!destAccount)
return Status{Error::rpcINVALID_PARAMS, "destinationMalformed"};
}
std::uint32_t limit = 200; std::uint32_t limit = 200;
if (request.contains("limit")) if (auto const status = getLimit(request, limit); status)
{ return status;
if (!request.at("limit").is_int64())
return Status{Error::rpcINVALID_PARAMS, "limitNotInt"};
limit = request.at("limit").as_int64();
if (limit <= 0)
return Status{Error::rpcINVALID_PARAMS, "limitNotPositive"};
}
std::optional<std::string> marker = {}; std::optional<std::string> marker = {};
if (request.contains("marker")) if (request.contains(JS(marker)))
{ {
if (!request.at("marker").is_string()) if (!request.at(JS(marker)).is_string())
return Status{Error::rpcINVALID_PARAMS, "markerNotString"}; return Status{Error::rpcINVALID_PARAMS, "markerNotString"};
marker = request.at("marker").as_string().c_str(); marker = request.at(JS(marker)).as_string().c_str();
} }
response["account"] = ripple::to_string(*accountID); response[JS(account)] = ripple::to_string(accountID);
response["channels"] = boost::json::value(boost::json::array_kind); response[JS(channels)] = boost::json::value(boost::json::array_kind);
boost::json::array& jsonChannels = response.at("channels").as_array(); boost::json::array& jsonChannels = response.at(JS(channels)).as_array();
auto const addToResponse = [&](ripple::SLE const& sle) { auto const addToResponse = [&](ripple::SLE const& sle) {
if (sle.getType() == ripple::ltPAYCHAN && if (sle.getType() == ripple::ltPAYCHAN &&
sle.getAccountID(ripple::sfAccount) == *accountID && sle.getAccountID(ripple::sfAccount) == accountID &&
(!destAccount || (!destAccount ||
*destAccount == sle.getAccountID(ripple::sfDestination))) destAccount == sle.getAccountID(ripple::sfDestination)))
{ {
if (limit-- == 0) if (limit-- == 0)
{ {
@@ -122,23 +100,23 @@ doAccountChannels(Context const& context)
auto next = traverseOwnedNodes( auto next = traverseOwnedNodes(
*context.backend, *context.backend,
*accountID, accountID,
lgrInfo.seq, lgrInfo.seq,
limit, limit,
marker, marker,
context.yield, context.yield,
addToResponse); addToResponse);
response["ledger_hash"] = ripple::strHex(lgrInfo.hash); response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash);
response["ledger_index"] = lgrInfo.seq; response[JS(ledger_index)] = lgrInfo.seq;
if (auto status = std::get_if<RPC::Status>(&next)) if (auto status = std::get_if<RPC::Status>(&next))
return *status; return *status;
auto nextCursor = std::get<RPC::AccountCursor>(next); auto nextMarker = std::get<RPC::AccountCursor>(next);
if (nextCursor.isNonZero()) if (nextMarker.isNonZero())
response["marker"] = nextCursor.toString(); response[JS(marker)] = nextMarker.toString();
return response; return response;
} }

View File

@@ -24,17 +24,9 @@ doAccountCurrencies(Context const& context)
auto lgrInfo = std::get<ripple::LedgerInfo>(v); auto lgrInfo = std::get<ripple::LedgerInfo>(v);
if (!request.contains("account")) ripple::AccountID accountID;
return Status{Error::rpcINVALID_PARAMS, "missingAccount"}; if (auto const status = getAccount(request, accountID); status)
return status;
if (!request.at("account").is_string())
return Status{Error::rpcINVALID_PARAMS, "accountNotString"};
auto accountID =
accountFromStringStrict(request.at("account").as_string().c_str());
if (!accountID)
return Status{Error::rpcINVALID_PARAMS, "malformedAccount"};
std::set<std::string> send, receive; std::set<std::string> send, receive;
auto const addToResponse = [&](ripple::SLE const& sle) { auto const addToResponse = [&](ripple::SLE const& sle) {
@@ -61,26 +53,26 @@ doAccountCurrencies(Context const& context)
traverseOwnedNodes( traverseOwnedNodes(
*context.backend, *context.backend,
*accountID, accountID,
lgrInfo.seq, lgrInfo.seq,
std::numeric_limits<std::uint32_t>::max(), std::numeric_limits<std::uint32_t>::max(),
{}, {},
context.yield, context.yield,
addToResponse); addToResponse);
response["ledger_hash"] = ripple::strHex(lgrInfo.hash); response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash);
response["ledger_index"] = lgrInfo.seq; response[JS(ledger_index)] = lgrInfo.seq;
response["receive_currencies"] = response[JS(receive_currencies)] =
boost::json::value(boost::json::array_kind); boost::json::value(boost::json::array_kind);
boost::json::array& jsonReceive = boost::json::array& jsonReceive =
response.at("receive_currencies").as_array(); response.at(JS(receive_currencies)).as_array();
for (auto const& currency : receive) for (auto const& currency : receive)
jsonReceive.push_back(currency.c_str()); jsonReceive.push_back(currency.c_str());
response["send_currencies"] = boost::json::value(boost::json::array_kind); response[JS(send_currencies)] = boost::json::value(boost::json::array_kind);
boost::json::array& jsonSend = response.at("send_currencies").as_array(); boost::json::array& jsonSend = response.at(JS(send_currencies)).as_array();
for (auto const& currency : send) for (auto const& currency : send)
jsonSend.push_back(currency.c_str()); jsonSend.push_back(currency.c_str());

View File

@@ -29,10 +29,10 @@ doAccountInfo(Context const& context)
boost::json::object response = {}; boost::json::object response = {};
std::string strIdent; std::string strIdent;
if (request.contains("account")) if (request.contains(JS(account)))
strIdent = request.at("account").as_string().c_str(); strIdent = request.at(JS(account)).as_string().c_str();
else if (request.contains("ident")) else if (request.contains(JS(ident)))
strIdent = request.at("ident").as_string().c_str(); strIdent = request.at(JS(ident)).as_string().c_str();
else else
return Status{Error::rpcACT_MALFORMED}; return Status{Error::rpcACT_MALFORMED};
@@ -71,18 +71,18 @@ doAccountInfo(Context const& context)
return Status{Error::rpcDB_DESERIALIZATION}; return Status{Error::rpcDB_DESERIALIZATION};
// if (!binary) // if (!binary)
// response["account_data"] = getJson(sle); // response[JS(account_data)] = getJson(sle);
// else // else
// response["account_data"] = ripple::strHex(*dbResponse); // response[JS(account_data)] = ripple::strHex(*dbResponse);
// response["db_time"] = time; // response[JS(db_time)] = time;
response["account_data"] = toJson(sle); response[JS(account_data)] = toJson(sle);
response["ledger_hash"] = ripple::strHex(lgrInfo.hash); response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash);
response["ledger_index"] = lgrInfo.seq; response[JS(ledger_index)] = lgrInfo.seq;
// Return SignerList(s) if that is requested. // Return SignerList(s) if that is requested.
if (request.contains("signer_lists") && if (request.contains(JS(signer_lists)) &&
request.at("signer_lists").as_bool()) request.at(JS(signer_lists)).as_bool())
{ {
// We put the SignerList in an array because of an anticipated // We put the SignerList in an array because of an anticipated
// future when we support multiple signer lists on one account. // future when we support multiple signer lists on one account.
@@ -104,7 +104,7 @@ doAccountInfo(Context const& context)
signerList.push_back(toJson(sleSigners)); signerList.push_back(toJson(sleSigners));
} }
response["account_data"].as_object()["signer_lists"] = response[JS(account_data)].as_object()[JS(signer_lists)] =
std::move(signerList); std::move(signerList);
} }

View File

@@ -64,25 +64,25 @@ addLine(
ripple::STAmount const& saLimitPeer(lineLimitPeer); ripple::STAmount const& saLimitPeer(lineLimitPeer);
boost::json::object jPeer; boost::json::object jPeer;
jPeer["account"] = ripple::to_string(lineAccountIDPeer); jPeer[JS(account)] = ripple::to_string(lineAccountIDPeer);
jPeer["balance"] = saBalance.getText(); jPeer[JS(balance)] = saBalance.getText();
jPeer["currency"] = ripple::to_string(saBalance.issue().currency); jPeer[JS(currency)] = ripple::to_string(saBalance.issue().currency);
jPeer["limit"] = saLimit.getText(); jPeer[JS(limit)] = saLimit.getText();
jPeer["limit_peer"] = saLimitPeer.getText(); jPeer[JS(limit_peer)] = saLimitPeer.getText();
jPeer["quality_in"] = lineQualityIn; jPeer[JS(quality_in)] = lineQualityIn;
jPeer["quality_out"] = lineQualityOut; jPeer[JS(quality_out)] = lineQualityOut;
if (lineAuth) if (lineAuth)
jPeer["authorized"] = true; jPeer[JS(authorized)] = true;
if (lineAuthPeer) if (lineAuthPeer)
jPeer["peer_authorized"] = true; jPeer[JS(peer_authorized)] = true;
if (lineNoRipple || !lineDefaultRipple) if (lineNoRipple || !lineDefaultRipple)
jPeer["no_ripple"] = lineNoRipple; jPeer[JS(no_ripple)] = lineNoRipple;
if (lineNoRipple || !lineDefaultRipple) if (lineNoRipple || !lineDefaultRipple)
jPeer["no_ripple_peer"] = lineNoRipplePeer; jPeer[JS(no_ripple_peer)] = lineNoRipplePeer;
if (lineFreeze) if (lineFreeze)
jPeer["freeze"] = true; jPeer[JS(freeze)] = true;
if (lineFreezePeer) if (lineFreezePeer)
jPeer["freeze_peer"] = true; jPeer[JS(freeze_peer)] = true;
jsonLines.push_back(jPeer); jsonLines.push_back(jPeer);
} }
@@ -99,80 +99,56 @@ doAccountLines(Context const& context)
auto lgrInfo = std::get<ripple::LedgerInfo>(v); auto lgrInfo = std::get<ripple::LedgerInfo>(v);
if (!request.contains("account")) ripple::AccountID accountID;
return Status{Error::rpcINVALID_PARAMS, "missingAccount"}; if (auto const status = getAccount(request, accountID); status)
return status;
if (!request.at("account").is_string()) ripple::AccountID peerAccount;
return Status{Error::rpcINVALID_PARAMS, "accountNotString"}; if (auto const status = getAccount(request, peerAccount, JS(peer)); status)
return status;
auto accountID =
accountFromStringStrict(request.at("account").as_string().c_str());
if (!accountID)
return Status{Error::rpcINVALID_PARAMS, "malformedAccount"};
std::optional<ripple::AccountID> peerAccount;
if (request.contains("peer"))
{
if (!request.at("peer").is_string())
return Status{Error::rpcINVALID_PARAMS, "peerNotString"};
peerAccount =
accountFromStringStrict(request.at("peer").as_string().c_str());
if (!peerAccount)
return Status{Error::rpcINVALID_PARAMS, "peerMalformed"};
}
std::uint32_t limit = 200; std::uint32_t limit = 200;
if (request.contains("limit")) if (auto const status = getLimit(request, limit); status)
{ return status;
if (!request.at("limit").is_int64())
return Status{Error::rpcINVALID_PARAMS, "limitNotInt"};
limit = request.at("limit").as_int64(); std::optional<std::string> marker = {};
if (limit <= 0) if (request.contains(JS(marker)))
return Status{Error::rpcINVALID_PARAMS, "limitNotPositive"};
}
std::optional<std::string> cursor = {};
if (request.contains("marker"))
{ {
if (!request.at("marker").is_string()) if (!request.at(JS(marker)).is_string())
return Status{Error::rpcINVALID_PARAMS, "markerNotString"}; return Status{Error::rpcINVALID_PARAMS, "markerNotString"};
cursor = request.at("marker").as_string().c_str(); marker = request.at(JS(marker)).as_string().c_str();
} }
response["account"] = ripple::to_string(*accountID); response[JS(account)] = ripple::to_string(accountID);
response["ledger_hash"] = ripple::strHex(lgrInfo.hash); response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash);
response["ledger_index"] = lgrInfo.seq; response[JS(ledger_index)] = lgrInfo.seq;
response["lines"] = boost::json::value(boost::json::array_kind); response[JS(lines)] = boost::json::value(boost::json::array_kind);
boost::json::array& jsonLines = response.at("lines").as_array(); boost::json::array& jsonLines = response.at(JS(lines)).as_array();
auto const addToResponse = [&](ripple::SLE const& sle) -> void { auto const addToResponse = [&](ripple::SLE const& sle) -> void {
if (sle.getType() == ripple::ltRIPPLE_STATE) if (sle.getType() == ripple::ltRIPPLE_STATE)
{ {
addLine(jsonLines, sle, *accountID, peerAccount); addLine(jsonLines, sle, accountID, peerAccount);
} }
}; };
auto next = traverseOwnedNodes( auto next = traverseOwnedNodes(
*context.backend, *context.backend,
*accountID, accountID,
lgrInfo.seq, lgrInfo.seq,
limit, limit,
cursor, marker,
context.yield, context.yield,
addToResponse); addToResponse);
if (auto status = std::get_if<RPC::Status>(&next)) if (auto status = std::get_if<RPC::Status>(&next))
return *status; return *status;
auto nextCursor = std::get<RPC::AccountCursor>(next); auto nextMarker = std::get<RPC::AccountCursor>(next);
if (nextCursor.isNonZero()) if (nextMarker.isNonZero())
response["marker"] = nextCursor.toString(); response[JS(marker)] = nextMarker.toString();
return response; return response;
} }

View File

@@ -1,10 +1,12 @@
#include <ripple/app/ledger/Ledger.h> #include <ripple/app/ledger/Ledger.h>
#include <ripple/app/paths/TrustLine.h> #include <ripple/app/paths/TrustLine.h>
#include <ripple/app/tx/impl/details/NFTokenUtils.h>
#include <ripple/basics/StringUtilities.h> #include <ripple/basics/StringUtilities.h>
#include <ripple/protocol/ErrorCodes.h> #include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/Indexes.h> #include <ripple/protocol/Indexes.h>
#include <ripple/protocol/STLedgerEntry.h> #include <ripple/protocol/STLedgerEntry.h>
#include <ripple/protocol/jss.h> #include <ripple/protocol/jss.h>
#include <ripple/protocol/nftPageMask.h>
#include <boost/json.hpp> #include <boost/json.hpp>
#include <algorithm> #include <algorithm>
#include <rpc/RPCHelpers.h> #include <rpc/RPCHelpers.h>
@@ -23,7 +25,126 @@ std::unordered_map<std::string, ripple::LedgerEntryType> types{
{"escrow", ripple::ltESCROW}, {"escrow", ripple::ltESCROW},
{"deposit_preauth", ripple::ltDEPOSIT_PREAUTH}, {"deposit_preauth", ripple::ltDEPOSIT_PREAUTH},
{"check", ripple::ltCHECK}, {"check", ripple::ltCHECK},
}; {"nft_page", ripple::ltNFTOKEN_PAGE},
{"nft_offer", ripple::ltNFTOKEN_OFFER}};
Result
doAccountNFTs(Context const& context)
{
auto request = context.params;
boost::json::object response = {};
auto v = ledgerInfoFromRequest(context);
if (auto status = std::get_if<Status>(&v))
return *status;
auto lgrInfo = std::get<ripple::LedgerInfo>(v);
ripple::AccountID accountID;
if (auto const status = getAccount(request, accountID); status)
return status;
if (!accountID)
return Status{Error::rpcINVALID_PARAMS, "malformedAccount"};
// TODO: just check for existence without pulling
if (!context.backend->fetchLedgerObject(
ripple::keylet::account(accountID).key, lgrInfo.seq, context.yield))
return Status{Error::rpcACT_NOT_FOUND, "accountNotFound"};
std::uint32_t limit = 200;
if (auto const status = getLimit(request, limit); status)
return status;
ripple::uint256 marker;
if (auto const status = getHexMarker(request, marker); status)
return status;
auto const first =
ripple::keylet::nftpage(ripple::keylet::nftpage_min(accountID), marker);
auto const last = ripple::keylet::nftpage_max(accountID);
auto const key =
context.backend
->fetchSuccessorKey(first.key, lgrInfo.seq, context.yield)
.value_or(last.key);
auto const blob = context.backend->fetchLedgerObject(
ripple::Keylet(ripple::ltNFTOKEN_PAGE, key).key,
lgrInfo.seq,
context.yield);
std::optional<ripple::SLE const> cp{
ripple::SLE{ripple::SerialIter{blob->data(), blob->size()}, key}};
std::uint32_t cnt = 0;
response[JS(account_nfts)] = boost::json::value(boost::json::array_kind);
auto& nfts = response.at(JS(account_nfts)).as_array();
bool pastMarker = marker.isZero();
ripple::uint256 const maskedMarker = marker & ripple::nft::pageMask;
// Continue iteration from the current page:
while (true)
{
auto arr = cp->getFieldArray(ripple::sfNFTokens);
for (auto const& o : arr)
{
ripple::uint256 const nftokenID = o[ripple::sfNFTokenID];
ripple::uint256 const maskedNftokenID =
nftokenID & ripple::nft::pageMask;
if (!pastMarker && maskedNftokenID < maskedMarker)
continue;
if (!pastMarker && maskedNftokenID == maskedMarker &&
nftokenID <= marker)
continue;
{
nfts.push_back(
toBoostJson(o.getJson(ripple::JsonOptions::none)));
auto& obj = nfts.back().as_object();
// Pull out the components of the nft ID.
obj[SFS(sfFlags)] = ripple::nft::getFlags(nftokenID);
obj[SFS(sfIssuer)] =
to_string(ripple::nft::getIssuer(nftokenID));
obj[SFS(sfNFTokenTaxon)] =
ripple::nft::toUInt32(ripple::nft::getTaxon(nftokenID));
obj[JS(nft_serial)] = ripple::nft::getSerial(nftokenID);
if (std::uint16_t xferFee = {
ripple::nft::getTransferFee(nftokenID)})
obj[SFS(sfTransferFee)] = xferFee;
}
if (++cnt == limit)
{
response[JS(limit)] = limit;
response[JS(marker)] =
to_string(o.getFieldH256(ripple::sfNFTokenID));
return response;
}
}
if (auto npm = (*cp)[~ripple::sfNextPageMin])
{
auto const nextKey = ripple::Keylet(ripple::ltNFTOKEN_PAGE, *npm);
auto const nextBlob = context.backend->fetchLedgerObject(
nextKey.key, lgrInfo.seq, context.yield);
cp.emplace(ripple::SLE{
ripple::SerialIter{nextBlob->data(), nextBlob->size()},
nextKey.key});
}
else
break;
}
response[JS(account)] = ripple::toBase58(accountID);
return response;
}
Result Result
doAccountObjects(Context const& context) doAccountObjects(Context const& context)
@@ -37,54 +158,40 @@ doAccountObjects(Context const& context)
auto lgrInfo = std::get<ripple::LedgerInfo>(v); auto lgrInfo = std::get<ripple::LedgerInfo>(v);
if (!request.contains("account")) ripple::AccountID accountID;
return Status{Error::rpcINVALID_PARAMS, "missingAccount"}; if (auto const status = getAccount(request, accountID); status)
return status;
if (!request.at("account").is_string())
return Status{Error::rpcINVALID_PARAMS, "accountNotString"};
auto accountID =
accountFromStringStrict(request.at("account").as_string().c_str());
if (!accountID)
return Status{Error::rpcINVALID_PARAMS, "malformedAccount"};
std::uint32_t limit = 200; std::uint32_t limit = 200;
if (request.contains("limit")) if (auto const status = getLimit(request, limit); status)
{ return status;
if (!request.at("limit").is_int64())
return Status{Error::rpcINVALID_PARAMS, "limitNotInt"};
limit = request.at("limit").as_int64(); std::optional<std::string> marker = {};
if (limit <= 0)
return Status{Error::rpcINVALID_PARAMS, "limitNotPositive"};
}
std::optional<std::string> cursor = {};
if (request.contains("marker")) if (request.contains("marker"))
{ {
if (!request.at("marker").is_string()) if (!request.at("marker").is_string())
return Status{Error::rpcINVALID_PARAMS, "markerNotString"}; return Status{Error::rpcINVALID_PARAMS, "markerNotString"};
cursor = request.at("marker").as_string().c_str(); marker = request.at("marker").as_string().c_str();
} }
std::optional<ripple::LedgerEntryType> objectType = {}; std::optional<ripple::LedgerEntryType> objectType = {};
if (request.contains("type")) if (request.contains(JS(type)))
{ {
if (!request.at("type").is_string()) if (!request.at(JS(type)).is_string())
return Status{Error::rpcINVALID_PARAMS, "typeNotString"}; return Status{Error::rpcINVALID_PARAMS, "typeNotString"};
std::string typeAsString = request.at("type").as_string().c_str(); std::string typeAsString = request.at(JS(type)).as_string().c_str();
if (types.find(typeAsString) == types.end()) if (types.find(typeAsString) == types.end())
return Status{Error::rpcINVALID_PARAMS, "typeInvalid"}; return Status{Error::rpcINVALID_PARAMS, "typeInvalid"};
objectType = types[typeAsString]; objectType = types[typeAsString];
} }
response["account"] = ripple::to_string(*accountID); response[JS(account)] = ripple::to_string(accountID);
response["account_objects"] = boost::json::value(boost::json::array_kind); response[JS(account_objects)] = boost::json::value(boost::json::array_kind);
boost::json::array& jsonObjects = response.at("account_objects").as_array(); boost::json::array& jsonObjects =
response.at(JS(account_objects)).as_array();
auto const addToResponse = [&](ripple::SLE const& sle) { auto const addToResponse = [&](ripple::SLE const& sle) {
if (!objectType || objectType == sle.getType()) if (!objectType || objectType == sle.getType())
@@ -95,23 +202,23 @@ doAccountObjects(Context const& context)
auto next = traverseOwnedNodes( auto next = traverseOwnedNodes(
*context.backend, *context.backend,
*accountID, accountID,
lgrInfo.seq, lgrInfo.seq,
limit, limit,
cursor, marker,
context.yield, context.yield,
addToResponse); addToResponse);
response["ledger_hash"] = ripple::strHex(lgrInfo.hash); response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash);
response["ledger_index"] = lgrInfo.seq; response[JS(ledger_index)] = lgrInfo.seq;
if (auto status = std::get_if<RPC::Status>(&next)) if (auto status = std::get_if<RPC::Status>(&next))
return *status; return *status;
auto nextCursor = std::get<RPC::AccountCursor>(next); auto nextMarker = std::get<RPC::AccountCursor>(next);
if (nextCursor.isNonZero()) if (nextMarker.isNonZero())
response["marker"] = nextCursor.toString(); response[JS(marker)] = nextMarker.toString();
return response; return response;
} }

View File

@@ -27,37 +27,39 @@ addOffer(boost::json::array& offersJson, ripple::SLE const& offer)
if (!takerPays.native()) if (!takerPays.native())
{ {
obj["taker_pays"] = boost::json::value(boost::json::object_kind); obj[JS(taker_pays)] = boost::json::value(boost::json::object_kind);
boost::json::object& takerPaysJson = obj.at("taker_pays").as_object(); boost::json::object& takerPaysJson = obj.at(JS(taker_pays)).as_object();
takerPaysJson["value"] = takerPays.getText(); takerPaysJson[JS(value)] = takerPays.getText();
takerPaysJson["currency"] = ripple::to_string(takerPays.getCurrency()); takerPaysJson[JS(currency)] =
takerPaysJson["issuer"] = ripple::to_string(takerPays.getIssuer()); ripple::to_string(takerPays.getCurrency());
takerPaysJson[JS(issuer)] = ripple::to_string(takerPays.getIssuer());
} }
else else
{ {
obj["taker_pays"] = takerPays.getText(); obj[JS(taker_pays)] = takerPays.getText();
} }
if (!takerGets.native()) if (!takerGets.native())
{ {
obj["taker_gets"] = boost::json::value(boost::json::object_kind); obj[JS(taker_gets)] = boost::json::value(boost::json::object_kind);
boost::json::object& takerGetsJson = obj.at("taker_gets").as_object(); boost::json::object& takerGetsJson = obj.at(JS(taker_gets)).as_object();
takerGetsJson["value"] = takerGets.getText(); takerGetsJson[JS(value)] = takerGets.getText();
takerGetsJson["currency"] = ripple::to_string(takerGets.getCurrency()); takerGetsJson[JS(currency)] =
takerGetsJson["issuer"] = ripple::to_string(takerGets.getIssuer()); ripple::to_string(takerGets.getCurrency());
takerGetsJson[JS(issuer)] = ripple::to_string(takerGets.getIssuer());
} }
else else
{ {
obj["taker_gets"] = takerGets.getText(); obj[JS(taker_gets)] = takerGets.getText();
} }
obj["seq"] = offer.getFieldU32(ripple::sfSequence); obj[JS(seq)] = offer.getFieldU32(ripple::sfSequence);
obj["flags"] = offer.getFieldU32(ripple::sfFlags); obj[JS(flags)] = offer.getFieldU32(ripple::sfFlags);
obj["quality"] = rate.getText(); obj[JS(quality)] = rate.getText();
if (offer.isFieldPresent(ripple::sfExpiration)) if (offer.isFieldPresent(ripple::sfExpiration))
obj["expiration"] = offer.getFieldU32(ripple::sfExpiration); obj[JS(expiration)] = offer.getFieldU32(ripple::sfExpiration);
offersJson.push_back(obj); offersJson.push_back(obj);
}; };
@@ -74,43 +76,28 @@ doAccountOffers(Context const& context)
auto lgrInfo = std::get<ripple::LedgerInfo>(v); auto lgrInfo = std::get<ripple::LedgerInfo>(v);
if (!request.contains("account")) ripple::AccountID accountID;
return Status{Error::rpcINVALID_PARAMS, "missingAccount"}; if (auto const status = getAccount(request, accountID); status)
return status;
if (!request.at("account").is_string())
return Status{Error::rpcINVALID_PARAMS, "accountNotString"};
auto accountID =
accountFromStringStrict(request.at("account").as_string().c_str());
if (!accountID)
return Status{Error::rpcINVALID_PARAMS, "malformedAccount"};
std::uint32_t limit = 200; std::uint32_t limit = 200;
if (request.contains("limit")) if (auto const status = getLimit(request, limit); status)
{ return status;
if (!request.at("limit").is_int64())
return Status{Error::rpcINVALID_PARAMS, "limitNotInt"};
limit = request.at("limit").as_int64(); std::optional<std::string> marker = {};
if (limit <= 0) if (request.contains(JS(marker)))
return Status{Error::rpcINVALID_PARAMS, "limitNotPositive"};
}
std::optional<std::string> cursor = {};
if (request.contains("marker"))
{ {
if (!request.at("marker").is_string()) if (!request.at(JS(marker)).is_string())
return Status{Error::rpcINVALID_PARAMS, "markerNotString"}; return Status{Error::rpcINVALID_PARAMS, "markerNotString"};
cursor = request.at("marker").as_string().c_str(); marker = request.at(JS(marker)).as_string().c_str();
} }
response["account"] = ripple::to_string(*accountID); response[JS(account)] = ripple::to_string(accountID);
response["ledger_hash"] = ripple::strHex(lgrInfo.hash); response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash);
response["ledger_index"] = lgrInfo.seq; response[JS(ledger_index)] = lgrInfo.seq;
response["offers"] = boost::json::value(boost::json::array_kind); response[JS(offers)] = boost::json::value(boost::json::array_kind);
boost::json::array& jsonLines = response.at("offers").as_array(); boost::json::array& jsonLines = response.at(JS(offers)).as_array();
auto const addToResponse = [&](ripple::SLE const& sle) { auto const addToResponse = [&](ripple::SLE const& sle) {
if (sle.getType() == ripple::ltOFFER) if (sle.getType() == ripple::ltOFFER)
@@ -128,20 +115,20 @@ doAccountOffers(Context const& context)
auto next = traverseOwnedNodes( auto next = traverseOwnedNodes(
*context.backend, *context.backend,
*accountID, accountID,
lgrInfo.seq, lgrInfo.seq,
limit, limit,
cursor, marker,
context.yield, context.yield,
addToResponse); addToResponse);
if (auto status = std::get_if<RPC::Status>(&next)) if (auto status = std::get_if<RPC::Status>(&next))
return *status; return *status;
auto nextCursor = std::get<RPC::AccountCursor>(next); auto nextMarker = std::get<RPC::AccountCursor>(next);
if (nextCursor.isNonZero()) if (nextMarker.isNonZero())
response["marker"] = nextCursor.toString(); response[JS(marker)] = nextMarker.toString();
return response; return response;
} }

View File

@@ -12,60 +12,38 @@ doAccountTx(Context const& context)
auto request = context.params; auto request = context.params;
boost::json::object response = {}; boost::json::object response = {};
if (!request.contains("account")) ripple::AccountID accountID;
return Status{Error::rpcINVALID_PARAMS, "missingAccount"}; if (auto const status = getAccount(request, accountID); status)
return status;
if (!request.at("account").is_string()) bool const binary = getBool(request, JS(binary), false);
return Status{Error::rpcINVALID_PARAMS, "accountNotString"}; bool const forward = getBool(request, JS(forward), false);
auto accountID =
accountFromStringStrict(request.at("account").as_string().c_str());
if (!accountID)
return Status{Error::rpcINVALID_PARAMS, "malformedAccount"};
bool binary = false;
if (request.contains("binary"))
{
if (!request.at("binary").is_bool())
return Status{Error::rpcINVALID_PARAMS, "binaryFlagNotBool"};
binary = request.at("binary").as_bool();
}
bool forward = false;
if (request.contains("forward"))
{
if (!request.at("forward").is_bool())
return Status{Error::rpcINVALID_PARAMS, "forwardNotBool"};
forward = request.at("forward").as_bool();
}
std::optional<Backend::AccountTransactionsCursor> cursor; std::optional<Backend::AccountTransactionsCursor> cursor;
if (request.contains("marker")) if (request.contains(JS(marker)))
{ {
auto const& obj = request.at("marker").as_object(); auto const& obj = request.at(JS(marker)).as_object();
std::optional<std::uint32_t> transactionIndex = {}; std::optional<std::uint32_t> transactionIndex = {};
if (obj.contains("seq")) if (obj.contains(JS(seq)))
{ {
if (!obj.at("seq").is_int64()) if (!obj.at(JS(seq)).is_int64())
return Status{ return Status{
Error::rpcINVALID_PARAMS, "transactionIndexNotInt"}; Error::rpcINVALID_PARAMS, "transactionIndexNotInt"};
transactionIndex = transactionIndex =
boost::json::value_to<std::uint32_t>(obj.at("seq")); boost::json::value_to<std::uint32_t>(obj.at(JS(seq)));
} }
std::optional<std::uint32_t> ledgerIndex = {}; std::optional<std::uint32_t> ledgerIndex = {};
if (obj.contains("ledger")) if (obj.contains(JS(ledger)))
{ {
if (!obj.at("ledger").is_int64()) if (!obj.at(JS(ledger)).is_int64())
return Status{Error::rpcINVALID_PARAMS, "ledgerIndexNotInt"}; return Status{Error::rpcINVALID_PARAMS, "ledgerIndexNotInt"};
ledgerIndex = ledgerIndex =
boost::json::value_to<std::uint32_t>(obj.at("ledger")); boost::json::value_to<std::uint32_t>(obj.at(JS(ledger)));
} }
if (!transactionIndex || !ledgerIndex) if (!transactionIndex || !ledgerIndex)
@@ -75,9 +53,9 @@ doAccountTx(Context const& context)
} }
auto minIndex = context.range.minSequence; auto minIndex = context.range.minSequence;
if (request.contains("ledger_index_min")) if (request.contains(JS(ledger_index_min)))
{ {
auto& min = request.at("ledger_index_min"); auto& min = request.at(JS(ledger_index_min));
if (!min.is_int64()) if (!min.is_int64())
return Status{Error::rpcINVALID_PARAMS, "ledgerSeqMinNotNumber"}; return Status{Error::rpcINVALID_PARAMS, "ledgerSeqMinNotNumber"};
@@ -97,9 +75,9 @@ doAccountTx(Context const& context)
} }
auto maxIndex = context.range.maxSequence; auto maxIndex = context.range.maxSequence;
if (request.contains("ledger_index_max")) if (request.contains(JS(ledger_index_max)))
{ {
auto& max = request.at("ledger_index_max"); auto& max = request.at(JS(ledger_index_max));
if (!max.is_int64()) if (!max.is_int64())
return Status{Error::rpcINVALID_PARAMS, "ledgerSeqMaxNotNumber"}; return Status{Error::rpcINVALID_PARAMS, "ledgerSeqMaxNotNumber"};
@@ -121,24 +99,25 @@ doAccountTx(Context const& context)
cursor = {maxIndex, INT32_MAX}; cursor = {maxIndex, INT32_MAX};
} }
if (request.contains("ledger_index")) if (request.contains(JS(ledger_index)))
{ {
if (!request.at("ledger_index").is_int64()) if (!request.at(JS(ledger_index)).is_int64())
return Status{Error::rpcINVALID_PARAMS, "ledgerIndexNotNumber"}; return Status{Error::rpcINVALID_PARAMS, "ledgerIndexNotNumber"};
auto ledgerIndex = auto ledgerIndex =
boost::json::value_to<std::uint32_t>(request.at("ledger_index")); boost::json::value_to<std::uint32_t>(request.at(JS(ledger_index)));
maxIndex = minIndex = ledgerIndex; maxIndex = minIndex = ledgerIndex;
} }
if (request.contains("ledger_hash")) if (request.contains(JS(ledger_hash)))
{ {
if (!request.at("ledger_hash").is_string()) if (!request.at(JS(ledger_hash)).is_string())
return RPC::Status{ return RPC::Status{
RPC::Error::rpcINVALID_PARAMS, "ledgerHashNotString"}; RPC::Error::rpcINVALID_PARAMS, "ledgerHashNotString"};
ripple::uint256 ledgerHash; ripple::uint256 ledgerHash;
if (!ledgerHash.parseHex(request.at("ledger_hash").as_string().c_str())) if (!ledgerHash.parseHex(
request.at(JS(ledger_hash)).as_string().c_str()))
return RPC::Status{ return RPC::Status{
RPC::Error::rpcINVALID_PARAMS, "ledgerHashMalformed"}; RPC::Error::rpcINVALID_PARAMS, "ledgerHashMalformed"};
@@ -156,36 +135,30 @@ doAccountTx(Context const& context)
} }
std::uint32_t limit = 200; std::uint32_t limit = 200;
if (request.contains("limit")) if (auto const status = getLimit(request, limit); status)
{ return status;
if (!request.at("limit").is_int64())
return Status{Error::rpcINVALID_PARAMS, "limitNotInt"};
limit = request.at("limit").as_int64(); if (request.contains(JS(limit)))
if (limit <= 0) response[JS(limit)] = limit;
return Status{Error::rpcINVALID_PARAMS, "limitNotPositive"};
response["limit"] = limit;
}
boost::json::array txns; boost::json::array txns;
auto start = std::chrono::system_clock::now(); auto start = std::chrono::system_clock::now();
auto [blobs, retCursor] = context.backend->fetchAccountTransactions( auto [blobs, retCursor] = context.backend->fetchAccountTransactions(
*accountID, limit, forward, cursor, context.yield); accountID, limit, forward, cursor, context.yield);
auto end = std::chrono::system_clock::now(); auto end = std::chrono::system_clock::now();
BOOST_LOG_TRIVIAL(info) << __func__ << " db fetch took " BOOST_LOG_TRIVIAL(info) << __func__ << " db fetch took "
<< ((end - start).count() / 1000000000.0) << ((end - start).count() / 1000000000.0)
<< " num blobs = " << blobs.size(); << " num blobs = " << blobs.size();
response["account"] = ripple::to_string(*accountID); response[JS(account)] = ripple::to_string(accountID);
if (retCursor) if (retCursor)
{ {
boost::json::object cursorJson; boost::json::object cursorJson;
cursorJson["ledger"] = retCursor->ledgerSequence; cursorJson[JS(ledger)] = retCursor->ledgerSequence;
cursorJson["seq"] = retCursor->transactionIndex; cursorJson[JS(seq)] = retCursor->transactionIndex;
response["marker"] = cursorJson; response[JS(marker)] = cursorJson;
} }
std::optional<size_t> maxReturnedIndex; std::optional<size_t> maxReturnedIndex;
@@ -206,17 +179,18 @@ doAccountTx(Context const& context)
if (!binary) if (!binary)
{ {
auto [txn, meta] = toExpandedJson(txnPlusMeta); auto [txn, meta] = toExpandedJson(txnPlusMeta);
obj["meta"] = meta; obj[JS(meta)] = meta;
obj["tx"] = txn; obj[JS(tx)] = txn;
obj["tx"].as_object()["ledger_index"] = txnPlusMeta.ledgerSequence; obj[JS(tx)].as_object()[JS(ledger_index)] =
obj["tx"].as_object()["date"] = txnPlusMeta.date; txnPlusMeta.ledgerSequence;
obj[JS(tx)].as_object()[JS(date)] = txnPlusMeta.date;
} }
else else
{ {
obj["meta"] = ripple::strHex(txnPlusMeta.metadata); obj[JS(meta)] = ripple::strHex(txnPlusMeta.metadata);
obj["tx_blob"] = ripple::strHex(txnPlusMeta.transaction); obj[JS(tx_blob)] = ripple::strHex(txnPlusMeta.transaction);
obj["ledger_index"] = txnPlusMeta.ledgerSequence; obj[JS(ledger_index)] = txnPlusMeta.ledgerSequence;
obj["date"] = txnPlusMeta.date; obj[JS(date)] = txnPlusMeta.date;
} }
txns.push_back(obj); txns.push_back(obj);
@@ -229,22 +203,22 @@ doAccountTx(Context const& context)
assert(cursor); assert(cursor);
if (forward) if (forward)
{ {
response["ledger_index_min"] = cursor->ledgerSequence; response[JS(ledger_index_min)] = cursor->ledgerSequence;
if (blobs.size() >= limit) if (blobs.size() >= limit)
response["ledger_index_max"] = *maxReturnedIndex; response[JS(ledger_index_max)] = *maxReturnedIndex;
else else
response["ledger_index_max"] = maxIndex; response[JS(ledger_index_max)] = maxIndex;
} }
else else
{ {
response["ledger_index_max"] = cursor->ledgerSequence; response[JS(ledger_index_max)] = cursor->ledgerSequence;
if (blobs.size() >= limit) if (blobs.size() >= limit)
response["ledger_index_min"] = *minReturnedIndex; response[JS(ledger_index_min)] = *minReturnedIndex;
else else
response["ledger_index_min"] = minIndex; response[JS(ledger_index_min)] = minIndex;
} }
response["transactions"] = txns; response[JS(transactions)] = txns;
auto end2 = std::chrono::system_clock::now(); auto end2 = std::chrono::system_clock::now();
BOOST_LOG_TRIVIAL(info) << __func__ << " serialization took " BOOST_LOG_TRIVIAL(info) << __func__ << " serialization took "

View File

@@ -49,41 +49,20 @@ doBookOffers(Context const& context)
} }
std::uint32_t limit = 200; std::uint32_t limit = 200;
if (request.contains("limit")) if (auto const status = getLimit(request, limit); status)
{ return status;
if (!request.at("limit").is_int64())
return Status{Error::rpcINVALID_PARAMS, "limitNotInt"};
limit = request.at("limit").as_int64();
if (limit <= 0)
return Status{Error::rpcINVALID_PARAMS, "limitNotPositive"};
}
ripple::AccountID takerID = beast::zero; ripple::AccountID takerID = beast::zero;
if (request.contains("taker")) if (auto const status = getTaker(request, takerID); status)
{ return status;
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; ripple::uint256 marker = beast::zero;
if (request.contains("cursor")) if (auto const status = getHexMarker(request, marker); status)
{ return status;
if (!request.at("cursor").is_string())
return Status{Error::rpcINVALID_PARAMS, "cursorNotString"};
if (!cursor.parseHex(request.at("cursor").as_string().c_str()))
return Status{Error::rpcINVALID_PARAMS, "malformedCursor"};
}
auto start = std::chrono::system_clock::now(); auto start = std::chrono::system_clock::now();
auto [offers, retCursor] = context.backend->fetchBookOffers( auto [offers, retMarker] = context.backend->fetchBookOffers(
bookBase, lgrInfo.seq, limit, cursor, context.yield); bookBase, lgrInfo.seq, limit, marker, context.yield);
auto end = std::chrono::system_clock::now(); auto end = std::chrono::system_clock::now();
BOOST_LOG_TRIVIAL(warning) BOOST_LOG_TRIVIAL(warning)
@@ -92,10 +71,10 @@ doBookOffers(Context const& context)
.count() .count()
<< " milliseconds - request = " << request; << " milliseconds - request = " << request;
response["ledger_hash"] = ripple::strHex(lgrInfo.hash); response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash);
response["ledger_index"] = lgrInfo.seq; response[JS(ledger_index)] = lgrInfo.seq;
response["offers"] = postProcessOrderBook( response[JS(offers)] = postProcessOrderBook(
offers, book, takerID, *context.backend, lgrInfo.seq, context.yield); offers, book, takerID, *context.backend, lgrInfo.seq, context.yield);
auto end2 = std::chrono::system_clock::now(); auto end2 = std::chrono::system_clock::now();
@@ -106,8 +85,8 @@ doBookOffers(Context const& context)
.count() .count()
<< " milliseconds - request = " << request; << " milliseconds - request = " << request;
if (retCursor) if (retMarker)
response["marker"] = ripple::strHex(*retCursor); response["marker"] = ripple::strHex(*retMarker);
return response; return response;
} }

View File

@@ -27,19 +27,13 @@ doChannelAuthorize(Context const& context)
auto request = context.params; auto request = context.params;
boost::json::object response = {}; boost::json::object response = {};
if (!request.contains("channel_id")) if (!request.contains(JS(amount)))
return Status{Error::rpcINVALID_PARAMS, "missingChannelID"};
if (!request.at("channel_id").is_string())
return Status{Error::rpcINVALID_PARAMS, "channelIDNotString"};
if (!request.contains("amount"))
return Status{Error::rpcINVALID_PARAMS, "missingAmount"}; return Status{Error::rpcINVALID_PARAMS, "missingAmount"};
if (!request.at("amount").is_string()) if (!request.at(JS(amount)).is_string())
return Status{Error::rpcINVALID_PARAMS, "amountNotString"}; return Status{Error::rpcINVALID_PARAMS, "amountNotString"};
if (!request.contains("key_type") && !request.contains("secret")) if (!request.contains(JS(key_type)) && !request.contains(JS(secret)))
return Status{Error::rpcINVALID_PARAMS, "missingKeyTypeOrSecret"}; return Status{Error::rpcINVALID_PARAMS, "missingKeyTypeOrSecret"};
auto v = keypairFromRequst(request); auto v = keypairFromRequst(request);
@@ -50,10 +44,11 @@ doChannelAuthorize(Context const& context)
std::get<std::pair<ripple::PublicKey, ripple::SecretKey>>(v); std::get<std::pair<ripple::PublicKey, ripple::SecretKey>>(v);
ripple::uint256 channelId; ripple::uint256 channelId;
if (!channelId.parseHex(request.at("channel_id").as_string().c_str())) if (auto const status = getChannelId(request, channelId); status)
return Status{Error::rpcCHANNEL_MALFORMED, "malformedChannelID"}; return status;
auto optDrops = ripple::to_uint64(request.at("amount").as_string().c_str()); auto optDrops =
ripple::to_uint64(request.at(JS(amount)).as_string().c_str());
if (!optDrops) if (!optDrops)
return Status{Error::rpcCHANNEL_AMT_MALFORMED, "couldNotParseAmount"}; return Status{Error::rpcCHANNEL_AMT_MALFORMED, "couldNotParseAmount"};
@@ -67,7 +62,7 @@ doChannelAuthorize(Context const& context)
try try
{ {
auto const buf = ripple::sign(pk, sk, msg.slice()); auto const buf = ripple::sign(pk, sk, msg.slice());
response["signature"] = ripple::strHex(buf); response[JS(signature)] = ripple::strHex(buf);
} }
catch (std::exception&) catch (std::exception&)
{ {

View File

@@ -16,33 +16,28 @@ doChannelVerify(Context const& context)
auto request = context.params; auto request = context.params;
boost::json::object response = {}; boost::json::object response = {};
if (!request.contains("channel_id")) if (!request.contains(JS(amount)))
return Status{Error::rpcINVALID_PARAMS, "missingChannelID"};
if (!request.at("channel_id").is_string())
return Status{Error::rpcINVALID_PARAMS, "channelIDNotString"};
if (!request.contains("amount"))
return Status{Error::rpcINVALID_PARAMS, "missingAmount"}; return Status{Error::rpcINVALID_PARAMS, "missingAmount"};
if (!request.at("amount").is_string()) if (!request.at(JS(amount)).is_string())
return Status{Error::rpcINVALID_PARAMS, "amountNotString"}; return Status{Error::rpcINVALID_PARAMS, "amountNotString"};
if (!request.contains("signature")) if (!request.contains(JS(signature)))
return Status{Error::rpcINVALID_PARAMS, "missingSignature"}; return Status{Error::rpcINVALID_PARAMS, "missingSignature"};
if (!request.at("signature").is_string()) if (!request.at(JS(signature)).is_string())
return Status{Error::rpcINVALID_PARAMS, "signatureNotString"}; return Status{Error::rpcINVALID_PARAMS, "signatureNotString"};
if (!request.contains("public_key")) if (!request.contains(JS(public_key)))
return Status{Error::rpcINVALID_PARAMS, "missingPublicKey"}; return Status{Error::rpcINVALID_PARAMS, "missingPublicKey"};
if (!request.at("public_key").is_string()) if (!request.at(JS(public_key)).is_string())
return Status{Error::rpcINVALID_PARAMS, "publicKeyNotString"}; return Status{Error::rpcINVALID_PARAMS, "publicKeyNotString"};
std::optional<ripple::PublicKey> pk; std::optional<ripple::PublicKey> pk;
{ {
std::string const strPk = request.at("public_key").as_string().c_str(); std::string const strPk =
request.at(JS(public_key)).as_string().c_str();
pk = ripple::parseBase58<ripple::PublicKey>( pk = ripple::parseBase58<ripple::PublicKey>(
ripple::TokenType::AccountPublic, strPk); ripple::TokenType::AccountPublic, strPk);
@@ -62,17 +57,18 @@ doChannelVerify(Context const& context)
} }
ripple::uint256 channelId; ripple::uint256 channelId;
if (!channelId.parseHex(request.at("channel_id").as_string().c_str())) if (auto const status = getChannelId(request, channelId); status)
return Status{Error::rpcCHANNEL_MALFORMED, "malformedChannelID"}; return status;
auto optDrops = ripple::to_uint64(request.at("amount").as_string().c_str()); auto optDrops =
ripple::to_uint64(request.at(JS(amount)).as_string().c_str());
if (!optDrops) if (!optDrops)
return Status{Error::rpcCHANNEL_AMT_MALFORMED, "couldNotParseAmount"}; return Status{Error::rpcCHANNEL_AMT_MALFORMED, "couldNotParseAmount"};
std::uint64_t drops = *optDrops; std::uint64_t drops = *optDrops;
auto sig = ripple::strUnHex(request.at("signature").as_string().c_str()); auto sig = ripple::strUnHex(request.at(JS(signature)).as_string().c_str());
if (!sig || !sig->size()) if (!sig || !sig->size())
return Status{Error::rpcINVALID_PARAMS, "invalidSignature"}; return Status{Error::rpcINVALID_PARAMS, "invalidSignature"};
@@ -81,7 +77,7 @@ doChannelVerify(Context const& context)
ripple::serializePayChanAuthorization( ripple::serializePayChanAuthorization(
msg, channelId, ripple::XRPAmount(drops)); msg, channelId, ripple::XRPAmount(drops));
response["signature_verified"] = response[JS(signature_verified)] =
ripple::verify(*pk, msg.slice(), ripple::makeSlice(*sig), true); ripple::verify(*pk, msg.slice(), ripple::makeSlice(*sig), true);
return response; return response;

View File

@@ -9,17 +9,9 @@ doGatewayBalances(Context const& context)
auto request = context.params; auto request = context.params;
boost::json::object response = {}; boost::json::object response = {};
if (!request.contains("account")) ripple::AccountID accountID;
return Status{Error::rpcINVALID_PARAMS, "missingAccount"}; if (auto const status = getAccount(request, accountID); status)
return status;
if (!request.at("account").is_string())
return Status{Error::rpcINVALID_PARAMS, "accountNotString"};
auto accountID =
accountFromStringStrict(request.at("account").as_string().c_str());
if (!accountID)
return Status{Error::rpcINVALID_PARAMS, "malformedAccount"};
auto v = ledgerInfoFromRequest(context); auto v = ledgerInfoFromRequest(context);
if (auto status = std::get_if<Status>(&v)) if (auto status = std::get_if<Status>(&v))
@@ -81,7 +73,7 @@ doGatewayBalances(Context const& context)
if (!valid) if (!valid)
{ {
response["error"] = "invalidHotWallet"; response[JS(error)] = "invalidHotWallet";
return response; return response;
} }
} }
@@ -148,7 +140,7 @@ doGatewayBalances(Context const& context)
traverseOwnedNodes( traverseOwnedNodes(
*context.backend, *context.backend,
*accountID, accountID,
lgrInfo.seq, lgrInfo.seq,
std::numeric_limits<std::uint32_t>::max(), std::numeric_limits<std::uint32_t>::max(),
{}, {},
@@ -162,7 +154,7 @@ doGatewayBalances(Context const& context)
{ {
obj[ripple::to_string(k)] = v.getText(); obj[ripple::to_string(k)] = v.getText();
} }
response["obligations"] = std::move(obj); response[JS(obligations)] = std::move(obj);
} }
auto toJson = auto toJson =
@@ -177,9 +169,9 @@ doGatewayBalances(Context const& context)
for (auto const& balance : accBalances) for (auto const& balance : accBalances)
{ {
boost::json::object entry; boost::json::object entry;
entry["currency"] = entry[JS(currency)] =
ripple::to_string(balance.issue().currency); ripple::to_string(balance.issue().currency);
entry["value"] = balance.getText(); entry[JS(value)] = balance.getText();
arr.push_back(std::move(entry)); arr.push_back(std::move(entry));
} }
obj[ripple::to_string(accId)] = std::move(arr); obj[ripple::to_string(accId)] = std::move(arr);
@@ -189,14 +181,14 @@ doGatewayBalances(Context const& context)
}; };
if (auto balances = toJson(hotBalances); balances.size()) if (auto balances = toJson(hotBalances); balances.size())
response["balances"] = balances; response[JS(balances)] = balances;
if (auto balances = toJson(frozenBalances); balances.size()) if (auto balances = toJson(frozenBalances); balances.size())
response["frozen_balances"] = balances; response[JS(frozen_balances)] = balances;
if (auto balances = toJson(assets); assets.size()) if (auto balances = toJson(assets); assets.size())
response["assets"] = toJson(assets); response[JS(assets)] = toJson(assets);
response["account"] = request.at("account"); response[JS(account)] = request.at(JS(account));
response["ledger_index"] = lgrInfo.seq; response[JS(ledger_index)] = lgrInfo.seq;
response["ledger_hash"] = ripple::strHex(lgrInfo.hash); response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash);
return response; return response;
} }
} // namespace RPC } // namespace RPC

View File

@@ -10,30 +10,30 @@ doLedger(Context const& context)
boost::json::object response = {}; boost::json::object response = {};
bool binary = false; bool binary = false;
if (params.contains("binary")) if (params.contains(JS(binary)))
{ {
if (!params.at("binary").is_bool()) if (!params.at(JS(binary)).is_bool())
return Status{Error::rpcINVALID_PARAMS, "binaryFlagNotBool"}; return Status{Error::rpcINVALID_PARAMS, "binaryFlagNotBool"};
binary = params.at("binary").as_bool(); binary = params.at(JS(binary)).as_bool();
} }
bool transactions = false; bool transactions = false;
if (params.contains("transactions")) if (params.contains(JS(transactions)))
{ {
if (!params.at("transactions").is_bool()) if (!params.at(JS(transactions)).is_bool())
return Status{Error::rpcINVALID_PARAMS, "transactionsFlagNotBool"}; return Status{Error::rpcINVALID_PARAMS, "transactionsFlagNotBool"};
transactions = params.at("transactions").as_bool(); transactions = params.at(JS(transactions)).as_bool();
} }
bool expand = false; bool expand = false;
if (params.contains("expand")) if (params.contains(JS(expand)))
{ {
if (!params.at("expand").is_bool()) if (!params.at(JS(expand)).is_bool())
return Status{Error::rpcINVALID_PARAMS, "expandFlagNotBool"}; return Status{Error::rpcINVALID_PARAMS, "expandFlagNotBool"};
expand = params.at("expand").as_bool(); expand = params.at(JS(expand)).as_bool();
} }
bool diff = false; bool diff = false;
@@ -54,35 +54,34 @@ doLedger(Context const& context)
boost::json::object header; boost::json::object header;
if (binary) if (binary)
{ {
header["ledger_data"] = ripple::strHex(ledgerInfoToBlob(lgrInfo)); header[JS(ledger_data)] = ripple::strHex(ledgerInfoToBlob(lgrInfo));
} }
else else
{ {
header["accepted"] = true; header[JS(accepted)] = true;
header["account_hash"] = ripple::strHex(lgrInfo.accountHash); header[JS(account_hash)] = ripple::strHex(lgrInfo.accountHash);
header["close_flags"] = lgrInfo.closeFlags; header[JS(close_flags)] = lgrInfo.closeFlags;
header["close_time"] = lgrInfo.closeTime.time_since_epoch().count(); header[JS(close_time)] = lgrInfo.closeTime.time_since_epoch().count();
header["close_time_human"] = ripple::to_string(lgrInfo.closeTime); header[JS(close_time_human)] = ripple::to_string(lgrInfo.closeTime);
; header[JS(close_time_resolution)] = lgrInfo.closeTimeResolution.count();
header["close_time_resolution"] = lgrInfo.closeTimeResolution.count(); header[JS(closed)] = true;
header["closed"] = true; header[JS(hash)] = ripple::strHex(lgrInfo.hash);
header["hash"] = ripple::strHex(lgrInfo.hash); header[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash);
header["ledger_hash"] = ripple::strHex(lgrInfo.hash); header[JS(ledger_index)] = std::to_string(lgrInfo.seq);
header["ledger_index"] = std::to_string(lgrInfo.seq); header[JS(parent_close_time)] =
header["parent_close_time"] =
lgrInfo.parentCloseTime.time_since_epoch().count(); lgrInfo.parentCloseTime.time_since_epoch().count();
header["parent_hash"] = ripple::strHex(lgrInfo.parentHash); header[JS(parent_hash)] = ripple::strHex(lgrInfo.parentHash);
header["seqNum"] = std::to_string(lgrInfo.seq); header[JS(seqNum)] = std::to_string(lgrInfo.seq);
header["totalCoins"] = ripple::to_string(lgrInfo.drops); header[JS(totalCoins)] = ripple::to_string(lgrInfo.drops);
header["total_coins"] = ripple::to_string(lgrInfo.drops); header[JS(total_coins)] = ripple::to_string(lgrInfo.drops);
header["transaction_hash"] = ripple::strHex(lgrInfo.txHash); header[JS(transaction_hash)] = ripple::strHex(lgrInfo.txHash);
} }
header["closed"] = true; header[JS(closed)] = true;
if (transactions) if (transactions)
{ {
header["transactions"] = boost::json::value(boost::json::array_kind); header[JS(transactions)] = boost::json::value(boost::json::array_kind);
boost::json::array& jsonTxs = header.at("transactions").as_array(); boost::json::array& jsonTxs = header.at(JS(transactions)).as_array();
if (expand) if (expand)
{ {
auto txns = context.backend->fetchAllTransactionsInLedger( auto txns = context.backend->fetchAllTransactionsInLedger(
@@ -98,14 +97,14 @@ doLedger(Context const& context)
{ {
auto [txn, meta] = toExpandedJson(obj); auto [txn, meta] = toExpandedJson(obj);
entry = txn; entry = txn;
entry["metaData"] = meta; entry[JS(metaData)] = meta;
} }
else else
{ {
entry["tx_blob"] = ripple::strHex(obj.transaction); entry[JS(tx_blob)] = ripple::strHex(obj.transaction);
entry["meta"] = ripple::strHex(obj.metadata); entry[JS(meta)] = ripple::strHex(obj.metadata);
} }
// entry["ledger_index"] = obj.ledgerSequence; // entry[JS(ledger_index)] = obj.ledgerSequence;
return entry; return entry;
}); });
} }
@@ -133,7 +132,7 @@ doLedger(Context const& context)
for (auto const& obj : diff) for (auto const& obj : diff)
{ {
boost::json::object entry; boost::json::object entry;
entry["id"] = ripple::strHex(obj.key); entry[JS(id)] = ripple::strHex(obj.key);
if (binary) if (binary)
entry["object"] = ripple::strHex(obj.blob); entry["object"] = ripple::strHex(obj.blob);
else if (obj.blob.size()) else if (obj.blob.size())
@@ -149,9 +148,9 @@ doLedger(Context const& context)
} }
} }
response["ledger"] = header; response[JS(ledger)] = header;
response["ledger_hash"] = ripple::strHex(lgrInfo.hash); response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash);
response["ledger_index"] = lgrInfo.seq; response[JS(ledger_index)] = lgrInfo.seq;
return response; return response;
} }

View File

@@ -28,23 +28,12 @@ doLedgerData(Context const& context)
auto request = context.params; auto request = context.params;
boost::json::object response = {}; boost::json::object response = {};
bool binary = false; bool const binary = getBool(request, "binary", false);
if (request.contains("binary"))
{
if (!request.at("binary").is_bool())
return Status{Error::rpcINVALID_PARAMS, "binaryFlagNotBool"};
binary = request.at("binary").as_bool(); std::uint32_t limit = binary ? 2048 : 256;
} if (auto const status = getLimit(request, limit); status)
return status;
std::size_t limit = binary ? 2048 : 256;
if (request.contains("limit"))
{
if (!request.at("limit").is_int64())
return Status{Error::rpcINVALID_PARAMS, "limitNotInteger"};
limit = boost::json::value_to<int>(request.at("limit"));
}
bool outOfOrder = false; bool outOfOrder = false;
if (request.contains("out_of_order")) if (request.contains("out_of_order"))
{ {
@@ -53,18 +42,18 @@ doLedgerData(Context const& context)
outOfOrder = request.at("out_of_order").as_bool(); outOfOrder = request.at("out_of_order").as_bool();
} }
std::optional<ripple::uint256> cursor; std::optional<ripple::uint256> marker;
std::optional<uint32_t> diffCursor; std::optional<uint32_t> diffMarker;
if (request.contains("marker")) if (request.contains(JS(marker)))
{ {
if (!request.at("marker").is_string()) if (!request.at(JS(marker)).is_string())
{ {
if (outOfOrder) if (outOfOrder)
{ {
if (!request.at("marker").is_int64()) if (!request.at(JS(marker)).is_int64())
return Status{ return Status{
Error::rpcINVALID_PARAMS, "markerNotStringOrInt"}; Error::rpcINVALID_PARAMS, "markerNotStringOrInt"};
diffCursor = value_to<uint32_t>(request.at("marker")); diffMarker = value_to<uint32_t>(request.at(JS(marker)));
} }
else else
return Status{Error::rpcINVALID_PARAMS, "markerNotString"}; return Status{Error::rpcINVALID_PARAMS, "markerNotString"};
@@ -73,8 +62,8 @@ doLedgerData(Context const& context)
{ {
BOOST_LOG_TRIVIAL(debug) << __func__ << " : parsing marker"; BOOST_LOG_TRIVIAL(debug) << __func__ << " : parsing marker";
cursor = ripple::uint256{}; marker = ripple::uint256{};
if (!cursor->parseHex(request.at("marker").as_string().c_str())) if (!marker->parseHex(request.at(JS(marker)).as_string().c_str()))
return Status{Error::rpcINVALID_PARAMS, "markerMalformed"}; return Status{Error::rpcINVALID_PARAMS, "markerMalformed"};
} }
} }
@@ -85,48 +74,49 @@ doLedgerData(Context const& context)
auto lgrInfo = std::get<ripple::LedgerInfo>(v); auto lgrInfo = std::get<ripple::LedgerInfo>(v);
boost::json::object header; boost::json::object header;
// no cursor means this is the first call, so we return header info // no marker means this is the first call, so we return header info
if (!cursor) if (!marker)
{ {
if (binary) if (binary)
{ {
header["ledger_data"] = ripple::strHex(ledgerInfoToBlob(lgrInfo)); header[JS(ledger_data)] = ripple::strHex(ledgerInfoToBlob(lgrInfo));
} }
else else
{ {
header["accepted"] = true; header[JS(accepted)] = true;
header["account_hash"] = ripple::strHex(lgrInfo.accountHash); header[JS(account_hash)] = ripple::strHex(lgrInfo.accountHash);
header["close_flags"] = lgrInfo.closeFlags; header[JS(close_flags)] = lgrInfo.closeFlags;
header["close_time"] = lgrInfo.closeTime.time_since_epoch().count(); header[JS(close_time)] =
header["close_time_human"] = ripple::to_string(lgrInfo.closeTime); lgrInfo.closeTime.time_since_epoch().count();
; header[JS(close_time_human)] = ripple::to_string(lgrInfo.closeTime);
header["close_time_resolution"] = header[JS(close_time_resolution)] =
lgrInfo.closeTimeResolution.count(); lgrInfo.closeTimeResolution.count();
header["closed"] = true; header[JS(closed)] = true;
header["hash"] = ripple::strHex(lgrInfo.hash); header[JS(hash)] = ripple::strHex(lgrInfo.hash);
header["ledger_hash"] = ripple::strHex(lgrInfo.hash); header[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash);
header["ledger_index"] = std::to_string(lgrInfo.seq); header[JS(ledger_index)] = std::to_string(lgrInfo.seq);
header["parent_close_time"] = header[JS(parent_close_time)] =
lgrInfo.parentCloseTime.time_since_epoch().count(); lgrInfo.parentCloseTime.time_since_epoch().count();
header["parent_hash"] = ripple::strHex(lgrInfo.parentHash); header[JS(parent_hash)] = ripple::strHex(lgrInfo.parentHash);
header["seqNum"] = std::to_string(lgrInfo.seq); header[JS(seqNum)] = std::to_string(lgrInfo.seq);
header["totalCoins"] = ripple::to_string(lgrInfo.drops); header[JS(totalCoins)] = ripple::to_string(lgrInfo.drops);
header["total_coins"] = ripple::to_string(lgrInfo.drops); header[JS(total_coins)] = ripple::to_string(lgrInfo.drops);
header["transaction_hash"] = ripple::strHex(lgrInfo.txHash); header[JS(transaction_hash)] = ripple::strHex(lgrInfo.txHash);
response["ledger"] = header; response[JS(ledger)] = header;
} }
} }
response["ledger_hash"] = ripple::strHex(lgrInfo.hash);
response["ledger_index"] = lgrInfo.seq; response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash);
response[JS(ledger_index)] = lgrInfo.seq;
auto start = std::chrono::system_clock::now(); auto start = std::chrono::system_clock::now();
std::vector<Backend::LedgerObject> results; std::vector<Backend::LedgerObject> results;
if (diffCursor) if (diffMarker)
{ {
assert(outOfOrder); assert(outOfOrder);
auto diff = auto diff =
context.backend->fetchLedgerDiff(*diffCursor, context.yield); context.backend->fetchLedgerDiff(*diffMarker, context.yield);
std::vector<ripple::uint256> keys; std::vector<ripple::uint256> keys;
for (auto&& [key, object] : diff) for (auto&& [key, object] : diff)
{ {
@@ -143,13 +133,13 @@ doLedgerData(Context const& context)
if (obj.size()) if (obj.size())
results.push_back({std::move(keys[i]), std::move(obj)}); results.push_back({std::move(keys[i]), std::move(obj)});
} }
if (*diffCursor > lgrInfo.seq) if (*diffMarker > lgrInfo.seq)
response["marker"] = *diffCursor - 1; response["marker"] = *diffMarker - 1;
} }
else else
{ {
auto page = context.backend->fetchLedgerPage( auto page = context.backend->fetchLedgerPage(
cursor, lgrInfo.seq, limit, outOfOrder, context.yield); marker, lgrInfo.seq, limit, outOfOrder, context.yield);
results = std::move(page.objects); results = std::move(page.objects);
if (page.cursor) if (page.cursor)
response["marker"] = ripple::strHex(*(page.cursor)); response["marker"] = ripple::strHex(*(page.cursor));
@@ -175,14 +165,14 @@ doLedgerData(Context const& context)
if (binary) if (binary)
{ {
boost::json::object entry; boost::json::object entry;
entry["data"] = ripple::serializeHex(sle); entry[JS(data)] = ripple::serializeHex(sle);
entry["index"] = ripple::to_string(sle.key()); entry[JS(index)] = ripple::to_string(sle.key());
objects.push_back(std::move(entry)); objects.push_back(std::move(entry));
} }
else else
objects.push_back(toJson(sle)); objects.push_back(toJson(sle));
} }
response["state"] = std::move(objects); response[JS(state)] = std::move(objects);
auto end2 = std::chrono::system_clock::now(); auto end2 = std::chrono::system_clock::now();
time = std::chrono::duration_cast<std::chrono::microseconds>(end2 - end) time = std::chrono::duration_cast<std::chrono::microseconds>(end2 - end)

View File

@@ -20,8 +20,7 @@ doLedgerEntry(Context const& context)
auto request = context.params; auto request = context.params;
boost::json::object response = {}; boost::json::object response = {};
bool binary = bool const binary = getBool(request, "binary", false);
request.contains("binary") ? request.at("binary").as_bool() : false;
auto v = ledgerInfoFromRequest(context); auto v = ledgerInfoFromRequest(context);
if (auto status = std::get_if<Status>(&v)) if (auto status = std::get_if<Status>(&v))
@@ -30,59 +29,64 @@ doLedgerEntry(Context const& context)
auto lgrInfo = std::get<ripple::LedgerInfo>(v); auto lgrInfo = std::get<ripple::LedgerInfo>(v);
ripple::uint256 key; ripple::uint256 key;
if (request.contains("index")) if (request.contains(JS(index)))
{ {
if (!request.at("index").is_string()) if (!request.at(JS(index)).is_string())
return Status{Error::rpcINVALID_PARAMS, "indexNotString"}; return Status{Error::rpcINVALID_PARAMS, "indexNotString"};
if (!key.parseHex(request.at("index").as_string().c_str())) if (!key.parseHex(request.at(JS(index)).as_string().c_str()))
return Status{Error::rpcINVALID_PARAMS, "malformedIndex"}; return Status{Error::rpcINVALID_PARAMS, "malformedIndex"};
} }
else if (request.contains("account_root")) else if (request.contains(JS(account_root)))
{ {
if (!request.at("account_root").is_string()) if (!request.at(JS(account_root)).is_string())
return Status{Error::rpcINVALID_PARAMS, "account_rootNotString"}; return Status{Error::rpcINVALID_PARAMS, "account_rootNotString"};
auto const account = ripple::parseBase58<ripple::AccountID>( auto const account = ripple::parseBase58<ripple::AccountID>(
request.at("account_root").as_string().c_str()); request.at(JS(account_root)).as_string().c_str());
if (!account || account->isZero()) if (!account || account->isZero())
return Status{Error::rpcINVALID_PARAMS, "malformedAddress"}; return Status{Error::rpcINVALID_PARAMS, "malformedAddress"};
else else
key = ripple::keylet::account(*account).key; key = ripple::keylet::account(*account).key;
} }
else if (request.contains("check")) else if (request.contains(JS(check)))
{ {
if (!request.at("check").is_string()) if (!request.at(JS(check)).is_string())
return Status{Error::rpcINVALID_PARAMS, "checkNotString"}; return Status{Error::rpcINVALID_PARAMS, "checkNotString"};
if (!key.parseHex(request.at("check").as_string().c_str())) if (!key.parseHex(request.at(JS(check)).as_string().c_str()))
{ {
return Status{Error::rpcINVALID_PARAMS, "checkMalformed"}; return Status{Error::rpcINVALID_PARAMS, "checkMalformed"};
} }
} }
else if (request.contains("deposit_preauth")) else if (request.contains(JS(deposit_preauth)))
{ {
if (!request.at("deposit_preauth").is_object()) if (!request.at(JS(deposit_preauth)).is_object())
{ {
if (!request.at("deposit_preauth").is_string() || if (!request.at(JS(deposit_preauth)).is_string() ||
!key.parseHex( !key.parseHex(
request.at("deposit_preauth").as_string().c_str())) request.at(JS(deposit_preauth)).as_string().c_str()))
{ {
return Status{ return Status{
Error::rpcINVALID_PARAMS, "deposit_preauthMalformed"}; Error::rpcINVALID_PARAMS, "deposit_preauthMalformed"};
} }
} }
else if ( else if (
!request.at("deposit_preauth").as_object().contains("owner") || !request.at(JS(deposit_preauth)).as_object().contains(JS(owner)) ||
!request.at("deposit_preauth").as_object().at("owner").is_string()) !request.at(JS(deposit_preauth))
.as_object()
.at(JS(owner))
.is_string())
{ {
return Status{Error::rpcINVALID_PARAMS, "ownerNotString"}; return Status{Error::rpcINVALID_PARAMS, "ownerNotString"};
} }
else if ( else if (
!request.at("deposit_preauth").as_object().contains("authorized") || !request.at(JS(deposit_preauth))
!request.at("deposit_preauth")
.as_object() .as_object()
.at("authorized") .contains(JS(authorized)) ||
!request.at(JS(deposit_preauth))
.as_object()
.at(JS(authorized))
.is_string()) .is_string())
{ {
return Status{Error::rpcINVALID_PARAMS, "authorizedNotString"}; return Status{Error::rpcINVALID_PARAMS, "authorizedNotString"};
@@ -90,13 +94,13 @@ doLedgerEntry(Context const& context)
else else
{ {
boost::json::object const& deposit_preauth = boost::json::object const& deposit_preauth =
request.at("deposit_preauth").as_object(); request.at(JS(deposit_preauth)).as_object();
auto const owner = ripple::parseBase58<ripple::AccountID>( auto const owner = ripple::parseBase58<ripple::AccountID>(
deposit_preauth.at("owner").as_string().c_str()); deposit_preauth.at(JS(owner)).as_string().c_str());
auto const authorized = ripple::parseBase58<ripple::AccountID>( auto const authorized = ripple::parseBase58<ripple::AccountID>(
deposit_preauth.at("authorized").as_string().c_str()); deposit_preauth.at(JS(authorized)).as_string().c_str());
if (!owner) if (!owner)
return Status{Error::rpcINVALID_PARAMS, "malformedOwner"}; return Status{Error::rpcINVALID_PARAMS, "malformedOwner"};
@@ -106,37 +110,37 @@ doLedgerEntry(Context const& context)
key = ripple::keylet::depositPreauth(*owner, *authorized).key; key = ripple::keylet::depositPreauth(*owner, *authorized).key;
} }
} }
else if (request.contains("directory")) else if (request.contains(JS(directory)))
{ {
if (!request.at("directory").is_object()) if (!request.at(JS(directory)).is_object())
{ {
if (!request.at("directory").is_string()) if (!request.at(JS(directory)).is_string())
return Status{Error::rpcINVALID_PARAMS, "directoryNotString"}; return Status{Error::rpcINVALID_PARAMS, "directoryNotString"};
if (!key.parseHex(request.at("directory").as_string().c_str())) if (!key.parseHex(request.at(JS(directory)).as_string().c_str()))
{ {
return Status{Error::rpcINVALID_PARAMS, "malformedDirectory"}; return Status{Error::rpcINVALID_PARAMS, "malformedDirectory"};
} }
} }
else if ( else if (
request.at("directory").as_object().contains("sub_index") && request.at(JS(directory)).as_object().contains(JS(sub_index)) &&
!request.at("directory").as_object().at("sub_index").is_int64()) !request.at(JS(directory)).as_object().at(JS(sub_index)).is_int64())
{ {
return Status{Error::rpcINVALID_PARAMS, "sub_indexNotInt"}; return Status{Error::rpcINVALID_PARAMS, "sub_indexNotInt"};
} }
else else
{ {
auto directory = request.at("directory").as_object(); auto directory = request.at(JS(directory)).as_object();
std::uint64_t subIndex = directory.contains("sub_index") std::uint64_t subIndex = directory.contains(JS(sub_index))
? boost::json::value_to<std::uint64_t>( ? boost::json::value_to<std::uint64_t>(
directory.at("sub_index")) directory.at(JS(sub_index)))
: 0; : 0;
if (directory.contains("dir_root")) if (directory.contains(JS(dir_root)))
{ {
ripple::uint256 uDirRoot; ripple::uint256 uDirRoot;
if (directory.contains("owner")) if (directory.contains(JS(owner)))
{ {
// May not specify both dir_root and owner. // May not specify both dir_root and owner.
return Status{ return Status{
@@ -144,7 +148,7 @@ doLedgerEntry(Context const& context)
"mayNotSpecifyBothDirRootAndOwner"}; "mayNotSpecifyBothDirRootAndOwner"};
} }
else if (!uDirRoot.parseHex( else if (!uDirRoot.parseHex(
directory.at("dir_root").as_string().c_str())) directory.at(JS(dir_root)).as_string().c_str()))
{ {
return Status{Error::rpcINVALID_PARAMS, "malformedDirRoot"}; return Status{Error::rpcINVALID_PARAMS, "malformedDirRoot"};
} }
@@ -153,10 +157,10 @@ doLedgerEntry(Context const& context)
key = ripple::keylet::page(uDirRoot, subIndex).key; key = ripple::keylet::page(uDirRoot, subIndex).key;
} }
} }
else if (directory.contains("owner")) else if (directory.contains(JS(owner)))
{ {
auto const ownerID = ripple::parseBase58<ripple::AccountID>( auto const ownerID = ripple::parseBase58<ripple::AccountID>(
directory.at("owner").as_string().c_str()); directory.at(JS(owner)).as_string().c_str());
if (!ownerID) if (!ownerID)
{ {
@@ -176,31 +180,31 @@ doLedgerEntry(Context const& context)
} }
} }
} }
else if (request.contains("escrow")) else if (request.contains(JS(escrow)))
{ {
if (!request.at("escrow").is_object()) if (!request.at(JS(escrow)).is_object())
{ {
if (!key.parseHex(request.at("escrow").as_string().c_str())) if (!key.parseHex(request.at(JS(escrow)).as_string().c_str()))
return Status{Error::rpcINVALID_PARAMS, "malformedEscrow"}; return Status{Error::rpcINVALID_PARAMS, "malformedEscrow"};
} }
else if ( else if (
!request.at("escrow").as_object().contains("owner") || !request.at(JS(escrow)).as_object().contains(JS(owner)) ||
!request.at("escrow").as_object().at("owner").is_string()) !request.at(JS(escrow)).as_object().at(JS(owner)).is_string())
{ {
return Status{Error::rpcINVALID_PARAMS, "malformedOwner"}; return Status{Error::rpcINVALID_PARAMS, "malformedOwner"};
} }
else if ( else if (
!request.at("escrow").as_object().contains("seq") || !request.at(JS(escrow)).as_object().contains(JS(seq)) ||
!request.at("escrow").as_object().at("seq").is_int64()) !request.at(JS(escrow)).as_object().at(JS(seq)).is_int64())
{ {
return Status{Error::rpcINVALID_PARAMS, "malformedSeq"}; return Status{Error::rpcINVALID_PARAMS, "malformedSeq"};
} }
else else
{ {
auto const id = auto const id =
ripple::parseBase58<ripple::AccountID>(request.at("escrow") ripple::parseBase58<ripple::AccountID>(request.at(JS(escrow))
.as_object() .as_object()
.at("owner") .at(JS(owner))
.as_string() .as_string()
.c_str()); .c_str());
@@ -209,120 +213,122 @@ doLedgerEntry(Context const& context)
else else
{ {
std::uint32_t seq = std::uint32_t seq =
request.at("escrow").as_object().at("seq").as_int64(); request.at(JS(escrow)).as_object().at(JS(seq)).as_int64();
key = ripple::keylet::escrow(*id, seq).key; key = ripple::keylet::escrow(*id, seq).key;
} }
} }
} }
else if (request.contains("offer")) else if (request.contains(JS(offer)))
{ {
if (!request.at("offer").is_object()) if (!request.at(JS(offer)).is_object())
{ {
if (!key.parseHex(request.at("offer").as_string().c_str())) if (!key.parseHex(request.at(JS(offer)).as_string().c_str()))
return Status{Error::rpcINVALID_PARAMS, "malformedOffer"}; return Status{Error::rpcINVALID_PARAMS, "malformedOffer"};
} }
else if ( else if (
!request.at("offer").as_object().contains("account") || !request.at(JS(offer)).as_object().contains(JS(account)) ||
!request.at("offer").as_object().at("account").is_string()) !request.at(JS(offer)).as_object().at(JS(account)).is_string())
{ {
return Status{Error::rpcINVALID_PARAMS, "malformedAccount"}; return Status{Error::rpcINVALID_PARAMS, "malformedAccount"};
} }
else if ( else if (
!request.at("offer").as_object().contains("seq") || !request.at(JS(offer)).as_object().contains(JS(seq)) ||
!request.at("offer").as_object().at("seq").is_int64()) !request.at(JS(offer)).as_object().at(JS(seq)).is_int64())
{ {
return Status{Error::rpcINVALID_PARAMS, "malformedSeq"}; return Status{Error::rpcINVALID_PARAMS, "malformedSeq"};
} }
else else
{ {
auto offer = request.at("offer").as_object(); auto offer = request.at(JS(offer)).as_object();
auto const id = ripple::parseBase58<ripple::AccountID>( auto const id = ripple::parseBase58<ripple::AccountID>(
offer.at("account").as_string().c_str()); offer.at(JS(account)).as_string().c_str());
if (!id) if (!id)
return Status{Error::rpcINVALID_PARAMS, "malformedAccount"}; return Status{Error::rpcINVALID_PARAMS, "malformedAccount"};
else else
{ {
std::uint32_t seq = std::uint32_t seq =
boost::json::value_to<std::uint32_t>(offer.at("seq")); boost::json::value_to<std::uint32_t>(offer.at(JS(seq)));
key = ripple::keylet::offer(*id, seq).key; key = ripple::keylet::offer(*id, seq).key;
} }
} }
} }
else if (request.contains("payment_channel")) else if (request.contains(JS(payment_channel)))
{ {
if (!request.at("payment_channel").is_string()) if (!request.at(JS(payment_channel)).is_string())
return Status{Error::rpcINVALID_PARAMS, "paymentChannelNotString"}; return Status{Error::rpcINVALID_PARAMS, "paymentChannelNotString"};
if (!key.parseHex(request.at("payment_channel").as_string().c_str())) if (!key.parseHex(request.at(JS(payment_channel)).as_string().c_str()))
return Status{Error::rpcINVALID_PARAMS, "malformedPaymentChannel"}; return Status{Error::rpcINVALID_PARAMS, "malformedPaymentChannel"};
} }
else if (request.contains("ripple_state")) else if (request.contains(JS(ripple_state)))
{ {
if (!request.at("ripple_state").is_object()) if (!request.at(JS(ripple_state)).is_object())
return Status{Error::rpcINVALID_PARAMS, "rippleStateNotObject"}; return Status{Error::rpcINVALID_PARAMS, "rippleStateNotObject"};
ripple::Currency currency; ripple::Currency currency;
boost::json::object const& state = boost::json::object const& state =
request.at("ripple_state").as_object(); request.at(JS(ripple_state)).as_object();
if (!state.contains("currency") || !state.at("currency").is_string()) if (!state.contains(JS(currency)) ||
!state.at(JS(currency)).is_string())
{ {
return Status{Error::rpcINVALID_PARAMS, "malformedCurrency"}; return Status{Error::rpcINVALID_PARAMS, "malformedCurrency"};
} }
if (!state.contains("accounts") || !state.at("accounts").is_array() || if (!state.contains(JS(accounts)) ||
2 != state.at("accounts").as_array().size() || !state.at(JS(accounts)).is_array() ||
!state.at("accounts").as_array().at(0).is_string() || 2 != state.at(JS(accounts)).as_array().size() ||
!state.at("accounts").as_array().at(1).is_string() || !state.at(JS(accounts)).as_array().at(0).is_string() ||
(state.at("accounts").as_array().at(0).as_string() == !state.at(JS(accounts)).as_array().at(1).is_string() ||
state.at("accounts").as_array().at(1).as_string())) (state.at(JS(accounts)).as_array().at(0).as_string() ==
state.at(JS(accounts)).as_array().at(1).as_string()))
{ {
return Status{Error::rpcINVALID_PARAMS, "malformedAccounts"}; return Status{Error::rpcINVALID_PARAMS, "malformedAccounts"};
} }
auto const id1 = ripple::parseBase58<ripple::AccountID>( auto const id1 = ripple::parseBase58<ripple::AccountID>(
state.at("accounts").as_array().at(0).as_string().c_str()); state.at(JS(accounts)).as_array().at(0).as_string().c_str());
auto const id2 = ripple::parseBase58<ripple::AccountID>( auto const id2 = ripple::parseBase58<ripple::AccountID>(
state.at("accounts").as_array().at(1).as_string().c_str()); state.at(JS(accounts)).as_array().at(1).as_string().c_str());
if (!id1 || !id2) if (!id1 || !id2)
return Status{Error::rpcINVALID_PARAMS, "malformedAccounts"}; return Status{Error::rpcINVALID_PARAMS, "malformedAccounts"};
else if (!ripple::to_currency( else if (!ripple::to_currency(
currency, state.at("currency").as_string().c_str())) currency, state.at(JS(currency)).as_string().c_str()))
return Status{Error::rpcINVALID_PARAMS, "malformedCurrency"}; return Status{Error::rpcINVALID_PARAMS, "malformedCurrency"};
key = ripple::keylet::line(*id1, *id2, currency).key; key = ripple::keylet::line(*id1, *id2, currency).key;
} }
else if (request.contains("ticket")) else if (request.contains(JS(ticket)))
{ {
if (!request.at("ticket").is_object()) if (!request.at(JS(ticket)).is_object())
{ {
if (!request.at("ticket").is_string()) if (!request.at(JS(ticket)).is_string())
return Status{Error::rpcINVALID_PARAMS, "ticketNotString"}; return Status{Error::rpcINVALID_PARAMS, "ticketNotString"};
if (!key.parseHex(request.at("ticket").as_string().c_str())) if (!key.parseHex(request.at(JS(ticket)).as_string().c_str()))
return Status{Error::rpcINVALID_PARAMS, "malformedTicket"}; return Status{Error::rpcINVALID_PARAMS, "malformedTicket"};
} }
else if ( else if (
!request.at("ticket").as_object().contains("account") || !request.at(JS(ticket)).as_object().contains(JS(account)) ||
!request.at("ticket").as_object().at("account").is_string()) !request.at(JS(ticket)).as_object().at(JS(account)).is_string())
{ {
return Status{Error::rpcINVALID_PARAMS, "malformedTicketAccount"}; return Status{Error::rpcINVALID_PARAMS, "malformedTicketAccount"};
} }
else if ( else if (
!request.at("ticket").as_object().contains("ticket_seq") || !request.at(JS(ticket)).as_object().contains(JS(ticket_seq)) ||
!request.at("ticket").as_object().at("ticket_seq").is_int64()) !request.at(JS(ticket)).as_object().at(JS(ticket_seq)).is_int64())
{ {
return Status{Error::rpcINVALID_PARAMS, "malformedTicketSeq"}; return Status{Error::rpcINVALID_PARAMS, "malformedTicketSeq"};
} }
else else
{ {
auto const id = auto const id =
ripple::parseBase58<ripple::AccountID>(request.at("ticket") ripple::parseBase58<ripple::AccountID>(request.at(JS(ticket))
.as_object() .as_object()
.at("account") .at(JS(account))
.as_string() .as_string()
.c_str()); .c_str());
@@ -331,8 +337,10 @@ doLedgerEntry(Context const& context)
Error::rpcINVALID_PARAMS, "malformedTicketAccount"}; Error::rpcINVALID_PARAMS, "malformedTicketAccount"};
else else
{ {
std::uint32_t seq = std::uint32_t seq = request.at(JS(offer))
request.at("offer").as_object().at("ticket_seq").as_int64(); .as_object()
.at(JS(ticket_seq))
.as_int64();
key = ripple::getTicketIndex(*id, seq); key = ripple::getTicketIndex(*id, seq);
} }
@@ -351,19 +359,19 @@ doLedgerEntry(Context const& context)
if (!dbResponse or dbResponse->size() == 0) if (!dbResponse or dbResponse->size() == 0)
return Status{Error::rpcOBJECT_NOT_FOUND, "entryNotFound"}; return Status{Error::rpcOBJECT_NOT_FOUND, "entryNotFound"};
response["index"] = ripple::strHex(key); response[JS(index)] = ripple::strHex(key);
response["ledger_hash"] = ripple::strHex(lgrInfo.hash); response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash);
response["ledger_index"] = lgrInfo.seq; response[JS(ledger_index)] = lgrInfo.seq;
if (binary) if (binary)
{ {
response["node_binary"] = ripple::strHex(*dbResponse); response[JS(node_binary)] = ripple::strHex(*dbResponse);
} }
else else
{ {
ripple::STLedgerEntry sle{ ripple::STLedgerEntry sle{
ripple::SerialIter{dbResponse->data(), dbResponse->size()}, key}; ripple::SerialIter{dbResponse->data(), dbResponse->size()}, key};
response["node"] = toJson(sle); response[JS(node)] = toJson(sle);
} }
return response; return response;

View File

@@ -16,8 +16,8 @@ doLedgerRange(Context const& context)
} }
else else
{ {
response["ledger_index_min"] = range->minSequence; response[JS(ledger_index_min)] = range->minSequence;
response["ledger_index_max"] = range->maxSequence; response[JS(ledger_index_max)] = range->maxSequence;
} }
return response; return response;

View File

@@ -0,0 +1,178 @@
#include <ripple/app/ledger/Ledger.h>
#include <ripple/basics/StringUtilities.h>
#include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/STLedgerEntry.h>
#include <ripple/protocol/jss.h>
#include <boost/json.hpp>
#include <algorithm>
#include <rpc/RPCHelpers.h>
namespace RPC {
static void
appendNftOfferJson(ripple::SLE const& offer, boost::json::array& offers)
{
offers.push_back(boost::json::object_kind);
boost::json::object& obj(offers.back().as_object());
obj.at(JS(index)) = ripple::to_string(offer.key());
obj.at(JS(flags)) = (offer)[ripple::sfFlags];
obj.at(JS(owner)) = ripple::toBase58(offer.getAccountID(ripple::sfOwner));
if (offer.isFieldPresent(ripple::sfDestination))
obj[JS(destination)] =
ripple::toBase58(offer.getAccountID(ripple::sfDestination));
if (offer.isFieldPresent(ripple::sfExpiration))
obj.at(JS(expiration)) = offer.getFieldU32(ripple::sfExpiration);
obj.at(JS(amount)) = toBoostJson(offer.getFieldAmount(ripple::sfAmount)
.getJson(ripple::JsonOptions::none));
}
static Result
enumerateNFTOffers(
Context const& context,
ripple::uint256 const& tokenid,
ripple::Keylet const& directory)
{
auto const& request = context.params;
auto v = ledgerInfoFromRequest(context);
if (auto status = std::get_if<Status>(&v))
return *status;
auto lgrInfo = std::get<ripple::LedgerInfo>(v);
// TODO: just check for existence without pulling
if (!context.backend->fetchLedgerObject(
directory.key, lgrInfo.seq, context.yield))
return Status{Error::rpcOBJECT_NOT_FOUND, "notFound"};
std::uint32_t limit = 200;
if (auto const status = getLimit(request, limit); status)
return status;
boost::json::object response = {};
response[JS(nft_id)] = ripple::to_string(tokenid);
response[JS(offers)] = boost::json::value(boost::json::array_kind);
auto& jsonOffers = response[JS(offers)].as_array();
std::vector<ripple::SLE> offers;
std::uint64_t reserve(limit);
ripple::uint256 cursor;
if (request.contains(JS(marker)))
{
// We have a start point. Use limit - 1 from the result and use the
// very last one for the resume.
auto const& marker(request.at(JS(marker)));
if (!marker.is_string())
return Status{Error::rpcINVALID_PARAMS, "markerNotString"};
if (!cursor.parseHex(marker.as_string().c_str()))
return Status{Error::rpcINVALID_PARAMS, "malformedCursor"};
auto const sle =
read(ripple::keylet::nftoffer(cursor), lgrInfo, context);
if (!sle || tokenid != sle->getFieldH256(ripple::sfNFTokenID))
return Status{Error::rpcOBJECT_NOT_FOUND, "notFound"};
if (tokenid != sle->getFieldH256(ripple::sfNFTokenID))
return Status{Error::rpcINVALID_PARAMS, "invalidTokenid"};
appendNftOfferJson(*sle, jsonOffers);
offers.reserve(reserve);
}
else
{
// We have no start point, limit should be one higher than requested.
offers.reserve(++reserve);
}
auto result = traverseOwnedNodes(
*context.backend,
directory,
cursor,
0,
lgrInfo.seq,
limit,
{},
context.yield,
[&offers](ripple::SLE const& offer) {
if (offer.getType() == ripple::ltNFTOKEN_OFFER)
{
offers.emplace_back(offer);
return true;
}
return false;
});
if (auto status = std::get_if<RPC::Status>(&result))
return *status;
if (offers.size() == reserve)
{
response.at(JS(limit)) = limit;
response.at(JS(marker)) = to_string(offers.back().key());
offers.pop_back();
}
for (auto const& offer : offers)
appendNftOfferJson(offer, jsonOffers);
return response;
}
std::variant<ripple::uint256, Status>
getTokenid(boost::json::object const& request)
{
if (!request.contains(JS(nft_id)))
return Status{Error::rpcINVALID_PARAMS, "missingTokenid"};
if (!request.at(JS(nft_id)).is_string())
return Status{Error::rpcINVALID_PARAMS, "tokenidNotString"};
ripple::uint256 tokenid;
if (!tokenid.parseHex(request.at(JS(nft_id)).as_string().c_str()))
return Status{Error::rpcINVALID_PARAMS, "malformedCursor"};
return tokenid;
}
Result
doNFTOffers(Context const& context, bool sells)
{
auto const v = getTokenid(context.params);
if (auto const status = std::get_if<Status>(&v))
return *status;
auto const getKeylet = [sells, &v]() {
if (sells)
return ripple::keylet::nft_sells(std::get<ripple::uint256>(v));
return ripple::keylet::nft_buys(std::get<ripple::uint256>(v));
};
return enumerateNFTOffers(
context, std::get<ripple::uint256>(v), getKeylet());
}
Result
doNFTSellOffers(Context const& context)
{
return doNFTOffers(context, true);
}
Result
doNFTBuyOffers(Context const& context)
{
return doNFTOffers(context, false);
}
} // namespace RPC

View File

@@ -10,9 +10,9 @@ getBaseTx(
ripple::Fees const& fees) ripple::Fees const& fees)
{ {
boost::json::object tx; boost::json::object tx;
tx["Sequence"] = accountSeq; tx[JS(Sequence)] = accountSeq;
tx["Account"] = ripple::toBase58(accountID); tx[JS(Account)] = ripple::toBase58(accountID);
tx["Fee"] = RPC::toBoostJson(fees.units.jsonClipped()); tx[JS(Fee)] = RPC::toBoostJson(fees.units.jsonClipped());
return tx; return tx;
} }
@@ -21,11 +21,9 @@ doNoRippleCheck(Context const& context)
{ {
auto const& request = context.params; auto const& request = context.params;
auto accountID = ripple::AccountID accountID;
accountFromStringStrict(getRequiredString(request, "account")); if (auto const status = getAccount(request, accountID); status)
return status;
if (!accountID)
return Status{Error::rpcINVALID_PARAMS, "malformedAccount"};
std::string role = getRequiredString(request, "role"); std::string role = getRequiredString(request, "role");
bool roleGateway = false; bool roleGateway = false;
@@ -36,7 +34,9 @@ doNoRippleCheck(Context const& context)
return Status{Error::rpcINVALID_PARAMS, "role field is invalid"}; return Status{Error::rpcINVALID_PARAMS, "role field is invalid"};
} }
size_t limit = getUInt(request, "limit", 300); std::uint32_t limit = 300;
if (auto const status = getLimit(request, limit); status)
return status;
bool includeTxs = getBool(request, "transactions", false); bool includeTxs = getBool(request, "transactions", false);
@@ -51,11 +51,11 @@ doNoRippleCheck(Context const& context)
boost::json::array transactions; boost::json::array transactions;
auto keylet = ripple::keylet::account(*accountID); auto keylet = ripple::keylet::account(accountID);
auto accountObj = context.backend->fetchLedgerObject( auto accountObj = context.backend->fetchLedgerObject(
keylet.key, lgrInfo.seq, context.yield); keylet.key, lgrInfo.seq, context.yield);
if (!accountObj) if (!accountObj)
throw AccountNotFoundError(ripple::toBase58(*accountID)); throw AccountNotFoundError(ripple::toBase58(accountID));
ripple::SerialIter it{accountObj->data(), accountObj->size()}; ripple::SerialIter it{accountObj->data(), accountObj->size()};
ripple::SLE sle{it, keylet.key}; ripple::SLE sle{it, keylet.key};
@@ -79,16 +79,16 @@ doNoRippleCheck(Context const& context)
"You should immediately set your default ripple flag"); "You should immediately set your default ripple flag");
if (includeTxs) if (includeTxs)
{ {
auto tx = getBaseTx(*accountID, accountSeq++, *fees); auto tx = getBaseTx(accountID, accountSeq++, *fees);
tx["TransactionType"] = "AccountSet"; tx[JS(TransactionType)] = JS(AccountSet);
tx["SetFlag"] = 8; tx[JS(SetFlag)] = 8;
transactions.push_back(tx); transactions.push_back(tx);
} }
} }
traverseOwnedNodes( traverseOwnedNodes(
*context.backend, *context.backend,
*accountID, accountID,
lgrInfo.seq, lgrInfo.seq,
std::numeric_limits<std::uint32_t>::max(), std::numeric_limits<std::uint32_t>::max(),
{}, {},
@@ -141,11 +141,11 @@ doNoRippleCheck(Context const& context)
ripple::STAmount limitAmount(ownedItem.getFieldAmount( ripple::STAmount limitAmount(ownedItem.getFieldAmount(
bLow ? ripple::sfLowLimit : ripple::sfHighLimit)); bLow ? ripple::sfLowLimit : ripple::sfHighLimit));
limitAmount.setIssuer(peer); limitAmount.setIssuer(peer);
auto tx = getBaseTx(*accountID, accountSeq++, *fees); auto tx = getBaseTx(accountID, accountSeq++, *fees);
tx["TransactionType"] = "TrustSet"; tx[JS(TransactionType)] = JS(TrustSet);
tx["LimitAmount"] = RPC::toBoostJson( tx[JS(LimitAmount)] = RPC::toBoostJson(
limitAmount.getJson(ripple::JsonOptions::none)); limitAmount.getJson(ripple::JsonOptions::none));
tx["Flags"] = bNoRipple ? ripple::tfClearNoRipple tx[JS(Flags)] = bNoRipple ? ripple::tfClearNoRipple
: ripple::tfSetNoRipple; : ripple::tfSetNoRipple;
transactions.push_back(tx); transactions.push_back(tx);
} }
@@ -158,11 +158,11 @@ doNoRippleCheck(Context const& context)
}); });
boost::json::object response; boost::json::object response;
response["ledger_index"] = lgrInfo.seq; response[JS(ledger_index)] = lgrInfo.seq;
response["ledger_hash"] = ripple::strHex(lgrInfo.hash); response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash);
response["problems"] = std::move(problems); response["problems"] = std::move(problems);
if (includeTxs) if (includeTxs)
response["transactions"] = std::move(transactions); response[JS(transactions)] = std::move(transactions);
return response; return response;
} }

View File

@@ -1,6 +1,10 @@
// rngfill.h doesn't compile without this include
#include <cassert>
#include <ripple/beast/utility/rngfill.h> #include <ripple/beast/utility/rngfill.h>
#include <ripple/crypto/csprng.h> #include <ripple/crypto/csprng.h>
#include <rpc/RPCHelpers.h> #include <rpc/RPCHelpers.h>
namespace RPC { namespace RPC {
Result Result
@@ -10,7 +14,8 @@ doRandom(Context const& context)
beast::rngfill(rand.begin(), rand.size(), ripple::crypto_prng()); beast::rngfill(rand.begin(), rand.size(), ripple::crypto_prng());
boost::json::object result; boost::json::object result;
result["random"] = ripple::strHex(rand); result[JS(random)] = ripple::strHex(rand);
return result; return result;
} }
} // namespace RPC } // namespace RPC

View File

@@ -36,42 +36,42 @@ doServerInfo(Context const& context)
if (age < 0) if (age < 0)
age = 0; age = 0;
response["info"] = boost::json::object{}; response[JS(info)] = boost::json::object{};
boost::json::object& info = response["info"].as_object(); boost::json::object& info = response[JS(info)].as_object();
info["complete_ledgers"] = std::to_string(range->minSequence) + "-" + info[JS(complete_ledgers)] = std::to_string(range->minSequence) + "-" +
std::to_string(range->maxSequence); std::to_string(range->maxSequence);
info["counters"] = boost::json::object{}; info[JS(counters)] = boost::json::object{};
info["counters"].as_object()["rpc"] = context.counters.report(); info[JS(counters)].as_object()[JS(rpc)] = context.counters.report();
auto serverInfoRippled = context.balancer->forwardToRippled( auto serverInfoRippled = context.balancer->forwardToRippled(
{{"command", "server_info"}}, context.clientIp, context.yield); {{"counters", "server_info"}}, context.clientIp, context.yield);
info["load_factor"] = 1; info[JS(load_factor)] = 1;
if (serverInfoRippled && !serverInfoRippled->contains("error")) if (serverInfoRippled && !serverInfoRippled->contains(JS(error)))
{ {
try try
{ {
auto& rippledResult = serverInfoRippled->at("result").as_object(); auto& rippledResult = serverInfoRippled->at(JS(result)).as_object();
auto& rippledInfo = rippledResult.at("info").as_object(); auto& rippledInfo = rippledResult.at(JS(info)).as_object();
info["load_factor"] = rippledInfo["load_factor"]; info[JS(load_factor)] = rippledInfo[JS(load_factor)];
info["validation_quorum"] = rippledInfo["validation_quorum"]; info[JS(validation_quorum)] = rippledInfo[JS(validation_quorum)];
} }
catch (std::exception const&) catch (std::exception const&)
{ {
} }
} }
info["validated_ledger"] = boost::json::object{}; info[JS(validated_ledger)] = boost::json::object{};
boost::json::object& validated = info["validated_ledger"].as_object(); boost::json::object& validated = info[JS(validated_ledger)].as_object();
validated["age"] = age; validated[JS(age)] = age;
validated["hash"] = ripple::strHex(lgrInfo->hash); validated[JS(hash)] = ripple::strHex(lgrInfo->hash);
validated["seq"] = lgrInfo->seq; validated[JS(seq)] = lgrInfo->seq;
validated["base_fee_xrp"] = fees->base.decimalXRP(); validated[JS(base_fee_xrp)] = fees->base.decimalXRP();
validated["reserve_base_xrp"] = fees->reserve.decimalXRP(); validated[JS(reserve_base_xrp)] = fees->reserve.decimalXRP();
validated["reserve_inc_xrp"] = fees->increment.decimalXRP(); validated[JS(reserve_inc_xrp)] = fees->increment.decimalXRP();
response["cache"] = boost::json::object{}; response["cache"] = boost::json::object{};
auto& cache = response["cache"].as_object(); auto& cache = response["cache"].as_object();

View File

@@ -17,7 +17,7 @@ static std::unordered_set<std::string> validCommonStreams{
Status Status
validateStreams(boost::json::object const& request) validateStreams(boost::json::object const& request)
{ {
boost::json::array const& streams = request.at("streams").as_array(); boost::json::array const& streams = request.at(JS(streams)).as_array();
for (auto const& stream : streams) for (auto const& stream : streams)
{ {
@@ -40,7 +40,7 @@ subscribeToStreams(
std::shared_ptr<WsBase> session, std::shared_ptr<WsBase> session,
SubscriptionManager& manager) SubscriptionManager& manager)
{ {
boost::json::array const& streams = request.at("streams").as_array(); boost::json::array const& streams = request.at(JS(streams)).as_array();
boost::json::object response; boost::json::object response;
for (auto const& stream : streams) for (auto const& stream : streams)
@@ -69,7 +69,7 @@ unsubscribeToStreams(
std::shared_ptr<WsBase> session, std::shared_ptr<WsBase> session,
SubscriptionManager& manager) SubscriptionManager& manager)
{ {
boost::json::array const& streams = request.at("streams").as_array(); boost::json::array const& streams = request.at(JS(streams)).as_array();
for (auto const& stream : streams) for (auto const& stream : streams)
{ {
@@ -114,7 +114,7 @@ subscribeToAccounts(
std::shared_ptr<WsBase> session, std::shared_ptr<WsBase> session,
SubscriptionManager& manager) SubscriptionManager& manager)
{ {
boost::json::array const& accounts = request.at("accounts").as_array(); boost::json::array const& accounts = request.at(JS(accounts)).as_array();
for (auto const& account : accounts) for (auto const& account : accounts)
{ {
@@ -138,7 +138,7 @@ unsubscribeToAccounts(
std::shared_ptr<WsBase> session, std::shared_ptr<WsBase> session,
SubscriptionManager& manager) SubscriptionManager& manager)
{ {
boost::json::array const& accounts = request.at("accounts").as_array(); boost::json::array const& accounts = request.at(JS(accounts)).as_array();
for (auto const& account : accounts) for (auto const& account : accounts)
{ {
@@ -163,7 +163,7 @@ subscribeToAccountsProposed(
SubscriptionManager& manager) SubscriptionManager& manager)
{ {
boost::json::array const& accounts = boost::json::array const& accounts =
request.at("accounts_proposed").as_array(); request.at(JS(accounts_proposed)).as_array();
for (auto const& account : accounts) for (auto const& account : accounts)
{ {
@@ -188,7 +188,7 @@ unsubscribeToAccountsProposed(
SubscriptionManager& manager) SubscriptionManager& manager)
{ {
boost::json::array const& accounts = boost::json::array const& accounts =
request.at("accounts_proposed").as_array(); request.at(JS(accounts_proposed)).as_array();
for (auto const& account : accounts) for (auto const& account : accounts)
{ {
@@ -212,55 +212,44 @@ validateAndGetBooks(
boost::json::object const& request, boost::json::object const& request,
std::shared_ptr<Backend::BackendInterface const> const& backend) std::shared_ptr<Backend::BackendInterface const> const& backend)
{ {
if (!request.at("books").is_array()) if (!request.at(JS(books)).is_array())
return Status{Error::rpcINVALID_PARAMS, "booksNotArray"}; return Status{Error::rpcINVALID_PARAMS, "booksNotArray"};
boost::json::array const& books = request.at("books").as_array(); boost::json::array const& books = request.at(JS(books)).as_array();
std::vector<ripple::Book> booksToSub; std::vector<ripple::Book> booksToSub;
std::optional<Backend::LedgerRange> rng; std::optional<Backend::LedgerRange> rng;
boost::json::array snapshot; boost::json::array snapshot;
for (auto const& book : books) for (auto const& book : books)
{ {
auto parsed = parseBook(book.as_object()); auto parsedBook = parseBook(book.as_object());
if (auto status = std::get_if<Status>(&parsed)) if (auto status = std::get_if<Status>(&parsedBook))
return *status; return *status;
else
{ auto b = std::get<ripple::Book>(parsedBook);
auto b = std::get<ripple::Book>(parsed);
booksToSub.push_back(b); booksToSub.push_back(b);
bool both = book.as_object().contains("both"); bool both = book.as_object().contains(JS(both));
if (both) if (both)
booksToSub.push_back(ripple::reversed(b)); booksToSub.push_back(ripple::reversed(b));
if (book.as_object().contains("snapshot")) if (book.as_object().contains(JS(snapshot)))
{ {
if (!rng) if (!rng)
rng = backend->fetchLedgerRange(); rng = backend->fetchLedgerRange();
ripple::AccountID takerID = beast::zero; ripple::AccountID takerID = beast::zero;
if (book.as_object().contains("taker")) if (book.as_object().contains(JS(taker)))
{ if (auto const status = getTaker(book.as_object(), takerID);
auto parsed = parseTaker(request.at("taker")); status)
if (auto status = std::get_if<Status>(&parsed)) return status;
return *status;
else
{
takerID = std::get<ripple::AccountID>(parsed);
}
}
auto getOrderBook = [&snapshot, &backend, &rng, &takerID]( auto getOrderBook = [&snapshot, &backend, &rng, &takerID](
auto book, auto book,
boost::asio::yield_context& yield) { boost::asio::yield_context& yield) {
auto bookBase = getBookBase(book); auto bookBase = getBookBase(book);
auto [offers, retCursor] = backend->fetchBookOffers( auto [offers, retMarker] = backend->fetchBookOffers(
bookBase, rng->maxSequence, 200, {}, yield); bookBase, rng->maxSequence, 200, {}, yield);
auto orderBook = postProcessOrderBook( auto orderBook = postProcessOrderBook(
offers, offers, book, takerID, *backend, rng->maxSequence, yield);
book,
takerID,
*backend,
rng->maxSequence,
yield);
std::copy( std::copy(
orderBook.begin(), orderBook.begin(),
orderBook.end(), orderBook.end(),
@@ -271,9 +260,9 @@ validateAndGetBooks(
getOrderBook(ripple::reversed(b), yield); getOrderBook(ripple::reversed(b), yield);
} }
} }
}
return std::make_pair(booksToSub, snapshot); return std::make_pair(booksToSub, snapshot);
} }
void void
subscribeToBooks( subscribeToBooks(
std::vector<ripple::Book> const& books, std::vector<ripple::Book> const& books,
@@ -285,14 +274,15 @@ subscribeToBooks(
manager.subBook(book, session); manager.subBook(book, session);
} }
} }
Result Result
doSubscribe(Context const& context) doSubscribe(Context const& context)
{ {
auto request = context.params; auto request = context.params;
if (request.contains("streams")) if (request.contains(JS(streams)))
{ {
if (!request.at("streams").is_array()) if (!request.at(JS(streams)).is_array())
return Status{Error::rpcINVALID_PARAMS, "streamsNotArray"}; return Status{Error::rpcINVALID_PARAMS, "streamsNotArray"};
auto status = validateStreams(request); auto status = validateStreams(request);
@@ -301,25 +291,25 @@ doSubscribe(Context const& context)
return status; return status;
} }
if (request.contains("accounts")) if (request.contains(JS(accounts)))
{ {
if (!request.at("accounts").is_array()) if (!request.at(JS(accounts)).is_array())
return Status{Error::rpcINVALID_PARAMS, "accountsNotArray"}; return Status{Error::rpcINVALID_PARAMS, "accountsNotArray"};
boost::json::array accounts = request.at("accounts").as_array(); boost::json::array accounts = request.at(JS(accounts)).as_array();
auto status = validateAccounts(accounts); auto status = validateAccounts(accounts);
if (status) if (status)
return status; return status;
} }
if (request.contains("accounts_proposed")) if (request.contains(JS(accounts_proposed)))
{ {
if (!request.at("accounts_proposed").is_array()) if (!request.at(JS(accounts_proposed)).is_array())
return Status{Error::rpcINVALID_PARAMS, "accountsProposedNotArray"}; return Status{Error::rpcINVALID_PARAMS, "accountsProposedNotArray"};
boost::json::array accounts = boost::json::array accounts =
request.at("accounts_proposed").as_array(); request.at(JS(accounts_proposed)).as_array();
auto status = validateAccounts(accounts); auto status = validateAccounts(accounts);
if (status) if (status)
@@ -327,7 +317,7 @@ doSubscribe(Context const& context)
} }
std::vector<ripple::Book> books; std::vector<ripple::Book> books;
boost::json::array snapshot; boost::json::array snapshot;
if (request.contains("books")) if (request.contains(JS(books)))
{ {
auto parsed = auto parsed =
validateAndGetBooks(context.yield, request, context.backend); validateAndGetBooks(context.yield, request, context.backend);
@@ -341,22 +331,22 @@ doSubscribe(Context const& context)
} }
boost::json::object response; boost::json::object response;
if (request.contains("streams")) if (request.contains(JS(streams)))
response = subscribeToStreams( response = subscribeToStreams(
context.yield, request, context.session, *context.subscriptions); context.yield, request, context.session, *context.subscriptions);
if (request.contains("accounts")) if (request.contains(JS(accounts)))
subscribeToAccounts(request, context.session, *context.subscriptions); subscribeToAccounts(request, context.session, *context.subscriptions);
if (request.contains("accounts_proposed")) if (request.contains(JS(accounts_proposed)))
subscribeToAccountsProposed( subscribeToAccountsProposed(
request, context.session, *context.subscriptions); request, context.session, *context.subscriptions);
if (request.contains("books")) if (request.contains(JS(books)))
subscribeToBooks(books, context.session, *context.subscriptions); subscribeToBooks(books, context.session, *context.subscriptions);
if (snapshot.size()) if (snapshot.size())
response["offers"] = snapshot; response[JS(offers)] = snapshot;
return response; return response;
} }
@@ -365,9 +355,9 @@ doUnsubscribe(Context const& context)
{ {
auto request = context.params; auto request = context.params;
if (request.contains("streams")) if (request.contains(JS(streams)))
{ {
if (!request.at("streams").is_array()) if (!request.at(JS(streams)).is_array())
return Status{Error::rpcINVALID_PARAMS, "streamsNotArray"}; return Status{Error::rpcINVALID_PARAMS, "streamsNotArray"};
auto status = validateStreams(request); auto status = validateStreams(request);
@@ -376,38 +366,38 @@ doUnsubscribe(Context const& context)
return status; return status;
} }
if (request.contains("accounts")) if (request.contains(JS(accounts)))
{ {
if (!request.at("accounts").is_array()) if (!request.at(JS(accounts)).is_array())
return Status{Error::rpcINVALID_PARAMS, "accountsNotArray"}; return Status{Error::rpcINVALID_PARAMS, "accountsNotArray"};
boost::json::array accounts = request.at("accounts").as_array(); boost::json::array accounts = request.at(JS(accounts)).as_array();
auto status = validateAccounts(accounts); auto status = validateAccounts(accounts);
if (status) if (status)
return status; return status;
} }
if (request.contains("accounts_proposed")) if (request.contains(JS(accounts_proposed)))
{ {
if (!request.at("accounts_proposed").is_array()) if (!request.at(JS(accounts_proposed)).is_array())
return Status{Error::rpcINVALID_PARAMS, "accountsProposedNotArray"}; return Status{Error::rpcINVALID_PARAMS, "accountsProposedNotArray"};
boost::json::array accounts = boost::json::array accounts =
request.at("accounts_proposed").as_array(); request.at(JS(accounts_proposed)).as_array();
auto status = validateAccounts(accounts); auto status = validateAccounts(accounts);
if (status) if (status)
return status; return status;
} }
if (request.contains("streams")) if (request.contains(JS(streams)))
unsubscribeToStreams(request, context.session, *context.subscriptions); unsubscribeToStreams(request, context.session, *context.subscriptions);
if (request.contains("accounts")) if (request.contains(JS(accounts)))
unsubscribeToAccounts(request, context.session, *context.subscriptions); unsubscribeToAccounts(request, context.session, *context.subscriptions);
if (request.contains("accounts_proposed")) if (request.contains(JS(accounts_proposed)))
unsubscribeToAccountsProposed( unsubscribeToAccountsProposed(
request, context.session, *context.subscriptions); request, context.session, *context.subscriptions);

View File

@@ -13,7 +13,7 @@ doTransactionEntry(Context const& context)
auto lgrInfo = std::get<ripple::LedgerInfo>(v); auto lgrInfo = std::get<ripple::LedgerInfo>(v);
ripple::uint256 hash; ripple::uint256 hash;
if (!hash.parseHex(getRequiredString(context.params, "tx_hash"))) if (!hash.parseHex(getRequiredString(context.params, JS(tx_hash))))
return Status{Error::rpcINVALID_PARAMS, "malformedTransaction"}; return Status{Error::rpcINVALID_PARAMS, "malformedTransaction"};
auto dbResponse = context.backend->fetchTransaction(hash, context.yield); auto dbResponse = context.backend->fetchTransaction(hash, context.yield);
@@ -33,10 +33,10 @@ doTransactionEntry(Context const& context)
"Transaction not found."}; "Transaction not found."};
auto [txn, meta] = toExpandedJson(*dbResponse); auto [txn, meta] = toExpandedJson(*dbResponse);
response["tx_json"] = std::move(txn); response[JS(tx_json)] = std::move(txn);
response["metadata"] = std::move(meta); response[JS(metadata)] = std::move(meta);
response["ledger_index"] = lgrInfo.seq; response[JS(ledger_index)] = lgrInfo.seq;
response["ledger_hash"] = ripple::strHex(lgrInfo.hash); response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash);
return response; return response;
} }

View File

@@ -14,23 +14,23 @@ doTx(Context const& context)
auto request = context.params; auto request = context.params;
boost::json::object response = {}; boost::json::object response = {};
if (!request.contains("transaction")) if (!request.contains(JS(transaction)))
return Status{Error::rpcINVALID_PARAMS, "specifyTransaction"}; return Status{Error::rpcINVALID_PARAMS, "specifyTransaction"};
if (!request.at("transaction").is_string()) if (!request.at(JS(transaction)).is_string())
return Status{Error::rpcINVALID_PARAMS, "transactionNotString"}; return Status{Error::rpcINVALID_PARAMS, "transactionNotString"};
ripple::uint256 hash; ripple::uint256 hash;
if (!hash.parseHex(request.at("transaction").as_string().c_str())) if (!hash.parseHex(request.at(JS(transaction)).as_string().c_str()))
return Status{Error::rpcINVALID_PARAMS, "malformedTransaction"}; return Status{Error::rpcINVALID_PARAMS, "malformedTransaction"};
bool binary = false; bool binary = false;
if (request.contains("binary")) if (request.contains(JS(binary)))
{ {
if (!request.at("binary").is_bool()) if (!request.at(JS(binary)).is_bool())
return Status{Error::rpcINVALID_PARAMS, "binaryFlagNotBool"}; return Status{Error::rpcINVALID_PARAMS, "binaryFlagNotBool"};
binary = request.at("binary").as_bool(); binary = request.at(JS(binary)).as_bool();
} }
auto range = context.backend->fetchLedgerRange(); auto range = context.backend->fetchLedgerRange();
@@ -45,16 +45,16 @@ doTx(Context const& context)
{ {
auto [txn, meta] = toExpandedJson(*dbResponse); auto [txn, meta] = toExpandedJson(*dbResponse);
response = txn; response = txn;
response["meta"] = meta; response[JS(meta)] = meta;
} }
else else
{ {
response["tx"] = ripple::strHex(dbResponse->transaction); response[JS(tx)] = ripple::strHex(dbResponse->transaction);
response["meta"] = ripple::strHex(dbResponse->metadata); response[JS(meta)] = ripple::strHex(dbResponse->metadata);
response["hash"] = std::move(request.at("transaction").as_string()); response[JS(hash)] = std::move(request.at(JS(transaction)).as_string());
} }
response["date"] = dbResponse->date; response[JS(date)] = dbResponse->date;
response["ledger_index"] = dbResponse->ledgerSequence; response[JS(ledger_index)] = dbResponse->ledgerSequence;
return response; return response;
} }

View File

@@ -253,7 +253,6 @@ SubscriptionManager::pubTransaction(
std::string pubMsg{boost::json::serialize(pubObj)}; std::string pubMsg{boost::json::serialize(pubObj)};
txSubscribers_.publish(pubMsg); txSubscribers_.publish(pubMsg);
auto journal = ripple::debugLog();
auto accounts = meta->getAffectedAccounts(); auto accounts = meta->getAffectedAccounts();
for (auto const& account : accounts) for (auto const& account : accounts)