diff --git a/CMakeLists.txt b/CMakeLists.txt index 8eba531f..69a6ce46 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -62,6 +62,8 @@ target_sources(clio PRIVATE src/rpc/handlers/AccountTx.cpp # Dex src/rpc/handlers/BookOffers.cpp + # NFT + src/rpc/handlers/NFTOffers.cpp # Payment Channel src/rpc/handlers/ChannelAuthorize.cpp src/rpc/handlers/ChannelVerify.cpp diff --git a/src/backend/BackendInterface.cpp b/src/backend/BackendInterface.cpp index 6f969617..a8b11c54 100644 --- a/src/backend/BackendInterface.cpp +++ b/src/backend/BackendInterface.cpp @@ -259,7 +259,8 @@ BackendInterface::fetchLedgerPage( ripple::uint256 const& curCursor = keys.size() ? keys.back() : cursor ? *cursor : firstKey; - uint32_t seq = outOfOrder ? range->maxSequence : ledgerSequence; + std::uint32_t const seq = + outOfOrder ? range->maxSequence : ledgerSequence; auto succ = fetchSuccessorKey(curCursor, seq, yield); if (!succ) reachedEnd = true; diff --git a/src/rpc/Handlers.h b/src/rpc/Handlers.h index 02bcbf28..315be86e 100644 --- a/src/rpc/Handlers.h +++ b/src/rpc/Handlers.h @@ -21,6 +21,9 @@ doAccountCurrencies(Context const& context); Result doAccountLines(Context const& context); +Result +doAccountNFTs(Context const& context); + Result doAccountObjects(Context const& context); @@ -45,6 +48,13 @@ doChannelVerify(Context const& context); Result doBookOffers(Context const& context); +// NFT methods +Result +doNFTBuyOffers(Context const& context); + +Result +doNFTSellOffers(Context const& context); + // ledger methods Result doLedger(Context const& context); diff --git a/src/rpc/RPC.cpp b/src/rpc/RPC.cpp index 218f926d..9c79a91e 100644 --- a/src/rpc/RPC.cpp +++ b/src/rpc/RPC.cpp @@ -124,6 +124,7 @@ static std::unordered_map> {"account_currencies", &doAccountCurrencies}, {"account_info", &doAccountInfo}, {"account_lines", &doAccountLines}, + {"account_nfts", &doAccountNFTs}, {"account_objects", &doAccountObjects}, {"account_offers", &doAccountOffers}, {"account_tx", &doAccountTx}, @@ -137,6 +138,8 @@ static std::unordered_map> {"ledger_entry", &doLedgerEntry}, {"ledger_range", &doLedgerRange}, {"ledger_data", &doLedgerData}, + {"nft_buy_offers", &doNFTBuyOffers}, + {"nft_sell_offers", &doNFTSellOffers}, {"subscribe", &doSubscribe}, {"server_info", &doServerInfo}, {"unsubscribe", &doUnsubscribe}, diff --git a/src/rpc/RPC.h b/src/rpc/RPC.h index b514dfba..dfbf9689 100644 --- a/src/rpc/RPC.h +++ b/src/rpc/RPC.h @@ -107,6 +107,7 @@ struct Status : error(error_), message(message_) { } + Status(Error error_, std::string strCode_, std::string message_) : error(error_), strCode(strCode_), message(message_) { diff --git a/src/rpc/RPCHelpers.cpp b/src/rpc/RPCHelpers.cpp index 3a065a8b..95e85899 100644 --- a/src/rpc/RPCHelpers.cpp +++ b/src/rpc/RPCHelpers.cpp @@ -13,6 +13,7 @@ getBool(boost::json::object const& request, std::string const& field) else throw InvalidParamsError("Invalid field " + field + ", not bool."); } + bool getBool( boost::json::object const& request, @@ -24,6 +25,7 @@ getBool( else return dfault; } + bool 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 throw InvalidParamsError("Invalid field " + field + ", not string."); } + std::string 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 throw InvalidParamsError("Missing field " + field); } + std::string getString( boost::json::object const& request, @@ -172,6 +176,112 @@ getString( 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(&parsed)) + return *status; + else + takerID = std::get(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 getDeliveredAmount( std::shared_ptr const& txn, @@ -537,11 +647,35 @@ traverseOwnedNodes( if (!parsedCursor) return Status(ripple::rpcINVALID_PARAMS, "Malformed cursor"); - auto cursor = AccountCursor({beast::zero, 0}); - 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 +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 jsonCursor, + boost::asio::yield_context& yield, + std::function atOwnedNode) +{ + auto cursor = AccountCursor({beast::zero, 0}); + + auto const rootIndex = owner; auto currentIndex = rootIndex; std::vector keys; @@ -550,7 +684,7 @@ traverseOwnedNodes( auto start = std::chrono::system_clock::now(); // 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 hintDir = @@ -563,7 +697,7 @@ traverseOwnedNodes( for (auto const& key : sle.getFieldV256(ripple::sfIndexes)) { - if (key == hexCursor) + if (key == hexMarker) { // We found the hint, we can start here currentIndex = hintIndex; @@ -589,7 +723,7 @@ traverseOwnedNodes( { if (!found) { - if (key == hexCursor) + if (key == hexMarker) found = true; } else @@ -678,6 +812,23 @@ traverseOwnedNodes( return AccountCursor({beast::zero, 0}); } +std::shared_ptr +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::SerialIter{blob->data(), blob->size()}, keylet.key); + } + + return nullptr; +} + std::optional 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}}; } + std::variant parseTaker(boost::json::value const& taker) { diff --git a/src/rpc/RPCHelpers.h b/src/rpc/RPCHelpers.h index 932dc703..a21ef548 100644 --- a/src/rpc/RPCHelpers.h +++ b/src/rpc/RPCHelpers.h @@ -14,6 +14,13 @@ #include #include +// 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 { std::optional accountFromStringStrict(std::string const& account); @@ -93,6 +100,24 @@ traverseOwnedNodes( boost::asio::yield_context& yield, std::function atOwnedNode); +std::variant +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 jsonCursor, + boost::asio::yield_context& yield, + std::function atOwnedNode); + +std::shared_ptr +read( + ripple::Keylet const& keylet, + ripple::LedgerInfo const& lgrInfo, + Context const& context); + std::variant> keypairFromRequst(boost::json::object const& request); @@ -200,5 +225,27 @@ getString( boost::json::object const& request, std::string const& field, 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 #endif diff --git a/src/rpc/handlers/AccountChannels.cpp b/src/rpc/handlers/AccountChannels.cpp index 318b17db..07ef86cf 100644 --- a/src/rpc/handlers/AccountChannels.cpp +++ b/src/rpc/handlers/AccountChannels.cpp @@ -17,27 +17,27 @@ void addChannel(boost::json::array& jsonLines, ripple::SLE const& line) { boost::json::object jDst; - jDst["channel_id"] = ripple::to_string(line.key()); - jDst["account"] = ripple::to_string(line.getAccountID(ripple::sfAccount)); - jDst["destination_account"] = + jDst[JS(channel_id)] = ripple::to_string(line.key()); + jDst[JS(account)] = ripple::to_string(line.getAccountID(ripple::sfAccount)); + jDst[JS(destination_account)] = ripple::to_string(line.getAccountID(ripple::sfDestination)); - jDst["amount"] = line[ripple::sfAmount].getText(); - jDst["balance"] = line[ripple::sfBalance].getText(); + jDst[JS(amount)] = line[ripple::sfAmount].getText(); + jDst[JS(balance)] = line[ripple::sfBalance].getText(); if (publicKeyType(line[ripple::sfPublicKey])) { ripple::PublicKey const pk(line[ripple::sfPublicKey]); - jDst["public_key"] = toBase58(ripple::TokenType::AccountPublic, pk); - jDst["public_key_hex"] = strHex(pk); + jDst[JS(public_key)] = toBase58(ripple::TokenType::AccountPublic, 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]) - jDst["expiration"] = *v; + jDst[JS(expiration)] = *v; if (auto const& v = line[~ripple::sfCancelAfter]) - jDst["cancel_after"] = *v; + jDst[JS(cancel_after)] = *v; if (auto const& v = line[~ripple::sfSourceTag]) - jDst["source_tag"] = *v; + jDst[JS(source_tag)] = *v; if (auto const& v = line[~ripple::sfDestinationTag]) - jDst["destination_tag"] = *v; + jDst[JS(destination_tag)] = *v; jsonLines.push_back(jDst); } @@ -54,60 +54,38 @@ doAccountChannels(Context const& context) auto lgrInfo = std::get(v); - if (!request.contains("account")) - return Status{Error::rpcINVALID_PARAMS, "missingAccount"}; + ripple::AccountID accountID; + 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::optional 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"}; - } + ripple::AccountID destAccount; + if (auto const status = + getAccount(request, destAccount, JS(destination_account)); + status) + return status; std::uint32_t limit = 200; - if (request.contains("limit")) - { - 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"}; - } + if (auto const status = getLimit(request, limit); status) + return status; std::optional 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"}; - marker = request.at("marker").as_string().c_str(); + marker = request.at(JS(marker)).as_string().c_str(); } - response["account"] = ripple::to_string(*accountID); - response["channels"] = boost::json::value(boost::json::array_kind); - boost::json::array& jsonChannels = response.at("channels").as_array(); + response[JS(account)] = ripple::to_string(accountID); + response[JS(channels)] = boost::json::value(boost::json::array_kind); + boost::json::array& jsonChannels = response.at(JS(channels)).as_array(); auto const addToResponse = [&](ripple::SLE const& sle) { if (sle.getType() == ripple::ltPAYCHAN && - sle.getAccountID(ripple::sfAccount) == *accountID && + sle.getAccountID(ripple::sfAccount) == accountID && (!destAccount || - *destAccount == sle.getAccountID(ripple::sfDestination))) + destAccount == sle.getAccountID(ripple::sfDestination))) { if (limit-- == 0) { @@ -122,23 +100,23 @@ doAccountChannels(Context const& context) auto next = traverseOwnedNodes( *context.backend, - *accountID, + accountID, lgrInfo.seq, limit, marker, context.yield, addToResponse); - 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; if (auto status = std::get_if(&next)) return *status; - auto nextCursor = std::get(next); + auto nextMarker = std::get(next); - if (nextCursor.isNonZero()) - response["marker"] = nextCursor.toString(); + if (nextMarker.isNonZero()) + response[JS(marker)] = nextMarker.toString(); return response; } diff --git a/src/rpc/handlers/AccountCurrencies.cpp b/src/rpc/handlers/AccountCurrencies.cpp index 7eff2057..25278dff 100644 --- a/src/rpc/handlers/AccountCurrencies.cpp +++ b/src/rpc/handlers/AccountCurrencies.cpp @@ -24,17 +24,9 @@ doAccountCurrencies(Context const& context) auto lgrInfo = std::get(v); - if (!request.contains("account")) - return Status{Error::rpcINVALID_PARAMS, "missingAccount"}; - - 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"}; + ripple::AccountID accountID; + if (auto const status = getAccount(request, accountID); status) + return status; std::set send, receive; auto const addToResponse = [&](ripple::SLE const& sle) { @@ -61,26 +53,26 @@ doAccountCurrencies(Context const& context) traverseOwnedNodes( *context.backend, - *accountID, + accountID, lgrInfo.seq, std::numeric_limits::max(), {}, context.yield, addToResponse); - 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; - response["receive_currencies"] = + response[JS(receive_currencies)] = boost::json::value(boost::json::array_kind); boost::json::array& jsonReceive = - response.at("receive_currencies").as_array(); + response.at(JS(receive_currencies)).as_array(); for (auto const& currency : receive) jsonReceive.push_back(currency.c_str()); - response["send_currencies"] = boost::json::value(boost::json::array_kind); - boost::json::array& jsonSend = response.at("send_currencies").as_array(); + response[JS(send_currencies)] = boost::json::value(boost::json::array_kind); + boost::json::array& jsonSend = response.at(JS(send_currencies)).as_array(); for (auto const& currency : send) jsonSend.push_back(currency.c_str()); diff --git a/src/rpc/handlers/AccountInfo.cpp b/src/rpc/handlers/AccountInfo.cpp index 5e57dce6..b51d5089 100644 --- a/src/rpc/handlers/AccountInfo.cpp +++ b/src/rpc/handlers/AccountInfo.cpp @@ -29,10 +29,10 @@ doAccountInfo(Context const& context) boost::json::object response = {}; std::string strIdent; - if (request.contains("account")) - strIdent = request.at("account").as_string().c_str(); - else if (request.contains("ident")) - strIdent = request.at("ident").as_string().c_str(); + if (request.contains(JS(account))) + strIdent = request.at(JS(account)).as_string().c_str(); + else if (request.contains(JS(ident))) + strIdent = request.at(JS(ident)).as_string().c_str(); else return Status{Error::rpcACT_MALFORMED}; @@ -71,18 +71,18 @@ doAccountInfo(Context const& context) return Status{Error::rpcDB_DESERIALIZATION}; // if (!binary) - // response["account_data"] = getJson(sle); + // response[JS(account_data)] = getJson(sle); // else - // response["account_data"] = ripple::strHex(*dbResponse); - // response["db_time"] = time; + // response[JS(account_data)] = ripple::strHex(*dbResponse); + // response[JS(db_time)] = time; - response["account_data"] = toJson(sle); - response["ledger_hash"] = ripple::strHex(lgrInfo.hash); - response["ledger_index"] = lgrInfo.seq; + response[JS(account_data)] = toJson(sle); + response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash); + response[JS(ledger_index)] = lgrInfo.seq; // Return SignerList(s) if that is requested. - if (request.contains("signer_lists") && - request.at("signer_lists").as_bool()) + if (request.contains(JS(signer_lists)) && + request.at(JS(signer_lists)).as_bool()) { // We put the SignerList in an array because of an anticipated // future when we support multiple signer lists on one account. @@ -104,7 +104,7 @@ doAccountInfo(Context const& context) signerList.push_back(toJson(sleSigners)); } - response["account_data"].as_object()["signer_lists"] = + response[JS(account_data)].as_object()[JS(signer_lists)] = std::move(signerList); } diff --git a/src/rpc/handlers/AccountLines.cpp b/src/rpc/handlers/AccountLines.cpp index 3ed92eb2..66eb701c 100644 --- a/src/rpc/handlers/AccountLines.cpp +++ b/src/rpc/handlers/AccountLines.cpp @@ -64,25 +64,25 @@ addLine( ripple::STAmount const& saLimitPeer(lineLimitPeer); boost::json::object jPeer; - jPeer["account"] = ripple::to_string(lineAccountIDPeer); - jPeer["balance"] = saBalance.getText(); - jPeer["currency"] = ripple::to_string(saBalance.issue().currency); - jPeer["limit"] = saLimit.getText(); - jPeer["limit_peer"] = saLimitPeer.getText(); - jPeer["quality_in"] = lineQualityIn; - jPeer["quality_out"] = lineQualityOut; + jPeer[JS(account)] = ripple::to_string(lineAccountIDPeer); + jPeer[JS(balance)] = saBalance.getText(); + jPeer[JS(currency)] = ripple::to_string(saBalance.issue().currency); + jPeer[JS(limit)] = saLimit.getText(); + jPeer[JS(limit_peer)] = saLimitPeer.getText(); + jPeer[JS(quality_in)] = lineQualityIn; + jPeer[JS(quality_out)] = lineQualityOut; if (lineAuth) - jPeer["authorized"] = true; + jPeer[JS(authorized)] = true; if (lineAuthPeer) - jPeer["peer_authorized"] = true; + jPeer[JS(peer_authorized)] = true; if (lineNoRipple || !lineDefaultRipple) - jPeer["no_ripple"] = lineNoRipple; + jPeer[JS(no_ripple)] = lineNoRipple; if (lineNoRipple || !lineDefaultRipple) - jPeer["no_ripple_peer"] = lineNoRipplePeer; + jPeer[JS(no_ripple_peer)] = lineNoRipplePeer; if (lineFreeze) - jPeer["freeze"] = true; + jPeer[JS(freeze)] = true; if (lineFreezePeer) - jPeer["freeze_peer"] = true; + jPeer[JS(freeze_peer)] = true; jsonLines.push_back(jPeer); } @@ -99,82 +99,58 @@ doAccountLines(Context const& context) auto lgrInfo = std::get(v); - if (!request.contains("account")) - return Status{Error::rpcINVALID_PARAMS, "missingAccount"}; + ripple::AccountID accountID; + 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::optional 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"}; - } + ripple::AccountID peerAccount; + if (auto const status = getAccount(request, peerAccount, JS(peer)); status) + return status; std::uint32_t limit = 200; - if (request.contains("limit")) - { - if (!request.at("limit").is_int64()) - return Status{Error::rpcINVALID_PARAMS, "limitNotInt"}; + if (auto const status = getLimit(request, limit); status) + return status; - limit = request.at("limit").as_int64(); - if (limit <= 0) - return Status{Error::rpcINVALID_PARAMS, "limitNotPositive"}; - } - - std::optional cursor = {}; - if (request.contains("marker")) + std::optional 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"}; - cursor = request.at("marker").as_string().c_str(); + marker = request.at(JS(marker)).as_string().c_str(); } - response["account"] = ripple::to_string(*accountID); - response["ledger_hash"] = ripple::strHex(lgrInfo.hash); - response["ledger_index"] = lgrInfo.seq; - response["lines"] = boost::json::value(boost::json::array_kind); - boost::json::array& jsonLines = response.at("lines").as_array(); + response[JS(account)] = ripple::to_string(accountID); + response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash); + response[JS(ledger_index)] = lgrInfo.seq; + response[JS(lines)] = boost::json::value(boost::json::array_kind); + boost::json::array& jsonLines = response.at(JS(lines)).as_array(); auto const addToResponse = [&](ripple::SLE const& sle) -> void { if (sle.getType() == ripple::ltRIPPLE_STATE) { - addLine(jsonLines, sle, *accountID, peerAccount); + addLine(jsonLines, sle, accountID, peerAccount); } }; auto next = traverseOwnedNodes( *context.backend, - *accountID, + accountID, lgrInfo.seq, limit, - cursor, + marker, context.yield, addToResponse); if (auto status = std::get_if(&next)) return *status; - auto nextCursor = std::get(next); + auto nextMarker = std::get(next); - if (nextCursor.isNonZero()) - response["marker"] = nextCursor.toString(); + if (nextMarker.isNonZero()) + response[JS(marker)] = nextMarker.toString(); return response; } -} // namespace RPC \ No newline at end of file +} // namespace RPC diff --git a/src/rpc/handlers/AccountObjects.cpp b/src/rpc/handlers/AccountObjects.cpp index 15350174..45afab76 100644 --- a/src/rpc/handlers/AccountObjects.cpp +++ b/src/rpc/handlers/AccountObjects.cpp @@ -1,10 +1,12 @@ #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -23,7 +25,126 @@ std::unordered_map types{ {"escrow", ripple::ltESCROW}, {"deposit_preauth", ripple::ltDEPOSIT_PREAUTH}, {"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(&v)) + return *status; + + auto lgrInfo = std::get(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 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 doAccountObjects(Context const& context) @@ -37,54 +158,40 @@ doAccountObjects(Context const& context) auto lgrInfo = std::get(v); - if (!request.contains("account")) - return Status{Error::rpcINVALID_PARAMS, "missingAccount"}; - - 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"}; + ripple::AccountID accountID; + if (auto const status = getAccount(request, accountID); status) + return status; std::uint32_t limit = 200; - if (request.contains("limit")) - { - if (!request.at("limit").is_int64()) - return Status{Error::rpcINVALID_PARAMS, "limitNotInt"}; + if (auto const status = getLimit(request, limit); status) + return status; - limit = request.at("limit").as_int64(); - if (limit <= 0) - return Status{Error::rpcINVALID_PARAMS, "limitNotPositive"}; - } - - std::optional cursor = {}; + std::optional marker = {}; if (request.contains("marker")) { if (!request.at("marker").is_string()) return Status{Error::rpcINVALID_PARAMS, "markerNotString"}; - cursor = request.at("marker").as_string().c_str(); + marker = request.at("marker").as_string().c_str(); } std::optional 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"}; - 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()) return Status{Error::rpcINVALID_PARAMS, "typeInvalid"}; objectType = types[typeAsString]; } - response["account"] = ripple::to_string(*accountID); - response["account_objects"] = boost::json::value(boost::json::array_kind); - boost::json::array& jsonObjects = response.at("account_objects").as_array(); + response[JS(account)] = ripple::to_string(accountID); + response[JS(account_objects)] = boost::json::value(boost::json::array_kind); + boost::json::array& jsonObjects = + response.at(JS(account_objects)).as_array(); auto const addToResponse = [&](ripple::SLE const& sle) { if (!objectType || objectType == sle.getType()) @@ -95,23 +202,23 @@ doAccountObjects(Context const& context) auto next = traverseOwnedNodes( *context.backend, - *accountID, + accountID, lgrInfo.seq, limit, - cursor, + marker, context.yield, addToResponse); - 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; if (auto status = std::get_if(&next)) return *status; - auto nextCursor = std::get(next); + auto nextMarker = std::get(next); - if (nextCursor.isNonZero()) - response["marker"] = nextCursor.toString(); + if (nextMarker.isNonZero()) + response[JS(marker)] = nextMarker.toString(); return response; } diff --git a/src/rpc/handlers/AccountOffers.cpp b/src/rpc/handlers/AccountOffers.cpp index b9fd34db..675e626d 100644 --- a/src/rpc/handlers/AccountOffers.cpp +++ b/src/rpc/handlers/AccountOffers.cpp @@ -27,37 +27,39 @@ addOffer(boost::json::array& offersJson, ripple::SLE const& offer) if (!takerPays.native()) { - obj["taker_pays"] = boost::json::value(boost::json::object_kind); - boost::json::object& takerPaysJson = obj.at("taker_pays").as_object(); + obj[JS(taker_pays)] = boost::json::value(boost::json::object_kind); + boost::json::object& takerPaysJson = obj.at(JS(taker_pays)).as_object(); - takerPaysJson["value"] = takerPays.getText(); - takerPaysJson["currency"] = ripple::to_string(takerPays.getCurrency()); - takerPaysJson["issuer"] = ripple::to_string(takerPays.getIssuer()); + takerPaysJson[JS(value)] = takerPays.getText(); + takerPaysJson[JS(currency)] = + ripple::to_string(takerPays.getCurrency()); + takerPaysJson[JS(issuer)] = ripple::to_string(takerPays.getIssuer()); } else { - obj["taker_pays"] = takerPays.getText(); + obj[JS(taker_pays)] = takerPays.getText(); } if (!takerGets.native()) { - obj["taker_gets"] = boost::json::value(boost::json::object_kind); - boost::json::object& takerGetsJson = obj.at("taker_gets").as_object(); + obj[JS(taker_gets)] = boost::json::value(boost::json::object_kind); + boost::json::object& takerGetsJson = obj.at(JS(taker_gets)).as_object(); - takerGetsJson["value"] = takerGets.getText(); - takerGetsJson["currency"] = ripple::to_string(takerGets.getCurrency()); - takerGetsJson["issuer"] = ripple::to_string(takerGets.getIssuer()); + takerGetsJson[JS(value)] = takerGets.getText(); + takerGetsJson[JS(currency)] = + ripple::to_string(takerGets.getCurrency()); + takerGetsJson[JS(issuer)] = ripple::to_string(takerGets.getIssuer()); } else { - obj["taker_gets"] = takerGets.getText(); + obj[JS(taker_gets)] = takerGets.getText(); } - obj["seq"] = offer.getFieldU32(ripple::sfSequence); - obj["flags"] = offer.getFieldU32(ripple::sfFlags); - obj["quality"] = rate.getText(); + obj[JS(seq)] = offer.getFieldU32(ripple::sfSequence); + obj[JS(flags)] = offer.getFieldU32(ripple::sfFlags); + obj[JS(quality)] = rate.getText(); if (offer.isFieldPresent(ripple::sfExpiration)) - obj["expiration"] = offer.getFieldU32(ripple::sfExpiration); + obj[JS(expiration)] = offer.getFieldU32(ripple::sfExpiration); offersJson.push_back(obj); }; @@ -74,43 +76,28 @@ doAccountOffers(Context const& context) auto lgrInfo = std::get(v); - if (!request.contains("account")) - return Status{Error::rpcINVALID_PARAMS, "missingAccount"}; - - 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"}; + ripple::AccountID accountID; + if (auto const status = getAccount(request, accountID); status) + return status; std::uint32_t limit = 200; - if (request.contains("limit")) - { - if (!request.at("limit").is_int64()) - return Status{Error::rpcINVALID_PARAMS, "limitNotInt"}; + if (auto const status = getLimit(request, limit); status) + return status; - limit = request.at("limit").as_int64(); - if (limit <= 0) - return Status{Error::rpcINVALID_PARAMS, "limitNotPositive"}; - } - - std::optional cursor = {}; - if (request.contains("marker")) + std::optional 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"}; - cursor = request.at("marker").as_string().c_str(); + marker = request.at(JS(marker)).as_string().c_str(); } - response["account"] = ripple::to_string(*accountID); - response["ledger_hash"] = ripple::strHex(lgrInfo.hash); - response["ledger_index"] = lgrInfo.seq; - response["offers"] = boost::json::value(boost::json::array_kind); - boost::json::array& jsonLines = response.at("offers").as_array(); + response[JS(account)] = ripple::to_string(accountID); + response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash); + response[JS(ledger_index)] = lgrInfo.seq; + response[JS(offers)] = boost::json::value(boost::json::array_kind); + boost::json::array& jsonLines = response.at(JS(offers)).as_array(); auto const addToResponse = [&](ripple::SLE const& sle) { if (sle.getType() == ripple::ltOFFER) @@ -128,22 +115,22 @@ doAccountOffers(Context const& context) auto next = traverseOwnedNodes( *context.backend, - *accountID, + accountID, lgrInfo.seq, limit, - cursor, + marker, context.yield, addToResponse); if (auto status = std::get_if(&next)) return *status; - auto nextCursor = std::get(next); + auto nextMarker = std::get(next); - if (nextCursor.isNonZero()) - response["marker"] = nextCursor.toString(); + if (nextMarker.isNonZero()) + response[JS(marker)] = nextMarker.toString(); return response; } -} // namespace RPC \ No newline at end of file +} // namespace RPC diff --git a/src/rpc/handlers/AccountTx.cpp b/src/rpc/handlers/AccountTx.cpp index 4d4caaf5..1fe42656 100644 --- a/src/rpc/handlers/AccountTx.cpp +++ b/src/rpc/handlers/AccountTx.cpp @@ -12,60 +12,38 @@ doAccountTx(Context const& context) auto request = context.params; boost::json::object response = {}; - if (!request.contains("account")) - return Status{Error::rpcINVALID_PARAMS, "missingAccount"}; + ripple::AccountID accountID; + 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"}; - - 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(); - } + bool const binary = getBool(request, JS(binary), false); + bool const forward = getBool(request, JS(forward), false); std::optional 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 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{ Error::rpcINVALID_PARAMS, "transactionIndexNotInt"}; transactionIndex = - boost::json::value_to(obj.at("seq")); + boost::json::value_to(obj.at(JS(seq))); } std::optional 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"}; ledgerIndex = - boost::json::value_to(obj.at("ledger")); + boost::json::value_to(obj.at(JS(ledger))); } if (!transactionIndex || !ledgerIndex) @@ -75,9 +53,9 @@ doAccountTx(Context const& context) } 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()) return Status{Error::rpcINVALID_PARAMS, "ledgerSeqMinNotNumber"}; @@ -97,9 +75,9 @@ doAccountTx(Context const& context) } 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()) return Status{Error::rpcINVALID_PARAMS, "ledgerSeqMaxNotNumber"}; @@ -121,24 +99,25 @@ doAccountTx(Context const& context) 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"}; auto ledgerIndex = - boost::json::value_to(request.at("ledger_index")); + boost::json::value_to(request.at(JS(ledger_index))); 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{ RPC::Error::rpcINVALID_PARAMS, "ledgerHashNotString"}; 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{ RPC::Error::rpcINVALID_PARAMS, "ledgerHashMalformed"}; @@ -156,36 +135,30 @@ doAccountTx(Context const& context) } std::uint32_t limit = 200; - if (request.contains("limit")) - { - if (!request.at("limit").is_int64()) - return Status{Error::rpcINVALID_PARAMS, "limitNotInt"}; + if (auto const status = getLimit(request, limit); status) + return status; - limit = request.at("limit").as_int64(); - if (limit <= 0) - return Status{Error::rpcINVALID_PARAMS, "limitNotPositive"}; - - response["limit"] = limit; - } + if (request.contains(JS(limit))) + response[JS(limit)] = limit; boost::json::array txns; auto start = std::chrono::system_clock::now(); 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(); BOOST_LOG_TRIVIAL(info) << __func__ << " db fetch took " << ((end - start).count() / 1000000000.0) << " num blobs = " << blobs.size(); - response["account"] = ripple::to_string(*accountID); + response[JS(account)] = ripple::to_string(accountID); if (retCursor) { boost::json::object cursorJson; - cursorJson["ledger"] = retCursor->ledgerSequence; - cursorJson["seq"] = retCursor->transactionIndex; - response["marker"] = cursorJson; + cursorJson[JS(ledger)] = retCursor->ledgerSequence; + cursorJson[JS(seq)] = retCursor->transactionIndex; + response[JS(marker)] = cursorJson; } std::optional maxReturnedIndex; @@ -206,17 +179,18 @@ doAccountTx(Context const& context) if (!binary) { auto [txn, meta] = toExpandedJson(txnPlusMeta); - obj["meta"] = meta; - obj["tx"] = txn; - obj["tx"].as_object()["ledger_index"] = txnPlusMeta.ledgerSequence; - obj["tx"].as_object()["date"] = txnPlusMeta.date; + obj[JS(meta)] = meta; + obj[JS(tx)] = txn; + obj[JS(tx)].as_object()[JS(ledger_index)] = + txnPlusMeta.ledgerSequence; + obj[JS(tx)].as_object()[JS(date)] = txnPlusMeta.date; } else { - obj["meta"] = ripple::strHex(txnPlusMeta.metadata); - obj["tx_blob"] = ripple::strHex(txnPlusMeta.transaction); - obj["ledger_index"] = txnPlusMeta.ledgerSequence; - obj["date"] = txnPlusMeta.date; + obj[JS(meta)] = ripple::strHex(txnPlusMeta.metadata); + obj[JS(tx_blob)] = ripple::strHex(txnPlusMeta.transaction); + obj[JS(ledger_index)] = txnPlusMeta.ledgerSequence; + obj[JS(date)] = txnPlusMeta.date; } txns.push_back(obj); @@ -229,22 +203,22 @@ doAccountTx(Context const& context) assert(cursor); if (forward) { - response["ledger_index_min"] = cursor->ledgerSequence; + response[JS(ledger_index_min)] = cursor->ledgerSequence; if (blobs.size() >= limit) - response["ledger_index_max"] = *maxReturnedIndex; + response[JS(ledger_index_max)] = *maxReturnedIndex; else - response["ledger_index_max"] = maxIndex; + response[JS(ledger_index_max)] = maxIndex; } else { - response["ledger_index_max"] = cursor->ledgerSequence; + response[JS(ledger_index_max)] = cursor->ledgerSequence; if (blobs.size() >= limit) - response["ledger_index_min"] = *minReturnedIndex; + response[JS(ledger_index_min)] = *minReturnedIndex; 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(); BOOST_LOG_TRIVIAL(info) << __func__ << " serialization took " diff --git a/src/rpc/handlers/BookOffers.cpp b/src/rpc/handlers/BookOffers.cpp index 562becf7..6efa33f4 100644 --- a/src/rpc/handlers/BookOffers.cpp +++ b/src/rpc/handlers/BookOffers.cpp @@ -49,41 +49,20 @@ doBookOffers(Context const& context) } std::uint32_t limit = 200; - if (request.contains("limit")) - { - 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"}; - } + if (auto const status = getLimit(request, limit); status) + return status; ripple::AccountID takerID = beast::zero; - if (request.contains("taker")) - { - auto parsed = parseTaker(request["taker"]); - if (auto status = std::get_if(&parsed)) - return *status; - else - { - takerID = std::get(parsed); - } - } + if (auto const status = getTaker(request, takerID); status) + return status; - ripple::uint256 cursor = beast::zero; - if (request.contains("cursor")) - { - 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"}; - } + ripple::uint256 marker = beast::zero; + if (auto const status = getHexMarker(request, marker); status) + return status; auto start = std::chrono::system_clock::now(); - auto [offers, retCursor] = context.backend->fetchBookOffers( - bookBase, lgrInfo.seq, limit, cursor, context.yield); + auto [offers, retMarker] = context.backend->fetchBookOffers( + bookBase, lgrInfo.seq, limit, marker, context.yield); auto end = std::chrono::system_clock::now(); BOOST_LOG_TRIVIAL(warning) @@ -92,10 +71,10 @@ doBookOffers(Context const& context) .count() << " milliseconds - request = " << request; - 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; - response["offers"] = postProcessOrderBook( + response[JS(offers)] = postProcessOrderBook( offers, book, takerID, *context.backend, lgrInfo.seq, context.yield); auto end2 = std::chrono::system_clock::now(); @@ -106,8 +85,8 @@ doBookOffers(Context const& context) .count() << " milliseconds - request = " << request; - if (retCursor) - response["marker"] = ripple::strHex(*retCursor); + if (retMarker) + response["marker"] = ripple::strHex(*retMarker); return response; } diff --git a/src/rpc/handlers/ChannelAuthorize.cpp b/src/rpc/handlers/ChannelAuthorize.cpp index a7a20f63..4f8fe894 100644 --- a/src/rpc/handlers/ChannelAuthorize.cpp +++ b/src/rpc/handlers/ChannelAuthorize.cpp @@ -27,19 +27,13 @@ doChannelAuthorize(Context const& context) auto request = context.params; boost::json::object response = {}; - if (!request.contains("channel_id")) - return Status{Error::rpcINVALID_PARAMS, "missingChannelID"}; - - if (!request.at("channel_id").is_string()) - return Status{Error::rpcINVALID_PARAMS, "channelIDNotString"}; - - if (!request.contains("amount")) + if (!request.contains(JS(amount))) 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"}; - if (!request.contains("key_type") && !request.contains("secret")) + if (!request.contains(JS(key_type)) && !request.contains(JS(secret))) return Status{Error::rpcINVALID_PARAMS, "missingKeyTypeOrSecret"}; auto v = keypairFromRequst(request); @@ -50,10 +44,11 @@ doChannelAuthorize(Context const& context) std::get>(v); ripple::uint256 channelId; - if (!channelId.parseHex(request.at("channel_id").as_string().c_str())) - return Status{Error::rpcCHANNEL_MALFORMED, "malformedChannelID"}; + if (auto const status = getChannelId(request, channelId); status) + 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) return Status{Error::rpcCHANNEL_AMT_MALFORMED, "couldNotParseAmount"}; @@ -67,7 +62,7 @@ doChannelAuthorize(Context const& context) try { auto const buf = ripple::sign(pk, sk, msg.slice()); - response["signature"] = ripple::strHex(buf); + response[JS(signature)] = ripple::strHex(buf); } catch (std::exception&) { diff --git a/src/rpc/handlers/ChannelVerify.cpp b/src/rpc/handlers/ChannelVerify.cpp index 1f7e159e..1cf7ba4b 100644 --- a/src/rpc/handlers/ChannelVerify.cpp +++ b/src/rpc/handlers/ChannelVerify.cpp @@ -16,33 +16,28 @@ doChannelVerify(Context const& context) auto request = context.params; boost::json::object response = {}; - if (!request.contains("channel_id")) - return Status{Error::rpcINVALID_PARAMS, "missingChannelID"}; - - if (!request.at("channel_id").is_string()) - return Status{Error::rpcINVALID_PARAMS, "channelIDNotString"}; - - if (!request.contains("amount")) + if (!request.contains(JS(amount))) 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"}; - if (!request.contains("signature")) + if (!request.contains(JS(signature))) 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"}; - if (!request.contains("public_key")) + if (!request.contains(JS(public_key))) 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"}; std::optional 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::TokenType::AccountPublic, strPk); @@ -62,17 +57,18 @@ doChannelVerify(Context const& context) } ripple::uint256 channelId; - if (!channelId.parseHex(request.at("channel_id").as_string().c_str())) - return Status{Error::rpcCHANNEL_MALFORMED, "malformedChannelID"}; + if (auto const status = getChannelId(request, channelId); status) + 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) return Status{Error::rpcCHANNEL_AMT_MALFORMED, "couldNotParseAmount"}; 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()) return Status{Error::rpcINVALID_PARAMS, "invalidSignature"}; @@ -81,7 +77,7 @@ doChannelVerify(Context const& context) ripple::serializePayChanAuthorization( msg, channelId, ripple::XRPAmount(drops)); - response["signature_verified"] = + response[JS(signature_verified)] = ripple::verify(*pk, msg.slice(), ripple::makeSlice(*sig), true); return response; diff --git a/src/rpc/handlers/GatewayBalances.cpp b/src/rpc/handlers/GatewayBalances.cpp index 492c80e2..4a873799 100644 --- a/src/rpc/handlers/GatewayBalances.cpp +++ b/src/rpc/handlers/GatewayBalances.cpp @@ -9,17 +9,9 @@ doGatewayBalances(Context const& context) auto request = context.params; boost::json::object response = {}; - if (!request.contains("account")) - return Status{Error::rpcINVALID_PARAMS, "missingAccount"}; - - 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"}; + ripple::AccountID accountID; + if (auto const status = getAccount(request, accountID); status) + return status; auto v = ledgerInfoFromRequest(context); if (auto status = std::get_if(&v)) @@ -81,7 +73,7 @@ doGatewayBalances(Context const& context) if (!valid) { - response["error"] = "invalidHotWallet"; + response[JS(error)] = "invalidHotWallet"; return response; } } @@ -148,7 +140,7 @@ doGatewayBalances(Context const& context) traverseOwnedNodes( *context.backend, - *accountID, + accountID, lgrInfo.seq, std::numeric_limits::max(), {}, @@ -162,7 +154,7 @@ doGatewayBalances(Context const& context) { obj[ripple::to_string(k)] = v.getText(); } - response["obligations"] = std::move(obj); + response[JS(obligations)] = std::move(obj); } auto toJson = @@ -177,9 +169,9 @@ doGatewayBalances(Context const& context) for (auto const& balance : accBalances) { boost::json::object entry; - entry["currency"] = + entry[JS(currency)] = ripple::to_string(balance.issue().currency); - entry["value"] = balance.getText(); + entry[JS(value)] = balance.getText(); arr.push_back(std::move(entry)); } obj[ripple::to_string(accId)] = std::move(arr); @@ -189,14 +181,14 @@ doGatewayBalances(Context const& context) }; if (auto balances = toJson(hotBalances); balances.size()) - response["balances"] = balances; + response[JS(balances)] = balances; if (auto balances = toJson(frozenBalances); balances.size()) - response["frozen_balances"] = balances; + response[JS(frozen_balances)] = balances; if (auto balances = toJson(assets); assets.size()) - response["assets"] = toJson(assets); - response["account"] = request.at("account"); - response["ledger_index"] = lgrInfo.seq; - response["ledger_hash"] = ripple::strHex(lgrInfo.hash); + response[JS(assets)] = toJson(assets); + response[JS(account)] = request.at(JS(account)); + response[JS(ledger_index)] = lgrInfo.seq; + response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash); return response; } } // namespace RPC diff --git a/src/rpc/handlers/Ledger.cpp b/src/rpc/handlers/Ledger.cpp index 9af07936..7f5602cc 100644 --- a/src/rpc/handlers/Ledger.cpp +++ b/src/rpc/handlers/Ledger.cpp @@ -10,30 +10,30 @@ doLedger(Context const& context) boost::json::object response = {}; 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"}; - binary = params.at("binary").as_bool(); + binary = params.at(JS(binary)).as_bool(); } 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"}; - transactions = params.at("transactions").as_bool(); + transactions = params.at(JS(transactions)).as_bool(); } 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"}; - expand = params.at("expand").as_bool(); + expand = params.at(JS(expand)).as_bool(); } bool diff = false; @@ -54,35 +54,34 @@ doLedger(Context const& context) boost::json::object header; if (binary) { - header["ledger_data"] = ripple::strHex(ledgerInfoToBlob(lgrInfo)); + header[JS(ledger_data)] = ripple::strHex(ledgerInfoToBlob(lgrInfo)); } else { - header["accepted"] = true; - header["account_hash"] = ripple::strHex(lgrInfo.accountHash); - header["close_flags"] = lgrInfo.closeFlags; - header["close_time"] = lgrInfo.closeTime.time_since_epoch().count(); - header["close_time_human"] = ripple::to_string(lgrInfo.closeTime); - ; - header["close_time_resolution"] = lgrInfo.closeTimeResolution.count(); - header["closed"] = true; - header["hash"] = ripple::strHex(lgrInfo.hash); - header["ledger_hash"] = ripple::strHex(lgrInfo.hash); - header["ledger_index"] = std::to_string(lgrInfo.seq); - header["parent_close_time"] = + header[JS(accepted)] = true; + header[JS(account_hash)] = ripple::strHex(lgrInfo.accountHash); + header[JS(close_flags)] = lgrInfo.closeFlags; + header[JS(close_time)] = lgrInfo.closeTime.time_since_epoch().count(); + header[JS(close_time_human)] = ripple::to_string(lgrInfo.closeTime); + header[JS(close_time_resolution)] = lgrInfo.closeTimeResolution.count(); + header[JS(closed)] = true; + header[JS(hash)] = ripple::strHex(lgrInfo.hash); + header[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash); + header[JS(ledger_index)] = std::to_string(lgrInfo.seq); + header[JS(parent_close_time)] = lgrInfo.parentCloseTime.time_since_epoch().count(); - header["parent_hash"] = ripple::strHex(lgrInfo.parentHash); - header["seqNum"] = std::to_string(lgrInfo.seq); - header["totalCoins"] = ripple::to_string(lgrInfo.drops); - header["total_coins"] = ripple::to_string(lgrInfo.drops); - header["transaction_hash"] = ripple::strHex(lgrInfo.txHash); + header[JS(parent_hash)] = ripple::strHex(lgrInfo.parentHash); + header[JS(seqNum)] = std::to_string(lgrInfo.seq); + header[JS(totalCoins)] = ripple::to_string(lgrInfo.drops); + header[JS(total_coins)] = ripple::to_string(lgrInfo.drops); + header[JS(transaction_hash)] = ripple::strHex(lgrInfo.txHash); } - header["closed"] = true; + header[JS(closed)] = true; if (transactions) { - header["transactions"] = boost::json::value(boost::json::array_kind); - boost::json::array& jsonTxs = header.at("transactions").as_array(); + header[JS(transactions)] = boost::json::value(boost::json::array_kind); + boost::json::array& jsonTxs = header.at(JS(transactions)).as_array(); if (expand) { auto txns = context.backend->fetchAllTransactionsInLedger( @@ -98,14 +97,14 @@ doLedger(Context const& context) { auto [txn, meta] = toExpandedJson(obj); entry = txn; - entry["metaData"] = meta; + entry[JS(metaData)] = meta; } else { - entry["tx_blob"] = ripple::strHex(obj.transaction); - entry["meta"] = ripple::strHex(obj.metadata); + entry[JS(tx_blob)] = ripple::strHex(obj.transaction); + entry[JS(meta)] = ripple::strHex(obj.metadata); } - // entry["ledger_index"] = obj.ledgerSequence; + // entry[JS(ledger_index)] = obj.ledgerSequence; return entry; }); } @@ -133,7 +132,7 @@ doLedger(Context const& context) for (auto const& obj : diff) { boost::json::object entry; - entry["id"] = ripple::strHex(obj.key); + entry[JS(id)] = ripple::strHex(obj.key); if (binary) entry["object"] = ripple::strHex(obj.blob); else if (obj.blob.size()) @@ -149,9 +148,9 @@ doLedger(Context const& context) } } - response["ledger"] = header; - response["ledger_hash"] = ripple::strHex(lgrInfo.hash); - response["ledger_index"] = lgrInfo.seq; + response[JS(ledger)] = header; + response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash); + response[JS(ledger_index)] = lgrInfo.seq; return response; } diff --git a/src/rpc/handlers/LedgerData.cpp b/src/rpc/handlers/LedgerData.cpp index d4f44b82..d6ddcaa5 100644 --- a/src/rpc/handlers/LedgerData.cpp +++ b/src/rpc/handlers/LedgerData.cpp @@ -28,23 +28,12 @@ doLedgerData(Context const& context) auto request = context.params; boost::json::object response = {}; - bool binary = false; - if (request.contains("binary")) - { - if (!request.at("binary").is_bool()) - return Status{Error::rpcINVALID_PARAMS, "binaryFlagNotBool"}; + bool const binary = getBool(request, "binary", false); - 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(request.at("limit")); - } bool outOfOrder = false; if (request.contains("out_of_order")) { @@ -53,18 +42,18 @@ doLedgerData(Context const& context) outOfOrder = request.at("out_of_order").as_bool(); } - std::optional cursor; - std::optional diffCursor; - if (request.contains("marker")) + std::optional marker; + std::optional diffMarker; + if (request.contains(JS(marker))) { - if (!request.at("marker").is_string()) + if (!request.at(JS(marker)).is_string()) { if (outOfOrder) { - if (!request.at("marker").is_int64()) + if (!request.at(JS(marker)).is_int64()) return Status{ Error::rpcINVALID_PARAMS, "markerNotStringOrInt"}; - diffCursor = value_to(request.at("marker")); + diffMarker = value_to(request.at(JS(marker))); } else return Status{Error::rpcINVALID_PARAMS, "markerNotString"}; @@ -73,8 +62,8 @@ doLedgerData(Context const& context) { BOOST_LOG_TRIVIAL(debug) << __func__ << " : parsing marker"; - cursor = ripple::uint256{}; - if (!cursor->parseHex(request.at("marker").as_string().c_str())) + marker = ripple::uint256{}; + if (!marker->parseHex(request.at(JS(marker)).as_string().c_str())) return Status{Error::rpcINVALID_PARAMS, "markerMalformed"}; } } @@ -85,48 +74,49 @@ doLedgerData(Context const& context) auto lgrInfo = std::get(v); boost::json::object header; - // no cursor means this is the first call, so we return header info - if (!cursor) + // no marker means this is the first call, so we return header info + if (!marker) { if (binary) { - header["ledger_data"] = ripple::strHex(ledgerInfoToBlob(lgrInfo)); + header[JS(ledger_data)] = ripple::strHex(ledgerInfoToBlob(lgrInfo)); } else { - header["accepted"] = true; - header["account_hash"] = ripple::strHex(lgrInfo.accountHash); - header["close_flags"] = lgrInfo.closeFlags; - header["close_time"] = lgrInfo.closeTime.time_since_epoch().count(); - header["close_time_human"] = ripple::to_string(lgrInfo.closeTime); - ; - header["close_time_resolution"] = + header[JS(accepted)] = true; + header[JS(account_hash)] = ripple::strHex(lgrInfo.accountHash); + header[JS(close_flags)] = lgrInfo.closeFlags; + header[JS(close_time)] = + lgrInfo.closeTime.time_since_epoch().count(); + header[JS(close_time_human)] = ripple::to_string(lgrInfo.closeTime); + header[JS(close_time_resolution)] = lgrInfo.closeTimeResolution.count(); - header["closed"] = true; - header["hash"] = ripple::strHex(lgrInfo.hash); - header["ledger_hash"] = ripple::strHex(lgrInfo.hash); - header["ledger_index"] = std::to_string(lgrInfo.seq); - header["parent_close_time"] = + header[JS(closed)] = true; + header[JS(hash)] = ripple::strHex(lgrInfo.hash); + header[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash); + header[JS(ledger_index)] = std::to_string(lgrInfo.seq); + header[JS(parent_close_time)] = lgrInfo.parentCloseTime.time_since_epoch().count(); - header["parent_hash"] = ripple::strHex(lgrInfo.parentHash); - header["seqNum"] = std::to_string(lgrInfo.seq); - header["totalCoins"] = ripple::to_string(lgrInfo.drops); - header["total_coins"] = ripple::to_string(lgrInfo.drops); - header["transaction_hash"] = ripple::strHex(lgrInfo.txHash); + header[JS(parent_hash)] = ripple::strHex(lgrInfo.parentHash); + header[JS(seqNum)] = std::to_string(lgrInfo.seq); + header[JS(totalCoins)] = ripple::to_string(lgrInfo.drops); + header[JS(total_coins)] = ripple::to_string(lgrInfo.drops); + 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(); std::vector results; - if (diffCursor) + if (diffMarker) { assert(outOfOrder); auto diff = - context.backend->fetchLedgerDiff(*diffCursor, context.yield); + context.backend->fetchLedgerDiff(*diffMarker, context.yield); std::vector keys; for (auto&& [key, object] : diff) { @@ -143,13 +133,13 @@ doLedgerData(Context const& context) if (obj.size()) results.push_back({std::move(keys[i]), std::move(obj)}); } - if (*diffCursor > lgrInfo.seq) - response["marker"] = *diffCursor - 1; + if (*diffMarker > lgrInfo.seq) + response["marker"] = *diffMarker - 1; } else { auto page = context.backend->fetchLedgerPage( - cursor, lgrInfo.seq, limit, outOfOrder, context.yield); + marker, lgrInfo.seq, limit, outOfOrder, context.yield); results = std::move(page.objects); if (page.cursor) response["marker"] = ripple::strHex(*(page.cursor)); @@ -175,14 +165,14 @@ doLedgerData(Context const& context) if (binary) { boost::json::object entry; - entry["data"] = ripple::serializeHex(sle); - entry["index"] = ripple::to_string(sle.key()); + entry[JS(data)] = ripple::serializeHex(sle); + entry[JS(index)] = ripple::to_string(sle.key()); objects.push_back(std::move(entry)); } else objects.push_back(toJson(sle)); } - response["state"] = std::move(objects); + response[JS(state)] = std::move(objects); auto end2 = std::chrono::system_clock::now(); time = std::chrono::duration_cast(end2 - end) diff --git a/src/rpc/handlers/LedgerEntry.cpp b/src/rpc/handlers/LedgerEntry.cpp index b1eea972..e53ac0c4 100644 --- a/src/rpc/handlers/LedgerEntry.cpp +++ b/src/rpc/handlers/LedgerEntry.cpp @@ -20,8 +20,7 @@ doLedgerEntry(Context const& context) auto request = context.params; boost::json::object response = {}; - bool binary = - request.contains("binary") ? request.at("binary").as_bool() : false; + bool const binary = getBool(request, "binary", false); auto v = ledgerInfoFromRequest(context); if (auto status = std::get_if(&v)) @@ -30,59 +29,64 @@ doLedgerEntry(Context const& context) auto lgrInfo = std::get(v); 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"}; - 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"}; } - 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"}; auto const account = ripple::parseBase58( - request.at("account_root").as_string().c_str()); + request.at(JS(account_root)).as_string().c_str()); if (!account || account->isZero()) return Status{Error::rpcINVALID_PARAMS, "malformedAddress"}; else 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"}; - 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"}; } } - 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( - request.at("deposit_preauth").as_string().c_str())) + request.at(JS(deposit_preauth)).as_string().c_str())) { return Status{ Error::rpcINVALID_PARAMS, "deposit_preauthMalformed"}; } } else if ( - !request.at("deposit_preauth").as_object().contains("owner") || - !request.at("deposit_preauth").as_object().at("owner").is_string()) + !request.at(JS(deposit_preauth)).as_object().contains(JS(owner)) || + !request.at(JS(deposit_preauth)) + .as_object() + .at(JS(owner)) + .is_string()) { return Status{Error::rpcINVALID_PARAMS, "ownerNotString"}; } else if ( - !request.at("deposit_preauth").as_object().contains("authorized") || - !request.at("deposit_preauth") + !request.at(JS(deposit_preauth)) .as_object() - .at("authorized") + .contains(JS(authorized)) || + !request.at(JS(deposit_preauth)) + .as_object() + .at(JS(authorized)) .is_string()) { return Status{Error::rpcINVALID_PARAMS, "authorizedNotString"}; @@ -90,13 +94,13 @@ doLedgerEntry(Context const& context) else { boost::json::object const& deposit_preauth = - request.at("deposit_preauth").as_object(); + request.at(JS(deposit_preauth)).as_object(); auto const owner = ripple::parseBase58( - deposit_preauth.at("owner").as_string().c_str()); + deposit_preauth.at(JS(owner)).as_string().c_str()); auto const authorized = ripple::parseBase58( - deposit_preauth.at("authorized").as_string().c_str()); + deposit_preauth.at(JS(authorized)).as_string().c_str()); if (!owner) return Status{Error::rpcINVALID_PARAMS, "malformedOwner"}; @@ -106,37 +110,37 @@ doLedgerEntry(Context const& context) 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"}; - 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"}; } } else if ( - request.at("directory").as_object().contains("sub_index") && - !request.at("directory").as_object().at("sub_index").is_int64()) + request.at(JS(directory)).as_object().contains(JS(sub_index)) && + !request.at(JS(directory)).as_object().at(JS(sub_index)).is_int64()) { return Status{Error::rpcINVALID_PARAMS, "sub_indexNotInt"}; } else { - auto directory = request.at("directory").as_object(); - std::uint64_t subIndex = directory.contains("sub_index") + auto directory = request.at(JS(directory)).as_object(); + std::uint64_t subIndex = directory.contains(JS(sub_index)) ? boost::json::value_to( - directory.at("sub_index")) + directory.at(JS(sub_index))) : 0; - if (directory.contains("dir_root")) + if (directory.contains(JS(dir_root))) { ripple::uint256 uDirRoot; - if (directory.contains("owner")) + if (directory.contains(JS(owner))) { // May not specify both dir_root and owner. return Status{ @@ -144,7 +148,7 @@ doLedgerEntry(Context const& context) "mayNotSpecifyBothDirRootAndOwner"}; } 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"}; } @@ -153,10 +157,10 @@ doLedgerEntry(Context const& context) key = ripple::keylet::page(uDirRoot, subIndex).key; } } - else if (directory.contains("owner")) + else if (directory.contains(JS(owner))) { auto const ownerID = ripple::parseBase58( - directory.at("owner").as_string().c_str()); + directory.at(JS(owner)).as_string().c_str()); 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"}; } else if ( - !request.at("escrow").as_object().contains("owner") || - !request.at("escrow").as_object().at("owner").is_string()) + !request.at(JS(escrow)).as_object().contains(JS(owner)) || + !request.at(JS(escrow)).as_object().at(JS(owner)).is_string()) { return Status{Error::rpcINVALID_PARAMS, "malformedOwner"}; } else if ( - !request.at("escrow").as_object().contains("seq") || - !request.at("escrow").as_object().at("seq").is_int64()) + !request.at(JS(escrow)).as_object().contains(JS(seq)) || + !request.at(JS(escrow)).as_object().at(JS(seq)).is_int64()) { return Status{Error::rpcINVALID_PARAMS, "malformedSeq"}; } else { auto const id = - ripple::parseBase58(request.at("escrow") + ripple::parseBase58(request.at(JS(escrow)) .as_object() - .at("owner") + .at(JS(owner)) .as_string() .c_str()); @@ -209,120 +213,122 @@ doLedgerEntry(Context const& context) else { 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; } } } - 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"}; } else if ( - !request.at("offer").as_object().contains("account") || - !request.at("offer").as_object().at("account").is_string()) + !request.at(JS(offer)).as_object().contains(JS(account)) || + !request.at(JS(offer)).as_object().at(JS(account)).is_string()) { return Status{Error::rpcINVALID_PARAMS, "malformedAccount"}; } else if ( - !request.at("offer").as_object().contains("seq") || - !request.at("offer").as_object().at("seq").is_int64()) + !request.at(JS(offer)).as_object().contains(JS(seq)) || + !request.at(JS(offer)).as_object().at(JS(seq)).is_int64()) { return Status{Error::rpcINVALID_PARAMS, "malformedSeq"}; } else { - auto offer = request.at("offer").as_object(); + auto offer = request.at(JS(offer)).as_object(); auto const id = ripple::parseBase58( - offer.at("account").as_string().c_str()); + offer.at(JS(account)).as_string().c_str()); if (!id) return Status{Error::rpcINVALID_PARAMS, "malformedAccount"}; else { std::uint32_t seq = - boost::json::value_to(offer.at("seq")); + boost::json::value_to(offer.at(JS(seq))); 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"}; - 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"}; } - 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"}; ripple::Currency currency; 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"}; } - if (!state.contains("accounts") || !state.at("accounts").is_array() || - 2 != state.at("accounts").as_array().size() || - !state.at("accounts").as_array().at(0).is_string() || - !state.at("accounts").as_array().at(1).is_string() || - (state.at("accounts").as_array().at(0).as_string() == - state.at("accounts").as_array().at(1).as_string())) + if (!state.contains(JS(accounts)) || + !state.at(JS(accounts)).is_array() || + 2 != state.at(JS(accounts)).as_array().size() || + !state.at(JS(accounts)).as_array().at(0).is_string() || + !state.at(JS(accounts)).as_array().at(1).is_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"}; } auto const id1 = ripple::parseBase58( - 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( - 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) return Status{Error::rpcINVALID_PARAMS, "malformedAccounts"}; 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"}; 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"}; - 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"}; } else if ( - !request.at("ticket").as_object().contains("account") || - !request.at("ticket").as_object().at("account").is_string()) + !request.at(JS(ticket)).as_object().contains(JS(account)) || + !request.at(JS(ticket)).as_object().at(JS(account)).is_string()) { return Status{Error::rpcINVALID_PARAMS, "malformedTicketAccount"}; } else if ( - !request.at("ticket").as_object().contains("ticket_seq") || - !request.at("ticket").as_object().at("ticket_seq").is_int64()) + !request.at(JS(ticket)).as_object().contains(JS(ticket_seq)) || + !request.at(JS(ticket)).as_object().at(JS(ticket_seq)).is_int64()) { return Status{Error::rpcINVALID_PARAMS, "malformedTicketSeq"}; } else { auto const id = - ripple::parseBase58(request.at("ticket") + ripple::parseBase58(request.at(JS(ticket)) .as_object() - .at("account") + .at(JS(account)) .as_string() .c_str()); @@ -331,8 +337,10 @@ doLedgerEntry(Context const& context) Error::rpcINVALID_PARAMS, "malformedTicketAccount"}; else { - std::uint32_t seq = - request.at("offer").as_object().at("ticket_seq").as_int64(); + std::uint32_t seq = request.at(JS(offer)) + .as_object() + .at(JS(ticket_seq)) + .as_int64(); key = ripple::getTicketIndex(*id, seq); } @@ -351,19 +359,19 @@ doLedgerEntry(Context const& context) if (!dbResponse or dbResponse->size() == 0) return Status{Error::rpcOBJECT_NOT_FOUND, "entryNotFound"}; - response["index"] = ripple::strHex(key); - response["ledger_hash"] = ripple::strHex(lgrInfo.hash); - response["ledger_index"] = lgrInfo.seq; + response[JS(index)] = ripple::strHex(key); + response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash); + response[JS(ledger_index)] = lgrInfo.seq; if (binary) { - response["node_binary"] = ripple::strHex(*dbResponse); + response[JS(node_binary)] = ripple::strHex(*dbResponse); } else { ripple::STLedgerEntry sle{ ripple::SerialIter{dbResponse->data(), dbResponse->size()}, key}; - response["node"] = toJson(sle); + response[JS(node)] = toJson(sle); } return response; diff --git a/src/rpc/handlers/LedgerRange.cpp b/src/rpc/handlers/LedgerRange.cpp index 911657aa..34af8e77 100644 --- a/src/rpc/handlers/LedgerRange.cpp +++ b/src/rpc/handlers/LedgerRange.cpp @@ -16,11 +16,11 @@ doLedgerRange(Context const& context) } else { - response["ledger_index_min"] = range->minSequence; - response["ledger_index_max"] = range->maxSequence; + response[JS(ledger_index_min)] = range->minSequence; + response[JS(ledger_index_max)] = range->maxSequence; } return response; } -} // namespace RPC \ No newline at end of file +} // namespace RPC diff --git a/src/rpc/handlers/NFTOffers.cpp b/src/rpc/handlers/NFTOffers.cpp new file mode 100644 index 00000000..f7bdbb02 --- /dev/null +++ b/src/rpc/handlers/NFTOffers.cpp @@ -0,0 +1,178 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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(&v)) + return *status; + + auto lgrInfo = std::get(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 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(&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 +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(&v)) + return *status; + + auto const getKeylet = [sells, &v]() { + if (sells) + return ripple::keylet::nft_sells(std::get(v)); + + return ripple::keylet::nft_buys(std::get(v)); + }; + + return enumerateNFTOffers( + context, std::get(v), getKeylet()); +} + +Result +doNFTSellOffers(Context const& context) +{ + return doNFTOffers(context, true); +} + +Result +doNFTBuyOffers(Context const& context) +{ + return doNFTOffers(context, false); +} + +} // namespace RPC diff --git a/src/rpc/handlers/NoRippleCheck.cpp b/src/rpc/handlers/NoRippleCheck.cpp index 763e3951..894e0bce 100644 --- a/src/rpc/handlers/NoRippleCheck.cpp +++ b/src/rpc/handlers/NoRippleCheck.cpp @@ -10,9 +10,9 @@ getBaseTx( ripple::Fees const& fees) { boost::json::object tx; - tx["Sequence"] = accountSeq; - tx["Account"] = ripple::toBase58(accountID); - tx["Fee"] = RPC::toBoostJson(fees.units.jsonClipped()); + tx[JS(Sequence)] = accountSeq; + tx[JS(Account)] = ripple::toBase58(accountID); + tx[JS(Fee)] = RPC::toBoostJson(fees.units.jsonClipped()); return tx; } @@ -21,11 +21,9 @@ doNoRippleCheck(Context const& context) { auto const& request = context.params; - auto accountID = - accountFromStringStrict(getRequiredString(request, "account")); - - if (!accountID) - return Status{Error::rpcINVALID_PARAMS, "malformedAccount"}; + ripple::AccountID accountID; + if (auto const status = getAccount(request, accountID); status) + return status; std::string role = getRequiredString(request, "role"); bool roleGateway = false; @@ -36,7 +34,9 @@ doNoRippleCheck(Context const& context) 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); @@ -51,11 +51,11 @@ doNoRippleCheck(Context const& context) boost::json::array transactions; - auto keylet = ripple::keylet::account(*accountID); + auto keylet = ripple::keylet::account(accountID); auto accountObj = context.backend->fetchLedgerObject( keylet.key, lgrInfo.seq, context.yield); if (!accountObj) - throw AccountNotFoundError(ripple::toBase58(*accountID)); + throw AccountNotFoundError(ripple::toBase58(accountID)); ripple::SerialIter it{accountObj->data(), accountObj->size()}; ripple::SLE sle{it, keylet.key}; @@ -79,16 +79,16 @@ doNoRippleCheck(Context const& context) "You should immediately set your default ripple flag"); if (includeTxs) { - auto tx = getBaseTx(*accountID, accountSeq++, *fees); - tx["TransactionType"] = "AccountSet"; - tx["SetFlag"] = 8; + auto tx = getBaseTx(accountID, accountSeq++, *fees); + tx[JS(TransactionType)] = JS(AccountSet); + tx[JS(SetFlag)] = 8; transactions.push_back(tx); } } traverseOwnedNodes( *context.backend, - *accountID, + accountID, lgrInfo.seq, std::numeric_limits::max(), {}, @@ -141,12 +141,12 @@ doNoRippleCheck(Context const& context) ripple::STAmount limitAmount(ownedItem.getFieldAmount( bLow ? ripple::sfLowLimit : ripple::sfHighLimit)); limitAmount.setIssuer(peer); - auto tx = getBaseTx(*accountID, accountSeq++, *fees); - tx["TransactionType"] = "TrustSet"; - tx["LimitAmount"] = RPC::toBoostJson( + auto tx = getBaseTx(accountID, accountSeq++, *fees); + tx[JS(TransactionType)] = JS(TrustSet); + tx[JS(LimitAmount)] = RPC::toBoostJson( limitAmount.getJson(ripple::JsonOptions::none)); - tx["Flags"] = bNoRipple ? ripple::tfClearNoRipple - : ripple::tfSetNoRipple; + tx[JS(Flags)] = bNoRipple ? ripple::tfClearNoRipple + : ripple::tfSetNoRipple; transactions.push_back(tx); } @@ -158,11 +158,11 @@ doNoRippleCheck(Context const& context) }); boost::json::object response; - response["ledger_index"] = lgrInfo.seq; - response["ledger_hash"] = ripple::strHex(lgrInfo.hash); + response[JS(ledger_index)] = lgrInfo.seq; + response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash); response["problems"] = std::move(problems); if (includeTxs) - response["transactions"] = std::move(transactions); + response[JS(transactions)] = std::move(transactions); return response; } diff --git a/src/rpc/handlers/Random.cpp b/src/rpc/handlers/Random.cpp index 39fa5a9a..6d7af7c3 100644 --- a/src/rpc/handlers/Random.cpp +++ b/src/rpc/handlers/Random.cpp @@ -1,6 +1,10 @@ +// rngfill.h doesn't compile without this include +#include + #include #include #include + namespace RPC { Result @@ -10,7 +14,8 @@ doRandom(Context const& context) beast::rngfill(rand.begin(), rand.size(), ripple::crypto_prng()); boost::json::object result; - result["random"] = ripple::strHex(rand); + result[JS(random)] = ripple::strHex(rand); return result; } + } // namespace RPC diff --git a/src/rpc/handlers/ServerInfo.cpp b/src/rpc/handlers/ServerInfo.cpp index 01c9f173..22854f15 100644 --- a/src/rpc/handlers/ServerInfo.cpp +++ b/src/rpc/handlers/ServerInfo.cpp @@ -36,42 +36,42 @@ doServerInfo(Context const& context) if (age < 0) age = 0; - response["info"] = boost::json::object{}; - boost::json::object& info = response["info"].as_object(); + response[JS(info)] = boost::json::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); - info["counters"] = boost::json::object{}; - info["counters"].as_object()["rpc"] = context.counters.report(); + info[JS(counters)] = boost::json::object{}; + info[JS(counters)].as_object()[JS(rpc)] = context.counters.report(); auto serverInfoRippled = context.balancer->forwardToRippled( - {{"command", "server_info"}}, context.clientIp, context.yield); + {{"counters", "server_info"}}, context.clientIp, context.yield); - info["load_factor"] = 1; - if (serverInfoRippled && !serverInfoRippled->contains("error")) + info[JS(load_factor)] = 1; + if (serverInfoRippled && !serverInfoRippled->contains(JS(error))) { try { - auto& rippledResult = serverInfoRippled->at("result").as_object(); - auto& rippledInfo = rippledResult.at("info").as_object(); - info["load_factor"] = rippledInfo["load_factor"]; - info["validation_quorum"] = rippledInfo["validation_quorum"]; + auto& rippledResult = serverInfoRippled->at(JS(result)).as_object(); + auto& rippledInfo = rippledResult.at(JS(info)).as_object(); + info[JS(load_factor)] = rippledInfo[JS(load_factor)]; + info[JS(validation_quorum)] = rippledInfo[JS(validation_quorum)]; } catch (std::exception const&) { } } - info["validated_ledger"] = boost::json::object{}; - boost::json::object& validated = info["validated_ledger"].as_object(); + info[JS(validated_ledger)] = boost::json::object{}; + boost::json::object& validated = info[JS(validated_ledger)].as_object(); - validated["age"] = age; - validated["hash"] = ripple::strHex(lgrInfo->hash); - validated["seq"] = lgrInfo->seq; - validated["base_fee_xrp"] = fees->base.decimalXRP(); - validated["reserve_base_xrp"] = fees->reserve.decimalXRP(); - validated["reserve_inc_xrp"] = fees->increment.decimalXRP(); + validated[JS(age)] = age; + validated[JS(hash)] = ripple::strHex(lgrInfo->hash); + validated[JS(seq)] = lgrInfo->seq; + validated[JS(base_fee_xrp)] = fees->base.decimalXRP(); + validated[JS(reserve_base_xrp)] = fees->reserve.decimalXRP(); + validated[JS(reserve_inc_xrp)] = fees->increment.decimalXRP(); response["cache"] = boost::json::object{}; auto& cache = response["cache"].as_object(); diff --git a/src/rpc/handlers/Subscribe.cpp b/src/rpc/handlers/Subscribe.cpp index 013838af..b01f6c49 100644 --- a/src/rpc/handlers/Subscribe.cpp +++ b/src/rpc/handlers/Subscribe.cpp @@ -17,7 +17,7 @@ static std::unordered_set validCommonStreams{ Status 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) { @@ -40,7 +40,7 @@ subscribeToStreams( std::shared_ptr session, 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; for (auto const& stream : streams) @@ -69,7 +69,7 @@ unsubscribeToStreams( std::shared_ptr session, 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) { @@ -114,7 +114,7 @@ subscribeToAccounts( std::shared_ptr session, 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) { @@ -138,7 +138,7 @@ unsubscribeToAccounts( std::shared_ptr session, 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) { @@ -163,7 +163,7 @@ subscribeToAccountsProposed( SubscriptionManager& manager) { boost::json::array const& accounts = - request.at("accounts_proposed").as_array(); + request.at(JS(accounts_proposed)).as_array(); for (auto const& account : accounts) { @@ -188,7 +188,7 @@ unsubscribeToAccountsProposed( SubscriptionManager& manager) { boost::json::array const& accounts = - request.at("accounts_proposed").as_array(); + request.at(JS(accounts_proposed)).as_array(); for (auto const& account : accounts) { @@ -212,68 +212,57 @@ validateAndGetBooks( boost::json::object const& request, std::shared_ptr const& backend) { - if (!request.at("books").is_array()) + if (!request.at(JS(books)).is_array()) 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 booksToSub; std::optional rng; boost::json::array snapshot; for (auto const& book : books) { - auto parsed = parseBook(book.as_object()); - if (auto status = std::get_if(&parsed)) + auto parsedBook = parseBook(book.as_object()); + if (auto status = std::get_if(&parsedBook)) return *status; - else + + auto b = std::get(parsedBook); + booksToSub.push_back(b); + bool both = book.as_object().contains(JS(both)); + if (both) + booksToSub.push_back(ripple::reversed(b)); + + if (book.as_object().contains(JS(snapshot))) { - auto b = std::get(parsed); - booksToSub.push_back(b); - bool both = book.as_object().contains("both"); + if (!rng) + rng = backend->fetchLedgerRange(); + ripple::AccountID takerID = beast::zero; + if (book.as_object().contains(JS(taker))) + if (auto const status = getTaker(book.as_object(), takerID); + status) + return status; + + auto getOrderBook = [&snapshot, &backend, &rng, &takerID]( + auto book, + boost::asio::yield_context& yield) { + auto bookBase = getBookBase(book); + auto [offers, retMarker] = backend->fetchBookOffers( + bookBase, rng->maxSequence, 200, {}, yield); + + auto orderBook = postProcessOrderBook( + offers, book, takerID, *backend, rng->maxSequence, yield); + std::copy( + orderBook.begin(), + orderBook.end(), + std::back_inserter(snapshot)); + }; + getOrderBook(b, yield); if (both) - booksToSub.push_back(ripple::reversed(b)); - - if (book.as_object().contains("snapshot")) - { - if (!rng) - rng = backend->fetchLedgerRange(); - ripple::AccountID takerID = beast::zero; - if (book.as_object().contains("taker")) - { - auto parsed = parseTaker(request.at("taker")); - if (auto status = std::get_if(&parsed)) - return *status; - else - { - takerID = std::get(parsed); - } - } - auto getOrderBook = [&snapshot, &backend, &rng, &takerID]( - auto book, - boost::asio::yield_context& yield) { - auto bookBase = getBookBase(book); - auto [offers, retCursor] = backend->fetchBookOffers( - bookBase, rng->maxSequence, 200, {}, yield); - - auto orderBook = postProcessOrderBook( - offers, - book, - takerID, - *backend, - rng->maxSequence, - yield); - std::copy( - orderBook.begin(), - orderBook.end(), - std::back_inserter(snapshot)); - }; - getOrderBook(b, yield); - if (both) - getOrderBook(ripple::reversed(b), yield); - } + getOrderBook(ripple::reversed(b), yield); } } return std::make_pair(booksToSub, snapshot); } + void subscribeToBooks( std::vector const& books, @@ -285,14 +274,15 @@ subscribeToBooks( manager.subBook(book, session); } } + Result doSubscribe(Context const& context) { 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"}; auto status = validateStreams(request); @@ -301,25 +291,25 @@ doSubscribe(Context const& context) 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"}; - boost::json::array accounts = request.at("accounts").as_array(); + boost::json::array accounts = request.at(JS(accounts)).as_array(); auto status = validateAccounts(accounts); if (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"}; boost::json::array accounts = - request.at("accounts_proposed").as_array(); + request.at(JS(accounts_proposed)).as_array(); auto status = validateAccounts(accounts); if (status) @@ -327,7 +317,7 @@ doSubscribe(Context const& context) } std::vector books; boost::json::array snapshot; - if (request.contains("books")) + if (request.contains(JS(books))) { auto parsed = validateAndGetBooks(context.yield, request, context.backend); @@ -341,22 +331,22 @@ doSubscribe(Context const& context) } boost::json::object response; - if (request.contains("streams")) + if (request.contains(JS(streams))) response = subscribeToStreams( context.yield, request, context.session, *context.subscriptions); - if (request.contains("accounts")) + if (request.contains(JS(accounts))) subscribeToAccounts(request, context.session, *context.subscriptions); - if (request.contains("accounts_proposed")) + if (request.contains(JS(accounts_proposed))) subscribeToAccountsProposed( request, context.session, *context.subscriptions); - if (request.contains("books")) + if (request.contains(JS(books))) subscribeToBooks(books, context.session, *context.subscriptions); if (snapshot.size()) - response["offers"] = snapshot; + response[JS(offers)] = snapshot; return response; } @@ -365,9 +355,9 @@ doUnsubscribe(Context const& context) { 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"}; auto status = validateStreams(request); @@ -376,38 +366,38 @@ doUnsubscribe(Context const& context) 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"}; - boost::json::array accounts = request.at("accounts").as_array(); + boost::json::array accounts = request.at(JS(accounts)).as_array(); auto status = validateAccounts(accounts); if (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"}; boost::json::array accounts = - request.at("accounts_proposed").as_array(); + request.at(JS(accounts_proposed)).as_array(); auto status = validateAccounts(accounts); if (status) return status; } - if (request.contains("streams")) + if (request.contains(JS(streams))) unsubscribeToStreams(request, context.session, *context.subscriptions); - if (request.contains("accounts")) + if (request.contains(JS(accounts))) unsubscribeToAccounts(request, context.session, *context.subscriptions); - if (request.contains("accounts_proposed")) + if (request.contains(JS(accounts_proposed))) unsubscribeToAccountsProposed( request, context.session, *context.subscriptions); diff --git a/src/rpc/handlers/TransactionEntry.cpp b/src/rpc/handlers/TransactionEntry.cpp index 940d02a8..3458cf34 100644 --- a/src/rpc/handlers/TransactionEntry.cpp +++ b/src/rpc/handlers/TransactionEntry.cpp @@ -13,7 +13,7 @@ doTransactionEntry(Context const& context) auto lgrInfo = std::get(v); 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"}; auto dbResponse = context.backend->fetchTransaction(hash, context.yield); @@ -33,10 +33,10 @@ doTransactionEntry(Context const& context) "Transaction not found."}; auto [txn, meta] = toExpandedJson(*dbResponse); - response["tx_json"] = std::move(txn); - response["metadata"] = std::move(meta); - response["ledger_index"] = lgrInfo.seq; - response["ledger_hash"] = ripple::strHex(lgrInfo.hash); + response[JS(tx_json)] = std::move(txn); + response[JS(metadata)] = std::move(meta); + response[JS(ledger_index)] = lgrInfo.seq; + response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash); return response; } diff --git a/src/rpc/handlers/Tx.cpp b/src/rpc/handlers/Tx.cpp index d626ee22..03de231b 100644 --- a/src/rpc/handlers/Tx.cpp +++ b/src/rpc/handlers/Tx.cpp @@ -14,23 +14,23 @@ doTx(Context const& context) auto request = context.params; boost::json::object response = {}; - if (!request.contains("transaction")) + if (!request.contains(JS(transaction))) 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"}; 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"}; 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"}; - binary = request.at("binary").as_bool(); + binary = request.at(JS(binary)).as_bool(); } auto range = context.backend->fetchLedgerRange(); @@ -45,16 +45,16 @@ doTx(Context const& context) { auto [txn, meta] = toExpandedJson(*dbResponse); response = txn; - response["meta"] = meta; + response[JS(meta)] = meta; } else { - response["tx"] = ripple::strHex(dbResponse->transaction); - response["meta"] = ripple::strHex(dbResponse->metadata); - response["hash"] = std::move(request.at("transaction").as_string()); + response[JS(tx)] = ripple::strHex(dbResponse->transaction); + response[JS(meta)] = ripple::strHex(dbResponse->metadata); + response[JS(hash)] = std::move(request.at(JS(transaction)).as_string()); } - response["date"] = dbResponse->date; - response["ledger_index"] = dbResponse->ledgerSequence; + response[JS(date)] = dbResponse->date; + response[JS(ledger_index)] = dbResponse->ledgerSequence; return response; } diff --git a/src/subscriptions/SubscriptionManager.cpp b/src/subscriptions/SubscriptionManager.cpp index 3470c001..8cc9eb72 100644 --- a/src/subscriptions/SubscriptionManager.cpp +++ b/src/subscriptions/SubscriptionManager.cpp @@ -253,7 +253,6 @@ SubscriptionManager::pubTransaction( std::string pubMsg{boost::json::serialize(pubObj)}; txSubscribers_.publish(pubMsg); - auto journal = ripple::debugLog(); auto accounts = meta->getAffectedAccounts(); for (auto const& account : accounts)