From 3c98a065f6b7065c1dfdf5597f9a51ebb59baa84 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Wed, 10 Jan 2024 15:20:26 -0500 Subject: [PATCH 01/64] Refactoring currency (WIP) --- src/feed/impl/TransactionFeed.cpp | 2 +- src/rpc/RPCHelpers.cpp | 31 ++++++++++++++------------ src/rpc/handlers/AccountCurrencies.cpp | 4 ++-- src/rpc/handlers/AccountLines.cpp | 2 +- src/rpc/handlers/GatewayBalances.cpp | 2 +- 5 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/feed/impl/TransactionFeed.cpp b/src/feed/impl/TransactionFeed.cpp index c37566058..985360c51 100644 --- a/src/feed/impl/TransactionFeed.cpp +++ b/src/feed/impl/TransactionFeed.cpp @@ -161,7 +161,7 @@ TransactionFeed::pub( if (tx->getTxnType() == ripple::ttOFFER_CREATE) { auto const account = tx->getAccountID(ripple::sfAccount); auto const amount = tx->getFieldAmount(ripple::sfTakerGets); - if (account != amount.issue().account) { + if (account != amount.issue().account()) { auto fetchFundsSynchronous = [&]() { data::synchronous([&](boost::asio::yield_context yield) { ownerFunds = rpc::accountFunds(*backend, lgrInfo.seq, amount, account, yield); diff --git a/src/rpc/RPCHelpers.cpp b/src/rpc/RPCHelpers.cpp index 5794a57aa..4a30d773e 100644 --- a/src/rpc/RPCHelpers.cpp +++ b/src/rpc/RPCHelpers.cpp @@ -1023,7 +1023,7 @@ accountFunds( return amount; } - return accountHolds(backend, sequence, id, amount.getCurrency(), amount.getIssuer(), true, yield); + return accountHolds(backend, sequence, id, amount.getAsset(), amount.getIssuer(), true, yield); } ripple::STAmount @@ -1031,30 +1031,33 @@ accountHolds( BackendInterface const& backend, std::uint32_t sequence, ripple::AccountID const& account, - ripple::Currency const& currency, + ripple::Asset const& asset, ripple::AccountID const& issuer, bool const zeroIfFrozen, boost::asio::yield_context yield ) { ripple::STAmount amount; - if (ripple::isXRP(currency)) { + if (ripple::isXRP(asset)) { return {xrpLiquid(backend, sequence, account, yield)}; } - auto key = ripple::keylet::line(account, issuer, currency).key; + + // TODO: check MPTs! + + auto key = ripple::keylet::line(account, issuer, asset).key; auto const blob = backend.fetchLedgerObject(key, sequence, yield); if (!blob) { - amount.clear({currency, issuer}); + amount.clear({asset, issuer}); return amount; } ripple::SerialIter it{blob->data(), blob->size()}; ripple::SLE const sle{it, key}; - if (zeroIfFrozen && isFrozen(backend, sequence, account, currency, issuer, yield)) { - amount.clear(ripple::Issue(currency, issuer)); + if (zeroIfFrozen && isFrozen(backend, sequence, account, asset, issuer, yield)) { + amount.clear(ripple::Issue(asset, issuer)); } else { amount = sle.getFieldAmount(ripple::sfBalance); if (account > issuer) { @@ -1103,10 +1106,10 @@ postProcessOrderBook( std::map umBalance; - bool const globalFreeze = isGlobalFrozen(backend, ledgerSequence, book.out.account, yield) || - isGlobalFrozen(backend, ledgerSequence, book.in.account, yield); + bool const globalFreeze = isGlobalFrozen(backend, ledgerSequence, book.out.account(), yield) || + isGlobalFrozen(backend, ledgerSequence, book.in.account(), yield); - auto rate = transferRate(backend, ledgerSequence, book.out.account, yield); + auto rate = transferRate(backend, ledgerSequence, book.out.account(), yield); for (auto const& obj : offers) { try { @@ -1120,7 +1123,7 @@ postProcessOrderBook( ripple::STAmount saOwnerFunds; bool firstOwnerOffer = true; - if (book.out.account == uOfferOwnerID) { + if (book.out.account() == uOfferOwnerID) { // If an offer is selling issuer's own IOUs, it is fully // funded. saOwnerFunds = saTakerGets; @@ -1137,7 +1140,7 @@ postProcessOrderBook( firstOwnerOffer = false; } else { saOwnerFunds = accountHolds( - backend, ledgerSequence, uOfferOwnerID, book.out.currency, book.out.account, true, yield + backend, ledgerSequence, uOfferOwnerID, book.out.asset(), book.out.account(), true, yield ); if (saOwnerFunds < beast::zero) @@ -1154,9 +1157,9 @@ postProcessOrderBook( if (rate != ripple::parityRate // Have a tranfer fee. - && takerID != book.out.account + && takerID != book.out.account() // Not taking offers of own IOUs. - && book.out.account != uOfferOwnerID) + && book.out.account() != uOfferOwnerID) // Offer owner not issuing ownfunds { // Need to charge a transfer fee to offer owner. diff --git a/src/rpc/handlers/AccountCurrencies.cpp b/src/rpc/handlers/AccountCurrencies.cpp index 6a3c7ffbe..d31f850f1 100644 --- a/src/rpc/handlers/AccountCurrencies.cpp +++ b/src/rpc/handlers/AccountCurrencies.cpp @@ -75,10 +75,10 @@ AccountCurrenciesHandler::process(AccountCurrenciesHandler::Input input, Context balance.negate(); if (balance < lineLimit) - response.receiveCurrencies.insert(ripple::to_string(balance.getCurrency())); + response.receiveCurrencies.insert(ripple::to_string(static_castbalance.getAsset())); if ((-balance) < lineLimitPeer) - response.sendCurrencies.insert(ripple::to_string(balance.getCurrency())); + response.sendCurrencies.insert(ripple::to_string(static_cast(balance.getAsset()))); } return true; diff --git a/src/rpc/handlers/AccountLines.cpp b/src/rpc/handlers/AccountLines.cpp index 7e4ef4cb6..3d7abca47 100644 --- a/src/rpc/handlers/AccountLines.cpp +++ b/src/rpc/handlers/AccountLines.cpp @@ -93,7 +93,7 @@ AccountLinesHandler::addLine( LineResponse line; line.account = ripple::to_string(lineAccountIDPeer); line.balance = saBalance.getText(); - line.currency = ripple::to_string(saBalance.issue().currency); + line.currency = ripple::to_string(static_cast(saBalance.issue().asset())); line.limit = saLimit.getText(); line.limitPeer = saLimitPeer.getText(); line.qualityIn = lineQualityIn; diff --git a/src/rpc/handlers/GatewayBalances.cpp b/src/rpc/handlers/GatewayBalances.cpp index 9223eb97f..0b99a5b42 100644 --- a/src/rpc/handlers/GatewayBalances.cpp +++ b/src/rpc/handlers/GatewayBalances.cpp @@ -173,7 +173,7 @@ tag_invoke(boost::json::value_from_tag, boost::json::value& jv, GatewayBalancesH boost::json::array arr; for (auto const& balance : accBalances) { boost::json::object entry; - entry[JS(currency)] = ripple::to_string(balance.issue().currency); + entry[JS(currency)] = ripple::to_string(static_cast(balance.issue().asset())); entry[JS(value)] = balance.getText(); arr.push_back(std::move(entry)); } From f8bbf0c90ad52f147f0faf3736059517f4b406d1 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Wed, 10 Jan 2024 15:57:19 -0500 Subject: [PATCH 02/64] compile --- src/rpc/RPCHelpers.h | 2 +- src/rpc/handlers/AccountCurrencies.cpp | 2 +- src/rpc/handlers/AccountOffers.cpp | 2 +- src/rpc/handlers/GatewayBalances.cpp | 2 +- src/rpc/handlers/Ledger.cpp | 2 +- src/rpc/handlers/NoRippleCheck.cpp | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/rpc/RPCHelpers.h b/src/rpc/RPCHelpers.h index a3754625f..1b8d06ceb 100644 --- a/src/rpc/RPCHelpers.h +++ b/src/rpc/RPCHelpers.h @@ -223,7 +223,7 @@ accountHolds( BackendInterface const& backend, std::uint32_t sequence, ripple::AccountID const& account, - ripple::Currency const& currency, + ripple::Asset const& asset, ripple::AccountID const& issuer, bool zeroIfFrozen, boost::asio::yield_context yield diff --git a/src/rpc/handlers/AccountCurrencies.cpp b/src/rpc/handlers/AccountCurrencies.cpp index d31f850f1..87fc09fab 100644 --- a/src/rpc/handlers/AccountCurrencies.cpp +++ b/src/rpc/handlers/AccountCurrencies.cpp @@ -75,7 +75,7 @@ AccountCurrenciesHandler::process(AccountCurrenciesHandler::Input input, Context balance.negate(); if (balance < lineLimit) - response.receiveCurrencies.insert(ripple::to_string(static_castbalance.getAsset())); + response.receiveCurrencies.insert(ripple::to_string(static_cast(balance.getAsset()))); if ((-balance) < lineLimitPeer) response.sendCurrencies.insert(ripple::to_string(static_cast(balance.getAsset()))); diff --git a/src/rpc/handlers/AccountOffers.cpp b/src/rpc/handlers/AccountOffers.cpp index a54bc25f5..f20ba0986 100644 --- a/src/rpc/handlers/AccountOffers.cpp +++ b/src/rpc/handlers/AccountOffers.cpp @@ -144,7 +144,7 @@ tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountOffersHan jsonObject[field] = amount.getText(); } else { jsonObject[field] = { - {JS(currency), ripple::to_string(amount.getCurrency())}, + {JS(currency), ripple::to_string(static_cast(amount.getAsset()))}, {JS(issuer), ripple::to_string(amount.getIssuer())}, {JS(value), amount.getText()}, }; diff --git a/src/rpc/handlers/GatewayBalances.cpp b/src/rpc/handlers/GatewayBalances.cpp index 0b99a5b42..3d827714f 100644 --- a/src/rpc/handlers/GatewayBalances.cpp +++ b/src/rpc/handlers/GatewayBalances.cpp @@ -111,7 +111,7 @@ GatewayBalancesHandler::process(GatewayBalancesHandler::Input input, Context con output.frozenBalances[peer].push_back(-balance); } else { // normal negative balance, obligation to customer - auto& bal = output.sums[balance.getCurrency()]; + auto& bal = output.sums[balance.getAsset()]; if (bal == beast::zero) { // This is needed to set the currency code correctly bal = -balance; diff --git a/src/rpc/handlers/Ledger.cpp b/src/rpc/handlers/Ledger.cpp index 1296fecba..cc70a532c 100644 --- a/src/rpc/handlers/Ledger.cpp +++ b/src/rpc/handlers/Ledger.cpp @@ -132,7 +132,7 @@ LedgerHandler::process(LedgerHandler::Input input, Context const& ctx) const *sharedPtrBackend_, lgrInfo.seq, account, - amount.getCurrency(), + amount.getAsset(), amount.getIssuer(), false, // fhIGNORE_FREEZE from rippled ctx.yield diff --git a/src/rpc/handlers/NoRippleCheck.cpp b/src/rpc/handlers/NoRippleCheck.cpp index 8914e2af9..07f4d7eef 100644 --- a/src/rpc/handlers/NoRippleCheck.cpp +++ b/src/rpc/handlers/NoRippleCheck.cpp @@ -145,7 +145,7 @@ NoRippleCheckHandler::process(NoRippleCheckHandler::Input input, Context const& ownedItem.getFieldAmount(bLow ? ripple::sfHighLimit : ripple::sfLowLimit); problem += fmt::format( - "{} line to {}", to_string(peerLimit.getCurrency()), to_string(peerLimit.getIssuer()) + "{} line to {}", to_string(static_cast(peerLimit.getAsset())), to_string(peerLimit.getIssuer()) ); output.problems.emplace_back(problem); From 924dd7282ef6de62b8c1fefb920b2b8d02814772 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Wed, 10 Jan 2024 16:14:53 -0500 Subject: [PATCH 03/64] remove static cast --- src/rpc/handlers/AccountCurrencies.cpp | 4 ++-- src/rpc/handlers/AccountLines.cpp | 2 +- src/rpc/handlers/AccountOffers.cpp | 2 +- src/rpc/handlers/GatewayBalances.cpp | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/rpc/handlers/AccountCurrencies.cpp b/src/rpc/handlers/AccountCurrencies.cpp index 87fc09fab..da55a64c5 100644 --- a/src/rpc/handlers/AccountCurrencies.cpp +++ b/src/rpc/handlers/AccountCurrencies.cpp @@ -75,10 +75,10 @@ AccountCurrenciesHandler::process(AccountCurrenciesHandler::Input input, Context balance.negate(); if (balance < lineLimit) - response.receiveCurrencies.insert(ripple::to_string(static_cast(balance.getAsset()))); + response.receiveCurrencies.insert(ripple::to_string(balance.getAsset())); if ((-balance) < lineLimitPeer) - response.sendCurrencies.insert(ripple::to_string(static_cast(balance.getAsset()))); + response.sendCurrencies.insert(ripple::to_string(balance.getAsset())); } return true; diff --git a/src/rpc/handlers/AccountLines.cpp b/src/rpc/handlers/AccountLines.cpp index 3d7abca47..12562f2f9 100644 --- a/src/rpc/handlers/AccountLines.cpp +++ b/src/rpc/handlers/AccountLines.cpp @@ -93,7 +93,7 @@ AccountLinesHandler::addLine( LineResponse line; line.account = ripple::to_string(lineAccountIDPeer); line.balance = saBalance.getText(); - line.currency = ripple::to_string(static_cast(saBalance.issue().asset())); + line.currency = ripple::to_string(saBalance.issue().asset()); line.limit = saLimit.getText(); line.limitPeer = saLimitPeer.getText(); line.qualityIn = lineQualityIn; diff --git a/src/rpc/handlers/AccountOffers.cpp b/src/rpc/handlers/AccountOffers.cpp index f20ba0986..da0226fde 100644 --- a/src/rpc/handlers/AccountOffers.cpp +++ b/src/rpc/handlers/AccountOffers.cpp @@ -144,7 +144,7 @@ tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountOffersHan jsonObject[field] = amount.getText(); } else { jsonObject[field] = { - {JS(currency), ripple::to_string(static_cast(amount.getAsset()))}, + {JS(currency), ripple::to_string(amount.getAsset())}, {JS(issuer), ripple::to_string(amount.getIssuer())}, {JS(value), amount.getText()}, }; diff --git a/src/rpc/handlers/GatewayBalances.cpp b/src/rpc/handlers/GatewayBalances.cpp index 3d827714f..79f3a96ac 100644 --- a/src/rpc/handlers/GatewayBalances.cpp +++ b/src/rpc/handlers/GatewayBalances.cpp @@ -173,7 +173,7 @@ tag_invoke(boost::json::value_from_tag, boost::json::value& jv, GatewayBalancesH boost::json::array arr; for (auto const& balance : accBalances) { boost::json::object entry; - entry[JS(currency)] = ripple::to_string(static_cast(balance.issue().asset())); + entry[JS(currency)] = ripple::to_string(balance.issue().asset()); entry[JS(value)] = balance.getText(); arr.push_back(std::move(entry)); } From 59bcb97650847db468c329e62f8436a604ca3502 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Tue, 9 Jan 2024 11:51:48 -0500 Subject: [PATCH 04/64] wip --- src/rpc/common/Validators.cpp | 12 ++++++++++++ src/rpc/handlers/LedgerEntry.cpp | 9 ++++++++- src/rpc/handlers/LedgerEntry.h | 3 +++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/rpc/common/Validators.cpp b/src/rpc/common/Validators.cpp index 5866080dc..6a9e9e9cd 100644 --- a/src/rpc/common/Validators.cpp +++ b/src/rpc/common/Validators.cpp @@ -68,6 +68,18 @@ checkIsU32Numeric(std::string_view sv) return ec == std::errc(); } +CustomValidator Uint192HexStringValidator = + CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { + if (!value.is_string()) + return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "NotString"}}; + + ripple::uint192 ledgerHash; + if (!ledgerHash.parseHex(value.as_string().c_str())) + return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "Malformed"}}; + + return MaybeError{}; + }}; + CustomValidator Uint256HexStringValidator = CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { if (!value.is_string()) diff --git a/src/rpc/handlers/LedgerEntry.cpp b/src/rpc/handlers/LedgerEntry.cpp index 617767193..dd5762f92 100644 --- a/src/rpc/handlers/LedgerEntry.cpp +++ b/src/rpc/handlers/LedgerEntry.cpp @@ -63,7 +63,12 @@ LedgerEntryHandler::process(LedgerEntryHandler::Input input, Context const& ctx) key = ripple::keylet::account(*ripple::parseBase58(*(input.accountRoot))).key; } else if (input.did) { key = ripple::keylet::did(*ripple::parseBase58(*(input.did))).key; - } else if (input.directory) { + } + else if (input.mptIssuanceID){ + auto const mptIssuanceID = ripple::uint192{std::string_view(*(input.mptIssuanceID))}; + key = ripple::keylet::mptIssuance(mptIssuanceID).key; + } + else if (input.directory) { auto const keyOrStatus = composeKeyFromDirectory(*input.directory); if (auto const status = std::get_if(&keyOrStatus)) return Error{*status}; @@ -243,6 +248,8 @@ tag_invoke(boost::json::value_to_tag, boost::json::va input.accountRoot = jv.at(JS(account_root)).as_string().c_str(); } else if (jsonObject.contains(JS(did))) { input.did = jv.at(JS(did)).as_string().c_str(); + } else if (jsonObject.contains(JS(mpt_issuance_id))) { + input.mptIssuanceID = jv.at(JS(mpt_issuance_id)).as_string().c_str(); } // no need to check if_object again, validator only allows string or object else if (jsonObject.contains(JS(directory))) { diff --git a/src/rpc/handlers/LedgerEntry.h b/src/rpc/handlers/LedgerEntry.h index 6cff05517..8c978a3c3 100644 --- a/src/rpc/handlers/LedgerEntry.h +++ b/src/rpc/handlers/LedgerEntry.h @@ -58,6 +58,8 @@ class LedgerEntryHandler { std::optional accountRoot; // account id to address did object std::optional did; + // mpt issuance id to address mptIssuance object + std::optional mptIssuanceID; // TODO: extract into custom objects, remove json from Input std::optional directory; std::optional offer; @@ -131,6 +133,7 @@ class LedgerEntryHandler { {JS(index), malformedRequestHexStringValidator}, {JS(account_root), validation::AccountBase58Validator}, {JS(did), validation::AccountBase58Validator}, + {JS(mpt_issuance_id), validation::Uint192HexStringValidator}, {JS(check), malformedRequestHexStringValidator}, {JS(deposit_preauth), validation::Type{}, From f182a1d4dde86a1ac4ad4f70dfc7212291e4557f Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Thu, 11 Jan 2024 11:35:23 -0500 Subject: [PATCH 05/64] Working account_objects and Ledger_entry --- src/rpc/common/Validators.h | 8 ++++++++ src/rpc/handlers/AccountObjects.cpp | 13 ++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/rpc/common/Validators.h b/src/rpc/common/Validators.h index b7eb89e20..c6d3f26a6 100644 --- a/src/rpc/common/Validators.h +++ b/src/rpc/common/Validators.h @@ -478,6 +478,14 @@ extern CustomValidator AccountBase58Validator; */ extern CustomValidator AccountMarkerValidator; +/** + * @brief Provides a commonly used validator for uint192 hex string. + * + * It must be a string and also a decodable hex. + * Transaction index, ledger hash all use this validator. + */ +extern CustomValidator Uint192HexStringValidator; + /** * @brief Provides a commonly used validator for uint256 hex string. * diff --git a/src/rpc/handlers/AccountObjects.cpp b/src/rpc/handlers/AccountObjects.cpp index 7c8596b4b..d9caaaabd 100644 --- a/src/rpc/handlers/AccountObjects.cpp +++ b/src/rpc/handlers/AccountObjects.cpp @@ -61,6 +61,8 @@ std::unordered_map const AccountObjectsHan {JS(nft_page), ripple::ltNFTOKEN_PAGE}, {JS(nft_offer), ripple::ltNFTOKEN_OFFER}, {JS(did), ripple::ltDID}, + {JS(mpt_issuance), ripple::ltMPTOKEN_ISSUANCE}, + {JS(mptoken), ripple::ltMPTOKEN}, }; std::unordered_set const AccountObjectsHandler::TYPES_KEYS = [] { @@ -99,6 +101,8 @@ AccountObjectsHandler::process(AccountObjectsHandler::Input input, Context const ripple::ltNFTOKEN_PAGE, ripple::ltPAYCHAN, ripple::ltRIPPLE_STATE, + ripple::ltMPTOKEN_ISSUANCE, + ripple::ltMPTOKEN }; typeFilter.emplace(); @@ -153,7 +157,14 @@ tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountObjectsHa std::cbegin(output.accountObjects), std::cend(output.accountObjects), std::back_inserter(objects), - [](auto const& sle) { return toJson(sle); } + [](auto const& sle) { + auto sleJson = toJson(sle); + if (sle.getType() == ripple::ltMPTOKEN_ISSUANCE) + sleJson["mpt_issuance_id"] = ripple::to_string(ripple::getMptID( + sle.getAccountID(ripple::sfIssuer), + sle[ripple::sfSequence])); + + return sleJson; } ); jv = { From b908b4eb2043eb85172c1436d285b1ed2f26688e Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Thu, 11 Jan 2024 14:15:47 -0500 Subject: [PATCH 06/64] Synthetic MPT ID --- src/feed/impl/TransactionFeed.cpp | 1 + src/rpc/RPCHelpers.cpp | 56 +++++++++++++++++++++++++++++++ src/rpc/RPCHelpers.h | 7 ++++ 3 files changed, 64 insertions(+) diff --git a/src/feed/impl/TransactionFeed.cpp b/src/feed/impl/TransactionFeed.cpp index 985360c51..0b9ad450e 100644 --- a/src/feed/impl/TransactionFeed.cpp +++ b/src/feed/impl/TransactionFeed.cpp @@ -178,6 +178,7 @@ TransactionFeed::pub( pubObj[JS(meta)] = rpc::toJson(*meta); rpc::insertDeliveredAmount(pubObj[JS(meta)].as_object(), tx, meta, txMeta.date); rpc::insertDeliverMaxAlias(pubObj[txKey].as_object(), version); + rpc::insertMPTIssuanceID(pubObj[JS(meta)].as_object(), tx, meta); pubObj[JS(type)] = "transaction"; pubObj[JS(validated)] = true; diff --git a/src/rpc/RPCHelpers.cpp b/src/rpc/RPCHelpers.cpp index 4a30d773e..daa931388 100644 --- a/src/rpc/RPCHelpers.cpp +++ b/src/rpc/RPCHelpers.cpp @@ -261,6 +261,7 @@ toExpandedJson( auto metaJson = toJson(*meta); insertDeliveredAmount(metaJson, txn, meta, blobs.date); insertDeliverMaxAlias(txnJson, apiVersion); + insertMPTIssuanceID(metaJson, txn, meta); if (nftEnabled == NFTokenjson::ENABLE) { Json::Value nftJson; @@ -316,6 +317,61 @@ insertDeliveredAmount( return false; } +static std::optional +getMPTissuanceID( + std::shared_ptr const& meta +) +{ + ripple::TxMeta const& transactionMeta = *meta; + + for (ripple::STObject const& node : transactionMeta.getNodes()) + { + if (node.getFieldU16(ripple::sfLedgerEntryType) != ripple::ltMPTOKEN_ISSUANCE || + node.getFName() != ripple::sfCreatedNode) + continue; + + auto const& mptNode = + node.peekAtField(ripple::sfNewFields).downcast(); + return ripple::getMptID( + mptNode.getAccountID(ripple::sfIssuer), mptNode.getFieldU32(ripple::sfSequence)); + } + + return {}; +} + +static bool +canHaveMPTIssuanceID( + std::shared_ptr const& txn, + std::shared_ptr const& meta +) +{ + ripple::TxType const tt{txn->getTxnType()}; + if (tt != ripple::ttMPTOKEN_ISSUANCE_CREATE) + return false; + + if (meta->getResultTER() != ripple::tesSUCCESS) + return false; + + return true; +} + +bool +insertMPTIssuanceID( + boost::json::object& metaJson, + std::shared_ptr const& txn, + std::shared_ptr const& meta +){ + if (canHaveMPTIssuanceID(txn, meta)) { + if (auto const amt = getMPTissuanceID(meta)) { + metaJson["mpt_issuance_id"] = ripple::to_string(*amt); + } else { + metaJson["mpt_issuance_id"] = "unavailable"; + } + return true; + } + return false; +} + void insertDeliverMaxAlias(boost::json::object& txJson, std::uint32_t const apiVersion) { diff --git a/src/rpc/RPCHelpers.h b/src/rpc/RPCHelpers.h index 1b8d06ceb..6ae469879 100644 --- a/src/rpc/RPCHelpers.h +++ b/src/rpc/RPCHelpers.h @@ -104,6 +104,13 @@ insertDeliveredAmount( uint32_t date ); +bool +insertMPTIssuanceID( + boost::json::object& metaJson, + std::shared_ptr const& txn, + std::shared_ptr const& meta +); + boost::json::object toJson(ripple::STBase const& obj); From 2e297faeb9ee589377894bddc86995afa8cf0b49 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Thu, 11 Jan 2024 14:27:35 -0500 Subject: [PATCH 07/64] reafactor --- src/rpc/RPCHelpers.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/rpc/RPCHelpers.cpp b/src/rpc/RPCHelpers.cpp index daa931388..f74270de8 100644 --- a/src/rpc/RPCHelpers.cpp +++ b/src/rpc/RPCHelpers.cpp @@ -361,15 +361,15 @@ insertMPTIssuanceID( std::shared_ptr const& txn, std::shared_ptr const& meta ){ - if (canHaveMPTIssuanceID(txn, meta)) { - if (auto const amt = getMPTissuanceID(meta)) { - metaJson["mpt_issuance_id"] = ripple::to_string(*amt); - } else { - metaJson["mpt_issuance_id"] = "unavailable"; - } - return true; - } - return false; + if (!canHaveMPTIssuanceID(txn, meta)) + return false; + + if (auto const amt = getMPTissuanceID(meta)) + metaJson["mpt_issuance_id"] = ripple::to_string(*amt); + else + metaJson["mpt_issuance_id"] = "unavailable"; + + return true; } void From efc839cee432bb9659dee1040c6a240f0681c9c1 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Sat, 13 Jan 2024 15:05:59 -0500 Subject: [PATCH 08/64] Working ledger entry --- src/rpc/handlers/LedgerEntry.cpp | 17 ++++++++++++----- src/rpc/handlers/LedgerEntry.h | 14 ++++++++++++-- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/rpc/handlers/LedgerEntry.cpp b/src/rpc/handlers/LedgerEntry.cpp index dd5762f92..19e65f82e 100644 --- a/src/rpc/handlers/LedgerEntry.cpp +++ b/src/rpc/handlers/LedgerEntry.cpp @@ -64,8 +64,8 @@ LedgerEntryHandler::process(LedgerEntryHandler::Input input, Context const& ctx) } else if (input.did) { key = ripple::keylet::did(*ripple::parseBase58(*(input.did))).key; } - else if (input.mptIssuanceID){ - auto const mptIssuanceID = ripple::uint192{std::string_view(*(input.mptIssuanceID))}; + else if (input.mptIssuance){ + auto const mptIssuanceID = ripple::uint192{std::string_view(*(input.mptIssuance))}; key = ripple::keylet::mptIssuance(mptIssuanceID).key; } else if (input.directory) { @@ -116,6 +116,10 @@ LedgerEntryHandler::process(LedgerEntryHandler::Input input, Context const& ctx) getIssuerFromJson(input.amm->at(JS(asset))), getIssuerFromJson(input.amm->at(JS(asset2))) ) .key; + } else if (input.mptoken) { + auto const holder = ripple::parseBase58(input.mptoken->at(JS(account)).as_string().c_str()); + auto const mptIssuanceID = ripple::uint192{std::string_view(input.mptoken->at(JS(mpt_issuance_id)).as_string().c_str())}; + key = ripple::keylet::mptoken(mptIssuanceID, *holder).key; } else { // Must specify 1 of the following fields to indicate what type if (ctx.apiVersion == 1) @@ -230,7 +234,8 @@ tag_invoke(boost::json::value_to_tag, boost::json::va {JS(deposit_preauth), ripple::ltDEPOSIT_PREAUTH}, {JS(ticket), ripple::ltTICKET}, {JS(nft_page), ripple::ltNFTOKEN_PAGE}, - {JS(amm), ripple::ltAMM} + {JS(amm), ripple::ltAMM}, + {JS(mptoken), ripple::ltMPTOKEN} }; auto const indexFieldType = @@ -248,8 +253,8 @@ tag_invoke(boost::json::value_to_tag, boost::json::va input.accountRoot = jv.at(JS(account_root)).as_string().c_str(); } else if (jsonObject.contains(JS(did))) { input.did = jv.at(JS(did)).as_string().c_str(); - } else if (jsonObject.contains(JS(mpt_issuance_id))) { - input.mptIssuanceID = jv.at(JS(mpt_issuance_id)).as_string().c_str(); + } else if (jsonObject.contains(JS(mpt_issuance))) { + input.mptIssuance = jv.at(JS(mpt_issuance)).as_string().c_str(); } // no need to check if_object again, validator only allows string or object else if (jsonObject.contains(JS(directory))) { @@ -266,6 +271,8 @@ tag_invoke(boost::json::value_to_tag, boost::json::va input.ticket = jv.at(JS(ticket)).as_object(); } else if (jsonObject.contains(JS(amm))) { input.amm = jv.at(JS(amm)).as_object(); + } else if (jsonObject.contains(JS(mptoken))) { + input.mptoken = jv.at(JS(mptoken)).as_object(); } return input; diff --git a/src/rpc/handlers/LedgerEntry.h b/src/rpc/handlers/LedgerEntry.h index 8c978a3c3..642a3e86e 100644 --- a/src/rpc/handlers/LedgerEntry.h +++ b/src/rpc/handlers/LedgerEntry.h @@ -59,7 +59,7 @@ class LedgerEntryHandler { // account id to address did object std::optional did; // mpt issuance id to address mptIssuance object - std::optional mptIssuanceID; + std::optional mptIssuance; // TODO: extract into custom objects, remove json from Input std::optional directory; std::optional offer; @@ -68,6 +68,7 @@ class LedgerEntryHandler { std::optional depositPreauth; std::optional ticket; std::optional amm; + std::optional mptoken; }; using Result = HandlerReturnType; @@ -133,7 +134,6 @@ class LedgerEntryHandler { {JS(index), malformedRequestHexStringValidator}, {JS(account_root), validation::AccountBase58Validator}, {JS(did), validation::AccountBase58Validator}, - {JS(mpt_issuance_id), validation::Uint192HexStringValidator}, {JS(check), malformedRequestHexStringValidator}, {JS(deposit_preauth), validation::Type{}, @@ -209,6 +209,16 @@ class LedgerEntryHandler { }, ammAssetValidator}, }, + }}, + {JS(mpt_issuance), validation::Uint192HexStringValidator}, + {JS(mptoken), + validation::Type{}, + meta::IfType{malformedRequestHexStringValidator}, + meta::IfType{ + meta::Section{ + {JS(account), validation::Required{}, validation::AccountBase58Validator}, + {JS(mpt_issuance_id), validation::Required{}, validation::Uint192HexStringValidator}, + }, }} }; From 5bc4ca547d163fb3bdc6d11d04de548d0d84558b Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Sat, 13 Jan 2024 16:32:57 -0500 Subject: [PATCH 09/64] use translation --- src/rpc/RPCHelpers.cpp | 4 ++-- src/rpc/handlers/AccountObjects.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rpc/RPCHelpers.cpp b/src/rpc/RPCHelpers.cpp index f74270de8..81d55138c 100644 --- a/src/rpc/RPCHelpers.cpp +++ b/src/rpc/RPCHelpers.cpp @@ -365,9 +365,9 @@ insertMPTIssuanceID( return false; if (auto const amt = getMPTissuanceID(meta)) - metaJson["mpt_issuance_id"] = ripple::to_string(*amt); + metaJson[JS(mpt_issuance_id)] = ripple::to_string(*amt); else - metaJson["mpt_issuance_id"] = "unavailable"; + metaJson[JS(mpt_issuance_id)] = "unavailable"; return true; } diff --git a/src/rpc/handlers/AccountObjects.cpp b/src/rpc/handlers/AccountObjects.cpp index d9caaaabd..8f8b6c33a 100644 --- a/src/rpc/handlers/AccountObjects.cpp +++ b/src/rpc/handlers/AccountObjects.cpp @@ -160,7 +160,7 @@ tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountObjectsHa [](auto const& sle) { auto sleJson = toJson(sle); if (sle.getType() == ripple::ltMPTOKEN_ISSUANCE) - sleJson["mpt_issuance_id"] = ripple::to_string(ripple::getMptID( + sleJson[JS(mpt_issuance_id)] = ripple::to_string(ripple::getMptID( sle.getAccountID(ripple::sfIssuer), sle[ripple::sfSequence])); From 5bbf673b4b3be40b00564cc95ffa805aa55d3f2c Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Mon, 15 Jan 2024 12:00:44 -0500 Subject: [PATCH 10/64] ledger_entry tests --- unittests/rpc/handlers/LedgerEntryTests.cpp | 71 ++++++++++++++++++++- unittests/util/TestObject.cpp | 35 ++++++++++ unittests/util/TestObject.h | 6 ++ 3 files changed, 111 insertions(+), 1 deletion(-) diff --git a/unittests/rpc/handlers/LedgerEntryTests.cpp b/unittests/rpc/handlers/LedgerEntryTests.cpp index 97879acf9..f014e5253 100644 --- a/unittests/rpc/handlers/LedgerEntryTests.cpp +++ b/unittests/rpc/handlers/LedgerEntryTests.cpp @@ -869,6 +869,35 @@ generateTestValuesForParametersTest() "malformedRequest", "Malformed request." }, + ParamTestCaseBundle{ + "InvalidMPTIssuanceStringIndex", + R"({ + "mpt_issuance": "invalid" + })", + "invalidParams", + "mpt_issuanceMalformed" + }, + ParamTestCaseBundle{ + "InvalidMPTokenStringIndex", + R"({ + "mptoken": "invalid" + })", + "malformedRequest", + "Malformed request." + }, + ParamTestCaseBundle{ + "MissingMPTokenID", + fmt::format( + R"({{ + "mptoken": {{ + "account": "{}" + }} + }})", + ACCOUNT + ), + "invalidParams", + "Required field \'mpt_issuance_id\' missing" + }, }; } @@ -1302,7 +1331,47 @@ generateTestValuesForNormalPathTest() ), ripple::keylet::amm(GetIssue("XRP", ripple::toBase58(ripple::xrpAccount())), GetIssue("JPY", ACCOUNT2)).key, CreateAMMObject(ACCOUNT, "XRP", ripple::toBase58(ripple::xrpAccount()), "JPY", ACCOUNT2) - } + }, + NormalPathTestBundle{ + "MPTIssuance", + fmt::format( + R"({{ + "binary": true, + "mpt_issuance": "{}" + }})", + ripple::to_string(ripple::getMptID(account1, 2)) + ), + ripple::keylet::mptIssuance(ripple::getMptID(account1, 2)).key, + CreateMPTIssuanceObject(ACCOUNT, 2, "metadata") + }, + NormalPathTestBundle{ + "MPTokenViaIndex", + fmt::format( + R"({{ + "binary": true, + "mptoken": "{}" + }})", + INDEX1 + ), + ripple::uint256{INDEX1}, + CreateMPTokenObject(ACCOUNT, ripple::getMptID(account1, 2)) + }, + NormalPathTestBundle{ + "MPTokenViaObject", + fmt::format( + R"({{ + "binary": true, + "mptoken": {{ + "account": "{}", + "mpt_issuance_id": "{}" + }} + }})", + ACCOUNT, + ripple::to_string(ripple::getMptID(account1, 2)) + ), + ripple::keylet::mptoken(ripple::getMptID(account1, 2), account1).key, + CreateMPTokenObject(ACCOUNT, ripple::getMptID(account1, 2)) + }, }; } diff --git a/unittests/util/TestObject.cpp b/unittests/util/TestObject.cpp index 9a1ced992..613e2c7b5 100644 --- a/unittests/util/TestObject.cpp +++ b/unittests/util/TestObject.cpp @@ -818,3 +818,38 @@ CreateAMMObject( amm.setFieldU32(ripple::sfFlags, 0); return amm; } + +ripple::STObject +CreateMPTIssuanceObject(std::string_view accountId, std::uint32_t seq, std::string_view metadata) +{ + ripple::STObject mptIssuance(ripple::sfLedgerEntry); + mptIssuance.setAccountID(ripple::sfIssuer, GetAccountIDWithString(accountId)); + mptIssuance.setFieldU16(ripple::sfLedgerEntryType, ripple::ltMPTOKEN_ISSUANCE); + mptIssuance.setFieldU32(ripple::sfFlags, 0); + mptIssuance.setFieldU32(ripple::sfSequence, seq); + mptIssuance.setFieldU64(ripple::sfOwnerNode, 0); + mptIssuance.setFieldH256(ripple::sfPreviousTxnID, ripple::uint256{}); + mptIssuance.setFieldU32(ripple::sfPreviousTxnLgrSeq, 0); + mptIssuance.setFieldU64(ripple::sfMaximumAmount, 0); + mptIssuance.setFieldU64(ripple::sfOutstandingAmount, 0); + ripple::Slice const sliceMetadata(metadata.data(), metadata.size()); + mptIssuance.setFieldVL(ripple::sfMPTokenMetadata, sliceMetadata); + + return mptIssuance; +} + +ripple::STObject +CreateMPTokenObject(std::string_view accountId, ripple::uint192 issuanceID) +{ + ripple::STObject mptoken(ripple::sfLedgerEntry); + mptoken.setAccountID(ripple::sfAccount, GetAccountIDWithString(accountId)); + mptoken[ripple::sfMPTokenIssuanceID] = issuanceID; + mptoken.setFieldU16(ripple::sfLedgerEntryType, ripple::ltMPTOKEN); + mptoken.setFieldU32(ripple::sfFlags, 0); + mptoken.setFieldU64(ripple::sfMPTAmount, 1); + mptoken.setFieldU64(ripple::sfOwnerNode, 0); + mptoken.setFieldH256(ripple::sfPreviousTxnID, ripple::uint256{}); + mptoken.setFieldU32(ripple::sfPreviousTxnLgrSeq, 0); + + return mptoken; +} diff --git a/unittests/util/TestObject.h b/unittests/util/TestObject.h index 1303473b9..8a02b45e0 100644 --- a/unittests/util/TestObject.h +++ b/unittests/util/TestObject.h @@ -289,3 +289,9 @@ CreateAMMObject( [[nodiscard]] ripple::STObject CreateDidObject(std::string_view accountId, std::string_view didDoc, std::string_view uri, std::string_view data); + +[[nodiscard]] ripple::STObject +CreateMPTIssuanceObject(std::string_view accountId, std::uint32_t seq, std::string_view metadata); + +[[nodiscard]] ripple::STObject +CreateMPTokenObject(std::string_view accountId, ripple::uint192 issuanceID); \ No newline at end of file From 9ac779dda2a2ccf00bcd8ee76b732636307b9cf0 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Mon, 15 Jan 2024 13:46:11 -0500 Subject: [PATCH 11/64] account object test --- .../rpc/handlers/AccountObjectsTests.cpp | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/unittests/rpc/handlers/AccountObjectsTests.cpp b/unittests/rpc/handlers/AccountObjectsTests.cpp index 492db5612..d0d1de3c9 100644 --- a/unittests/rpc/handlers/AccountObjectsTests.cpp +++ b/unittests/rpc/handlers/AccountObjectsTests.cpp @@ -1629,3 +1629,92 @@ TEST_F(RPCAccountObjectsHandlerTest, LimitMoreThanMax) EXPECT_EQ(*output, json::parse(expectedOut)); }); } + +TEST_F(RPCAccountObjectsHandlerTest, TypeFilterMPTIssuanceType) +{ + backend->setRange(MINSEQ, MAXSEQ); + auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, MAXSEQ); + EXPECT_CALL(*backend, fetchLedgerBySequence).WillOnce(Return(ledgerinfo)); + + auto const account = GetAccountIDWithString(ACCOUNT); + auto const accountKk = ripple::keylet::account(account).key; + EXPECT_CALL(*backend, doFetchLedgerObject(accountKk, MAXSEQ, _)).WillOnce(Return(Blob{'f', 'a', 'k', 'e'})); + + auto const ownerDir = CreateOwnerDirLedgerObject({ripple::uint256{INDEX1}}, INDEX1); + auto const ownerDirKk = ripple::keylet::ownerDir(account).key; + EXPECT_CALL(*backend, doFetchLedgerObject(ownerDirKk, 30, _)).WillOnce(Return(ownerDir.getSerializer().peekData())); + + // nft null + auto const nftMaxKK = ripple::keylet::nftpage_max(account).key; + EXPECT_CALL(*backend, doFetchLedgerObject(nftMaxKK, 30, _)).WillOnce(Return(std::nullopt)); + + std::vector bbs; + // put 1 mpt issuance + auto const issuanceObject = CreateMPTIssuanceObject(ACCOUNT, 2, "metadata"); + bbs.push_back(issuanceObject.getSerializer().peekData()); + + EXPECT_CALL(*backend, doFetchLedgerObjects).WillOnce(Return(bbs)); + + auto static const input = json::parse(fmt::format( + R"({{ + "account": "{}", + "type": "mpt_issuance" + }})", + ACCOUNT + )); + + auto const handler = AnyHandler{AccountObjectsHandler{backend}}; + runSpawn([&](auto yield) { + auto const output = handler.process(input, Context{yield}); + ASSERT_TRUE(output); + auto const& accountObjects = output->as_object().at("account_objects").as_array(); + ASSERT_EQ(accountObjects.size(), 1); + EXPECT_EQ(accountObjects.front().at("LedgerEntryType").as_string(), "MPTokenIssuance"); + + // make sure mptID is synethetically parsed if object is mptIssuance + EXPECT_EQ(accountObjects.front().at("mpt_issuance_id").as_string(), ripple::to_string(ripple::getMptID(GetAccountIDWithString(ACCOUNT), 2))); + }); +} + +TEST_F(RPCAccountObjectsHandlerTest, TypeFilterMPTokenType) +{ + backend->setRange(MINSEQ, MAXSEQ); + auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, MAXSEQ); + EXPECT_CALL(*backend, fetchLedgerBySequence).WillOnce(Return(ledgerinfo)); + + auto const account = GetAccountIDWithString(ACCOUNT); + auto const accountKk = ripple::keylet::account(account).key; + EXPECT_CALL(*backend, doFetchLedgerObject(accountKk, MAXSEQ, _)).WillOnce(Return(Blob{'f', 'a', 'k', 'e'})); + + auto const ownerDir = CreateOwnerDirLedgerObject({ripple::uint256{INDEX1}}, INDEX1); + auto const ownerDirKk = ripple::keylet::ownerDir(account).key; + EXPECT_CALL(*backend, doFetchLedgerObject(ownerDirKk, 30, _)).WillOnce(Return(ownerDir.getSerializer().peekData())); + + // nft null + auto const nftMaxKK = ripple::keylet::nftpage_max(account).key; + EXPECT_CALL(*backend, doFetchLedgerObject(nftMaxKK, 30, _)).WillOnce(Return(std::nullopt)); + + std::vector bbs; + // put 1 mpt issuance + auto const mptokenObject =CreateMPTokenObject(ACCOUNT, ripple::getMptID(GetAccountIDWithString(ACCOUNT), 2)); + bbs.push_back(mptokenObject.getSerializer().peekData()); + + EXPECT_CALL(*backend, doFetchLedgerObjects).WillOnce(Return(bbs)); + + auto static const input = json::parse(fmt::format( + R"({{ + "account": "{}", + "type": "mptoken" + }})", + ACCOUNT + )); + + auto const handler = AnyHandler{AccountObjectsHandler{backend}}; + runSpawn([&](auto yield) { + auto const output = handler.process(input, Context{yield}); + ASSERT_TRUE(output); + auto const& accountObjects = output->as_object().at("account_objects").as_array(); + ASSERT_EQ(accountObjects.size(), 1); + EXPECT_EQ(accountObjects.front().at("LedgerEntryType").as_string(), "MPToken"); + }); +} \ No newline at end of file From a97d5498c0282acb9a1ee167994e133a5c27c77f Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Fri, 26 Jan 2024 11:52:31 -0500 Subject: [PATCH 12/64] initial --- src/data/CassandraBackend.h | 60 ++++++++++++++++++++++++++++++++ src/data/Types.h | 6 ++++ src/data/cassandra/Schema.h | 37 ++++++++++++++++++++ src/etl/MPTHelpers.cpp | 68 +++++++++++++++++++++++++++++++++++++ src/etl/MPTHelpers.h | 52 ++++++++++++++++++++++++++++ src/etl/impl/AsyncData.h | 5 +++ src/etl/impl/LedgerLoader.h | 6 ++++ src/etl/impl/Transformer.h | 1 + 8 files changed, 235 insertions(+) create mode 100644 src/etl/MPTHelpers.cpp create mode 100644 src/etl/MPTHelpers.h diff --git a/src/data/CassandraBackend.h b/src/data/CassandraBackend.h index d9d3ede89..e0bc60aa7 100644 --- a/src/data/CassandraBackend.h +++ b/src/data/CassandraBackend.h @@ -530,6 +530,50 @@ class BasicCassandraBackend : public BackendInterface { return ret; } + MPTHoldersAndCursor + fetchMPTHolders( + ripple::uint192 const& mpt_id, + std::uint32_t const limit, + std::optional const& cursorIn, + std::uint32_t const ledgerSequence, + boost::asio::yield_context yield)const override + { + + auto const holderEntries = executor_.read(yield, schema_->selectMPTHolders, mpt_id, cursorIn.value_or(ripple::AccountID(0)), Limit{limit}); + + auto const& holderResults = holderEntries.value(); + if (not holderResults.hasRows()) + { + LOG(log_.debug()) << "No rows returned"; + return {}; + } + + std::vector mptKeys; + std::optional cursor; + for (auto const [holder] : extract(holderResults)) + { + mptKeys.push_back(ripple::getMptID(mpt_id, holder)); + cursor = holder; + } + + auto const mptObjects = doFetchLedgerObjects(mptKeys, ledgerSequence, yield); + + std::vector filteredMpt; + + // need to filter out the objs that don't exist at the ledger seq because these MPT are in no particular time order + for(auto const mpt: mptObjects){ + if (!mpt.size()) + continue; + + filteredMpt.push_back(mpt); + } + + if (mptKeys.size() == limit) + return {filteredMpt, cusor}; + + return {filteredMpt, {}} + } + std::optional doFetchLedgerObject(ripple::uint256 const& key, std::uint32_t const sequence, boost::asio::yield_context yield) const override @@ -827,6 +871,22 @@ class BasicCassandraBackend : public BackendInterface { executor_.write(std::move(statements)); } + void + writeMPTHolders(std::vector>&& data) override + { + std::vector statements; + for (auto const& record : data) + { + auto const mpt_id = record.first; + auto const holder = record.second; + + statements.push_back( + schema_->insertMPTHolder.bind(mpt_id, holder)); + } + + executor_.write(std::move(statements)); + } + void startWrites() const override { diff --git a/src/data/Types.h b/src/data/Types.h index 6e1327478..9e6e1de55 100644 --- a/src/data/Types.h +++ b/src/data/Types.h @@ -166,6 +166,12 @@ struct NFTsAndCursor { std::optional cursor; }; +struct MPTHoldersAndCursor +{ + std::vector mptokens; + std::optional cursor; +}; + /** * @brief Stores a range of sequences as a min and max pair. */ diff --git a/src/data/cassandra/Schema.h b/src/data/cassandra/Schema.h index f8b07d558..f47ccb2af 100644 --- a/src/data/cassandra/Schema.h +++ b/src/data/cassandra/Schema.h @@ -265,6 +265,20 @@ class Schema { settingsProvider_.get().getTtl() )); + statements.emplace_back(fmt::format( + R"( + CREATE TABLE IF NOT EXISTS {} + ( + mpt_id blob, + holder blob, + PRIMARY KEY (mpt_id, holder) + ) + WITH CLUSTERING ORDER BY (holder ASC) + AND default_time_to_live = {} + )", + qualifiedTableName(settingsProvider_.get(), "mp_token_holders"), + settingsProvider_.get().getTtl())); + return statements; }(); @@ -395,6 +409,16 @@ class Schema { )); }(); + PreparedStatement insertMPTHolder = [this]() { + return handle_.get().prepare(fmt::format( + R"( + INSERT INTO {} + (mpt_id, holder) + VALUES (?, ?) + )", + qualifiedTableName(settingsProvider_.get(), "mp_token_holders"))); + }(); + PreparedStatement insertLedgerHeader = [this]() { return handle_.get().prepare(fmt::format( R"( @@ -663,6 +687,19 @@ class Schema { )); }(); + PreparedStatement selectMPTHolders = [this]() { + return handle_.get().prepare(fmt::format( + R"( + SELECT holder + FROM {} + WHERE mpt_id = ? + AND holder > ? + ORDER BY holder ASC + LIMIT ? + )", + qualifiedTableName(settingsProvider_.get(), "mp_token_holders"))); + }(); + PreparedStatement selectLedgerByHash = [this]() { return handle_.get().prepare(fmt::format( R"( diff --git a/src/etl/MPTHelpers.cpp b/src/etl/MPTHelpers.cpp new file mode 100644 index 000000000..95c198941 --- /dev/null +++ b/src/etl/MPTHelpers.cpp @@ -0,0 +1,68 @@ +//------------------------------------------------------------------------------ +/* + This file is part of clio: https://github.com/XRPLF/clio + Copyright (c) 2024, the clio developers. + + Permission to use, copy, modify, and distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace etl { + +std::optional> +getMPTokenAuthorize(ripple::TxMeta const& txMeta, ripple::STTx const& sttx){ + for (ripple::STObject const& node : txMeta.getNodes()) + { + if (node.getFieldU16(ripple::sfLedgerEntryType) != ripple::ltMPTOKEN) + continue; + + if (node.getFName() == ripple::sfCreatedNode) + return std::make_pair(node[ripple::sfMPTokenIssuanceID], node[ripple::sfAccount]); + } + return {}; +} + +std::optional> +getMPTHolderFromTx(ripple::TxMeta const& txMeta, ripple::STTx const& sttx){ + if (txMeta.getResultTER() != ripple::tesSUCCESS + || sttx.getTxnType()!= ripple::TxType::ttMPTOKEN_AUTHORIZE) + return {}; + + return getMPTokenAuthorize(txMeta, sttx); +} + +std::optional> +getMPTHolderFromObj(std::uint32_t const seq, std::string const& key, std::string const& blob){ + ripple::STLedgerEntry const sle = + ripple::STLedgerEntry(ripple::SerialIter{blob.data(), blob.size()}, ripple::uint256::fromVoid(key.data())); + + if (sle.getFieldU16(ripple::sfLedgerEntryType) != ripple::ltMPTOKEN) + return {}; + + auto const mptIssuanceID = sle[ripple::sfMPTokenIssuanceID]; + auto const holder = sle.getAccountID(ripple::sfAccount); + + return std::make_pair(mptIssuanceID, holder); +} + +} // namespace etl \ No newline at end of file diff --git a/src/etl/MPTHelpers.h b/src/etl/MPTHelpers.h new file mode 100644 index 000000000..750b64df4 --- /dev/null +++ b/src/etl/MPTHelpers.h @@ -0,0 +1,52 @@ +//------------------------------------------------------------------------------ +/* + This file is part of clio: https://github.com/XRPLF/clio + Copyright (c) 2024, the clio developers. + + Permission to use, copy, modify, and distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +/** @file */ +#pragma once + +#include + +#include +#include + +namespace etl { + +/** + * @brief Pull CFTIssuance data from TX via ETLService. + * + * @param txMeta Transaction metadata + * @param sttx The transaction + * @return The CFTIssuance and issuer pair as a optional + */ +std::optional> +getMPTHolderFromTx(ripple::TxMeta const& txMeta, ripple::STTx const& sttx); + +/** + * @brief Pull CFTIssuance data from ledger object via loadInitialLedger. + * + * @param seq The ledger sequence to pull for + * @param key The owner key + * @param blob Object data as blob + * @return The CFTIssuance and issuer pair as a optional + */ +std::optional> +getMPTHolderFromObj(std::uint32_t const seq, std::string const& key, std::string const& blob); + + +} // namespace etl \ No newline at end of file diff --git a/src/etl/impl/AsyncData.h b/src/etl/impl/AsyncData.h index 7118b8495..b07669efa 100644 --- a/src/etl/impl/AsyncData.h +++ b/src/etl/impl/AsyncData.h @@ -139,6 +139,11 @@ class AsyncCallData { backend.writeSuccessor(std::move(lastKey_), request_.ledger().sequence(), std::string{obj.key()}); lastKey_ = obj.key(); backend.writeNFTs(getNFTDataFromObj(request_.ledger().sequence(), obj.key(), obj.data())); + + auto const maybeMPTHolder = getMPTHolderFromObj(request_.ledger().sequence(), obj.key(), obj.data()); + if(maybeMPTHolder) + backend.writeMPTHolders({*maybeMPTHolder}); + backend.writeLedgerObject( std::move(*obj.mutable_key()), request_.ledger().sequence(), std::move(*obj.mutable_data()) ); diff --git a/src/etl/impl/LedgerLoader.h b/src/etl/impl/LedgerLoader.h index 3eea075d2..9aba777a1 100644 --- a/src/etl/impl/LedgerLoader.h +++ b/src/etl/impl/LedgerLoader.h @@ -40,6 +40,7 @@ struct FormattedTransactionsData { std::vector accountTxData; std::vector nfTokenTxData; std::vector nfTokensData; + std::vector> mptHoldersData; }; namespace etl::detail { @@ -110,6 +111,10 @@ class LedgerLoader { if (maybeNFT) result.nfTokensData.push_back(*maybeNFT); + auto const maybeMPTHolder = getMPTHolderFromTx(txMeta, sttx); + if(maybeMPTHolder) + result.mptHoldersData.push_back(*maybeMPTHolder); + result.accountTxData.emplace_back(txMeta, sttx.getTransactionID()); static constexpr std::size_t KEY_SIZE = 32; std::string keyStr{reinterpret_cast(sttx.getTransactionID().data()), KEY_SIZE}; @@ -242,6 +247,7 @@ class LedgerLoader { backend_->writeAccountTransactions(std::move(insertTxResult.accountTxData)); backend_->writeNFTs(insertTxResult.nfTokensData); backend_->writeNFTTransactions(insertTxResult.nfTokenTxData); + backend_->writeMPTHolders(insertTxResult.mptHoldersData); } backend_->finishWrites(sequence); diff --git a/src/etl/impl/Transformer.h b/src/etl/impl/Transformer.h index f211799a1..c7bd2ea9e 100644 --- a/src/etl/impl/Transformer.h +++ b/src/etl/impl/Transformer.h @@ -201,6 +201,7 @@ class Transformer { backend_->writeAccountTransactions(std::move(insertTxResultOp->accountTxData)); backend_->writeNFTs(insertTxResultOp->nfTokensData); backend_->writeNFTTransactions(insertTxResultOp->nfTokenTxData); + backend_->writeMPTHolders(insertTxResultOp->mptHoldersData); auto [success, duration] = ::util::timed>([&]() { return backend_->finishWrites(lgrInfo.seq); }); From 0dae3cb6fa8726782afe40d1d17f83b9f034f511 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Fri, 26 Jan 2024 14:16:02 -0500 Subject: [PATCH 13/64] compile --- src/data/BackendInterface.h | 11 ++ src/data/CassandraBackend.h | 10 +- src/data/cassandra/impl/Statement.h | 7 +- src/etl/MPTHelpers.cpp | 14 ++- src/etl/MPTHelpers.h | 2 +- src/etl/impl/AsyncData.h | 3 +- src/etl/impl/LedgerLoader.h | 2 +- src/etl/impl/Transformer.h | 2 +- src/rpc/common/impl/HandlerProvider.cpp | 2 + src/rpc/handlers/MPTHolders.cpp | 139 ++++++++++++++++++++++++ src/rpc/handlers/MPTHolders.h | 88 +++++++++++++++ unittests/util/MockBackend.h | 9 ++ 12 files changed, 271 insertions(+), 18 deletions(-) create mode 100644 src/rpc/handlers/MPTHolders.cpp create mode 100644 src/rpc/handlers/MPTHolders.h diff --git a/src/data/BackendInterface.h b/src/data/BackendInterface.h index 9b0496669..ceca385e9 100644 --- a/src/data/BackendInterface.h +++ b/src/data/BackendInterface.h @@ -322,6 +322,14 @@ class BackendInterface { boost::asio::yield_context yield ) const = 0; + virtual MPTHoldersAndCursor + fetchMPTHolders( + ripple::uint192 const& mptID, + std::uint32_t const limit, + std::optional const& cursorIn, + std::uint32_t const ledgerSequence, + boost::asio::yield_context yield)const = 0; + /** * @brief Fetches a specific ledger object. * @@ -549,6 +557,9 @@ class BackendInterface { virtual void writeNFTTransactions(std::vector const& data) = 0; + virtual void + writeMPTHolders(std::vector>&& data) = 0; + /** * @brief Write a new successor. * diff --git a/src/data/CassandraBackend.h b/src/data/CassandraBackend.h index e0bc60aa7..d8f99ec4e 100644 --- a/src/data/CassandraBackend.h +++ b/src/data/CassandraBackend.h @@ -532,14 +532,14 @@ class BasicCassandraBackend : public BackendInterface { MPTHoldersAndCursor fetchMPTHolders( - ripple::uint192 const& mpt_id, + ripple::uint192 const& mptID, std::uint32_t const limit, std::optional const& cursorIn, std::uint32_t const ledgerSequence, boost::asio::yield_context yield)const override { - auto const holderEntries = executor_.read(yield, schema_->selectMPTHolders, mpt_id, cursorIn.value_or(ripple::AccountID(0)), Limit{limit}); + auto const holderEntries = executor_.read(yield, schema_->selectMPTHolders, mptID, cursorIn.value_or(ripple::AccountID(0)), Limit{limit}); auto const& holderResults = holderEntries.value(); if (not holderResults.hasRows()) @@ -552,7 +552,7 @@ class BasicCassandraBackend : public BackendInterface { std::optional cursor; for (auto const [holder] : extract(holderResults)) { - mptKeys.push_back(ripple::getMptID(mpt_id, holder)); + mptKeys.push_back(ripple::keylet::mptoken(mptID, holder).key); cursor = holder; } @@ -569,9 +569,9 @@ class BasicCassandraBackend : public BackendInterface { } if (mptKeys.size() == limit) - return {filteredMpt, cusor}; + return {filteredMpt, cursor}; - return {filteredMpt, {}} + return {filteredMpt, {}}; } std::optional diff --git a/src/data/cassandra/impl/Statement.h b/src/data/cassandra/impl/Statement.h index c718c0990..2916460c0 100644 --- a/src/data/cassandra/impl/Statement.h +++ b/src/data/cassandra/impl/Statement.h @@ -103,10 +103,11 @@ class Statement : public ManagedObject { using UintByteTupleType = std::tuple; using ByteVectorType = std::vector; - if constexpr (std::is_same_v) { + if constexpr (std::is_same_v || std::is_same_v) { auto const rc = bindBytes(value.data(), value.size()); - throwErrorIfNeeded(rc, "Bind ripple::uint256"); - } else if constexpr (std::is_same_v) { + throwErrorIfNeeded(rc, "Bind ripple::base_uint"); + } + else if constexpr (std::is_same_v) { auto const rc = bindBytes(value.data(), value.size()); throwErrorIfNeeded(rc, "Bind ripple::AccountID"); } else if constexpr (std::is_same_v) { diff --git a/src/etl/MPTHelpers.cpp b/src/etl/MPTHelpers.cpp index 95c198941..ee48dea24 100644 --- a/src/etl/MPTHelpers.cpp +++ b/src/etl/MPTHelpers.cpp @@ -29,15 +29,17 @@ namespace etl { -std::optional> -getMPTokenAuthorize(ripple::TxMeta const& txMeta, ripple::STTx const& sttx){ +static std::optional> +getMPTokenAuthorize(ripple::TxMeta const& txMeta){ for (ripple::STObject const& node : txMeta.getNodes()) { if (node.getFieldU16(ripple::sfLedgerEntryType) != ripple::ltMPTOKEN) continue; - if (node.getFName() == ripple::sfCreatedNode) - return std::make_pair(node[ripple::sfMPTokenIssuanceID], node[ripple::sfAccount]); + if (node.getFName() == ripple::sfCreatedNode){ + auto const& newMPT = node.peekAtField(ripple::sfNewFields).downcast(); + return std::make_pair(newMPT[ripple::sfMPTokenIssuanceID], newMPT[ripple::sfAccount]); + } } return {}; } @@ -48,11 +50,11 @@ getMPTHolderFromTx(ripple::TxMeta const& txMeta, ripple::STTx const& sttx){ || sttx.getTxnType()!= ripple::TxType::ttMPTOKEN_AUTHORIZE) return {}; - return getMPTokenAuthorize(txMeta, sttx); + return getMPTokenAuthorize(txMeta); } std::optional> -getMPTHolderFromObj(std::uint32_t const seq, std::string const& key, std::string const& blob){ +getMPTHolderFromObj(std::string const& key, std::string const& blob){ ripple::STLedgerEntry const sle = ripple::STLedgerEntry(ripple::SerialIter{blob.data(), blob.size()}, ripple::uint256::fromVoid(key.data())); diff --git a/src/etl/MPTHelpers.h b/src/etl/MPTHelpers.h index 750b64df4..493fd9423 100644 --- a/src/etl/MPTHelpers.h +++ b/src/etl/MPTHelpers.h @@ -46,7 +46,7 @@ getMPTHolderFromTx(ripple::TxMeta const& txMeta, ripple::STTx const& sttx); * @return The CFTIssuance and issuer pair as a optional */ std::optional> -getMPTHolderFromObj(std::uint32_t const seq, std::string const& key, std::string const& blob); +getMPTHolderFromObj(std::string const& key, std::string const& blob); } // namespace etl \ No newline at end of file diff --git a/src/etl/impl/AsyncData.h b/src/etl/impl/AsyncData.h index b07669efa..b86006e50 100644 --- a/src/etl/impl/AsyncData.h +++ b/src/etl/impl/AsyncData.h @@ -21,6 +21,7 @@ #include "data/BackendInterface.h" #include "etl/NFTHelpers.h" +#include "etl/MPTHelpers.h" #include "util/Assert.h" #include "util/log/Logger.h" @@ -140,7 +141,7 @@ class AsyncCallData { lastKey_ = obj.key(); backend.writeNFTs(getNFTDataFromObj(request_.ledger().sequence(), obj.key(), obj.data())); - auto const maybeMPTHolder = getMPTHolderFromObj(request_.ledger().sequence(), obj.key(), obj.data()); + auto const maybeMPTHolder = getMPTHolderFromObj(obj.key(), obj.data()); if(maybeMPTHolder) backend.writeMPTHolders({*maybeMPTHolder}); diff --git a/src/etl/impl/LedgerLoader.h b/src/etl/impl/LedgerLoader.h index 9aba777a1..4a8082995 100644 --- a/src/etl/impl/LedgerLoader.h +++ b/src/etl/impl/LedgerLoader.h @@ -247,7 +247,7 @@ class LedgerLoader { backend_->writeAccountTransactions(std::move(insertTxResult.accountTxData)); backend_->writeNFTs(insertTxResult.nfTokensData); backend_->writeNFTTransactions(insertTxResult.nfTokenTxData); - backend_->writeMPTHolders(insertTxResult.mptHoldersData); + backend_->writeMPTHolders(std::move(insertTxResult.mptHoldersData)); } backend_->finishWrites(sequence); diff --git a/src/etl/impl/Transformer.h b/src/etl/impl/Transformer.h index c7bd2ea9e..1749bd901 100644 --- a/src/etl/impl/Transformer.h +++ b/src/etl/impl/Transformer.h @@ -201,7 +201,7 @@ class Transformer { backend_->writeAccountTransactions(std::move(insertTxResultOp->accountTxData)); backend_->writeNFTs(insertTxResultOp->nfTokensData); backend_->writeNFTTransactions(insertTxResultOp->nfTokenTxData); - backend_->writeMPTHolders(insertTxResultOp->mptHoldersData); + backend_->writeMPTHolders(std::move(insertTxResultOp->mptHoldersData)); auto [success, duration] = ::util::timed>([&]() { return backend_->finishWrites(lgrInfo.seq); }); diff --git a/src/rpc/common/impl/HandlerProvider.cpp b/src/rpc/common/impl/HandlerProvider.cpp index e6088b542..0156c4cbe 100644 --- a/src/rpc/common/impl/HandlerProvider.cpp +++ b/src/rpc/common/impl/HandlerProvider.cpp @@ -40,6 +40,7 @@ #include "rpc/handlers/LedgerData.h" #include "rpc/handlers/LedgerEntry.h" #include "rpc/handlers/LedgerRange.h" +#include "rpc/handlers/MPTHolders.h" #include "rpc/handlers/NFTBuyOffers.h" #include "rpc/handlers/NFTHistory.h" #include "rpc/handlers/NFTInfo.h" @@ -87,6 +88,7 @@ ProductionHandlerProvider::ProductionHandlerProvider( {"ledger_data", {LedgerDataHandler{backend}}}, {"ledger_entry", {LedgerEntryHandler{backend}}}, {"ledger_range", {LedgerRangeHandler{backend}}}, + {"mpt_holders", {MPTHoldersHandler{backend}, true}}, //clio only {"nfts_by_issuer", {NFTsByIssuerHandler{backend}, true}}, // clio only {"nft_history", {NFTHistoryHandler{backend}, true}}, // clio only {"nft_buy_offers", {NFTBuyOffersHandler{backend}}}, diff --git a/src/rpc/handlers/MPTHolders.cpp b/src/rpc/handlers/MPTHolders.cpp new file mode 100644 index 000000000..ad6551e3e --- /dev/null +++ b/src/rpc/handlers/MPTHolders.cpp @@ -0,0 +1,139 @@ +//------------------------------------------------------------------------------ +/* + This file is part of clio: https://github.com/XRPLF/clio + Copyright (c) 2023, the clio developers. + + Permission to use, copy, modify, and distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include "rpc/handlers/MPTHolders.h" + +#include "rpc/Errors.h" +#include "rpc/JS.h" +#include "rpc/RPCHelpers.h" +#include "rpc/common/Types.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace ripple; + +namespace rpc { + +MPTHoldersHandler::Result +MPTHoldersHandler::process(MPTHoldersHandler::Input input, Context const& ctx) const +{ + auto const range = sharedPtrBackend_->fetchLedgerRange(); + auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq( + *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence + ); + if (auto const status = std::get_if(&lgrInfoOrStatus)) + return Error{*status}; + + auto const lgrInfo = std::get(lgrInfoOrStatus); + + auto const limit = input.limit.value_or(MPTHoldersHandler::LIMIT_DEFAULT); + + auto const mptID = ripple::uint192{input.mptID.c_str()}; + + std::optional cursor; + if (input.marker) + cursor = ripple::AccountID{input.marker->c_str()}; + + auto const dbResponse = + sharedPtrBackend_->fetchMPTHolders(mptID, limit, cursor, lgrInfo.seq, ctx.yield); + + auto output = MPTHoldersHandler::Output{}; + + output.mptID = to_string(mptID); + output.limit = limit; + output.ledgerIndex = lgrInfo.seq; + + boost::json::array mpts; + for (auto const& mpt : dbResponse.mptokens) { + ripple::STLedgerEntry sle{ripple::SerialIter{mpt.data(), mpt.size()}, keylet::mptIssuance(mptID).key}; + boost::json::object mptJson; + + mptJson["mpt_amount"] = std::to_string(sle[ripple::sfMPTAmount]); + mptJson[JS(flags)] = sle[ripple::sfFlags]; + mptJson[JS(account)] = toBase58(sle.getAccountID(ripple::sfAccount)); + + if(sle[~ripple::sfLockedAmount]) + mptJson["locked_amount"] = std::to_string(sle[~ripple::sfLockedAmount].value_or(0)); + + output.mpts.push_back(mptJson); + } + + if (dbResponse.cursor.has_value()) + output.marker = strHex(*dbResponse.cursor); + + return output; +} + +void +tag_invoke(boost::json::value_from_tag, boost::json::value& jv, MPTHoldersHandler::Output const& output) +{ + jv = { + {JS(mpt_issuance_id), output.mptID}, + {JS(limit), output.limit}, + {JS(ledger_index), output.ledgerIndex}, + {"mpts", output.mpts}, + {JS(validated), output.validated}, + }; + + if (output.marker.has_value()) + jv.as_object()[JS(marker)] = *(output.marker); +} + +MPTHoldersHandler::Input +tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) +{ + auto const& jsonObject = jv.as_object(); + MPTHoldersHandler::Input input; + + input.mptID = jsonObject.at(JS(mpt_issuance_id)).as_string().c_str(); + + if (jsonObject.contains(JS(ledger_hash))) + input.ledgerHash = jsonObject.at(JS(ledger_hash)).as_string().c_str(); + + if (jsonObject.contains(JS(ledger_index))) { + if (!jsonObject.at(JS(ledger_index)).is_string()) { + input.ledgerIndex = jsonObject.at(JS(ledger_index)).as_int64(); + } else if (jsonObject.at(JS(ledger_index)).as_string() != "validated") { + input.ledgerIndex = std::stoi(jsonObject.at(JS(ledger_index)).as_string().c_str()); + } + } + + if (jsonObject.contains(JS(limit))) + input.limit = jsonObject.at(JS(limit)).as_int64(); + + if (jsonObject.contains(JS(marker))) + input.marker = jsonObject.at(JS(marker)).as_string().c_str(); + + return input; +} +} // namespace rpc diff --git a/src/rpc/handlers/MPTHolders.h b/src/rpc/handlers/MPTHolders.h new file mode 100644 index 000000000..3c08491e6 --- /dev/null +++ b/src/rpc/handlers/MPTHolders.h @@ -0,0 +1,88 @@ +//------------------------------------------------------------------------------ +/* + This file is part of clio: https://github.com/XRPLF/clio + Copyright (c) 2023, the clio developers. + + Permission to use, copy, modify, and distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#pragma once + +#include "data/BackendInterface.h" +#include "rpc/JS.h" +#include "rpc/common/Modifiers.h" +#include "rpc/common/Types.h" +#include "rpc/common/Validators.h" + +namespace rpc { +class MPTHoldersHandler { + std::shared_ptr sharedPtrBackend_; + +public: + static auto constexpr LIMIT_MIN = 1; + static auto constexpr LIMIT_MAX = 100; + static auto constexpr LIMIT_DEFAULT = 50; + + struct Output { + boost::json::array mpts; + uint32_t ledgerIndex; + std::string mptID; + bool validated = true; + uint32_t limit; + std::optional marker; + }; + + struct Input { + std::string mptID; + std::optional ledgerHash; + std::optional ledgerIndex; + std::optional marker; + std::optional limit; + }; + + using Result = HandlerReturnType; + + MPTHoldersHandler(std::shared_ptr const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend) + { + } + + static RpcSpecConstRef + spec([[maybe_unused]] uint32_t apiVersion) + { + static auto const rpcSpec = RpcSpec{ + {JS(mpt_issuance_id), validation::Required{}, validation::Uint192HexStringValidator}, + {JS(ledger_hash), validation::Uint256HexStringValidator}, + {JS(ledger_index), validation::LedgerIndexValidator}, + {JS(limit), + validation::Type{}, + validation::Min(1u), + modifiers::Clamp{LIMIT_MIN, LIMIT_MAX}}, + {JS(marker), validation::AccountValidator}, + }; + + return rpcSpec; + } + + Result + process(Input input, Context const& ctx) const; + +private: + friend void + tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Output const& output); + + friend Input + tag_invoke(boost::json::value_to_tag, boost::json::value const& jv); + +}; +} // namespace rpc diff --git a/unittests/util/MockBackend.h b/unittests/util/MockBackend.h index 3dfd44d5a..51c9efb5d 100644 --- a/unittests/util/MockBackend.h +++ b/unittests/util/MockBackend.h @@ -178,4 +178,13 @@ struct MockBackend : public BackendInterface { MOCK_METHOD(void, doWriteLedgerObject, (std::string&&, std::uint32_t const, std::string&&), (override)); MOCK_METHOD(bool, doFinishWrites, (), (override)); + + + MOCK_METHOD(void, writeMPTHolders, ((std::vector>&&)), (override)); + + MOCK_METHOD(MPTHoldersAndCursor, fetchMPTHolders, (ripple::uint192 const& mptID, + std::uint32_t const , + (std::optional const&) , + std::uint32_t const , + boost::asio::yield_context), (const, override)); }; From f9b494e844efc23fe85a49fa8288cc692174c1fe Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Fri, 26 Jan 2024 16:41:16 -0500 Subject: [PATCH 14/64] working marker --- CMakeLists.txt | 2 ++ src/rpc/RPCHelpers.cpp | 12 ++++++------ src/rpc/RPCHelpers.h | 2 +- src/rpc/common/Validators.cpp | 13 +++++++++++++ src/rpc/common/Validators.h | 10 +++++++++- src/rpc/handlers/MPTHolders.cpp | 11 ++++++----- src/rpc/handlers/MPTHolders.h | 2 +- 7 files changed, 38 insertions(+), 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a62836cf0..e58564344 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -103,6 +103,7 @@ target_sources (clio PRIVATE src/etl/ETLState.cpp src/etl/LoadBalancer.cpp src/etl/impl/ForwardCache.cpp + src/etl/MPTHelpers.cpp ## Feed src/feed/SubscriptionManager.cpp src/feed/impl/TransactionFeed.cpp @@ -141,6 +142,7 @@ target_sources (clio PRIVATE src/rpc/handlers/LedgerData.cpp src/rpc/handlers/LedgerEntry.cpp src/rpc/handlers/LedgerRange.cpp + src/rpc/handlers/MPTHolders.cpp src/rpc/handlers/NFTsByIssuer.cpp src/rpc/handlers/NFTBuyOffers.cpp src/rpc/handlers/NFTHistory.cpp diff --git a/src/rpc/RPCHelpers.cpp b/src/rpc/RPCHelpers.cpp index 81d55138c..a5893a323 100644 --- a/src/rpc/RPCHelpers.cpp +++ b/src/rpc/RPCHelpers.cpp @@ -1087,33 +1087,33 @@ accountHolds( BackendInterface const& backend, std::uint32_t sequence, ripple::AccountID const& account, - ripple::Asset const& asset, + ripple::Currency const& currency, ripple::AccountID const& issuer, bool const zeroIfFrozen, boost::asio::yield_context yield ) { ripple::STAmount amount; - if (ripple::isXRP(asset)) { + if (ripple::isXRP(currency)) { return {xrpLiquid(backend, sequence, account, yield)}; } // TODO: check MPTs! - auto key = ripple::keylet::line(account, issuer, asset).key; + auto key = ripple::keylet::line(account, issuer, currency).key; auto const blob = backend.fetchLedgerObject(key, sequence, yield); if (!blob) { - amount.clear({asset, issuer}); + amount.clear({currency, issuer}); return amount; } ripple::SerialIter it{blob->data(), blob->size()}; ripple::SLE const sle{it, key}; - if (zeroIfFrozen && isFrozen(backend, sequence, account, asset, issuer, yield)) { - amount.clear(ripple::Issue(asset, issuer)); + if (zeroIfFrozen && isFrozen(backend, sequence, account, currency, issuer, yield)) { + amount.clear(ripple::Issue(currency, issuer)); } else { amount = sle.getFieldAmount(ripple::sfBalance); if (account > issuer) { diff --git a/src/rpc/RPCHelpers.h b/src/rpc/RPCHelpers.h index 6ae469879..83eb003f8 100644 --- a/src/rpc/RPCHelpers.h +++ b/src/rpc/RPCHelpers.h @@ -230,7 +230,7 @@ accountHolds( BackendInterface const& backend, std::uint32_t sequence, ripple::AccountID const& account, - ripple::Asset const& asset, + ripple::Currency const& currency, ripple::AccountID const& issuer, bool zeroIfFrozen, boost::asio::yield_context yield diff --git a/src/rpc/common/Validators.cpp b/src/rpc/common/Validators.cpp index 6a9e9e9cd..7226b7532 100644 --- a/src/rpc/common/Validators.cpp +++ b/src/rpc/common/Validators.cpp @@ -68,6 +68,19 @@ checkIsU32Numeric(std::string_view sv) return ec == std::errc(); } +// TODO: Should refactor hex string validators due to duplicate code +CustomValidator Uint160HexStringValidator = + CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { + if (!value.is_string()) + return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "NotString"}}; + + ripple::uint160 ledgerHash; + if (!ledgerHash.parseHex(value.as_string().c_str())) + return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "Malformed"}}; + + return MaybeError{}; + }}; + CustomValidator Uint192HexStringValidator = CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { if (!value.is_string()) diff --git a/src/rpc/common/Validators.h b/src/rpc/common/Validators.h index c6d3f26a6..836bb3fec 100644 --- a/src/rpc/common/Validators.h +++ b/src/rpc/common/Validators.h @@ -478,11 +478,19 @@ extern CustomValidator AccountBase58Validator; */ extern CustomValidator AccountMarkerValidator; +/** + * @brief Provides a commonly used validator for uint160(AccountID) hex string. + * + * It must be a string and also a decodable hex. + * AccountID uses this validator. + */ +extern CustomValidator Uint160HexStringValidator; + /** * @brief Provides a commonly used validator for uint192 hex string. * * It must be a string and also a decodable hex. - * Transaction index, ledger hash all use this validator. + * MPTIssuanceID uses this validator. */ extern CustomValidator Uint192HexStringValidator; diff --git a/src/rpc/handlers/MPTHolders.cpp b/src/rpc/handlers/MPTHolders.cpp index ad6551e3e..173c00d87 100644 --- a/src/rpc/handlers/MPTHolders.cpp +++ b/src/rpc/handlers/MPTHolders.cpp @@ -78,13 +78,14 @@ MPTHoldersHandler::process(MPTHoldersHandler::Input input, Context const& ctx) c ripple::STLedgerEntry sle{ripple::SerialIter{mpt.data(), mpt.size()}, keylet::mptIssuance(mptID).key}; boost::json::object mptJson; - mptJson["mpt_amount"] = std::to_string(sle[ripple::sfMPTAmount]); - mptJson[JS(flags)] = sle[ripple::sfFlags]; mptJson[JS(account)] = toBase58(sle.getAccountID(ripple::sfAccount)); + mptJson[JS(flags)] = sle[ripple::sfFlags]; + mptJson["mpt_amount"] = toBoostJson(ripple::STUInt64{sle[sfMPTAmount]}.getJson(JsonOptions::none)); - if(sle[~ripple::sfLockedAmount]) - mptJson["locked_amount"] = std::to_string(sle[~ripple::sfLockedAmount].value_or(0)); + if (!sle[~ripple::sfLockedAmount]) + mptJson["locked_amount"] = toBoostJson(ripple::STUInt64{sle[~sfLockedAmount].value_or(0)}.getJson(JsonOptions::none)); + mptJson["mptoken_index"] = ripple::to_string(ripple::keylet::mptoken(mptID, sle.getAccountID(ripple::sfAccount)).key); output.mpts.push_back(mptJson); } @@ -101,7 +102,7 @@ tag_invoke(boost::json::value_from_tag, boost::json::value& jv, MPTHoldersHandle {JS(mpt_issuance_id), output.mptID}, {JS(limit), output.limit}, {JS(ledger_index), output.ledgerIndex}, - {"mpts", output.mpts}, + {"mptokens", output.mpts}, {JS(validated), output.validated}, }; diff --git a/src/rpc/handlers/MPTHolders.h b/src/rpc/handlers/MPTHolders.h index 3c08491e6..555799d2b 100644 --- a/src/rpc/handlers/MPTHolders.h +++ b/src/rpc/handlers/MPTHolders.h @@ -68,7 +68,7 @@ class MPTHoldersHandler { validation::Type{}, validation::Min(1u), modifiers::Clamp{LIMIT_MIN, LIMIT_MAX}}, - {JS(marker), validation::AccountValidator}, + {JS(marker), validation::Uint160HexStringValidator}, }; return rpcSpec; From 0adc32e56dc591734bfa3cb69369bf19717dffd7 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Wed, 31 Jan 2024 13:30:33 -0500 Subject: [PATCH 15/64] tests --- src/rpc/handlers/MPTHolders.cpp | 8 +- src/rpc/handlers/MPTHolders.h | 2 +- unittests/rpc/handlers/MPTHoldersTests.cpp | 563 +++++++++++++++++++++ 3 files changed, 571 insertions(+), 2 deletions(-) create mode 100644 unittests/rpc/handlers/MPTHoldersTests.cpp diff --git a/src/rpc/handlers/MPTHolders.cpp b/src/rpc/handlers/MPTHolders.cpp index 173c00d87..1066a49a8 100644 --- a/src/rpc/handlers/MPTHolders.cpp +++ b/src/rpc/handlers/MPTHolders.cpp @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ /* This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2023, the clio developers. + Copyright (c) 2024, the clio developers. Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -60,6 +60,12 @@ MPTHoldersHandler::process(MPTHoldersHandler::Input input, Context const& ctx) c auto const mptID = ripple::uint192{input.mptID.c_str()}; + auto const issuanceLedgerObject = + sharedPtrBackend_->fetchLedgerObject(ripple::keylet::mptIssuance(mptID).key, lgrInfo.seq, ctx.yield); + + if (!issuanceLedgerObject) + return Error{Status{RippledError::rpcOBJECT_NOT_FOUND, "objectNotFound"}}; + std::optional cursor; if (input.marker) cursor = ripple::AccountID{input.marker->c_str()}; diff --git a/src/rpc/handlers/MPTHolders.h b/src/rpc/handlers/MPTHolders.h index 555799d2b..531df2bf3 100644 --- a/src/rpc/handlers/MPTHolders.h +++ b/src/rpc/handlers/MPTHolders.h @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ /* This file is part of clio: https://github.com/XRPLF/clio - Copyright (c) 2023, the clio developers. + Copyright (c) 2024, the clio developers. Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above diff --git a/unittests/rpc/handlers/MPTHoldersTests.cpp b/unittests/rpc/handlers/MPTHoldersTests.cpp new file mode 100644 index 000000000..4727f17ff --- /dev/null +++ b/unittests/rpc/handlers/MPTHoldersTests.cpp @@ -0,0 +1,563 @@ +//------------------------------------------------------------------------------ +/* + This file is part of clio: https://github.com/XRPLF/clio + Copyright (c) 2024, the clio developers. + + Permission to use, copy, modify, and distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include "data/Types.h" +#include "rpc/Errors.h" +#include "rpc/common/AnyHandler.h" +#include "rpc/common/Types.h" +#include "rpc/handlers/MPTHolders.h" +#include "util/Fixtures.h" +#include "util/TestObject.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +using namespace rpc; +namespace json = boost::json; +using namespace testing; + +//constexpr static auto ISSUER_ACCOUNT = "rsS8ju2jYabSKJ6uzLarAS1gEzvRQ6JAiF"; +constexpr static auto HOLDER1_ACCOUNT = "rrnAZCqMahreZrKMcZU3t2DZ6yUndT4ubN"; +constexpr static auto HOLDER2_ACCOUNT = "rEiNkzogdHEzUxPfsri5XSMqtXUixf2Yx"; +constexpr static auto LEDGERHASH = "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652"; +constexpr static auto MPTID = "000004C463C52827307480341125DA0577DEFC38405B0E3E"; + + +static std::string MPTOUT1 = + R"({ + "account": "rrnAZCqMahreZrKMcZU3t2DZ6yUndT4ubN", + "flags": 0, + "mpt_amount": "1", + "mptoken_index": "D137F2E5A5767A06CB7A8F060ADE442A30CFF95028E1AF4B8767E3A56877205A" + })"; + +static std::string MPTOUT2 = + R"({ + "account": "rEiNkzogdHEzUxPfsri5XSMqtXUixf2Yx", + "flags": 0, + "mpt_amount": "1", + "mptoken_index": "36D91DEE5EFE4A93119A8B84C944A528F2B444329F3846E49FE921040DE17E65" + })"; + +class RPCMPTHoldersHandlerTest : public HandlerBaseTest {}; + +TEST_F(RPCMPTHoldersHandlerTest, NonHexLedgerHash) +{ + runSpawn([this](boost::asio::yield_context yield) { + auto const handler = AnyHandler{MPTHoldersHandler{backend}}; + auto const input = json::parse(fmt::format( + R"({{ + "mpt_issuance_id": "{}", + "ledger_hash": "xxx" + }})", + MPTID + )); + auto const output = handler.process(input, Context{std::ref(yield)}); + ASSERT_FALSE(output); + + auto const err = rpc::makeError(output.error()); + EXPECT_EQ(err.at("error").as_string(), "invalidParams"); + EXPECT_EQ(err.at("error_message").as_string(), "ledger_hashMalformed"); + }); +} + +TEST_F(RPCMPTHoldersHandlerTest, NonStringLedgerHash) +{ + runSpawn([this](boost::asio::yield_context yield) { + auto const handler = AnyHandler{MPTHoldersHandler{backend}}; + auto const input = json::parse(fmt::format( + R"({{ + "mpt_issuance_id": "{}", + "ledger_hash": 123 + }})", + MPTID + )); + auto const output = handler.process(input, Context{std::ref(yield)}); + ASSERT_FALSE(output); + + auto const err = rpc::makeError(output.error()); + EXPECT_EQ(err.at("error").as_string(), "invalidParams"); + EXPECT_EQ(err.at("error_message").as_string(), "ledger_hashNotString"); + }); +} + +TEST_F(RPCMPTHoldersHandlerTest, InvalidLedgerIndexString) +{ + runSpawn([this](boost::asio::yield_context yield) { + auto const handler = AnyHandler{MPTHoldersHandler{backend}}; + auto const input = json::parse(fmt::format( + R"({{ + "mpt_issuance_id": "{}", + "ledger_index": "notvalidated" + }})", + MPTID + )); + auto const output = handler.process(input, Context{std::ref(yield)}); + ASSERT_FALSE(output); + + auto const err = rpc::makeError(output.error()); + EXPECT_EQ(err.at("error").as_string(), "invalidParams"); + EXPECT_EQ(err.at("error_message").as_string(), "ledgerIndexMalformed"); + }); +} + +// error case: issuer invalid format, length is incorrect +TEST_F(RPCMPTHoldersHandlerTest, MPTIDInvalidFormat) +{ + runSpawn([this](boost::asio::yield_context yield) { + auto const handler = AnyHandler{MPTHoldersHandler{backend}}; + auto const input = json::parse(R"({ + "mpt_issuance_id": "xxx" + })"); + auto const output = handler.process(input, Context{std::ref(yield)}); + ASSERT_FALSE(output); + auto const err = rpc::makeError(output.error()); + EXPECT_EQ(err.at("error").as_string(), "invalidParams"); + EXPECT_EQ(err.at("error_message").as_string(), "mpt_issuance_idMalformed"); + }); +} + +// error case: issuer missing +TEST_F(RPCMPTHoldersHandlerTest, MPTIDMissing) +{ + runSpawn([this](boost::asio::yield_context yield) { + auto const handler = AnyHandler{MPTHoldersHandler{backend}}; + auto const input = json::parse(R"({})"); + auto const output = handler.process(input, Context{std::ref(yield)}); + ASSERT_FALSE(output); + auto const err = rpc::makeError(output.error()); + EXPECT_EQ(err.at("error").as_string(), "invalidParams"); + EXPECT_EQ(err.at("error_message").as_string(), "Required field 'mpt_issuance_id' missing"); + }); +} + +// error case: issuer invalid format +TEST_F(RPCMPTHoldersHandlerTest, MPTIDNotString) +{ + runSpawn([this](boost::asio::yield_context yield) { + auto const handler = AnyHandler{MPTHoldersHandler{backend}}; + auto const input = json::parse(R"({ + "mpt_issuance_id": 12 + })"); + auto const output = handler.process(input, Context{std::ref(yield)}); + ASSERT_FALSE(output); + + auto const err = rpc::makeError(output.error()); + EXPECT_EQ(err.at("error").as_string(), "invalidParams"); + EXPECT_EQ(err.at("error_message").as_string(), "mpt_issuance_idNotString"); + }); +} + +// error case ledger non exist via hash +TEST_F(RPCMPTHoldersHandlerTest, NonExistLedgerViaLedgerHash) +{ + // mock fetchLedgerByHash return empty + EXPECT_CALL(*backend, fetchLedgerByHash).Times(1); + ON_CALL(*backend, fetchLedgerByHash(ripple::uint256{LEDGERHASH}, _)) + .WillByDefault(Return(std::optional{})); + + auto const input = json::parse(fmt::format( + R"({{ + "mpt_issuance_id": "{}", + "ledger_hash": "{}" + }})", + MPTID, + LEDGERHASH + )); + runSpawn([&, this](boost::asio::yield_context yield) { + auto const handler = AnyHandler{MPTHoldersHandler{backend}}; + auto const output = handler.process(input, Context{std::ref(yield)}); + ASSERT_FALSE(output); + + auto const err = rpc::makeError(output.error()); + EXPECT_EQ(err.at("error").as_string(), "lgrNotFound"); + EXPECT_EQ(err.at("error_message").as_string(), "ledgerNotFound"); + }); +} + +// error case ledger non exist via index +TEST_F(RPCMPTHoldersHandlerTest, NonExistLedgerViaLedgerStringIndex) +{ + backend->setRange(10, 30); + // mock fetchLedgerBySequence return empty + EXPECT_CALL(*backend, fetchLedgerBySequence).WillOnce(Return(std::optional{})); + auto const input = json::parse(fmt::format( + R"({{ + "mpt_issuance_id": "{}", + "ledger_index": "4" + }})", + MPTID + )); + runSpawn([&, this](boost::asio::yield_context yield) { + auto const handler = AnyHandler{MPTHoldersHandler{backend}}; + auto const output = handler.process(input, Context{std::ref(yield)}); + ASSERT_FALSE(output); + auto const err = rpc::makeError(output.error()); + EXPECT_EQ(err.at("error").as_string(), "lgrNotFound"); + EXPECT_EQ(err.at("error_message").as_string(), "ledgerNotFound"); + }); +} + +TEST_F(RPCMPTHoldersHandlerTest, NonExistLedgerViaLedgerIntIndex) +{ + backend->setRange(10, 30); + // mock fetchLedgerBySequence return empty + EXPECT_CALL(*backend, fetchLedgerBySequence).WillOnce(Return(std::optional{})); + auto const input = json::parse(fmt::format( + R"({{ + "mpt_issuance_id": "{}", + "ledger_index": 4 + }})", + MPTID + )); + runSpawn([&, this](boost::asio::yield_context yield) { + auto const handler = AnyHandler{MPTHoldersHandler{backend}}; + auto const output = handler.process(input, Context{std::ref(yield)}); + ASSERT_FALSE(output); + auto const err = rpc::makeError(output.error()); + EXPECT_EQ(err.at("error").as_string(), "lgrNotFound"); + EXPECT_EQ(err.at("error_message").as_string(), "ledgerNotFound"); + }); +} + +// error case ledger > max seq via hash +// idk why this case will happen in reality +TEST_F(RPCMPTHoldersHandlerTest, NonExistLedgerViaLedgerHash2) +{ + backend->setRange(10, 30); + // mock fetchLedgerByHash return ledger but seq is 31 > 30 + auto ledgerinfo = CreateLedgerInfo(LEDGERHASH, 31); + ON_CALL(*backend, fetchLedgerByHash(ripple::uint256{LEDGERHASH}, _)).WillByDefault(Return(ledgerinfo)); + EXPECT_CALL(*backend, fetchLedgerByHash).Times(1); + auto const input = json::parse(fmt::format( + R"({{ + "mpt_issuance_id": "{}", + "ledger_hash": "{}" + }})", + MPTID, + LEDGERHASH + )); + runSpawn([&, this](boost::asio::yield_context yield) { + auto const handler = AnyHandler{MPTHoldersHandler{backend}}; + auto const output = handler.process(input, Context{std::ref(yield)}); + ASSERT_FALSE(output); + auto const err = rpc::makeError(output.error()); + EXPECT_EQ(err.at("error").as_string(), "lgrNotFound"); + EXPECT_EQ(err.at("error_message").as_string(), "ledgerNotFound"); + }); +} + +// error case ledger > max seq via index +TEST_F(RPCMPTHoldersHandlerTest, NonExistLedgerViaLedgerIndex2) +{ + backend->setRange(10, 30); + // no need to check from db,call fetchLedgerBySequence 0 time + // differ from previous logic + EXPECT_CALL(*backend, fetchLedgerBySequence).Times(0); + auto const input = json::parse(fmt::format( + R"({{ + "mpt_issuance_id": "{}", + "ledger_index": "31" + }})", + MPTID + )); + runSpawn([&, this](boost::asio::yield_context yield) { + auto const handler = AnyHandler{MPTHoldersHandler{backend}}; + auto const output = handler.process(input, Context{std::ref(yield)}); + ASSERT_FALSE(output); + auto const err = rpc::makeError(output.error()); + EXPECT_EQ(err.at("error").as_string(), "lgrNotFound"); + EXPECT_EQ(err.at("error_message").as_string(), "ledgerNotFound"); + }); +} + +// normal case when MPT does not exist +TEST_F(RPCMPTHoldersHandlerTest, MPTNotFound) +{ + backend->setRange(10, 30); + auto ledgerinfo = CreateLedgerInfo(LEDGERHASH, 30); + ON_CALL(*backend, fetchLedgerByHash(ripple::uint256{LEDGERHASH}, _)).WillByDefault(Return(ledgerinfo)); + EXPECT_CALL(*backend, fetchLedgerByHash).Times(1); + ON_CALL(*backend, doFetchLedgerObject).WillByDefault(Return(std::optional{})); + EXPECT_CALL(*backend, doFetchLedgerObject).Times(1); + + auto const input = json::parse(fmt::format( + R"({{ + "mpt_issuance_id": "{}", + "ledger_hash": "{}" + }})", + MPTID, + LEDGERHASH + )); + runSpawn([&, this](boost::asio::yield_context yield) { + auto handler = AnyHandler{MPTHoldersHandler{this->backend}}; + auto const output = handler.process(input, Context{yield}); + ASSERT_FALSE(output); + auto const err = rpc::makeError(output.error()); + EXPECT_EQ(err.at("error").as_string(), "objectNotFound"); + EXPECT_EQ(err.at("error_message").as_string(), "objectNotFound"); + }); +} + +// normal case when mpt has one holder +TEST_F(RPCMPTHoldersHandlerTest, DefaultParameters) +{ + auto const currentOutput = fmt::format( + R"({{ + "mpt_issuance_id": "{}", + "limit":50, + "ledger_index": 30, + "mptokens": [{}], + "validated": true + }})", + MPTID, + MPTOUT1 + ); + + backend->setRange(10, 30); + auto ledgerInfo = CreateLedgerInfo(LEDGERHASH, 30); + EXPECT_CALL(*backend, fetchLedgerBySequence).WillOnce(Return(ledgerInfo)); + auto const issuanceKk = ripple::keylet::mptIssuance(ripple::uint192(MPTID)).key; + ON_CALL(*backend, doFetchLedgerObject(issuanceKk, 30, _)).WillByDefault(Return(Blob{'f', 'a', 'k', 'e'})); + + std::vector const mpts = {CreateMPTokenObject(HOLDER1_ACCOUNT, ripple::uint192(MPTID)).getSerializer().peekData()}; + ON_CALL(*backend, fetchMPTHolders).WillByDefault(Return(MPTHoldersAndCursor{mpts, {}})); + EXPECT_CALL( + *backend, + fetchMPTHolders( + ripple::uint192(MPTID), testing::_, testing::Eq(std::nullopt), Const(30), testing::_ + ) + ) + .Times(1); + + auto const input = json::parse(fmt::format( + R"({{ + "mpt_issuance_id": "{}" + }})", + MPTID + )); + runSpawn([&, this](auto& yield) { + auto handler = AnyHandler{MPTHoldersHandler{this->backend}}; + auto const output = handler.process(input, Context{yield}); + ASSERT_TRUE(output); + EXPECT_EQ(json::parse(currentOutput), *output); + }); +} + +TEST_F(RPCMPTHoldersHandlerTest, SpecificLedgerIndex) +{ + auto const specificLedger = 20; + auto const currentOutput = fmt::format( + R"({{ + "mpt_issuance_id": "{}", + "limit":50, + "ledger_index": {}, + "mptokens": [{}], + "validated": true + }})", + MPTID, + specificLedger, + MPTOUT1 + ); + + backend->setRange(10, 30); + auto ledgerInfo = CreateLedgerInfo(LEDGERHASH, specificLedger); + ON_CALL(*backend, fetchLedgerBySequence(specificLedger, _)).WillByDefault(Return(ledgerInfo)); + EXPECT_CALL(*backend, fetchLedgerBySequence).Times(1); + auto const issuanceKk = ripple::keylet::mptIssuance(ripple::uint192(MPTID)).key; + ON_CALL(*backend, doFetchLedgerObject(issuanceKk, specificLedger, _)).WillByDefault(Return(Blob{'f', 'a', 'k', 'e'})); + + std::vector const mpts = {CreateMPTokenObject(HOLDER1_ACCOUNT, ripple::uint192(MPTID)).getSerializer().peekData()}; + ON_CALL(*backend, fetchMPTHolders).WillByDefault(Return(MPTHoldersAndCursor{mpts, {}})); + EXPECT_CALL( + *backend, + fetchMPTHolders( + ripple::uint192(MPTID), testing::_, testing::Eq(std::nullopt), Const(specificLedger), testing::_ + ) + ) + .Times(1); + + auto const input = json::parse(fmt::format( + R"({{ + "mpt_issuance_id": "{}", + "ledger_index": {} + }})", + MPTID, + specificLedger + )); + runSpawn([&, this](auto& yield) { + auto handler = AnyHandler{MPTHoldersHandler{this->backend}}; + auto const output = handler.process(input, Context{yield}); + ASSERT_TRUE(output); + EXPECT_EQ(json::parse(currentOutput), *output); + }); +} + +TEST_F(RPCMPTHoldersHandlerTest, MarkerParameter) +{ + auto const currentOutput = fmt::format( + R"({{ + "mpt_issuance_id": "{}", + "limit":50, + "ledger_index": 30, + "mptokens": [{}], + "validated": true, + "marker": "{}" + }})", + MPTID, + MPTOUT2, + ripple::strHex(GetAccountIDWithString(HOLDER1_ACCOUNT)) + ); + + backend->setRange(10, 30); + auto ledgerInfo = CreateLedgerInfo(LEDGERHASH, 30); + EXPECT_CALL(*backend, fetchLedgerBySequence).WillOnce(Return(ledgerInfo)); + auto const issuanceKk = ripple::keylet::mptIssuance(ripple::uint192(MPTID)).key; + ON_CALL(*backend, doFetchLedgerObject(issuanceKk, 30, _)).WillByDefault(Return(Blob{'f', 'a', 'k', 'e'})); + + std::vector const mpts = {CreateMPTokenObject(HOLDER2_ACCOUNT, ripple::uint192(MPTID)).getSerializer().peekData()}; + auto const marker = GetAccountIDWithString(HOLDER1_ACCOUNT); + ON_CALL(*backend, fetchMPTHolders).WillByDefault(Return(MPTHoldersAndCursor{mpts, marker})); + EXPECT_CALL( + *backend, + fetchMPTHolders( + ripple::uint192(MPTID), testing::_, testing::Eq(marker), Const(30), testing::_ + ) + ) + .Times(1); + + const auto HOLDER1_ACCOUNTID = ripple::strHex(GetAccountIDWithString(HOLDER1_ACCOUNT)); + auto const input = json::parse(fmt::format( + R"({{ + "mpt_issuance_id": "{}", + "marker": "{}" + }})", + MPTID, + HOLDER1_ACCOUNTID + )); + runSpawn([&, this](auto& yield) { + auto handler = AnyHandler{MPTHoldersHandler{this->backend}}; + auto const output = handler.process(input, Context{yield}); + ASSERT_TRUE(output); + EXPECT_EQ(json::parse(currentOutput), *output); + }); +} + + +TEST_F(RPCMPTHoldersHandlerTest, MultipleMPTs) +{ + auto const currentOutput = fmt::format( + R"({{ + "mpt_issuance_id": "{}", + "limit":50, + "ledger_index": 30, + "mptokens": [{}, {}], + "validated": true + }})", + MPTID, + MPTOUT1, + MPTOUT2 + ); + + backend->setRange(10, 30); + auto ledgerInfo = CreateLedgerInfo(LEDGERHASH, 30); + EXPECT_CALL(*backend, fetchLedgerBySequence).WillOnce(Return(ledgerInfo)); + auto const issuanceKk = ripple::keylet::mptIssuance(ripple::uint192(MPTID)).key; + ON_CALL(*backend, doFetchLedgerObject(issuanceKk, 30, _)).WillByDefault(Return(Blob{'f', 'a', 'k', 'e'})); + + std::vector const mpts = {CreateMPTokenObject(HOLDER1_ACCOUNT, ripple::uint192(MPTID)).getSerializer().peekData(), CreateMPTokenObject(HOLDER2_ACCOUNT, ripple::uint192(MPTID)).getSerializer().peekData()}; + ON_CALL(*backend, fetchMPTHolders).WillByDefault(Return(MPTHoldersAndCursor{mpts, {}})); + EXPECT_CALL( + *backend, + fetchMPTHolders( + ripple::uint192(MPTID), testing::_, testing::Eq(std::nullopt), Const(30), testing::_ + ) + ) + .Times(1); + + auto const input = json::parse(fmt::format( + R"({{ + "mpt_issuance_id": "{}" + }})", + MPTID + )); + runSpawn([&, this](auto& yield) { + auto handler = AnyHandler{MPTHoldersHandler{this->backend}}; + auto const output = handler.process(input, Context{yield}); + ASSERT_TRUE(output); + EXPECT_EQ(json::parse(currentOutput), *output); + }); +} + +TEST_F(RPCMPTHoldersHandlerTest, LimitMoreThanMAx) +{ + auto const currentOutput = fmt::format( + R"({{ + "mpt_issuance_id": "{}", + "limit":100, + "ledger_index": 30, + "mptokens": [{}], + "validated": true + }})", + MPTID, + MPTOUT1 + ); + + backend->setRange(10, 30); + auto ledgerInfo = CreateLedgerInfo(LEDGERHASH, 30); + EXPECT_CALL(*backend, fetchLedgerBySequence).WillOnce(Return(ledgerInfo)); + auto const issuanceKk = ripple::keylet::mptIssuance(ripple::uint192(MPTID)).key; + ON_CALL(*backend, doFetchLedgerObject(issuanceKk, 30, _)).WillByDefault(Return(Blob{'f', 'a', 'k', 'e'})); + + std::vector const mpts = {CreateMPTokenObject(HOLDER1_ACCOUNT, ripple::uint192(MPTID)).getSerializer().peekData()}; + ON_CALL(*backend, fetchMPTHolders).WillByDefault(Return(MPTHoldersAndCursor{mpts, {}})); + EXPECT_CALL( + *backend, + fetchMPTHolders( + ripple::uint192(MPTID), Const(MPTHoldersHandler::LIMIT_MAX), testing::Eq(std::nullopt), Const(30), testing::_ + ) + ) + .Times(1); + + auto const input = json::parse(fmt::format( + R"({{ + "mpt_issuance_id": "{}", + "limit": {} + }})", + MPTID, + MPTHoldersHandler::LIMIT_MAX + 1 + )); + runSpawn([&, this](auto& yield) { + auto handler = AnyHandler{MPTHoldersHandler{this->backend}}; + auto const output = handler.process(input, Context{yield}); + ASSERT_TRUE(output); + EXPECT_EQ(json::parse(currentOutput), *output); + }); +} \ No newline at end of file From 1d7e49fa9314583fb764322b2d8c7c3e76c20455 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Wed, 31 Jan 2024 13:37:14 -0500 Subject: [PATCH 16/64] typo --- src/rpc/RPCHelpers.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rpc/RPCHelpers.cpp b/src/rpc/RPCHelpers.cpp index a5893a323..14b7601e9 100644 --- a/src/rpc/RPCHelpers.cpp +++ b/src/rpc/RPCHelpers.cpp @@ -318,7 +318,7 @@ insertDeliveredAmount( } static std::optional -getMPTissuanceID( +getMPTIssuanceID( std::shared_ptr const& meta ) { @@ -364,7 +364,7 @@ insertMPTIssuanceID( if (!canHaveMPTIssuanceID(txn, meta)) return false; - if (auto const amt = getMPTissuanceID(meta)) + if (auto const amt = getMPTIssuanceID(meta)) metaJson[JS(mpt_issuance_id)] = ripple::to_string(*amt); else metaJson[JS(mpt_issuance_id)] = "unavailable"; From a700caa03054b015aecb689afe22cbf77d2993bc Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Wed, 31 Jan 2024 13:49:34 -0500 Subject: [PATCH 17/64] cmake --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index e58564344..afea6c28b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -238,6 +238,7 @@ if (tests) unittests/rpc/handlers/ServerInfoTests.cpp unittests/rpc/handlers/PingTests.cpp unittests/rpc/handlers/RandomTests.cpp + unittests/rpc/handlers/MPTHoldersTests.cpp unittests/rpc/handlers/NFTInfoTests.cpp unittests/rpc/handlers/NFTBuyOffersTests.cpp unittests/rpc/handlers/NFTsByIssuerTest.cpp From 7052364513f8219f6983bc52a3204bd0b4836d4a Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Wed, 31 Jan 2024 14:00:04 -0500 Subject: [PATCH 18/64] comments --- src/data/BackendInterface.h | 11 +++++++++++ src/etl/MPTHelpers.h | 9 ++++----- src/rpc/RPCHelpers.cpp | 2 +- src/rpc/RPCHelpers.h | 3 +++ src/rpc/handlers/MPTHolders.cpp | 4 ++-- 5 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/data/BackendInterface.h b/src/data/BackendInterface.h index ceca385e9..bf6ee2287 100644 --- a/src/data/BackendInterface.h +++ b/src/data/BackendInterface.h @@ -322,6 +322,17 @@ class BackendInterface { boost::asio::yield_context yield ) const = 0; + /** + * @brief Fetches all holders' balances for a MPTIssuanceID + * + * @param mptID MPTIssuanceID you wish you query. + * @param limit Paging limit. + * @param cursorIn Optional cursor to allow us to pick up from where we + * last left off. + * @param ledgerSequence The ledger sequence to fetch for + * @param yield Currently executing coroutine. + * @return std::vector of MPToken balances and an optional marker + */ virtual MPTHoldersAndCursor fetchMPTHolders( ripple::uint192 const& mptID, diff --git a/src/etl/MPTHelpers.h b/src/etl/MPTHelpers.h index 493fd9423..67ed24ebc 100644 --- a/src/etl/MPTHelpers.h +++ b/src/etl/MPTHelpers.h @@ -28,22 +28,21 @@ namespace etl { /** - * @brief Pull CFTIssuance data from TX via ETLService. + * @brief Pull MPT data from TX via ETLService. * * @param txMeta Transaction metadata * @param sttx The transaction - * @return The CFTIssuance and issuer pair as a optional + * @return The MPTIssuanceID and holder pair as a optional */ std::optional> getMPTHolderFromTx(ripple::TxMeta const& txMeta, ripple::STTx const& sttx); /** - * @brief Pull CFTIssuance data from ledger object via loadInitialLedger. + * @brief Pull MPT data from ledger object via loadInitialLedger. * - * @param seq The ledger sequence to pull for * @param key The owner key * @param blob Object data as blob - * @return The CFTIssuance and issuer pair as a optional + * @return The MPTIssuanceID and holder pair as a optional */ std::optional> getMPTHolderFromObj(std::string const& key, std::string const& blob); diff --git a/src/rpc/RPCHelpers.cpp b/src/rpc/RPCHelpers.cpp index 14b7601e9..b0ec77138 100644 --- a/src/rpc/RPCHelpers.cpp +++ b/src/rpc/RPCHelpers.cpp @@ -1098,7 +1098,7 @@ accountHolds( return {xrpLiquid(backend, sequence, account, yield)}; } - // TODO: check MPTs! + // TODO: Refactor for MPT phase 2 auto key = ripple::keylet::line(account, issuer, currency).key; diff --git a/src/rpc/RPCHelpers.h b/src/rpc/RPCHelpers.h index 83eb003f8..da6c6bbdb 100644 --- a/src/rpc/RPCHelpers.h +++ b/src/rpc/RPCHelpers.h @@ -104,6 +104,9 @@ insertDeliveredAmount( uint32_t date ); +/** + * @brief Add "mpt_issuance_id" into MPTokenIssuanceCreate transaction json. + */ bool insertMPTIssuanceID( boost::json::object& metaJson, diff --git a/src/rpc/handlers/MPTHolders.cpp b/src/rpc/handlers/MPTHolders.cpp index 1066a49a8..8649838ad 100644 --- a/src/rpc/handlers/MPTHolders.cpp +++ b/src/rpc/handlers/MPTHolders.cpp @@ -86,10 +86,10 @@ MPTHoldersHandler::process(MPTHoldersHandler::Input input, Context const& ctx) c mptJson[JS(account)] = toBase58(sle.getAccountID(ripple::sfAccount)); mptJson[JS(flags)] = sle[ripple::sfFlags]; - mptJson["mpt_amount"] = toBoostJson(ripple::STUInt64{sle[sfMPTAmount]}.getJson(JsonOptions::none)); + mptJson["mpt_amount"] = toBoostJson(ripple::STUInt64{sle[ripple::sfMPTAmount]}.getJson(JsonOptions::none)); if (!sle[~ripple::sfLockedAmount]) - mptJson["locked_amount"] = toBoostJson(ripple::STUInt64{sle[~sfLockedAmount].value_or(0)}.getJson(JsonOptions::none)); + mptJson["locked_amount"] = toBoostJson(ripple::STUInt64{sle[~ripple::sfLockedAmount].value_or(0)}.getJson(JsonOptions::none)); mptJson["mptoken_index"] = ripple::to_string(ripple::keylet::mptoken(mptID, sle.getAccountID(ripple::sfAccount)).key); output.mpts.push_back(mptJson); From ac5f48e0d5b64a972257e06e4536370235728fbd Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Wed, 31 Jan 2024 14:25:27 -0500 Subject: [PATCH 19/64] ledgerdata --- src/rpc/common/Validators.cpp | 8 ++++---- src/rpc/handlers/AccountObjects.cpp | 4 +++- src/rpc/handlers/LedgerData.cpp | 14 ++++++++++++-- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/rpc/common/Validators.cpp b/src/rpc/common/Validators.cpp index 7226b7532..156d85a27 100644 --- a/src/rpc/common/Validators.cpp +++ b/src/rpc/common/Validators.cpp @@ -74,8 +74,8 @@ CustomValidator Uint160HexStringValidator = if (!value.is_string()) return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "NotString"}}; - ripple::uint160 ledgerHash; - if (!ledgerHash.parseHex(value.as_string().c_str())) + ripple::uint160 parsedInt; + if (!parsedInt.parseHex(value.as_string().c_str())) return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "Malformed"}}; return MaybeError{}; @@ -86,8 +86,8 @@ CustomValidator Uint192HexStringValidator = if (!value.is_string()) return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "NotString"}}; - ripple::uint192 ledgerHash; - if (!ledgerHash.parseHex(value.as_string().c_str())) + ripple::uint192 parsedInt; + if (!parsedInt.parseHex(value.as_string().c_str())) return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "Malformed"}}; return MaybeError{}; diff --git a/src/rpc/handlers/AccountObjects.cpp b/src/rpc/handlers/AccountObjects.cpp index 8f8b6c33a..5952c55af 100644 --- a/src/rpc/handlers/AccountObjects.cpp +++ b/src/rpc/handlers/AccountObjects.cpp @@ -159,10 +159,12 @@ tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountObjectsHa std::back_inserter(objects), [](auto const& sle) { auto sleJson = toJson(sle); + + // if object type if mpt issuance, inject synthetic mpt id if (sle.getType() == ripple::ltMPTOKEN_ISSUANCE) sleJson[JS(mpt_issuance_id)] = ripple::to_string(ripple::getMptID( sle.getAccountID(ripple::sfIssuer), - sle[ripple::sfSequence])); + sle.getFieldU32(ripple::sfSequence))); return sleJson; } ); diff --git a/src/rpc/handlers/LedgerData.cpp b/src/rpc/handlers/LedgerData.cpp index 643bbcf00..c70ac5683 100644 --- a/src/rpc/handlers/LedgerData.cpp +++ b/src/rpc/handlers/LedgerData.cpp @@ -69,7 +69,9 @@ std::unordered_map const LedgerDataHandler {JS(ticket), ripple::ltTICKET}, {JS(nft_offer), ripple::ltNFTOKEN_OFFER}, {JS(nft_page), ripple::ltNFTOKEN_PAGE}, - {JS(amm), ripple::ltAMM} + {JS(amm), ripple::ltAMM}, + {JS(mpt_issuance), ripple::ltMPTOKEN_ISSUANCE}, + {JS(mptoken), ripple::ltMPTOKEN} }; // TODO: should be std::views::keys when clang supports it @@ -169,7 +171,15 @@ LedgerDataHandler::process(Input input, Context const& ctx) const entry[JS(index)] = ripple::to_string(sle.key()); output.states.push_back(std::move(entry)); } else { - output.states.push_back(toJson(sle)); + auto sleJson = toJson(sle); + + // if object type if mpt issuance, inject synthetic mpt id + if (sle.getType() == ripple::ltMPTOKEN_ISSUANCE) + sleJson[JS(mpt_issuance_id)] = ripple::to_string(ripple::getMptID( + sle.getAccountID(ripple::sfIssuer), + sle.getFieldU32(ripple::sfSequence))); + + output.states.push_back(sleJson); } } } From 2decaa80b03c581faf5c6ace66288e87695e2859 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Wed, 31 Jan 2024 15:55:51 -0500 Subject: [PATCH 20/64] ledgerdata test --- unittests/rpc/handlers/LedgerDataTests.cpp | 77 ++++++++++++++++++++++ unittests/rpc/handlers/MPTHoldersTests.cpp | 16 +++-- 2 files changed, 88 insertions(+), 5 deletions(-) diff --git a/unittests/rpc/handlers/LedgerDataTests.cpp b/unittests/rpc/handlers/LedgerDataTests.cpp index 113586354..fdae99f8a 100644 --- a/unittests/rpc/handlers/LedgerDataTests.cpp +++ b/unittests/rpc/handlers/LedgerDataTests.cpp @@ -726,3 +726,80 @@ TEST_F(RPCLedgerDataHandlerTest, JsonLimitMoreThanMax) EXPECT_EQ(output->as_object().at("ledger_index").as_uint64(), RANGEMAX); }); } + +TEST_F(RPCLedgerDataHandlerTest, TypeFilterMPTIssuance) +{ + backend->setRange(RANGEMIN, RANGEMAX); + + EXPECT_CALL(*backend, fetchLedgerBySequence).Times(1); + ON_CALL(*backend, fetchLedgerBySequence(RANGEMAX, _)).WillByDefault(Return(CreateLedgerInfo(LEDGERHASH, RANGEMAX))); + + std::vector bbs; + EXPECT_CALL(*backend, doFetchSuccessorKey).Times(1); + ON_CALL(*backend, doFetchSuccessorKey(_, RANGEMAX, _)).WillByDefault(Return(ripple::uint256{INDEX2})); + + auto const issuance = CreateMPTIssuanceObject(ACCOUNT, 2, "metadata"); + bbs.push_back(issuance.getSerializer().peekData()); + + ON_CALL(*backend, doFetchLedgerObjects).WillByDefault(Return(bbs)); + EXPECT_CALL(*backend, doFetchLedgerObjects).Times(1); + + runSpawn([&, this](auto yield) { + auto const handler = AnyHandler{LedgerDataHandler{backend}}; + auto const req = json::parse(R"({ + "limit":1, + "type":"mpt_issuance" + })"); + + auto output = handler.process(req, Context{yield}); + ASSERT_TRUE(output); + EXPECT_TRUE(output->as_object().contains("ledger")); + EXPECT_EQ(output->as_object().at("state").as_array().size(), 1); + EXPECT_EQ(output->as_object().at("marker").as_string(), INDEX2); + EXPECT_EQ(output->as_object().at("ledger_hash").as_string(), LEDGERHASH); + EXPECT_EQ(output->as_object().at("ledger_index").as_uint64(), RANGEMAX); + + auto const& objects = output->as_object().at("state").as_array(); + EXPECT_EQ(objects.front().at("LedgerEntryType").as_string(), "MPTokenIssuance"); + + // make sure mptID is synethetically parsed if object is mptIssuance + EXPECT_EQ(objects.front().at("mpt_issuance_id").as_string(), ripple::to_string(ripple::getMptID(GetAccountIDWithString(ACCOUNT), 2))); + }); +} + +TEST_F(RPCLedgerDataHandlerTest, TypeFilterMPToken) +{ + backend->setRange(RANGEMIN, RANGEMAX); + + EXPECT_CALL(*backend, fetchLedgerBySequence).Times(1); + ON_CALL(*backend, fetchLedgerBySequence(RANGEMAX, _)).WillByDefault(Return(CreateLedgerInfo(LEDGERHASH, RANGEMAX))); + + std::vector bbs; + EXPECT_CALL(*backend, doFetchSuccessorKey).Times(1); + ON_CALL(*backend, doFetchSuccessorKey(_, RANGEMAX, _)).WillByDefault(Return(ripple::uint256{INDEX2})); + + auto const mptoken = CreateMPTokenObject(ACCOUNT, ripple::getMptID(GetAccountIDWithString(ACCOUNT), 2)); + bbs.push_back(mptoken.getSerializer().peekData()); + + ON_CALL(*backend, doFetchLedgerObjects).WillByDefault(Return(bbs)); + EXPECT_CALL(*backend, doFetchLedgerObjects).Times(1); + + runSpawn([&, this](auto yield) { + auto const handler = AnyHandler{LedgerDataHandler{backend}}; + auto const req = json::parse(R"({ + "limit":1, + "type":"mptoken" + })"); + + auto output = handler.process(req, Context{yield}); + ASSERT_TRUE(output); + EXPECT_TRUE(output->as_object().contains("ledger")); + EXPECT_EQ(output->as_object().at("state").as_array().size(), 1); + EXPECT_EQ(output->as_object().at("marker").as_string(), INDEX2); + EXPECT_EQ(output->as_object().at("ledger_hash").as_string(), LEDGERHASH); + EXPECT_EQ(output->as_object().at("ledger_index").as_uint64(), RANGEMAX); + + auto const& objects = output->as_object().at("state").as_array(); + EXPECT_EQ(objects.front().at("LedgerEntryType").as_string(), "MPToken"); + }); +} \ No newline at end of file diff --git a/unittests/rpc/handlers/MPTHoldersTests.cpp b/unittests/rpc/handlers/MPTHoldersTests.cpp index 4727f17ff..67c70658f 100644 --- a/unittests/rpc/handlers/MPTHoldersTests.cpp +++ b/unittests/rpc/handlers/MPTHoldersTests.cpp @@ -347,7 +347,8 @@ TEST_F(RPCMPTHoldersHandlerTest, DefaultParameters) auto const issuanceKk = ripple::keylet::mptIssuance(ripple::uint192(MPTID)).key; ON_CALL(*backend, doFetchLedgerObject(issuanceKk, 30, _)).WillByDefault(Return(Blob{'f', 'a', 'k', 'e'})); - std::vector const mpts = {CreateMPTokenObject(HOLDER1_ACCOUNT, ripple::uint192(MPTID)).getSerializer().peekData()}; + auto const mptoken = CreateMPTokenObject(HOLDER1_ACCOUNT, ripple::uint192(MPTID)); + std::vector const mpts = {mptoken.getSerializer().peekData()}; ON_CALL(*backend, fetchMPTHolders).WillByDefault(Return(MPTHoldersAndCursor{mpts, {}})); EXPECT_CALL( *backend, @@ -394,7 +395,8 @@ TEST_F(RPCMPTHoldersHandlerTest, SpecificLedgerIndex) auto const issuanceKk = ripple::keylet::mptIssuance(ripple::uint192(MPTID)).key; ON_CALL(*backend, doFetchLedgerObject(issuanceKk, specificLedger, _)).WillByDefault(Return(Blob{'f', 'a', 'k', 'e'})); - std::vector const mpts = {CreateMPTokenObject(HOLDER1_ACCOUNT, ripple::uint192(MPTID)).getSerializer().peekData()}; + auto const mptoken = CreateMPTokenObject(HOLDER1_ACCOUNT, ripple::uint192(MPTID)); + std::vector const mpts = {mptoken.getSerializer().peekData()}; ON_CALL(*backend, fetchMPTHolders).WillByDefault(Return(MPTHoldersAndCursor{mpts, {}})); EXPECT_CALL( *backend, @@ -442,7 +444,8 @@ TEST_F(RPCMPTHoldersHandlerTest, MarkerParameter) auto const issuanceKk = ripple::keylet::mptIssuance(ripple::uint192(MPTID)).key; ON_CALL(*backend, doFetchLedgerObject(issuanceKk, 30, _)).WillByDefault(Return(Blob{'f', 'a', 'k', 'e'})); - std::vector const mpts = {CreateMPTokenObject(HOLDER2_ACCOUNT, ripple::uint192(MPTID)).getSerializer().peekData()}; + auto const mptoken = CreateMPTokenObject(HOLDER2_ACCOUNT, ripple::uint192(MPTID)); + std::vector const mpts = {mptoken.getSerializer().peekData()}; auto const marker = GetAccountIDWithString(HOLDER1_ACCOUNT); ON_CALL(*backend, fetchMPTHolders).WillByDefault(Return(MPTHoldersAndCursor{mpts, marker})); EXPECT_CALL( @@ -492,7 +495,9 @@ TEST_F(RPCMPTHoldersHandlerTest, MultipleMPTs) auto const issuanceKk = ripple::keylet::mptIssuance(ripple::uint192(MPTID)).key; ON_CALL(*backend, doFetchLedgerObject(issuanceKk, 30, _)).WillByDefault(Return(Blob{'f', 'a', 'k', 'e'})); - std::vector const mpts = {CreateMPTokenObject(HOLDER1_ACCOUNT, ripple::uint192(MPTID)).getSerializer().peekData(), CreateMPTokenObject(HOLDER2_ACCOUNT, ripple::uint192(MPTID)).getSerializer().peekData()}; + auto const mptoken1 = CreateMPTokenObject(HOLDER1_ACCOUNT, ripple::uint192(MPTID)); + auto const mptoken2 = CreateMPTokenObject(HOLDER2_ACCOUNT, ripple::uint192(MPTID)); + std::vector const mpts = {mptoken1.getSerializer().peekData(), mptoken2.getSerializer().peekData()}; ON_CALL(*backend, fetchMPTHolders).WillByDefault(Return(MPTHoldersAndCursor{mpts, {}})); EXPECT_CALL( *backend, @@ -536,7 +541,8 @@ TEST_F(RPCMPTHoldersHandlerTest, LimitMoreThanMAx) auto const issuanceKk = ripple::keylet::mptIssuance(ripple::uint192(MPTID)).key; ON_CALL(*backend, doFetchLedgerObject(issuanceKk, 30, _)).WillByDefault(Return(Blob{'f', 'a', 'k', 'e'})); - std::vector const mpts = {CreateMPTokenObject(HOLDER1_ACCOUNT, ripple::uint192(MPTID)).getSerializer().peekData()}; + auto const mptoken = CreateMPTokenObject(HOLDER1_ACCOUNT, ripple::uint192(MPTID)); + std::vector const mpts = {mptoken.getSerializer().peekData()}; ON_CALL(*backend, fetchMPTHolders).WillByDefault(Return(MPTHoldersAndCursor{mpts, {}})); EXPECT_CALL( *backend, From 880d0360625afd3e9dc1bb945ff30a8a77161563 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Wed, 31 Jan 2024 16:12:23 -0500 Subject: [PATCH 21/64] clang --- src/data/BackendInterface.h | 5 +-- src/data/CassandraBackend.h | 31 +++++++++--------- src/data/Types.h | 3 +- src/data/cassandra/Schema.h | 9 ++++-- src/data/cassandra/impl/Statement.h | 3 +- src/etl/MPTHelpers.cpp | 31 +++++++++--------- src/etl/MPTHelpers.h | 2 -- src/etl/impl/AsyncData.h | 6 ++-- src/etl/impl/LedgerLoader.h | 2 +- src/rpc/RPCHelpers.cpp | 29 +++++++---------- src/rpc/common/impl/HandlerProvider.cpp | 2 +- src/rpc/handlers/AccountObjects.cpp | 16 +++++----- src/rpc/handlers/LedgerData.cpp | 9 +++--- src/rpc/handlers/LedgerEntry.cpp | 12 +++---- src/rpc/handlers/LedgerEntry.h | 16 ++++------ src/rpc/handlers/MPTHolders.cpp | 11 ++++--- src/rpc/handlers/MPTHolders.h | 3 +- src/rpc/handlers/NoRippleCheck.cpp | 4 ++- .../rpc/handlers/AccountObjectsTests.cpp | 7 ++-- unittests/rpc/handlers/LedgerDataTests.cpp | 13 +++++--- unittests/rpc/handlers/MPTHoldersTests.cpp | 32 ++++++++----------- unittests/util/MockBackend.h | 18 +++++++---- 22 files changed, 128 insertions(+), 136 deletions(-) diff --git a/src/data/BackendInterface.h b/src/data/BackendInterface.h index bf6ee2287..b01f59f79 100644 --- a/src/data/BackendInterface.h +++ b/src/data/BackendInterface.h @@ -334,12 +334,13 @@ class BackendInterface { * @return std::vector of MPToken balances and an optional marker */ virtual MPTHoldersAndCursor - fetchMPTHolders( + fetchMPTHolders( ripple::uint192 const& mptID, std::uint32_t const limit, std::optional const& cursorIn, std::uint32_t const ledgerSequence, - boost::asio::yield_context yield)const = 0; + boost::asio::yield_context yield + ) const = 0; /** * @brief Fetches a specific ledger object. diff --git a/src/data/CassandraBackend.h b/src/data/CassandraBackend.h index d8f99ec4e..040180a20 100644 --- a/src/data/CassandraBackend.h +++ b/src/data/CassandraBackend.h @@ -531,27 +531,27 @@ class BasicCassandraBackend : public BackendInterface { } MPTHoldersAndCursor - fetchMPTHolders( + fetchMPTHolders( ripple::uint192 const& mptID, std::uint32_t const limit, std::optional const& cursorIn, std::uint32_t const ledgerSequence, - boost::asio::yield_context yield)const override + boost::asio::yield_context yield + ) const override { - - auto const holderEntries = executor_.read(yield, schema_->selectMPTHolders, mptID, cursorIn.value_or(ripple::AccountID(0)), Limit{limit}); + auto const holderEntries = executor_.read( + yield, schema_->selectMPTHolders, mptID, cursorIn.value_or(ripple::AccountID(0)), Limit{limit} + ); auto const& holderResults = holderEntries.value(); - if (not holderResults.hasRows()) - { + if (not holderResults.hasRows()) { LOG(log_.debug()) << "No rows returned"; return {}; } std::vector mptKeys; std::optional cursor; - for (auto const [holder] : extract(holderResults)) - { + for (auto const [holder] : extract(holderResults)) { mptKeys.push_back(ripple::keylet::mptoken(mptID, holder).key); cursor = holder; } @@ -560,17 +560,18 @@ class BasicCassandraBackend : public BackendInterface { std::vector filteredMpt; - // need to filter out the objs that don't exist at the ledger seq because these MPT are in no particular time order - for(auto const mpt: mptObjects){ + // need to filter out the objs that don't exist at the ledger seq because these MPT are in no particular time + // order + for (auto const mpt : mptObjects) { if (!mpt.size()) continue; filteredMpt.push_back(mpt); } - + if (mptKeys.size() == limit) return {filteredMpt, cursor}; - + return {filteredMpt, {}}; } @@ -875,13 +876,11 @@ class BasicCassandraBackend : public BackendInterface { writeMPTHolders(std::vector>&& data) override { std::vector statements; - for (auto const& record : data) - { + for (auto const& record : data) { auto const mpt_id = record.first; auto const holder = record.second; - statements.push_back( - schema_->insertMPTHolder.bind(mpt_id, holder)); + statements.push_back(schema_->insertMPTHolder.bind(mpt_id, holder)); } executor_.write(std::move(statements)); diff --git a/src/data/Types.h b/src/data/Types.h index 9e6e1de55..53213eda7 100644 --- a/src/data/Types.h +++ b/src/data/Types.h @@ -166,8 +166,7 @@ struct NFTsAndCursor { std::optional cursor; }; -struct MPTHoldersAndCursor -{ +struct MPTHoldersAndCursor { std::vector mptokens; std::optional cursor; }; diff --git a/src/data/cassandra/Schema.h b/src/data/cassandra/Schema.h index f47ccb2af..6e59b74f2 100644 --- a/src/data/cassandra/Schema.h +++ b/src/data/cassandra/Schema.h @@ -277,7 +277,8 @@ class Schema { AND default_time_to_live = {} )", qualifiedTableName(settingsProvider_.get(), "mp_token_holders"), - settingsProvider_.get().getTtl())); + settingsProvider_.get().getTtl() + )); return statements; }(); @@ -416,7 +417,8 @@ class Schema { (mpt_id, holder) VALUES (?, ?) )", - qualifiedTableName(settingsProvider_.get(), "mp_token_holders"))); + qualifiedTableName(settingsProvider_.get(), "mp_token_holders") + )); }(); PreparedStatement insertLedgerHeader = [this]() { @@ -697,7 +699,8 @@ class Schema { ORDER BY holder ASC LIMIT ? )", - qualifiedTableName(settingsProvider_.get(), "mp_token_holders"))); + qualifiedTableName(settingsProvider_.get(), "mp_token_holders") + )); }(); PreparedStatement selectLedgerByHash = [this]() { diff --git a/src/data/cassandra/impl/Statement.h b/src/data/cassandra/impl/Statement.h index 2916460c0..758d73ec8 100644 --- a/src/data/cassandra/impl/Statement.h +++ b/src/data/cassandra/impl/Statement.h @@ -106,8 +106,7 @@ class Statement : public ManagedObject { if constexpr (std::is_same_v || std::is_same_v) { auto const rc = bindBytes(value.data(), value.size()); throwErrorIfNeeded(rc, "Bind ripple::base_uint"); - } - else if constexpr (std::is_same_v) { + } else if constexpr (std::is_same_v) { auto const rc = bindBytes(value.data(), value.size()); throwErrorIfNeeded(rc, "Bind ripple::AccountID"); } else if constexpr (std::is_same_v) { diff --git a/src/etl/MPTHelpers.cpp b/src/etl/MPTHelpers.cpp index ee48dea24..1ab2515ba 100644 --- a/src/etl/MPTHelpers.cpp +++ b/src/etl/MPTHelpers.cpp @@ -17,45 +17,46 @@ */ //============================================================================== -#include -#include -#include -#include - #include #include #include #include +#include +#include +#include + +#include namespace etl { static std::optional> -getMPTokenAuthorize(ripple::TxMeta const& txMeta){ - for (ripple::STObject const& node : txMeta.getNodes()) - { +getMPTokenAuthorize(ripple::TxMeta const& txMeta) +{ + for (ripple::STObject const& node : txMeta.getNodes()) { if (node.getFieldU16(ripple::sfLedgerEntryType) != ripple::ltMPTOKEN) continue; - if (node.getFName() == ripple::sfCreatedNode){ + if (node.getFName() == ripple::sfCreatedNode) { auto const& newMPT = node.peekAtField(ripple::sfNewFields).downcast(); - return std::make_pair(newMPT[ripple::sfMPTokenIssuanceID], newMPT[ripple::sfAccount]); + return std::make_pair(newMPT[ripple::sfMPTokenIssuanceID], newMPT[ripple::sfAccount]); } } return {}; } std::optional> -getMPTHolderFromTx(ripple::TxMeta const& txMeta, ripple::STTx const& sttx){ - if (txMeta.getResultTER() != ripple::tesSUCCESS - || sttx.getTxnType()!= ripple::TxType::ttMPTOKEN_AUTHORIZE) +getMPTHolderFromTx(ripple::TxMeta const& txMeta, ripple::STTx const& sttx) +{ + if (txMeta.getResultTER() != ripple::tesSUCCESS || sttx.getTxnType() != ripple::TxType::ttMPTOKEN_AUTHORIZE) return {}; return getMPTokenAuthorize(txMeta); } std::optional> -getMPTHolderFromObj(std::string const& key, std::string const& blob){ - ripple::STLedgerEntry const sle = +getMPTHolderFromObj(std::string const& key, std::string const& blob) +{ + ripple::STLedgerEntry const sle = ripple::STLedgerEntry(ripple::SerialIter{blob.data(), blob.size()}, ripple::uint256::fromVoid(key.data())); if (sle.getFieldU16(ripple::sfLedgerEntryType) != ripple::ltMPTOKEN) diff --git a/src/etl/MPTHelpers.h b/src/etl/MPTHelpers.h index 67ed24ebc..9ce289f08 100644 --- a/src/etl/MPTHelpers.h +++ b/src/etl/MPTHelpers.h @@ -21,7 +21,6 @@ #pragma once #include - #include #include @@ -47,5 +46,4 @@ getMPTHolderFromTx(ripple::TxMeta const& txMeta, ripple::STTx const& sttx); std::optional> getMPTHolderFromObj(std::string const& key, std::string const& blob); - } // namespace etl \ No newline at end of file diff --git a/src/etl/impl/AsyncData.h b/src/etl/impl/AsyncData.h index b86006e50..ca9f5a288 100644 --- a/src/etl/impl/AsyncData.h +++ b/src/etl/impl/AsyncData.h @@ -20,8 +20,8 @@ #pragma once #include "data/BackendInterface.h" -#include "etl/NFTHelpers.h" #include "etl/MPTHelpers.h" +#include "etl/NFTHelpers.h" #include "util/Assert.h" #include "util/log/Logger.h" @@ -140,9 +140,9 @@ class AsyncCallData { backend.writeSuccessor(std::move(lastKey_), request_.ledger().sequence(), std::string{obj.key()}); lastKey_ = obj.key(); backend.writeNFTs(getNFTDataFromObj(request_.ledger().sequence(), obj.key(), obj.data())); - + auto const maybeMPTHolder = getMPTHolderFromObj(obj.key(), obj.data()); - if(maybeMPTHolder) + if (maybeMPTHolder) backend.writeMPTHolders({*maybeMPTHolder}); backend.writeLedgerObject( diff --git a/src/etl/impl/LedgerLoader.h b/src/etl/impl/LedgerLoader.h index 4a8082995..931280022 100644 --- a/src/etl/impl/LedgerLoader.h +++ b/src/etl/impl/LedgerLoader.h @@ -112,7 +112,7 @@ class LedgerLoader { result.nfTokensData.push_back(*maybeNFT); auto const maybeMPTHolder = getMPTHolderFromTx(txMeta, sttx); - if(maybeMPTHolder) + if (maybeMPTHolder) result.mptHoldersData.push_back(*maybeMPTHolder); result.accountTxData.emplace_back(txMeta, sttx.getTransactionID()); diff --git a/src/rpc/RPCHelpers.cpp b/src/rpc/RPCHelpers.cpp index b0ec77138..76d19af66 100644 --- a/src/rpc/RPCHelpers.cpp +++ b/src/rpc/RPCHelpers.cpp @@ -318,32 +318,24 @@ insertDeliveredAmount( } static std::optional -getMPTIssuanceID( - std::shared_ptr const& meta -) +getMPTIssuanceID(std::shared_ptr const& meta) { ripple::TxMeta const& transactionMeta = *meta; - for (ripple::STObject const& node : transactionMeta.getNodes()) - { + for (ripple::STObject const& node : transactionMeta.getNodes()) { if (node.getFieldU16(ripple::sfLedgerEntryType) != ripple::ltMPTOKEN_ISSUANCE || node.getFName() != ripple::sfCreatedNode) continue; - auto const& mptNode = - node.peekAtField(ripple::sfNewFields).downcast(); - return ripple::getMptID( - mptNode.getAccountID(ripple::sfIssuer), mptNode.getFieldU32(ripple::sfSequence)); + auto const& mptNode = node.peekAtField(ripple::sfNewFields).downcast(); + return ripple::getMptID(mptNode.getAccountID(ripple::sfIssuer), mptNode.getFieldU32(ripple::sfSequence)); } return {}; } static bool -canHaveMPTIssuanceID( - std::shared_ptr const& txn, - std::shared_ptr const& meta -) +canHaveMPTIssuanceID(std::shared_ptr const& txn, std::shared_ptr const& meta) { ripple::TxType const tt{txn->getTxnType()}; if (tt != ripple::ttMPTOKEN_ISSUANCE_CREATE) @@ -360,15 +352,16 @@ insertMPTIssuanceID( boost::json::object& metaJson, std::shared_ptr const& txn, std::shared_ptr const& meta -){ +) +{ if (!canHaveMPTIssuanceID(txn, meta)) return false; - if (auto const amt = getMPTIssuanceID(meta)) + if (auto const amt = getMPTIssuanceID(meta)) metaJson[JS(mpt_issuance_id)] = ripple::to_string(*amt); - else + else metaJson[JS(mpt_issuance_id)] = "unavailable"; - + return true; } @@ -1097,7 +1090,7 @@ accountHolds( if (ripple::isXRP(currency)) { return {xrpLiquid(backend, sequence, account, yield)}; } - + // TODO: Refactor for MPT phase 2 auto key = ripple::keylet::line(account, issuer, currency).key; diff --git a/src/rpc/common/impl/HandlerProvider.cpp b/src/rpc/common/impl/HandlerProvider.cpp index 0156c4cbe..70cc2e611 100644 --- a/src/rpc/common/impl/HandlerProvider.cpp +++ b/src/rpc/common/impl/HandlerProvider.cpp @@ -88,7 +88,7 @@ ProductionHandlerProvider::ProductionHandlerProvider( {"ledger_data", {LedgerDataHandler{backend}}}, {"ledger_entry", {LedgerEntryHandler{backend}}}, {"ledger_range", {LedgerRangeHandler{backend}}}, - {"mpt_holders", {MPTHoldersHandler{backend}, true}}, //clio only + {"mpt_holders", {MPTHoldersHandler{backend}, true}}, // clio only {"nfts_by_issuer", {NFTsByIssuerHandler{backend}, true}}, // clio only {"nft_history", {NFTHistoryHandler{backend}, true}}, // clio only {"nft_buy_offers", {NFTBuyOffersHandler{backend}}}, diff --git a/src/rpc/handlers/AccountObjects.cpp b/src/rpc/handlers/AccountObjects.cpp index 5952c55af..1941a142f 100644 --- a/src/rpc/handlers/AccountObjects.cpp +++ b/src/rpc/handlers/AccountObjects.cpp @@ -102,8 +102,7 @@ AccountObjectsHandler::process(AccountObjectsHandler::Input input, Context const ripple::ltPAYCHAN, ripple::ltRIPPLE_STATE, ripple::ltMPTOKEN_ISSUANCE, - ripple::ltMPTOKEN - }; + ripple::ltMPTOKEN}; typeFilter.emplace(); typeFilter->reserve(std::size(deletionBlockers)); @@ -157,16 +156,17 @@ tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountObjectsHa std::cbegin(output.accountObjects), std::cend(output.accountObjects), std::back_inserter(objects), - [](auto const& sle) { + [](auto const& sle) { auto sleJson = toJson(sle); // if object type if mpt issuance, inject synthetic mpt id if (sle.getType() == ripple::ltMPTOKEN_ISSUANCE) - sleJson[JS(mpt_issuance_id)] = ripple::to_string(ripple::getMptID( - sle.getAccountID(ripple::sfIssuer), - sle.getFieldU32(ripple::sfSequence))); - - return sleJson; } + sleJson[JS(mpt_issuance_id)] = ripple::to_string( + ripple::getMptID(sle.getAccountID(ripple::sfIssuer), sle.getFieldU32(ripple::sfSequence)) + ); + + return sleJson; + } ); jv = { diff --git a/src/rpc/handlers/LedgerData.cpp b/src/rpc/handlers/LedgerData.cpp index c70ac5683..41ffe283d 100644 --- a/src/rpc/handlers/LedgerData.cpp +++ b/src/rpc/handlers/LedgerData.cpp @@ -71,8 +71,7 @@ std::unordered_map const LedgerDataHandler {JS(nft_page), ripple::ltNFTOKEN_PAGE}, {JS(amm), ripple::ltAMM}, {JS(mpt_issuance), ripple::ltMPTOKEN_ISSUANCE}, - {JS(mptoken), ripple::ltMPTOKEN} -}; + {JS(mptoken), ripple::ltMPTOKEN}}; // TODO: should be std::views::keys when clang supports it std::unordered_set const LedgerDataHandler::TYPES_KEYS = [] { @@ -175,9 +174,9 @@ LedgerDataHandler::process(Input input, Context const& ctx) const // if object type if mpt issuance, inject synthetic mpt id if (sle.getType() == ripple::ltMPTOKEN_ISSUANCE) - sleJson[JS(mpt_issuance_id)] = ripple::to_string(ripple::getMptID( - sle.getAccountID(ripple::sfIssuer), - sle.getFieldU32(ripple::sfSequence))); + sleJson[JS(mpt_issuance_id)] = ripple::to_string( + ripple::getMptID(sle.getAccountID(ripple::sfIssuer), sle.getFieldU32(ripple::sfSequence)) + ); output.states.push_back(sleJson); } diff --git a/src/rpc/handlers/LedgerEntry.cpp b/src/rpc/handlers/LedgerEntry.cpp index 19e65f82e..f6c5886f3 100644 --- a/src/rpc/handlers/LedgerEntry.cpp +++ b/src/rpc/handlers/LedgerEntry.cpp @@ -63,12 +63,10 @@ LedgerEntryHandler::process(LedgerEntryHandler::Input input, Context const& ctx) key = ripple::keylet::account(*ripple::parseBase58(*(input.accountRoot))).key; } else if (input.did) { key = ripple::keylet::did(*ripple::parseBase58(*(input.did))).key; - } - else if (input.mptIssuance){ + } else if (input.mptIssuance) { auto const mptIssuanceID = ripple::uint192{std::string_view(*(input.mptIssuance))}; key = ripple::keylet::mptIssuance(mptIssuanceID).key; - } - else if (input.directory) { + } else if (input.directory) { auto const keyOrStatus = composeKeyFromDirectory(*input.directory); if (auto const status = std::get_if(&keyOrStatus)) return Error{*status}; @@ -118,7 +116,8 @@ LedgerEntryHandler::process(LedgerEntryHandler::Input input, Context const& ctx) .key; } else if (input.mptoken) { auto const holder = ripple::parseBase58(input.mptoken->at(JS(account)).as_string().c_str()); - auto const mptIssuanceID = ripple::uint192{std::string_view(input.mptoken->at(JS(mpt_issuance_id)).as_string().c_str())}; + auto const mptIssuanceID = + ripple::uint192{std::string_view(input.mptoken->at(JS(mpt_issuance_id)).as_string().c_str())}; key = ripple::keylet::mptoken(mptIssuanceID, *holder).key; } else { // Must specify 1 of the following fields to indicate what type @@ -235,8 +234,7 @@ tag_invoke(boost::json::value_to_tag, boost::json::va {JS(ticket), ripple::ltTICKET}, {JS(nft_page), ripple::ltNFTOKEN_PAGE}, {JS(amm), ripple::ltAMM}, - {JS(mptoken), ripple::ltMPTOKEN} - }; + {JS(mptoken), ripple::ltMPTOKEN}}; auto const indexFieldType = std::find_if(indexFieldTypeMap.begin(), indexFieldTypeMap.end(), [&jsonObject](auto const& pair) { diff --git a/src/rpc/handlers/LedgerEntry.h b/src/rpc/handlers/LedgerEntry.h index 642a3e86e..a8874f589 100644 --- a/src/rpc/handlers/LedgerEntry.h +++ b/src/rpc/handlers/LedgerEntry.h @@ -152,8 +152,7 @@ class LedgerEntryHandler { meta::IfType{meta::Section{ {JS(owner), validation::AccountBase58Validator}, {JS(dir_root), validation::Uint256HexStringValidator}, - {JS(sub_index), malformedRequestIntValidator} - }}}, + {JS(sub_index), malformedRequestIntValidator}}}}, {JS(escrow), validation::Type{}, meta::IfType{malformedRequestHexStringValidator}, @@ -199,28 +198,25 @@ class LedgerEntryHandler { {JS(asset), meta::WithCustomError{validation::Required{}, Status(ClioError::rpcMALFORMED_REQUEST)}, meta::WithCustomError{ - validation::Type{}, Status(ClioError::rpcMALFORMED_REQUEST) - }, + validation::Type{}, Status(ClioError::rpcMALFORMED_REQUEST)}, ammAssetValidator}, {JS(asset2), meta::WithCustomError{validation::Required{}, Status(ClioError::rpcMALFORMED_REQUEST)}, meta::WithCustomError{ - validation::Type{}, Status(ClioError::rpcMALFORMED_REQUEST) - }, + validation::Type{}, Status(ClioError::rpcMALFORMED_REQUEST)}, ammAssetValidator}, }, }}, {JS(mpt_issuance), validation::Uint192HexStringValidator}, - {JS(mptoken), - validation::Type{}, + {JS(mptoken), + validation::Type{}, meta::IfType{malformedRequestHexStringValidator}, meta::IfType{ meta::Section{ {JS(account), validation::Required{}, validation::AccountBase58Validator}, {JS(mpt_issuance_id), validation::Required{}, validation::Uint192HexStringValidator}, }, - }} - }; + }}}; return rpcSpec; } diff --git a/src/rpc/handlers/MPTHolders.cpp b/src/rpc/handlers/MPTHolders.cpp index 8649838ad..7f2654012 100644 --- a/src/rpc/handlers/MPTHolders.cpp +++ b/src/rpc/handlers/MPTHolders.cpp @@ -70,8 +70,7 @@ MPTHoldersHandler::process(MPTHoldersHandler::Input input, Context const& ctx) c if (input.marker) cursor = ripple::AccountID{input.marker->c_str()}; - auto const dbResponse = - sharedPtrBackend_->fetchMPTHolders(mptID, limit, cursor, lgrInfo.seq, ctx.yield); + auto const dbResponse = sharedPtrBackend_->fetchMPTHolders(mptID, limit, cursor, lgrInfo.seq, ctx.yield); auto output = MPTHoldersHandler::Output{}; @@ -89,9 +88,11 @@ MPTHoldersHandler::process(MPTHoldersHandler::Input input, Context const& ctx) c mptJson["mpt_amount"] = toBoostJson(ripple::STUInt64{sle[ripple::sfMPTAmount]}.getJson(JsonOptions::none)); if (!sle[~ripple::sfLockedAmount]) - mptJson["locked_amount"] = toBoostJson(ripple::STUInt64{sle[~ripple::sfLockedAmount].value_or(0)}.getJson(JsonOptions::none)); - - mptJson["mptoken_index"] = ripple::to_string(ripple::keylet::mptoken(mptID, sle.getAccountID(ripple::sfAccount)).key); + mptJson["locked_amount"] = + toBoostJson(ripple::STUInt64{sle[~ripple::sfLockedAmount].value_or(0)}.getJson(JsonOptions::none)); + + mptJson["mptoken_index"] = + ripple::to_string(ripple::keylet::mptoken(mptID, sle.getAccountID(ripple::sfAccount)).key); output.mpts.push_back(mptJson); } diff --git a/src/rpc/handlers/MPTHolders.h b/src/rpc/handlers/MPTHolders.h index 531df2bf3..85ad73c01 100644 --- a/src/rpc/handlers/MPTHolders.h +++ b/src/rpc/handlers/MPTHolders.h @@ -61,7 +61,7 @@ class MPTHoldersHandler { spec([[maybe_unused]] uint32_t apiVersion) { static auto const rpcSpec = RpcSpec{ - {JS(mpt_issuance_id), validation::Required{}, validation::Uint192HexStringValidator}, + {JS(mpt_issuance_id), validation::Required{}, validation::Uint192HexStringValidator}, {JS(ledger_hash), validation::Uint256HexStringValidator}, {JS(ledger_index), validation::LedgerIndexValidator}, {JS(limit), @@ -83,6 +83,5 @@ class MPTHoldersHandler { friend Input tag_invoke(boost::json::value_to_tag, boost::json::value const& jv); - }; } // namespace rpc diff --git a/src/rpc/handlers/NoRippleCheck.cpp b/src/rpc/handlers/NoRippleCheck.cpp index 07f4d7eef..3d840e7bd 100644 --- a/src/rpc/handlers/NoRippleCheck.cpp +++ b/src/rpc/handlers/NoRippleCheck.cpp @@ -145,7 +145,9 @@ NoRippleCheckHandler::process(NoRippleCheckHandler::Input input, Context const& ownedItem.getFieldAmount(bLow ? ripple::sfHighLimit : ripple::sfLowLimit); problem += fmt::format( - "{} line to {}", to_string(static_cast(peerLimit.getAsset())), to_string(peerLimit.getIssuer()) + "{} line to {}", + to_string(static_cast(peerLimit.getAsset())), + to_string(peerLimit.getIssuer()) ); output.problems.emplace_back(problem); diff --git a/unittests/rpc/handlers/AccountObjectsTests.cpp b/unittests/rpc/handlers/AccountObjectsTests.cpp index d0d1de3c9..4b05ba576 100644 --- a/unittests/rpc/handlers/AccountObjectsTests.cpp +++ b/unittests/rpc/handlers/AccountObjectsTests.cpp @@ -1672,7 +1672,10 @@ TEST_F(RPCAccountObjectsHandlerTest, TypeFilterMPTIssuanceType) EXPECT_EQ(accountObjects.front().at("LedgerEntryType").as_string(), "MPTokenIssuance"); // make sure mptID is synethetically parsed if object is mptIssuance - EXPECT_EQ(accountObjects.front().at("mpt_issuance_id").as_string(), ripple::to_string(ripple::getMptID(GetAccountIDWithString(ACCOUNT), 2))); + EXPECT_EQ( + accountObjects.front().at("mpt_issuance_id").as_string(), + ripple::to_string(ripple::getMptID(GetAccountIDWithString(ACCOUNT), 2)) + ); }); } @@ -1696,7 +1699,7 @@ TEST_F(RPCAccountObjectsHandlerTest, TypeFilterMPTokenType) std::vector bbs; // put 1 mpt issuance - auto const mptokenObject =CreateMPTokenObject(ACCOUNT, ripple::getMptID(GetAccountIDWithString(ACCOUNT), 2)); + auto const mptokenObject = CreateMPTokenObject(ACCOUNT, ripple::getMptID(GetAccountIDWithString(ACCOUNT), 2)); bbs.push_back(mptokenObject.getSerializer().peekData()); EXPECT_CALL(*backend, doFetchLedgerObjects).WillOnce(Return(bbs)); diff --git a/unittests/rpc/handlers/LedgerDataTests.cpp b/unittests/rpc/handlers/LedgerDataTests.cpp index fdae99f8a..eb0be1f22 100644 --- a/unittests/rpc/handlers/LedgerDataTests.cpp +++ b/unittests/rpc/handlers/LedgerDataTests.cpp @@ -738,7 +738,7 @@ TEST_F(RPCLedgerDataHandlerTest, TypeFilterMPTIssuance) EXPECT_CALL(*backend, doFetchSuccessorKey).Times(1); ON_CALL(*backend, doFetchSuccessorKey(_, RANGEMAX, _)).WillByDefault(Return(ripple::uint256{INDEX2})); - auto const issuance = CreateMPTIssuanceObject(ACCOUNT, 2, "metadata"); + auto const issuance = CreateMPTIssuanceObject(ACCOUNT, 2, "metadata"); bbs.push_back(issuance.getSerializer().peekData()); ON_CALL(*backend, doFetchLedgerObjects).WillByDefault(Return(bbs)); @@ -758,12 +758,15 @@ TEST_F(RPCLedgerDataHandlerTest, TypeFilterMPTIssuance) EXPECT_EQ(output->as_object().at("marker").as_string(), INDEX2); EXPECT_EQ(output->as_object().at("ledger_hash").as_string(), LEDGERHASH); EXPECT_EQ(output->as_object().at("ledger_index").as_uint64(), RANGEMAX); - + auto const& objects = output->as_object().at("state").as_array(); EXPECT_EQ(objects.front().at("LedgerEntryType").as_string(), "MPTokenIssuance"); // make sure mptID is synethetically parsed if object is mptIssuance - EXPECT_EQ(objects.front().at("mpt_issuance_id").as_string(), ripple::to_string(ripple::getMptID(GetAccountIDWithString(ACCOUNT), 2))); + EXPECT_EQ( + objects.front().at("mpt_issuance_id").as_string(), + ripple::to_string(ripple::getMptID(GetAccountIDWithString(ACCOUNT), 2)) + ); }); } @@ -795,10 +798,10 @@ TEST_F(RPCLedgerDataHandlerTest, TypeFilterMPToken) ASSERT_TRUE(output); EXPECT_TRUE(output->as_object().contains("ledger")); EXPECT_EQ(output->as_object().at("state").as_array().size(), 1); - EXPECT_EQ(output->as_object().at("marker").as_string(), INDEX2); + EXPECT_EQ(output->as_object().at("marker").as_string(), INDEX2); EXPECT_EQ(output->as_object().at("ledger_hash").as_string(), LEDGERHASH); EXPECT_EQ(output->as_object().at("ledger_index").as_uint64(), RANGEMAX); - + auto const& objects = output->as_object().at("state").as_array(); EXPECT_EQ(objects.front().at("LedgerEntryType").as_string(), "MPToken"); }); diff --git a/unittests/rpc/handlers/MPTHoldersTests.cpp b/unittests/rpc/handlers/MPTHoldersTests.cpp index 67c70658f..0f21bcaf2 100644 --- a/unittests/rpc/handlers/MPTHoldersTests.cpp +++ b/unittests/rpc/handlers/MPTHoldersTests.cpp @@ -43,13 +43,12 @@ using namespace rpc; namespace json = boost::json; using namespace testing; -//constexpr static auto ISSUER_ACCOUNT = "rsS8ju2jYabSKJ6uzLarAS1gEzvRQ6JAiF"; +// constexpr static auto ISSUER_ACCOUNT = "rsS8ju2jYabSKJ6uzLarAS1gEzvRQ6JAiF"; constexpr static auto HOLDER1_ACCOUNT = "rrnAZCqMahreZrKMcZU3t2DZ6yUndT4ubN"; constexpr static auto HOLDER2_ACCOUNT = "rEiNkzogdHEzUxPfsri5XSMqtXUixf2Yx"; constexpr static auto LEDGERHASH = "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652"; constexpr static auto MPTID = "000004C463C52827307480341125DA0577DEFC38405B0E3E"; - static std::string MPTOUT1 = R"({ "account": "rrnAZCqMahreZrKMcZU3t2DZ6yUndT4ubN", @@ -351,10 +350,7 @@ TEST_F(RPCMPTHoldersHandlerTest, DefaultParameters) std::vector const mpts = {mptoken.getSerializer().peekData()}; ON_CALL(*backend, fetchMPTHolders).WillByDefault(Return(MPTHoldersAndCursor{mpts, {}})); EXPECT_CALL( - *backend, - fetchMPTHolders( - ripple::uint192(MPTID), testing::_, testing::Eq(std::nullopt), Const(30), testing::_ - ) + *backend, fetchMPTHolders(ripple::uint192(MPTID), testing::_, testing::Eq(std::nullopt), Const(30), testing::_) ) .Times(1); @@ -393,7 +389,8 @@ TEST_F(RPCMPTHoldersHandlerTest, SpecificLedgerIndex) ON_CALL(*backend, fetchLedgerBySequence(specificLedger, _)).WillByDefault(Return(ledgerInfo)); EXPECT_CALL(*backend, fetchLedgerBySequence).Times(1); auto const issuanceKk = ripple::keylet::mptIssuance(ripple::uint192(MPTID)).key; - ON_CALL(*backend, doFetchLedgerObject(issuanceKk, specificLedger, _)).WillByDefault(Return(Blob{'f', 'a', 'k', 'e'})); + ON_CALL(*backend, doFetchLedgerObject(issuanceKk, specificLedger, _)) + .WillByDefault(Return(Blob{'f', 'a', 'k', 'e'})); auto const mptoken = CreateMPTokenObject(HOLDER1_ACCOUNT, ripple::uint192(MPTID)); std::vector const mpts = {mptoken.getSerializer().peekData()}; @@ -401,7 +398,7 @@ TEST_F(RPCMPTHoldersHandlerTest, SpecificLedgerIndex) EXPECT_CALL( *backend, fetchMPTHolders( - ripple::uint192(MPTID), testing::_, testing::Eq(std::nullopt), Const(specificLedger), testing::_ + ripple::uint192(MPTID), testing::_, testing::Eq(std::nullopt), Const(specificLedger), testing::_ ) ) .Times(1); @@ -449,14 +446,11 @@ TEST_F(RPCMPTHoldersHandlerTest, MarkerParameter) auto const marker = GetAccountIDWithString(HOLDER1_ACCOUNT); ON_CALL(*backend, fetchMPTHolders).WillByDefault(Return(MPTHoldersAndCursor{mpts, marker})); EXPECT_CALL( - *backend, - fetchMPTHolders( - ripple::uint192(MPTID), testing::_, testing::Eq(marker), Const(30), testing::_ - ) + *backend, fetchMPTHolders(ripple::uint192(MPTID), testing::_, testing::Eq(marker), Const(30), testing::_) ) .Times(1); - const auto HOLDER1_ACCOUNTID = ripple::strHex(GetAccountIDWithString(HOLDER1_ACCOUNT)); + auto const HOLDER1_ACCOUNTID = ripple::strHex(GetAccountIDWithString(HOLDER1_ACCOUNT)); auto const input = json::parse(fmt::format( R"({{ "mpt_issuance_id": "{}", @@ -473,7 +467,6 @@ TEST_F(RPCMPTHoldersHandlerTest, MarkerParameter) }); } - TEST_F(RPCMPTHoldersHandlerTest, MultipleMPTs) { auto const currentOutput = fmt::format( @@ -500,10 +493,7 @@ TEST_F(RPCMPTHoldersHandlerTest, MultipleMPTs) std::vector const mpts = {mptoken1.getSerializer().peekData(), mptoken2.getSerializer().peekData()}; ON_CALL(*backend, fetchMPTHolders).WillByDefault(Return(MPTHoldersAndCursor{mpts, {}})); EXPECT_CALL( - *backend, - fetchMPTHolders( - ripple::uint192(MPTID), testing::_, testing::Eq(std::nullopt), Const(30), testing::_ - ) + *backend, fetchMPTHolders(ripple::uint192(MPTID), testing::_, testing::Eq(std::nullopt), Const(30), testing::_) ) .Times(1); @@ -547,7 +537,11 @@ TEST_F(RPCMPTHoldersHandlerTest, LimitMoreThanMAx) EXPECT_CALL( *backend, fetchMPTHolders( - ripple::uint192(MPTID), Const(MPTHoldersHandler::LIMIT_MAX), testing::Eq(std::nullopt), Const(30), testing::_ + ripple::uint192(MPTID), + Const(MPTHoldersHandler::LIMIT_MAX), + testing::Eq(std::nullopt), + Const(30), + testing::_ ) ) .Times(1); diff --git a/unittests/util/MockBackend.h b/unittests/util/MockBackend.h index 51c9efb5d..c632f1645 100644 --- a/unittests/util/MockBackend.h +++ b/unittests/util/MockBackend.h @@ -179,12 +179,16 @@ struct MockBackend : public BackendInterface { MOCK_METHOD(bool, doFinishWrites, (), (override)); + MOCK_METHOD(void, writeMPTHolders, ((std::vector> &&)), (override)); - MOCK_METHOD(void, writeMPTHolders, ((std::vector>&&)), (override)); - - MOCK_METHOD(MPTHoldersAndCursor, fetchMPTHolders, (ripple::uint192 const& mptID, - std::uint32_t const , - (std::optional const&) , - std::uint32_t const , - boost::asio::yield_context), (const, override)); + MOCK_METHOD( + MPTHoldersAndCursor, + fetchMPTHolders, + (ripple::uint192 const& mptID, + std::uint32_t const, + (std::optional const&), + std::uint32_t const, + boost::asio::yield_context), + (const, override) + ); }; From 412fa2e47df89a842b3ef9f1b7e94a532180e206 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Wed, 31 Jan 2024 16:17:41 -0500 Subject: [PATCH 22/64] change order --- src/rpc/handlers/LedgerEntry.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rpc/handlers/LedgerEntry.cpp b/src/rpc/handlers/LedgerEntry.cpp index f6c5886f3..f8777acdf 100644 --- a/src/rpc/handlers/LedgerEntry.cpp +++ b/src/rpc/handlers/LedgerEntry.cpp @@ -63,9 +63,6 @@ LedgerEntryHandler::process(LedgerEntryHandler::Input input, Context const& ctx) key = ripple::keylet::account(*ripple::parseBase58(*(input.accountRoot))).key; } else if (input.did) { key = ripple::keylet::did(*ripple::parseBase58(*(input.did))).key; - } else if (input.mptIssuance) { - auto const mptIssuanceID = ripple::uint192{std::string_view(*(input.mptIssuance))}; - key = ripple::keylet::mptIssuance(mptIssuanceID).key; } else if (input.directory) { auto const keyOrStatus = composeKeyFromDirectory(*input.directory); if (auto const status = std::get_if(&keyOrStatus)) @@ -114,6 +111,9 @@ LedgerEntryHandler::process(LedgerEntryHandler::Input input, Context const& ctx) getIssuerFromJson(input.amm->at(JS(asset))), getIssuerFromJson(input.amm->at(JS(asset2))) ) .key; + } else if (input.mptIssuance) { + auto const mptIssuanceID = ripple::uint192{std::string_view(*(input.mptIssuance))}; + key = ripple::keylet::mptIssuance(mptIssuanceID).key; } else if (input.mptoken) { auto const holder = ripple::parseBase58(input.mptoken->at(JS(account)).as_string().c_str()); auto const mptIssuanceID = From 18a8a4a74029c05b3bd448a12289859bceb11209 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Wed, 31 Jan 2024 19:59:06 -0500 Subject: [PATCH 23/64] improve condition --- src/rpc/handlers/MPTHolders.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rpc/handlers/MPTHolders.cpp b/src/rpc/handlers/MPTHolders.cpp index 7f2654012..869e006ad 100644 --- a/src/rpc/handlers/MPTHolders.cpp +++ b/src/rpc/handlers/MPTHolders.cpp @@ -87,9 +87,9 @@ MPTHoldersHandler::process(MPTHoldersHandler::Input input, Context const& ctx) c mptJson[JS(flags)] = sle[ripple::sfFlags]; mptJson["mpt_amount"] = toBoostJson(ripple::STUInt64{sle[ripple::sfMPTAmount]}.getJson(JsonOptions::none)); - if (!sle[~ripple::sfLockedAmount]) + if (sle[ripple::sfLockedAmount]) mptJson["locked_amount"] = - toBoostJson(ripple::STUInt64{sle[~ripple::sfLockedAmount].value_or(0)}.getJson(JsonOptions::none)); + toBoostJson(ripple::STUInt64{sle[ripple::sfLockedAmount]}.getJson(JsonOptions::none)); mptJson["mptoken_index"] = ripple::to_string(ripple::keylet::mptoken(mptID, sle.getAccountID(ripple::sfAccount)).key); From 75eddfd416b4f9dd7ac8611a28714bf0bdd130e1 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Thu, 1 Feb 2024 10:45:15 -0500 Subject: [PATCH 24/64] tests --- src/rpc/handlers/MPTHolders.cpp | 3 +- unittests/rpc/handlers/MPTHoldersTests.cpp | 89 ++++++++++++++++++++++ unittests/util/TestObject.cpp | 9 ++- unittests/util/TestObject.h | 2 +- 4 files changed, 99 insertions(+), 4 deletions(-) diff --git a/src/rpc/handlers/MPTHolders.cpp b/src/rpc/handlers/MPTHolders.cpp index 869e006ad..421e11c6f 100644 --- a/src/rpc/handlers/MPTHolders.cpp +++ b/src/rpc/handlers/MPTHolders.cpp @@ -80,7 +80,7 @@ MPTHoldersHandler::process(MPTHoldersHandler::Input input, Context const& ctx) c boost::json::array mpts; for (auto const& mpt : dbResponse.mptokens) { - ripple::STLedgerEntry sle{ripple::SerialIter{mpt.data(), mpt.size()}, keylet::mptIssuance(mptID).key}; + ripple::STLedgerEntry const sle{ripple::SerialIter{mpt.data(), mpt.size()}, keylet::mptIssuance(mptID).key}; boost::json::object mptJson; mptJson[JS(account)] = toBase58(sle.getAccountID(ripple::sfAccount)); @@ -93,6 +93,7 @@ MPTHoldersHandler::process(MPTHoldersHandler::Input input, Context const& ctx) c mptJson["mptoken_index"] = ripple::to_string(ripple::keylet::mptoken(mptID, sle.getAccountID(ripple::sfAccount)).key); + output.mpts.push_back(mptJson); } diff --git a/unittests/rpc/handlers/MPTHoldersTests.cpp b/unittests/rpc/handlers/MPTHoldersTests.cpp index 0f21bcaf2..620f2eb2b 100644 --- a/unittests/rpc/handlers/MPTHoldersTests.cpp +++ b/unittests/rpc/handlers/MPTHoldersTests.cpp @@ -174,6 +174,46 @@ TEST_F(RPCMPTHoldersHandlerTest, MPTIDNotString) }); } +// error case: invalid marker format +TEST_F(RPCMPTHoldersHandlerTest, MarkerInvalidFormat) +{ + runSpawn([this](boost::asio::yield_context yield) { + auto const handler = AnyHandler{MPTHoldersHandler{backend}}; + auto const input = json::parse(fmt::format( + R"({{ + "mpt_issuance_id": "{}", + "marker": "xxx" + }})", + MPTID + )); + auto const output = handler.process(input, Context{std::ref(yield)}); + ASSERT_FALSE(output); + auto const err = rpc::makeError(output.error()); + EXPECT_EQ(err.at("error").as_string(), "invalidParams"); + EXPECT_EQ(err.at("error_message").as_string(), "markerMalformed"); + }); +} + +// error case: invalid marker type +TEST_F(RPCMPTHoldersHandlerTest, MarkerNotString) +{ + runSpawn([this](boost::asio::yield_context yield) { + auto const handler = AnyHandler{MPTHoldersHandler{backend}}; + auto const input = json::parse(fmt::format( + R"({{ + "mpt_issuance_id": "{}", + "marker": 1 + }})", + MPTID + )); + auto const output = handler.process(input, Context{std::ref(yield)}); + ASSERT_FALSE(output); + auto const err = rpc::makeError(output.error()); + EXPECT_EQ(err.at("error").as_string(), "invalidParams"); + EXPECT_EQ(err.at("error_message").as_string(), "markerNotString"); + }); +} + // error case ledger non exist via hash TEST_F(RPCMPTHoldersHandlerTest, NonExistLedgerViaLedgerHash) { @@ -368,6 +408,55 @@ TEST_F(RPCMPTHoldersHandlerTest, DefaultParameters) }); } +TEST_F(RPCMPTHoldersHandlerTest, CustomAmounts) +{ + // it's not possible to have locked_amount to be greater than mpt_amount, + // we are simply testing the response parsing of the api + auto const currentOutput = fmt::format( + R"({{ + "mpt_issuance_id": "{}", + "limit":50, + "ledger_index": 30, + "mptokens": [{{ + "account": "rrnAZCqMahreZrKMcZU3t2DZ6yUndT4ubN", + "flags": 0, + "mpt_amount": "0", + "locked_amount": "1", + "mptoken_index": "D137F2E5A5767A06CB7A8F060ADE442A30CFF95028E1AF4B8767E3A56877205A" + }}], + "validated": true + }})", + MPTID + ); + + backend->setRange(10, 30); + auto ledgerInfo = CreateLedgerInfo(LEDGERHASH, 30); + EXPECT_CALL(*backend, fetchLedgerBySequence).WillOnce(Return(ledgerInfo)); + auto const issuanceKk = ripple::keylet::mptIssuance(ripple::uint192(MPTID)).key; + ON_CALL(*backend, doFetchLedgerObject(issuanceKk, 30, _)).WillByDefault(Return(Blob{'f', 'a', 'k', 'e'})); + + auto const mptoken = CreateMPTokenObject(HOLDER1_ACCOUNT, ripple::uint192(MPTID), 0, 1); + std::vector const mpts = {mptoken.getSerializer().peekData()}; + ON_CALL(*backend, fetchMPTHolders).WillByDefault(Return(MPTHoldersAndCursor{mpts, {}})); + EXPECT_CALL( + *backend, fetchMPTHolders(ripple::uint192(MPTID), testing::_, testing::Eq(std::nullopt), Const(30), testing::_) + ) + .Times(1); + + auto const input = json::parse(fmt::format( + R"({{ + "mpt_issuance_id": "{}" + }})", + MPTID + )); + runSpawn([&, this](auto& yield) { + auto handler = AnyHandler{MPTHoldersHandler{this->backend}}; + auto const output = handler.process(input, Context{yield}); + ASSERT_TRUE(output); + EXPECT_EQ(json::parse(currentOutput), *output); + }); +} + TEST_F(RPCMPTHoldersHandlerTest, SpecificLedgerIndex) { auto const specificLedger = 20; diff --git a/unittests/util/TestObject.cpp b/unittests/util/TestObject.cpp index 613e2c7b5..fb9131daf 100644 --- a/unittests/util/TestObject.cpp +++ b/unittests/util/TestObject.cpp @@ -839,17 +839,22 @@ CreateMPTIssuanceObject(std::string_view accountId, std::uint32_t seq, std::stri } ripple::STObject -CreateMPTokenObject(std::string_view accountId, ripple::uint192 issuanceID) +CreateMPTokenObject(std::string_view accountId, ripple::uint192 issuanceID, std::uint64_t mptAmount, std::uint64_t lockedAmount) { ripple::STObject mptoken(ripple::sfLedgerEntry); mptoken.setAccountID(ripple::sfAccount, GetAccountIDWithString(accountId)); mptoken[ripple::sfMPTokenIssuanceID] = issuanceID; mptoken.setFieldU16(ripple::sfLedgerEntryType, ripple::ltMPTOKEN); mptoken.setFieldU32(ripple::sfFlags, 0); - mptoken.setFieldU64(ripple::sfMPTAmount, 1); mptoken.setFieldU64(ripple::sfOwnerNode, 0); mptoken.setFieldH256(ripple::sfPreviousTxnID, ripple::uint256{}); mptoken.setFieldU32(ripple::sfPreviousTxnLgrSeq, 0); + if (mptAmount) + mptoken.setFieldU64(ripple::sfMPTAmount, mptAmount); + + if (lockedAmount) + mptoken.setFieldU64(ripple::sfLockedAmount, lockedAmount); + return mptoken; } diff --git a/unittests/util/TestObject.h b/unittests/util/TestObject.h index 8a02b45e0..bd3a6f62d 100644 --- a/unittests/util/TestObject.h +++ b/unittests/util/TestObject.h @@ -294,4 +294,4 @@ CreateDidObject(std::string_view accountId, std::string_view didDoc, std::string CreateMPTIssuanceObject(std::string_view accountId, std::uint32_t seq, std::string_view metadata); [[nodiscard]] ripple::STObject -CreateMPTokenObject(std::string_view accountId, ripple::uint192 issuanceID); \ No newline at end of file +CreateMPTokenObject(std::string_view accountId, ripple::uint192 issuanceID, std::uint64_t mptAmount = 1, std::uint64_t lockedAmount = 0); \ No newline at end of file From 96288d01f96a9f5aec3e26ce055a86a7d075a122 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Thu, 1 Feb 2024 11:44:04 -0500 Subject: [PATCH 25/64] add deletion blocker --- src/util/LedgerUtils.cpp | 4 ++-- unittests/util/LedgerUtilsTests.cpp | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/util/LedgerUtils.cpp b/src/util/LedgerUtils.cpp index 8688aff27..b8d2ddf26 100644 --- a/src/util/LedgerUtils.cpp +++ b/src/util/LedgerUtils.cpp @@ -67,8 +67,8 @@ static std::unordered_map const LEDGER_TYPES_ {JS(xchain_owned_create_account_claim_id), LedgerTypeAttributes(ripple::ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID, true)}, {JS(did), LedgerTypeAttributes(ripple::ltDID)}, - {JS(mpt_issuance), LedgerTypeAttributes(ripple::ltMPTOKEN_ISSUANCE)}, - {JS(mptoken), LedgerTypeAttributes(ripple::ltMPTOKEN)}} + {JS(mpt_issuance), LedgerTypeAttributes(ripple::ltMPTOKEN_ISSUANCE, true)}, + {JS(mptoken), LedgerTypeAttributes(ripple::ltMPTOKEN, true)}} }; } // namespace detail diff --git a/unittests/util/LedgerUtilsTests.cpp b/unittests/util/LedgerUtilsTests.cpp index 88e834892..9be0c7454 100644 --- a/unittests/util/LedgerUtilsTests.cpp +++ b/unittests/util/LedgerUtilsTests.cpp @@ -84,7 +84,9 @@ TEST(LedgerUtilsTests, DeletionBlockerTypes) ripple::ltRIPPLE_STATE, ripple::ltXCHAIN_OWNED_CLAIM_ID, ripple::ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID, - ripple::ltBRIDGE + ripple::ltBRIDGE, + ripple::ltMPTOKEN_ISSUANCE, + ripple::ltMPTOKEN }; ASSERT_TRUE(std::size(deletionBlockers) == testedTypes.size()); From b6691bd6a874ac668f98fc5ceb7e5a7bae803ad0 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Mon, 5 Feb 2024 10:56:40 -0500 Subject: [PATCH 26/64] refactor validators and add tests --- src/rpc/common/Validators.cpp | 27 +++----------------------- src/rpc/common/Validators.h | 12 ++++++++++++ unittests/rpc/BaseTests.cpp | 36 +++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 24 deletions(-) diff --git a/src/rpc/common/Validators.cpp b/src/rpc/common/Validators.cpp index 7d9a4dbde..a77908ad1 100644 --- a/src/rpc/common/Validators.cpp +++ b/src/rpc/common/Validators.cpp @@ -72,38 +72,17 @@ checkIsU32Numeric(std::string_view sv) // TODO: Should refactor hex string validators due to duplicate code CustomValidator Uint160HexStringValidator = CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { - if (!value.is_string()) - return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "NotString"}}; - - ripple::uint160 parsedInt; - if (!parsedInt.parseHex(value.as_string().c_str())) - return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "Malformed"}}; - - return MaybeError{}; + return makeHexStringValidator(value, key); }}; CustomValidator Uint192HexStringValidator = CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { - if (!value.is_string()) - return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "NotString"}}; - - ripple::uint192 parsedInt; - if (!parsedInt.parseHex(value.as_string().c_str())) - return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "Malformed"}}; - - return MaybeError{}; + return makeHexStringValidator(value, key); }}; CustomValidator Uint256HexStringValidator = CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { - if (!value.is_string()) - return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "NotString"}}; - - ripple::uint256 ledgerHash; - if (!ledgerHash.parseHex(value.as_string().c_str())) - return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "Malformed"}}; - - return MaybeError{}; + return makeHexStringValidator(value, key); }}; CustomValidator LedgerIndexValidator = diff --git a/src/rpc/common/Validators.h b/src/rpc/common/Validators.h index ecca9f47a..b3ec1f2d9 100644 --- a/src/rpc/common/Validators.h +++ b/src/rpc/common/Validators.h @@ -457,6 +457,18 @@ class CustomValidator final { [[nodiscard]] bool checkIsU32Numeric(std::string_view sv); +template +MaybeError makeHexStringValidator(boost::json::value const& value, std::string_view key) { + if (!value.is_string()) + return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "NotString"}}; + + HexType parsedInt; + if (!parsedInt.parseHex(value.as_string().c_str())) + return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "Malformed"}}; + + return MaybeError{}; +} + /** * @brief Provides a commonly used validator for ledger index. * diff --git a/unittests/rpc/BaseTests.cpp b/unittests/rpc/BaseTests.cpp index 782baa041..18eccf5af 100644 --- a/unittests/rpc/BaseTests.cpp +++ b/unittests/rpc/BaseTests.cpp @@ -450,6 +450,42 @@ TEST_F(RPCBaseTest, AccountMarkerValidator) ASSERT_TRUE(spec.process(passingInput)); } +TEST_F(RPCBaseTest, Uint160HexStringValidator) +{ + auto const spec = RpcSpec{{"marker", Uint160HexStringValidator}}; + auto passingInput = + json::parse(R"({ "marker": "F609A18102218C75767209946A77523CBD97E225"})"); + ASSERT_TRUE(spec.process(passingInput)); + + auto failingInput = json::parse(R"({ "marker": 160})"); + auto err = spec.process(failingInput); + ASSERT_FALSE(err); + ASSERT_EQ(err.error().message, "markerNotString"); + + failingInput = json::parse(R"({ "marker": "F609A18102218C75767209946A77523CBD97E2253515BC"})"); + err = spec.process(failingInput); + ASSERT_FALSE(err); + ASSERT_EQ(err.error().message, "markerMalformed"); +} + +TEST_F(RPCBaseTest, Uint192HexStringValidator) +{ + auto const spec = RpcSpec{{"mpt_issuance_id", Uint192HexStringValidator}}; + auto passingInput = + json::parse(R"({ "mpt_issuance_id": "0000012F27A9DE73EAA1E8831FA253E19030A17E2D038198"})"); + ASSERT_TRUE(spec.process(passingInput)); + + auto failingInput = json::parse(R"({ "mpt_issuance_id": 192})"); + auto err = spec.process(failingInput); + ASSERT_FALSE(err); + ASSERT_EQ(err.error().message, "mpt_issuance_idNotString"); + + failingInput = json::parse(R"({ "mpt_issuance_id": "0000012F27A9DE73EAA1E8831FA253E19030A17E2D038198983515BC"})"); + err = spec.process(failingInput); + ASSERT_FALSE(err); + ASSERT_EQ(err.error().message, "mpt_issuance_idMalformed"); +} + TEST_F(RPCBaseTest, Uint256HexStringValidator) { auto const spec = RpcSpec{{"transaction", Uint256HexStringValidator}}; From 62b01fe4a5954e7e1e056f7294e9818747d0ec3a Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Mon, 5 Feb 2024 10:58:17 -0500 Subject: [PATCH 27/64] remove comment --- src/rpc/common/Validators.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/rpc/common/Validators.cpp b/src/rpc/common/Validators.cpp index a77908ad1..c6696ec70 100644 --- a/src/rpc/common/Validators.cpp +++ b/src/rpc/common/Validators.cpp @@ -69,7 +69,6 @@ checkIsU32Numeric(std::string_view sv) return ec == std::errc(); } -// TODO: Should refactor hex string validators due to duplicate code CustomValidator Uint160HexStringValidator = CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { return makeHexStringValidator(value, key); From f3d7f0df491f66788e261792f5ed6ed6daa3b718 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Mon, 5 Feb 2024 13:09:26 -0500 Subject: [PATCH 28/64] template parameter type checking --- src/rpc/common/Validators.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/rpc/common/Validators.hpp b/src/rpc/common/Validators.hpp index 16cd8d5fb..7492e4564 100644 --- a/src/rpc/common/Validators.hpp +++ b/src/rpc/common/Validators.hpp @@ -458,6 +458,9 @@ class CustomValidator final { checkIsU32Numeric(std::string_view sv); template +requires (std::is_same_v || + std::is_same_v || + std::is_same_v ) MaybeError makeHexStringValidator(boost::json::value const& value, std::string_view key) { if (!value.is_string()) return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "NotString"}}; From 203fa8d28ce397d7b0f6917e4a566df829677cba Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Mon, 5 Feb 2024 16:32:24 -0500 Subject: [PATCH 29/64] clang --- src/rpc/common/Validators.hpp | 8 ++++---- src/rpc/handlers/LedgerEntry.hpp | 3 ++- unittests/rpc/BaseTests.cpp | 6 ++---- unittests/rpc/handlers/LedgerEntryTests.cpp | 2 +- unittests/rpc/handlers/MPTHoldersTests.cpp | 16 ++++++++-------- unittests/util/MockBackend.hpp | 2 +- unittests/util/TestObject.cpp | 11 ++++++++--- unittests/util/TestObject.hpp | 7 ++++++- 8 files changed, 32 insertions(+), 23 deletions(-) diff --git a/src/rpc/common/Validators.hpp b/src/rpc/common/Validators.hpp index 7492e4564..caed442ff 100644 --- a/src/rpc/common/Validators.hpp +++ b/src/rpc/common/Validators.hpp @@ -458,10 +458,10 @@ class CustomValidator final { checkIsU32Numeric(std::string_view sv); template -requires (std::is_same_v || - std::is_same_v || - std::is_same_v ) -MaybeError makeHexStringValidator(boost::json::value const& value, std::string_view key) { + requires(std::is_same_v || std::is_same_v || std::is_same_v) +MaybeError +makeHexStringValidator(boost::json::value const& value, std::string_view key) +{ if (!value.is_string()) return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "NotString"}}; diff --git a/src/rpc/handlers/LedgerEntry.hpp b/src/rpc/handlers/LedgerEntry.hpp index e2b7e5135..90d1ffd5d 100644 --- a/src/rpc/handlers/LedgerEntry.hpp +++ b/src/rpc/handlers/LedgerEntry.hpp @@ -174,7 +174,8 @@ class LedgerEntryHandler { meta::IfType{meta::Section{ {JS(owner), validation::AccountBase58Validator}, {JS(dir_root), validation::Uint256HexStringValidator}, - {JS(sub_index), malformedRequestIntValidator}}}}, + {JS(sub_index), malformedRequestIntValidator} + }}}, {JS(escrow), validation::Type{}, meta::IfType{malformedRequestHexStringValidator}, diff --git a/unittests/rpc/BaseTests.cpp b/unittests/rpc/BaseTests.cpp index 18d292f6f..6b0520eda 100644 --- a/unittests/rpc/BaseTests.cpp +++ b/unittests/rpc/BaseTests.cpp @@ -453,8 +453,7 @@ TEST_F(RPCBaseTest, AccountMarkerValidator) TEST_F(RPCBaseTest, Uint160HexStringValidator) { auto const spec = RpcSpec{{"marker", Uint160HexStringValidator}}; - auto passingInput = - json::parse(R"({ "marker": "F609A18102218C75767209946A77523CBD97E225"})"); + auto passingInput = json::parse(R"({ "marker": "F609A18102218C75767209946A77523CBD97E225"})"); ASSERT_TRUE(spec.process(passingInput)); auto failingInput = json::parse(R"({ "marker": 160})"); @@ -471,8 +470,7 @@ TEST_F(RPCBaseTest, Uint160HexStringValidator) TEST_F(RPCBaseTest, Uint192HexStringValidator) { auto const spec = RpcSpec{{"mpt_issuance_id", Uint192HexStringValidator}}; - auto passingInput = - json::parse(R"({ "mpt_issuance_id": "0000012F27A9DE73EAA1E8831FA253E19030A17E2D038198"})"); + auto passingInput = json::parse(R"({ "mpt_issuance_id": "0000012F27A9DE73EAA1E8831FA253E19030A17E2D038198"})"); ASSERT_TRUE(spec.process(passingInput)); auto failingInput = json::parse(R"({ "mpt_issuance_id": 192})"); diff --git a/unittests/rpc/handlers/LedgerEntryTests.cpp b/unittests/rpc/handlers/LedgerEntryTests.cpp index 14f51abe8..02f69e7cc 100644 --- a/unittests/rpc/handlers/LedgerEntryTests.cpp +++ b/unittests/rpc/handlers/LedgerEntryTests.cpp @@ -2157,7 +2157,7 @@ generateTestValuesForNormalPathTest() ripple::to_string(ripple::getMptID(account1, 2)) ), ripple::keylet::mptIssuance(ripple::getMptID(account1, 2)).key, - CreateMPTIssuanceObject(ACCOUNT, 2, "metadata") + CreateMPTIssuanceObject(ACCOUNT, 2, "metadata") }, NormalPathTestBundle{ "MPTokenViaIndex", diff --git a/unittests/rpc/handlers/MPTHoldersTests.cpp b/unittests/rpc/handlers/MPTHoldersTests.cpp index d08254d76..e16a62519 100644 --- a/unittests/rpc/handlers/MPTHoldersTests.cpp +++ b/unittests/rpc/handlers/MPTHoldersTests.cpp @@ -179,13 +179,13 @@ TEST_F(RPCMPTHoldersHandlerTest, MarkerInvalidFormat) { runSpawn([this](boost::asio::yield_context yield) { auto const handler = AnyHandler{MPTHoldersHandler{backend}}; - auto const input = json::parse(fmt::format( - R"({{ + auto const input = json::parse(fmt::format( + R"({{ "mpt_issuance_id": "{}", "marker": "xxx" }})", - MPTID - )); + MPTID + )); auto const output = handler.process(input, Context{std::ref(yield)}); ASSERT_FALSE(output); auto const err = rpc::makeError(output.error()); @@ -199,13 +199,13 @@ TEST_F(RPCMPTHoldersHandlerTest, MarkerNotString) { runSpawn([this](boost::asio::yield_context yield) { auto const handler = AnyHandler{MPTHoldersHandler{backend}}; - auto const input = json::parse(fmt::format( - R"({{ + auto const input = json::parse(fmt::format( + R"({{ "mpt_issuance_id": "{}", "marker": 1 }})", - MPTID - )); + MPTID + )); auto const output = handler.process(input, Context{std::ref(yield)}); ASSERT_FALSE(output); auto const err = rpc::makeError(output.error()); diff --git a/unittests/util/MockBackend.hpp b/unittests/util/MockBackend.hpp index bf08b40fe..54cc74888 100644 --- a/unittests/util/MockBackend.hpp +++ b/unittests/util/MockBackend.hpp @@ -192,7 +192,7 @@ struct MockBackend : public BackendInterface { MOCK_METHOD(bool, doFinishWrites, (), (override)); - MOCK_METHOD(void, writeMPTHolders, ((std::vector> &&)), (override)); + MOCK_METHOD(void, writeMPTHolders, ((std::vector>&&)), (override)); MOCK_METHOD( MPTHoldersAndCursor, diff --git a/unittests/util/TestObject.cpp b/unittests/util/TestObject.cpp index 517315ff3..72376ef13 100644 --- a/unittests/util/TestObject.cpp +++ b/unittests/util/TestObject.cpp @@ -1022,7 +1022,12 @@ CreateMPTIssuanceObject(std::string_view accountId, std::uint32_t seq, std::stri } ripple::STObject -CreateMPTokenObject(std::string_view accountId, ripple::uint192 issuanceID, std::uint64_t mptAmount, std::uint64_t lockedAmount) +CreateMPTokenObject( + std::string_view accountId, + ripple::uint192 issuanceID, + std::uint64_t mptAmount, + std::uint64_t lockedAmount +) { ripple::STObject mptoken(ripple::sfLedgerEntry); mptoken.setAccountID(ripple::sfAccount, GetAccountIDWithString(accountId)); @@ -1034,10 +1039,10 @@ CreateMPTokenObject(std::string_view accountId, ripple::uint192 issuanceID, std: mptoken.setFieldU32(ripple::sfPreviousTxnLgrSeq, 0); if (mptAmount) - mptoken.setFieldU64(ripple::sfMPTAmount, mptAmount); + mptoken.setFieldU64(ripple::sfMPTAmount, mptAmount); if (lockedAmount) - mptoken.setFieldU64(ripple::sfLockedAmount, lockedAmount); + mptoken.setFieldU64(ripple::sfLockedAmount, lockedAmount); return mptoken; } \ No newline at end of file diff --git a/unittests/util/TestObject.hpp b/unittests/util/TestObject.hpp index a149063cd..7d830d617 100644 --- a/unittests/util/TestObject.hpp +++ b/unittests/util/TestObject.hpp @@ -365,4 +365,9 @@ CreateLPTCurrency(std::string_view assetCurrency, std::string_view asset2Currenc CreateMPTIssuanceObject(std::string_view accountId, std::uint32_t seq, std::string_view metadata); [[nodiscard]] ripple::STObject -CreateMPTokenObject(std::string_view accountId, ripple::uint192 issuanceID, std::uint64_t mptAmount = 1, std::uint64_t lockedAmount = 0); +CreateMPTokenObject( + std::string_view accountId, + ripple::uint192 issuanceID, + std::uint64_t mptAmount = 1, + std::uint64_t lockedAmount = 0 +); From b36886447be61cdafbe9bb38d05e45eef5e98ca9 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Mon, 5 Feb 2024 16:33:11 -0500 Subject: [PATCH 30/64] clang --- src/etl/MPTHelpers.cpp | 7 ++++--- src/etl/MPTHelpers.hpp | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/etl/MPTHelpers.cpp b/src/etl/MPTHelpers.cpp index dae4d99a5..366eae782 100644 --- a/src/etl/MPTHelpers.cpp +++ b/src/etl/MPTHelpers.cpp @@ -17,9 +17,10 @@ */ //============================================================================== -#include -#include -#include +#include "data/BackendInterface.hpp" +#include "data/DBHelpers.hpp" +#include "data/Types.hpp" + #include #include #include diff --git a/src/etl/MPTHelpers.hpp b/src/etl/MPTHelpers.hpp index 5ff051842..5652c4f57 100644 --- a/src/etl/MPTHelpers.hpp +++ b/src/etl/MPTHelpers.hpp @@ -20,7 +20,8 @@ /** @file */ #pragma once -#include +#include "data/DBHelpers.hpp" + #include #include From 494f56f1c46f80a83b8deed151ab3b1bb7b138cc Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Thu, 8 Feb 2024 14:35:39 -0500 Subject: [PATCH 31/64] doxygen and end of file line --- src/data/Types.hpp | 6 ++++++ src/etl/MPTHelpers.cpp | 2 +- src/etl/MPTHelpers.hpp | 2 +- unittests/rpc/handlers/MPTHoldersTests.cpp | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/data/Types.hpp b/src/data/Types.hpp index ca20ec383..076a4a353 100644 --- a/src/data/Types.hpp +++ b/src/data/Types.hpp @@ -164,11 +164,17 @@ struct NFT { } }; +/** + * @brief Represents an array of NFTs + */ struct NFTsAndCursor { std::vector nfts; std::optional cursor; }; +/** + * @brief Represents an array of MPTokens + */ struct MPTHoldersAndCursor { std::vector mptokens; std::optional cursor; diff --git a/src/etl/MPTHelpers.cpp b/src/etl/MPTHelpers.cpp index 366eae782..0efc1cd88 100644 --- a/src/etl/MPTHelpers.cpp +++ b/src/etl/MPTHelpers.cpp @@ -69,4 +69,4 @@ getMPTHolderFromObj(std::string const& key, std::string const& blob) return std::make_pair(mptIssuanceID, holder); } -} // namespace etl \ No newline at end of file +} // namespace etl diff --git a/src/etl/MPTHelpers.hpp b/src/etl/MPTHelpers.hpp index 5652c4f57..13da51772 100644 --- a/src/etl/MPTHelpers.hpp +++ b/src/etl/MPTHelpers.hpp @@ -47,4 +47,4 @@ getMPTHolderFromTx(ripple::TxMeta const& txMeta, ripple::STTx const& sttx); std::optional> getMPTHolderFromObj(std::string const& key, std::string const& blob); -} // namespace etl \ No newline at end of file +} // namespace etl diff --git a/unittests/rpc/handlers/MPTHoldersTests.cpp b/unittests/rpc/handlers/MPTHoldersTests.cpp index e16a62519..a583bc0da 100644 --- a/unittests/rpc/handlers/MPTHoldersTests.cpp +++ b/unittests/rpc/handlers/MPTHoldersTests.cpp @@ -649,4 +649,4 @@ TEST_F(RPCMPTHoldersHandlerTest, LimitMoreThanMAx) ASSERT_TRUE(output); EXPECT_EQ(json::parse(currentOutput), *output); }); -} \ No newline at end of file +} From 9a378b0a4a5d7a8cddd06ed1de5bac3c90bec8da Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Thu, 29 Feb 2024 13:25:54 -0500 Subject: [PATCH 32/64] revert issue changes --- src/feed/impl/TransactionFeed.cpp | 2 +- src/rpc/AMMHelpers.cpp | 8 ++++---- src/rpc/RPCHelpers.cpp | 16 ++++++++-------- src/rpc/handlers/AMMInfo.cpp | 4 ++-- src/rpc/handlers/AccountCurrencies.cpp | 4 ++-- src/rpc/handlers/AccountLines.cpp | 2 +- src/rpc/handlers/AccountOffers.cpp | 2 +- src/rpc/handlers/GatewayBalances.cpp | 4 ++-- src/rpc/handlers/Ledger.cpp | 2 +- src/rpc/handlers/NoRippleCheck.cpp | 4 +--- unittests/rpc/RPCHelpersTests.cpp | 4 ++-- 11 files changed, 25 insertions(+), 27 deletions(-) diff --git a/src/feed/impl/TransactionFeed.cpp b/src/feed/impl/TransactionFeed.cpp index 243f3fc03..2877817a0 100644 --- a/src/feed/impl/TransactionFeed.cpp +++ b/src/feed/impl/TransactionFeed.cpp @@ -161,7 +161,7 @@ TransactionFeed::pub( if (tx->getTxnType() == ripple::ttOFFER_CREATE) { auto const account = tx->getAccountID(ripple::sfAccount); auto const amount = tx->getFieldAmount(ripple::sfTakerGets); - if (account != amount.issue().account()) { + if (account != amount.issue().account) { auto fetchFundsSynchronous = [&]() { data::synchronous([&](boost::asio::yield_context yield) { ownerFunds = rpc::accountFunds(*backend, lgrInfo.seq, amount, account, yield); diff --git a/src/rpc/AMMHelpers.cpp b/src/rpc/AMMHelpers.cpp index 2b5269525..a7634c2d7 100644 --- a/src/rpc/AMMHelpers.cpp +++ b/src/rpc/AMMHelpers.cpp @@ -47,9 +47,9 @@ getAmmPoolHolds( ) { auto const assetInBalance = - accountHolds(backend, sequence, ammAccountID, issue1.asset(), issue1.account(), freezeHandling, yield); + accountHolds(backend, sequence, ammAccountID, issue1.currency, issue1.account, freezeHandling, yield); auto const assetOutBalance = - accountHolds(backend, sequence, ammAccountID, issue2.asset(), issue2.account(), freezeHandling, yield); + accountHolds(backend, sequence, ammAccountID, issue2.currency, issue2.account, freezeHandling, yield); return std::make_pair(assetInBalance, assetOutBalance); } @@ -80,8 +80,8 @@ getAmmLpHolds( return getAmmLpHolds( backend, sequence, - ammSle[ripple::sfAsset].asset(), - ammSle[ripple::sfAsset2].asset(), + ammSle[ripple::sfAsset].currency, + ammSle[ripple::sfAsset2].currency, ammSle[ripple::sfAccount], lpAccount, yield diff --git a/src/rpc/RPCHelpers.cpp b/src/rpc/RPCHelpers.cpp index 13b2a2cb6..a10da0f68 100644 --- a/src/rpc/RPCHelpers.cpp +++ b/src/rpc/RPCHelpers.cpp @@ -1077,7 +1077,7 @@ accountFunds( return amount; } - return accountHolds(backend, sequence, id, amount.getAsset(), amount.getIssuer(), true, yield); + return accountHolds(backend, sequence, id, amount.getCurrency(), amount.getIssuer(), true, yield); } ripple::STAmount @@ -1156,10 +1156,10 @@ postProcessOrderBook( std::map umBalance; - bool const globalFreeze = isGlobalFrozen(backend, ledgerSequence, book.out.account(), yield) || - isGlobalFrozen(backend, ledgerSequence, book.in.account(), yield); + bool const globalFreeze = isGlobalFrozen(backend, ledgerSequence, book.out.account, yield) || + isGlobalFrozen(backend, ledgerSequence, book.in.account, yield); - auto rate = transferRate(backend, ledgerSequence, book.out.account(), yield); + auto rate = transferRate(backend, ledgerSequence, book.out.account, yield); for (auto const& obj : offers) { try { @@ -1173,7 +1173,7 @@ postProcessOrderBook( ripple::STAmount saOwnerFunds; bool firstOwnerOffer = true; - if (book.out.account() == uOfferOwnerID) { + if (book.out.account == uOfferOwnerID) { // If an offer is selling issuer's own IOUs, it is fully // funded. saOwnerFunds = saTakerGets; @@ -1190,7 +1190,7 @@ postProcessOrderBook( firstOwnerOffer = false; } else { saOwnerFunds = accountHolds( - backend, ledgerSequence, uOfferOwnerID, book.out.asset(), book.out.account(), true, yield + backend, ledgerSequence, uOfferOwnerID, book.out.currency, book.out.account, true, yield ); if (saOwnerFunds < beast::zero) @@ -1207,9 +1207,9 @@ postProcessOrderBook( if (rate != ripple::parityRate // Have a tranfer fee. - && takerID != book.out.account() + && takerID != book.out.account // Not taking offers of own IOUs. - && book.out.account() != uOfferOwnerID) + && book.out.account != uOfferOwnerID) // Offer owner not issuing ownfunds { // Need to charge a transfer fee to offer owner. diff --git a/src/rpc/handlers/AMMInfo.cpp b/src/rpc/handlers/AMMInfo.cpp index daf893303..15a5a4b98 100644 --- a/src/rpc/handlers/AMMInfo.cpp +++ b/src/rpc/handlers/AMMInfo.cpp @@ -192,12 +192,12 @@ AMMInfoHandler::process(AMMInfoHandler::Input input, Context const& ctx) const if (!isXRP(asset1Balance)) { response.asset1Frozen = isFrozen( - *sharedPtrBackend_, lgrInfo.seq, ammAccountID, amm[sfAsset].asset(), amm[sfAsset].account(), ctx.yield + *sharedPtrBackend_, lgrInfo.seq, ammAccountID, amm[sfAsset].currency, amm[sfAsset].account, ctx.yield ); } if (!isXRP(asset2Balance)) { response.asset2Frozen = isFrozen( - *sharedPtrBackend_, lgrInfo.seq, ammAccountID, amm[sfAsset2].asset(), amm[sfAsset2].account(), ctx.yield + *sharedPtrBackend_, lgrInfo.seq, ammAccountID, amm[sfAsset2].currency, amm[sfAsset2].account, ctx.yield ); } diff --git a/src/rpc/handlers/AccountCurrencies.cpp b/src/rpc/handlers/AccountCurrencies.cpp index 3b1b64015..7c4dc1721 100644 --- a/src/rpc/handlers/AccountCurrencies.cpp +++ b/src/rpc/handlers/AccountCurrencies.cpp @@ -75,10 +75,10 @@ AccountCurrenciesHandler::process(AccountCurrenciesHandler::Input input, Context balance.negate(); if (balance < lineLimit) - response.receiveCurrencies.insert(ripple::to_string(balance.getAsset())); + response.receiveCurrencies.insert(ripple::to_string(balance.getCurrency())); if ((-balance) < lineLimitPeer) - response.sendCurrencies.insert(ripple::to_string(balance.getAsset())); + response.sendCurrencies.insert(ripple::to_string(balance.getCurrency())); } return true; diff --git a/src/rpc/handlers/AccountLines.cpp b/src/rpc/handlers/AccountLines.cpp index a2f1e01cb..c54a1e0d4 100644 --- a/src/rpc/handlers/AccountLines.cpp +++ b/src/rpc/handlers/AccountLines.cpp @@ -93,7 +93,7 @@ AccountLinesHandler::addLine( LineResponse line; line.account = ripple::to_string(lineAccountIDPeer); line.balance = saBalance.getText(); - line.currency = ripple::to_string(saBalance.issue().asset()); + line.currency = ripple::to_string(saBalance.issue().currency); line.limit = saLimit.getText(); line.limitPeer = saLimitPeer.getText(); line.qualityIn = lineQualityIn; diff --git a/src/rpc/handlers/AccountOffers.cpp b/src/rpc/handlers/AccountOffers.cpp index 0db65d231..21ae03aff 100644 --- a/src/rpc/handlers/AccountOffers.cpp +++ b/src/rpc/handlers/AccountOffers.cpp @@ -144,7 +144,7 @@ tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountOffersHan jsonObject[field] = amount.getText(); } else { jsonObject[field] = { - {JS(currency), ripple::to_string(amount.getAsset())}, + {JS(currency), ripple::to_string(amount.getCurrency())}, {JS(issuer), ripple::to_string(amount.getIssuer())}, {JS(value), amount.getText()}, }; diff --git a/src/rpc/handlers/GatewayBalances.cpp b/src/rpc/handlers/GatewayBalances.cpp index ee7b16deb..61696e56c 100644 --- a/src/rpc/handlers/GatewayBalances.cpp +++ b/src/rpc/handlers/GatewayBalances.cpp @@ -111,7 +111,7 @@ GatewayBalancesHandler::process(GatewayBalancesHandler::Input input, Context con output.frozenBalances[peer].push_back(-balance); } else { // normal negative balance, obligation to customer - auto& bal = output.sums[balance.getAsset()]; + auto& bal = output.sums[balance.getCurrency()]; if (bal == beast::zero) { // This is needed to set the currency code correctly bal = -balance; @@ -173,7 +173,7 @@ tag_invoke(boost::json::value_from_tag, boost::json::value& jv, GatewayBalancesH boost::json::array arr; for (auto const& balance : accBalances) { boost::json::object entry; - entry[JS(currency)] = ripple::to_string(balance.issue().asset()); + entry[JS(currency)] = ripple::to_string(balance.issue().currency); entry[JS(value)] = balance.getText(); arr.push_back(std::move(entry)); } diff --git a/src/rpc/handlers/Ledger.cpp b/src/rpc/handlers/Ledger.cpp index 2d3511030..9c8234d5a 100644 --- a/src/rpc/handlers/Ledger.cpp +++ b/src/rpc/handlers/Ledger.cpp @@ -132,7 +132,7 @@ LedgerHandler::process(LedgerHandler::Input input, Context const& ctx) const *sharedPtrBackend_, lgrInfo.seq, account, - amount.getAsset(), + amount.getCurrency(), amount.getIssuer(), false, // fhIGNORE_FREEZE from rippled ctx.yield diff --git a/src/rpc/handlers/NoRippleCheck.cpp b/src/rpc/handlers/NoRippleCheck.cpp index b67105b32..4a5571a52 100644 --- a/src/rpc/handlers/NoRippleCheck.cpp +++ b/src/rpc/handlers/NoRippleCheck.cpp @@ -145,9 +145,7 @@ NoRippleCheckHandler::process(NoRippleCheckHandler::Input input, Context const& ownedItem.getFieldAmount(bLow ? ripple::sfHighLimit : ripple::sfLowLimit); problem += fmt::format( - "{} line to {}", - to_string(static_cast(peerLimit.getAsset())), - to_string(peerLimit.getIssuer()) + "{} line to {}", to_string(peerLimit.getCurrency()), to_string(peerLimit.getIssuer()) ); output.problems.emplace_back(problem); diff --git a/unittests/rpc/RPCHelpersTests.cpp b/unittests/rpc/RPCHelpersTests.cpp index 17cbe9f3a..7205173ed 100644 --- a/unittests/rpc/RPCHelpersTests.cpp +++ b/unittests/rpc/RPCHelpersTests.cpp @@ -514,10 +514,10 @@ TEST_F(RPCHelpersTest, ParseIssue) })" ) .as_object()); - EXPECT_TRUE(issue.account() == GetAccountIDWithString(ACCOUNT2)); + EXPECT_TRUE(issue.account == GetAccountIDWithString(ACCOUNT2)); issue = parseIssue(boost::json::parse(R"({"currency": "XRP"})").as_object()); - EXPECT_TRUE(ripple::isXRP(issue.asset())); + EXPECT_TRUE(ripple::isXRP(issue.currency)); EXPECT_THROW(parseIssue(boost::json::parse(R"({"currency": 2})").as_object()), std::runtime_error); From 4d8585264cec50bfd3402c992897cbb8400000da Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Thu, 29 Feb 2024 15:11:40 -0500 Subject: [PATCH 33/64] update --- CMakeLists.txt | 4 ++-- src/etl/impl/AsyncData.hpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 239465982..ee6a3572a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -245,8 +245,6 @@ if (tests) unittests/rpc/handlers/AccountOffersTests.cpp unittests/rpc/handlers/AccountTxTests.cpp unittests/rpc/handlers/AMMInfoTests.cpp - unittests/rpc/handlers/MPTHoldersTests.cpp - # Backend unittests/rpc/handlers/BookChangesTests.cpp unittests/rpc/handlers/BookOffersTests.cpp unittests/rpc/handlers/DefaultProcessorTests.cpp @@ -256,6 +254,8 @@ if (tests) unittests/rpc/handlers/LedgerEntryTests.cpp unittests/rpc/handlers/LedgerRangeTests.cpp unittests/rpc/handlers/LedgerTests.cpp + unittests/rpc/handlers/MPTHoldersTests.cpp + # Backend unittests/rpc/handlers/NFTBuyOffersTests.cpp unittests/rpc/handlers/NFTHistoryTests.cpp unittests/rpc/handlers/NFTInfoTests.cpp diff --git a/src/etl/impl/AsyncData.hpp b/src/etl/impl/AsyncData.hpp index 34ab91e5c..8163284e6 100644 --- a/src/etl/impl/AsyncData.hpp +++ b/src/etl/impl/AsyncData.hpp @@ -21,8 +21,8 @@ #include "data/BackendInterface.hpp" #include "data/Types.hpp" -#include "etl/MPTHelpers.hpp" #include "etl/ETLHelpers.hpp" +#include "etl/MPTHelpers.hpp" #include "etl/NFTHelpers.hpp" #include "util/Assert.hpp" #include "util/log/Logger.hpp" From f2245554619bb1a364341ed54f9600e679bd4086 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Thu, 29 Feb 2024 15:20:40 -0500 Subject: [PATCH 34/64] revert --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ee6a3572a..61d4aeb2c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -244,6 +244,7 @@ if (tests) unittests/rpc/handlers/AccountObjectsTests.cpp unittests/rpc/handlers/AccountOffersTests.cpp unittests/rpc/handlers/AccountTxTests.cpp + # Backend unittests/rpc/handlers/AMMInfoTests.cpp unittests/rpc/handlers/BookChangesTests.cpp unittests/rpc/handlers/BookOffersTests.cpp @@ -255,7 +256,6 @@ if (tests) unittests/rpc/handlers/LedgerRangeTests.cpp unittests/rpc/handlers/LedgerTests.cpp unittests/rpc/handlers/MPTHoldersTests.cpp - # Backend unittests/rpc/handlers/NFTBuyOffersTests.cpp unittests/rpc/handlers/NFTHistoryTests.cpp unittests/rpc/handlers/NFTInfoTests.cpp From d28b21fc9c66ccb91efb6e5afb3a377a7858bd6f Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Thu, 29 Feb 2024 15:21:46 -0500 Subject: [PATCH 35/64] comment --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 61d4aeb2c..c454a9d74 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -244,8 +244,8 @@ if (tests) unittests/rpc/handlers/AccountObjectsTests.cpp unittests/rpc/handlers/AccountOffersTests.cpp unittests/rpc/handlers/AccountTxTests.cpp - # Backend unittests/rpc/handlers/AMMInfoTests.cpp + # Backend unittests/rpc/handlers/BookChangesTests.cpp unittests/rpc/handlers/BookOffersTests.cpp unittests/rpc/handlers/DefaultProcessorTests.cpp From 66a4fe7631b98cfa7fe0ffb72e25a453bae3a095 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Fri, 1 Mar 2024 16:01:49 -0500 Subject: [PATCH 36/64] improvements --- src/data/cassandra/Schema.hpp | 2 +- src/rpc/RPCHelpers.cpp | 4 +--- src/rpc/handlers/AccountObjects.cpp | 5 ++--- src/rpc/handlers/LedgerData.cpp | 5 ++--- src/rpc/handlers/MPTHolders.cpp | 7 +++---- 5 files changed, 9 insertions(+), 14 deletions(-) diff --git a/src/data/cassandra/Schema.hpp b/src/data/cassandra/Schema.hpp index 47a2cc9a2..b286548eb 100644 --- a/src/data/cassandra/Schema.hpp +++ b/src/data/cassandra/Schema.hpp @@ -273,7 +273,7 @@ class Schema { CREATE TABLE IF NOT EXISTS {} ( mpt_id blob, - holder blob, + holder blob, PRIMARY KEY (mpt_id, holder) ) WITH CLUSTERING ORDER BY (holder ASC) diff --git a/src/rpc/RPCHelpers.cpp b/src/rpc/RPCHelpers.cpp index e6f908a6a..6f650cd2d 100644 --- a/src/rpc/RPCHelpers.cpp +++ b/src/rpc/RPCHelpers.cpp @@ -328,7 +328,7 @@ getMPTIssuanceID(std::shared_ptr const& meta) continue; auto const& mptNode = node.peekAtField(ripple::sfNewFields).downcast(); - return ripple::getMptID(mptNode.getAccountID(ripple::sfIssuer), mptNode.getFieldU32(ripple::sfSequence)); + return ripple::getMptID(mptNode[ripple::sfIssuer], mptNode[ripple::sfSequence]); } return {}; @@ -359,8 +359,6 @@ insertMPTIssuanceID( if (auto const amt = getMPTIssuanceID(meta)) metaJson[JS(mpt_issuance_id)] = ripple::to_string(*amt); - else - metaJson[JS(mpt_issuance_id)] = "unavailable"; return true; } diff --git a/src/rpc/handlers/AccountObjects.cpp b/src/rpc/handlers/AccountObjects.cpp index d0848f083..2e4abfd0e 100644 --- a/src/rpc/handlers/AccountObjects.cpp +++ b/src/rpc/handlers/AccountObjects.cpp @@ -129,9 +129,8 @@ tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountObjectsHa // if object type if mpt issuance, inject synthetic mpt id if (sle.getType() == ripple::ltMPTOKEN_ISSUANCE) - sleJson[JS(mpt_issuance_id)] = ripple::to_string( - ripple::getMptID(sle.getAccountID(ripple::sfIssuer), sle.getFieldU32(ripple::sfSequence)) - ); + sleJson[JS(mpt_issuance_id)] = + ripple::to_string(ripple::getMptID(sle[ripple::sfIssuer], sle[ripple::sfSequence])); return sleJson; } diff --git a/src/rpc/handlers/LedgerData.cpp b/src/rpc/handlers/LedgerData.cpp index 084efa9d0..3e5a4e92c 100644 --- a/src/rpc/handlers/LedgerData.cpp +++ b/src/rpc/handlers/LedgerData.cpp @@ -143,9 +143,8 @@ LedgerDataHandler::process(Input input, Context const& ctx) const // if object type if mpt issuance, inject synthetic mpt id if (sle.getType() == ripple::ltMPTOKEN_ISSUANCE) - sleJson[JS(mpt_issuance_id)] = ripple::to_string( - ripple::getMptID(sle.getAccountID(ripple::sfIssuer), sle.getFieldU32(ripple::sfSequence)) - ); + sleJson[JS(mpt_issuance_id)] = + ripple::to_string(ripple::getMptID(sle[ripple::sfIssuer], sle[ripple::sfSequence])); output.states.push_back(sleJson); } diff --git a/src/rpc/handlers/MPTHolders.cpp b/src/rpc/handlers/MPTHolders.cpp index ab30aa6ed..7c471096e 100644 --- a/src/rpc/handlers/MPTHolders.cpp +++ b/src/rpc/handlers/MPTHolders.cpp @@ -83,16 +83,15 @@ MPTHoldersHandler::process(MPTHoldersHandler::Input input, Context const& ctx) c ripple::STLedgerEntry const sle{ripple::SerialIter{mpt.data(), mpt.size()}, keylet::mptIssuance(mptID).key}; boost::json::object mptJson; - mptJson[JS(account)] = toBase58(sle.getAccountID(ripple::sfAccount)); - mptJson[JS(flags)] = sle[ripple::sfFlags]; + mptJson[JS(account)] = toBase58(sle[ripple::sfAccount]); + mptJson[JS(flags)] = sle.getFlags(); mptJson["mpt_amount"] = toBoostJson(ripple::STUInt64{sle[ripple::sfMPTAmount]}.getJson(JsonOptions::none)); if (sle[ripple::sfLockedAmount]) mptJson["locked_amount"] = toBoostJson(ripple::STUInt64{sle[ripple::sfLockedAmount]}.getJson(JsonOptions::none)); - mptJson["mptoken_index"] = - ripple::to_string(ripple::keylet::mptoken(mptID, sle.getAccountID(ripple::sfAccount)).key); + mptJson["mptoken_index"] = ripple::to_string(ripple::keylet::mptoken(mptID, sle[ripple::sfAccount]).key); output.mpts.push_back(mptJson); } From 63b4f7c4937459b27928360922935af7a90d7899 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Fri, 1 Mar 2024 17:00:40 -0500 Subject: [PATCH 37/64] remove rvalue ref --- src/data/BackendInterface.hpp | 2 +- src/data/CassandraBackend.hpp | 2 +- unittests/util/MockBackend.hpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/data/BackendInterface.hpp b/src/data/BackendInterface.hpp index 4b0adc369..c52b21c75 100644 --- a/src/data/BackendInterface.hpp +++ b/src/data/BackendInterface.hpp @@ -583,7 +583,7 @@ class BackendInterface { writeNFTTransactions(std::vector const& data) = 0; virtual void - writeMPTHolders(std::vector>&& data) = 0; + writeMPTHolders(std::vector> const& data) = 0; /** * @brief Write a new successor. diff --git a/src/data/CassandraBackend.hpp b/src/data/CassandraBackend.hpp index 160978a78..979146ed0 100644 --- a/src/data/CassandraBackend.hpp +++ b/src/data/CassandraBackend.hpp @@ -895,7 +895,7 @@ class BasicCassandraBackend : public BackendInterface { } void - writeMPTHolders(std::vector>&& data) override + writeMPTHolders(std::vector> const& data) override { std::vector statements; for (auto const& record : data) { diff --git a/unittests/util/MockBackend.hpp b/unittests/util/MockBackend.hpp index 54cc74888..044791d1c 100644 --- a/unittests/util/MockBackend.hpp +++ b/unittests/util/MockBackend.hpp @@ -192,7 +192,7 @@ struct MockBackend : public BackendInterface { MOCK_METHOD(bool, doFinishWrites, (), (override)); - MOCK_METHOD(void, writeMPTHolders, ((std::vector>&&)), (override)); + MOCK_METHOD(void, writeMPTHolders, ((std::vector> const&)), (override)); MOCK_METHOD( MPTHoldersAndCursor, From 56301321fcfa52d63b145632831192594ff46aac Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Fri, 1 Mar 2024 17:00:53 -0500 Subject: [PATCH 38/64] clang --- unittests/util/MockBackend.hpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/unittests/util/MockBackend.hpp b/unittests/util/MockBackend.hpp index 044791d1c..4aea50b2f 100644 --- a/unittests/util/MockBackend.hpp +++ b/unittests/util/MockBackend.hpp @@ -192,7 +192,12 @@ struct MockBackend : public BackendInterface { MOCK_METHOD(bool, doFinishWrites, (), (override)); - MOCK_METHOD(void, writeMPTHolders, ((std::vector> const&)), (override)); + MOCK_METHOD( + void, + writeMPTHolders, + ((std::vector> const&)), + (override) + ); MOCK_METHOD( MPTHoldersAndCursor, From d316375abf4b6066459b4fd10ed9d71a751796fc Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Mon, 4 Mar 2024 10:58:29 -0500 Subject: [PATCH 39/64] use copyif --- src/data/CassandraBackend.hpp | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/data/CassandraBackend.hpp b/src/data/CassandraBackend.hpp index 979146ed0..5c3047beb 100644 --- a/src/data/CassandraBackend.hpp +++ b/src/data/CassandraBackend.hpp @@ -581,15 +581,9 @@ class BasicCassandraBackend : public BackendInterface { auto const mptObjects = doFetchLedgerObjects(mptKeys, ledgerSequence, yield); std::vector filteredMpt; - - // need to filter out the objs that don't exist at the ledger seq because these MPT are in no particular time - // order - for (auto const mpt : mptObjects) { - if (!mpt.size()) - continue; - - filteredMpt.push_back(mpt); - } + std::copy_if(mptObjects.begin(), mptObjects.end(), std::back_inserter(filteredMpt), [](Blob mpt) { + return mpt.size() != 0; + }); if (mptKeys.size() == limit) return {filteredMpt, cursor}; From b988f2d5bda6aa990d78a94f695b57afb1875a63 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Mon, 4 Mar 2024 11:05:55 -0500 Subject: [PATCH 40/64] comment --- src/data/CassandraBackend.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/data/CassandraBackend.hpp b/src/data/CassandraBackend.hpp index 5c3047beb..ca0832df4 100644 --- a/src/data/CassandraBackend.hpp +++ b/src/data/CassandraBackend.hpp @@ -580,6 +580,8 @@ class BasicCassandraBackend : public BackendInterface { auto const mptObjects = doFetchLedgerObjects(mptKeys, ledgerSequence, yield); + // need to filter out the objs that don't exist at the ledger seq because these MPT are in no particular time + // order. The MPToken at the specified ledger index may not exist. std::vector filteredMpt; std::copy_if(mptObjects.begin(), mptObjects.end(), std::back_inserter(filteredMpt), [](Blob mpt) { return mpt.size() != 0; From aac1e0cc13bd7bd9897100b168f1a0cae46f20de Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Mon, 4 Mar 2024 11:16:00 -0500 Subject: [PATCH 41/64] use getaccountid --- src/etl/MPTHelpers.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/etl/MPTHelpers.cpp b/src/etl/MPTHelpers.cpp index 0efc1cd88..225ce932e 100644 --- a/src/etl/MPTHelpers.cpp +++ b/src/etl/MPTHelpers.cpp @@ -39,7 +39,7 @@ getMPTokenAuthorize(ripple::TxMeta const& txMeta) if (node.getFName() == ripple::sfCreatedNode) { auto const& newMPT = node.peekAtField(ripple::sfNewFields).downcast(); - return std::make_pair(newMPT[ripple::sfMPTokenIssuanceID], newMPT[ripple::sfAccount]); + return std::make_pair(newMPT[ripple::sfMPTokenIssuanceID], newMPT.getAccountID(ripple::sfAccount)); } } return {}; From 90dc297380408afd026a162eddb322a5dc8f6c0d Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Mon, 4 Mar 2024 11:20:02 -0500 Subject: [PATCH 42/64] comment --- src/data/BackendInterface.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/data/BackendInterface.hpp b/src/data/BackendInterface.hpp index 137e7303c..ea18eb61f 100644 --- a/src/data/BackendInterface.hpp +++ b/src/data/BackendInterface.hpp @@ -585,6 +585,11 @@ class BackendInterface { virtual void writeNFTTransactions(std::vector const& data) = 0; + /** + * @brief Write accounts that started holding onto a MPT. + * + * @param data A vector of MPT ID and account pairs + */ virtual void writeMPTHolders(std::vector> const& data) = 0; From f4852a8a72719ae81a67bfd3c943a24563134afb Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Tue, 5 Mar 2024 10:24:41 -0500 Subject: [PATCH 43/64] update libxrpl --- conanfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conanfile.py b/conanfile.py index e4618f9b8..d2cb52d69 100644 --- a/conanfile.py +++ b/conanfile.py @@ -27,7 +27,7 @@ class Clio(ConanFile): 'protobuf/3.21.12', 'grpc/1.50.1', 'openssl/1.1.1u', - 'xrpl/2.0.0', + 'xrpl-mpt/2.1.0-rc1', 'libbacktrace/cci.20210118' ] From e7c77d34b0f6a084983c4bc5f6513fd19320d3c9 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Tue, 5 Mar 2024 10:45:55 -0500 Subject: [PATCH 44/64] doc --- src/etl/MPTHelpers.cpp | 6 +++++ src/rpc/RPCHelpers.cpp | 12 ++++++++++ src/rpc/RPCHelpers.hpp | 5 +++++ src/rpc/handlers/MPTHolders.hpp | 40 +++++++++++++++++++++++++++++++++ 4 files changed, 63 insertions(+) diff --git a/src/etl/MPTHelpers.cpp b/src/etl/MPTHelpers.cpp index 225ce932e..2c862efd2 100644 --- a/src/etl/MPTHelpers.cpp +++ b/src/etl/MPTHelpers.cpp @@ -30,6 +30,12 @@ namespace etl { +/** + * @brief Get the MPToken created from a transaction + * + * @param txMeta Transaction metadata + * @return MPT and holder account pair + */ static std::optional> getMPTokenAuthorize(ripple::TxMeta const& txMeta) { diff --git a/src/rpc/RPCHelpers.cpp b/src/rpc/RPCHelpers.cpp index 56f0ddc42..4d5730d2d 100644 --- a/src/rpc/RPCHelpers.cpp +++ b/src/rpc/RPCHelpers.cpp @@ -315,6 +315,12 @@ insertDeliveredAmount( return false; } +/** + * @brief Get the delivered amount + * + * @param meta The metadata + * @return The mpt_issuance_id or std::nullopt if not available + */ static std::optional getMPTIssuanceID(std::shared_ptr const& meta) { @@ -332,6 +338,12 @@ getMPTIssuanceID(std::shared_ptr const& meta) return {}; } +/** + * @brief Check if transaction has a new MPToken created + * + * @param meta The metadata + * @return True or false + */ static bool canHaveMPTIssuanceID(std::shared_ptr const& txn, std::shared_ptr const& meta) { diff --git a/src/rpc/RPCHelpers.hpp b/src/rpc/RPCHelpers.hpp index fa6ee3faf..6f0556a88 100644 --- a/src/rpc/RPCHelpers.hpp +++ b/src/rpc/RPCHelpers.hpp @@ -192,6 +192,11 @@ insertDeliveredAmount( /** * @brief Add "mpt_issuance_id" into MPTokenIssuanceCreate transaction json. + * + * @param metaJson The metadata json object to add "DeliveredAmount" + * @param txn The transaction object + * @param meta The metadata object + * @return true if the "mpt_issuance_id" is added to the metadata json object */ bool insertMPTIssuanceID( diff --git a/src/rpc/handlers/MPTHolders.hpp b/src/rpc/handlers/MPTHolders.hpp index 42947cf7b..104060a5d 100644 --- a/src/rpc/handlers/MPTHolders.hpp +++ b/src/rpc/handlers/MPTHolders.hpp @@ -27,6 +27,10 @@ #include "rpc/common/Validators.hpp" namespace rpc { + +/** + * @brief The mpt_holders command asks the Clio server for all holders of a particular MPTokenIssuance. + */ class MPTHoldersHandler { std::shared_ptr sharedPtrBackend_; @@ -35,6 +39,9 @@ class MPTHoldersHandler { static auto constexpr LIMIT_MAX = 100; static auto constexpr LIMIT_DEFAULT = 50; + /** + * @brief A struct to hold the output data of the command + */ struct Output { boost::json::array mpts; uint32_t ledgerIndex; @@ -44,6 +51,9 @@ class MPTHoldersHandler { std::optional marker; }; + /** + * @brief A struct to hold the input data for the command + */ struct Input { std::string mptID; std::optional ledgerHash; @@ -54,10 +64,21 @@ class MPTHoldersHandler { using Result = HandlerReturnType; + /** + * @brief Construct a new MPTHoldersHandler object + * + * @param sharedPtrBackend The backend to use + */ MPTHoldersHandler(std::shared_ptr const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend) { } + /** + * @brief Returns the API specification for the command + * + * @param apiVersion The api version to return the spec for + * @return The spec for the given apiVersion + */ static RpcSpecConstRef spec([[maybe_unused]] uint32_t apiVersion) { @@ -75,13 +96,32 @@ class MPTHoldersHandler { return rpcSpec; } + /** + * @brief Process the MPTHolders command + * + * @param input The input data for the command + * @param ctx The context of the request + * @return The result of the operation + */ Result process(Input input, Context const& ctx) const; private: + /** + * @brief Convert the Output to a JSON object + * + * @param [out] jv The JSON object to convert to + * @param output The output to convert + */ friend void tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Output const& output); + /** + * @brief Convert a JSON object to Input type + * + * @param jv The JSON object to convert + * @return Input parsed from the JSON object + */ friend Input tag_invoke(boost::json::value_to_tag, boost::json::value const& jv); }; From d9c54bef80f5d50a399a174ac2aca645f448cebf Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Tue, 5 Mar 2024 10:48:36 -0500 Subject: [PATCH 45/64] comment --- src/rpc/RPCHelpers.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rpc/RPCHelpers.hpp b/src/rpc/RPCHelpers.hpp index 6f0556a88..1c4b5698b 100644 --- a/src/rpc/RPCHelpers.hpp +++ b/src/rpc/RPCHelpers.hpp @@ -193,7 +193,7 @@ insertDeliveredAmount( /** * @brief Add "mpt_issuance_id" into MPTokenIssuanceCreate transaction json. * - * @param metaJson The metadata json object to add "DeliveredAmount" + * @param metaJson The metadata json object to add "MPTokenIssuanceID" * @param txn The transaction object * @param meta The metadata object * @return true if the "mpt_issuance_id" is added to the metadata json object From 8d798a6e71dbe7bc0a52f762181faf4bcaaeab76 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Tue, 5 Mar 2024 10:50:32 -0500 Subject: [PATCH 46/64] doc --- src/rpc/RPCHelpers.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/rpc/RPCHelpers.cpp b/src/rpc/RPCHelpers.cpp index 4d5730d2d..84b77d265 100644 --- a/src/rpc/RPCHelpers.cpp +++ b/src/rpc/RPCHelpers.cpp @@ -341,8 +341,9 @@ getMPTIssuanceID(std::shared_ptr const& meta) /** * @brief Check if transaction has a new MPToken created * + * @param txn The transaction * @param meta The metadata - * @return True or false + * @return true if the transaction can have a mpt_issuance_id */ static bool canHaveMPTIssuanceID(std::shared_ptr const& txn, std::shared_ptr const& meta) From 55386e82bbe46f38efeb719f9788e98394da8a5c Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Tue, 5 Mar 2024 11:13:42 -0500 Subject: [PATCH 47/64] change var name --- src/data/CassandraBackend.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/CassandraBackend.hpp b/src/data/CassandraBackend.hpp index ca0832df4..c6f66610f 100644 --- a/src/data/CassandraBackend.hpp +++ b/src/data/CassandraBackend.hpp @@ -895,7 +895,7 @@ class BasicCassandraBackend : public BackendInterface { { std::vector statements; for (auto const& record : data) { - auto const mpt_id = record.first; + auto const mptId = record.first; auto const holder = record.second; statements.push_back(schema_->insertMPTHolder.bind(mpt_id, holder)); From 0087ff589c13f1d6ed423e7cf6268c566970f270 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Tue, 5 Mar 2024 11:14:02 -0500 Subject: [PATCH 48/64] var name --- src/data/CassandraBackend.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/CassandraBackend.hpp b/src/data/CassandraBackend.hpp index c6f66610f..d74e00541 100644 --- a/src/data/CassandraBackend.hpp +++ b/src/data/CassandraBackend.hpp @@ -898,7 +898,7 @@ class BasicCassandraBackend : public BackendInterface { auto const mptId = record.first; auto const holder = record.second; - statements.push_back(schema_->insertMPTHolder.bind(mpt_id, holder)); + statements.push_back(schema_->insertMPTHolder.bind(mptId, holder)); } executor_.write(std::move(statements)); From 01453eed9d0bef7d20db65acb3a783daad76b556 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Tue, 5 Mar 2024 13:56:59 -0500 Subject: [PATCH 49/64] use toJson --- src/rpc/RPCHelpers.cpp | 14 +++++++++++--- src/rpc/handlers/AccountObjects.cpp | 11 +---------- src/rpc/handlers/LedgerData.cpp | 9 +-------- 3 files changed, 13 insertions(+), 21 deletions(-) diff --git a/src/rpc/RPCHelpers.cpp b/src/rpc/RPCHelpers.cpp index 84b77d265..232353567 100644 --- a/src/rpc/RPCHelpers.cpp +++ b/src/rpc/RPCHelpers.cpp @@ -368,10 +368,12 @@ insertMPTIssuanceID( if (!canHaveMPTIssuanceID(txn, meta)) return false; - if (auto const amt = getMPTIssuanceID(meta)) - metaJson[JS(mpt_issuance_id)] = ripple::to_string(*amt); + if (auto const id = getMPTIssuanceID(meta)){ + metaJson[JS(mpt_issuance_id)] = ripple::to_string(*id); + return true; + } - return true; + return false; } void @@ -413,6 +415,12 @@ toJson(ripple::SLE const& sle) value.as_object()["urlgravatar"] = str(boost::format("http://www.gravatar.com/avatar/%s") % md5); } } + + // if object type if mpt issuance, inject synthetic mpt id + if (sle.getType() == ripple::ltMPTOKEN_ISSUANCE) + value.as_object()[JS(mpt_issuance_id)] = + ripple::to_string(ripple::getMptID(sle[ripple::sfIssuer], sle[ripple::sfSequence])); + return value.as_object(); } diff --git a/src/rpc/handlers/AccountObjects.cpp b/src/rpc/handlers/AccountObjects.cpp index 2e4abfd0e..999ddc956 100644 --- a/src/rpc/handlers/AccountObjects.cpp +++ b/src/rpc/handlers/AccountObjects.cpp @@ -124,16 +124,7 @@ tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountObjectsHa std::cbegin(output.accountObjects), std::cend(output.accountObjects), std::back_inserter(objects), - [](auto const& sle) { - auto sleJson = toJson(sle); - - // if object type if mpt issuance, inject synthetic mpt id - if (sle.getType() == ripple::ltMPTOKEN_ISSUANCE) - sleJson[JS(mpt_issuance_id)] = - ripple::to_string(ripple::getMptID(sle[ripple::sfIssuer], sle[ripple::sfSequence])); - - return sleJson; - } + [](auto const& sle) { return toJson(sle); } ); jv = { diff --git a/src/rpc/handlers/LedgerData.cpp b/src/rpc/handlers/LedgerData.cpp index 3e5a4e92c..34a5fb528 100644 --- a/src/rpc/handlers/LedgerData.cpp +++ b/src/rpc/handlers/LedgerData.cpp @@ -139,14 +139,7 @@ LedgerDataHandler::process(Input input, Context const& ctx) const entry[JS(index)] = ripple::to_string(sle.key()); output.states.push_back(std::move(entry)); } else { - auto sleJson = toJson(sle); - - // if object type if mpt issuance, inject synthetic mpt id - if (sle.getType() == ripple::ltMPTOKEN_ISSUANCE) - sleJson[JS(mpt_issuance_id)] = - ripple::to_string(ripple::getMptID(sle[ripple::sfIssuer], sle[ripple::sfSequence])); - - output.states.push_back(sleJson); + output.states.push_back(toJson(sle)); } } } From 87838f6792de079a909f3638becd6534ce8fd95d Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Tue, 5 Mar 2024 13:57:15 -0500 Subject: [PATCH 50/64] clang --- src/rpc/RPCHelpers.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rpc/RPCHelpers.cpp b/src/rpc/RPCHelpers.cpp index 232353567..952524692 100644 --- a/src/rpc/RPCHelpers.cpp +++ b/src/rpc/RPCHelpers.cpp @@ -368,9 +368,9 @@ insertMPTIssuanceID( if (!canHaveMPTIssuanceID(txn, meta)) return false; - if (auto const id = getMPTIssuanceID(meta)){ - metaJson[JS(mpt_issuance_id)] = ripple::to_string(*id); - return true; + if (auto const id = getMPTIssuanceID(meta)) { + metaJson[JS(mpt_issuance_id)] = ripple::to_string(*id); + return true; } return false; From cee9cd5bbea0eb40b5e4fd4d26a67d10de91c9f6 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Tue, 5 Mar 2024 15:11:26 -0500 Subject: [PATCH 51/64] change libxrpl --- CMake/deps/libxrpl.cmake | 2 +- CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMake/deps/libxrpl.cmake b/CMake/deps/libxrpl.cmake index 852d763fb..51639e422 100644 --- a/CMake/deps/libxrpl.cmake +++ b/CMake/deps/libxrpl.cmake @@ -1 +1 @@ -find_package(xrpl REQUIRED) +find_package(xrpl-mpt REQUIRED) diff --git a/CMakeLists.txt b/CMakeLists.txt index c454a9d74..a0c9a7c36 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,7 +68,7 @@ target_link_libraries( PUBLIC fmt::fmt PUBLIC OpenSSL::Crypto PUBLIC OpenSSL::SSL - PUBLIC xrpl::libxrpl + PUBLIC xrpl-mpt::libxrpl PUBLIC dl PUBLIC libbacktrace::libbacktrace INTERFACE Threads::Threads From d2f40a9cd875a42741fd52db53863232d2ffd227 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Tue, 12 Mar 2024 14:42:24 -0400 Subject: [PATCH 52/64] revert libxrpl --- CMake/deps/libxrpl.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMake/deps/libxrpl.cmake b/CMake/deps/libxrpl.cmake index 51639e422..a68a52c7d 100644 --- a/CMake/deps/libxrpl.cmake +++ b/CMake/deps/libxrpl.cmake @@ -1 +1 @@ -find_package(xrpl-mpt REQUIRED) +find_package(xrpl REQUIRED CONFIG) From c4dbd11e97ffaddb6c92c77cd7b80925434df17c Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Tue, 12 Mar 2024 14:49:25 -0400 Subject: [PATCH 53/64] libxrpl --- CMake/deps/libxrpl.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMake/deps/libxrpl.cmake b/CMake/deps/libxrpl.cmake index a68a52c7d..5f42d687d 100644 --- a/CMake/deps/libxrpl.cmake +++ b/CMake/deps/libxrpl.cmake @@ -1 +1 @@ -find_package(xrpl REQUIRED CONFIG) +find_package(xrpl-mpt REQUIRED CONFIG) From a39c90a805ba8a1bb2117131d6707873edc8ccd2 Mon Sep 17 00:00:00 2001 From: Shawn Xie <35279399+shawnxie999@users.noreply.github.com> Date: Tue, 12 Mar 2024 14:50:34 -0400 Subject: [PATCH 54/64] Delete CMake/deps/libxrpl.cmake --- CMake/deps/libxrpl.cmake | 1 - 1 file changed, 1 deletion(-) delete mode 100644 CMake/deps/libxrpl.cmake diff --git a/CMake/deps/libxrpl.cmake b/CMake/deps/libxrpl.cmake deleted file mode 100644 index 5f42d687d..000000000 --- a/CMake/deps/libxrpl.cmake +++ /dev/null @@ -1 +0,0 @@ -find_package(xrpl-mpt REQUIRED CONFIG) From 3defe87eec5a0e64e165c48b3737110ee9fc4ebc Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Tue, 12 Mar 2024 14:52:03 -0400 Subject: [PATCH 55/64] add renamed libxrpl --- cmake/deps/libxrpl.cmake | 1 + 1 file changed, 1 insertion(+) create mode 100644 cmake/deps/libxrpl.cmake diff --git a/cmake/deps/libxrpl.cmake b/cmake/deps/libxrpl.cmake new file mode 100644 index 000000000..5f42d687d --- /dev/null +++ b/cmake/deps/libxrpl.cmake @@ -0,0 +1 @@ +find_package(xrpl-mpt REQUIRED CONFIG) From e75e2cf4612da6bc7b23304c74cccb8791ce9d2e Mon Sep 17 00:00:00 2001 From: Shawn Xie <35279399+shawnxie999@users.noreply.github.com> Date: Tue, 26 Mar 2024 16:19:06 -0400 Subject: [PATCH 56/64] comments --- src/data/BackendInterface.hpp | 5 ++-- src/data/CassandraBackend.hpp | 26 +++++++------------ src/data/DBHelpers.hpp | 8 ++++++ src/data/cassandra/Schema.hpp | 2 +- src/etl/MPTHelpers.cpp | 10 +++---- src/etl/MPTHelpers.hpp | 4 +-- src/etl/impl/LedgerLoader.hpp | 4 +-- src/etl/impl/Transformer.hpp | 2 +- src/rpc/handlers/LedgerEntry.cpp | 6 +++-- src/rpc/handlers/MPTHolders.cpp | 5 ---- .../rpc/handlers/AccountObjectsTests.cpp | 2 +- unittests/rpc/handlers/LedgerDataTests.cpp | 2 +- unittests/util/MockBackend.hpp | 7 +---- unittests/util/TestObject.cpp | 2 +- 14 files changed, 39 insertions(+), 46 deletions(-) diff --git a/src/data/BackendInterface.hpp b/src/data/BackendInterface.hpp index ea18eb61f..902ba546c 100644 --- a/src/data/BackendInterface.hpp +++ b/src/data/BackendInterface.hpp @@ -342,8 +342,7 @@ class BackendInterface { * * @param mptID MPTIssuanceID you wish you query. * @param limit Paging limit. - * @param cursorIn Optional cursor to allow us to pick up from where we - * last left off. + * @param cursorIn Optional cursor to allow us to pick up from where we last left off. * @param ledgerSequence The ledger sequence to fetch for * @param yield Currently executing coroutine. * @return std::vector of MPToken balances and an optional marker @@ -591,7 +590,7 @@ class BackendInterface { * @param data A vector of MPT ID and account pairs */ virtual void - writeMPTHolders(std::vector> const& data) = 0; + writeMPTHolders(std::vector const& data) = 0; /** * @brief Write a new successor. diff --git a/src/data/CassandraBackend.hpp b/src/data/CassandraBackend.hpp index d74e00541..11d25688b 100644 --- a/src/data/CassandraBackend.hpp +++ b/src/data/CassandraBackend.hpp @@ -578,19 +578,17 @@ class BasicCassandraBackend : public BackendInterface { cursor = holder; } - auto const mptObjects = doFetchLedgerObjects(mptKeys, ledgerSequence, yield); + auto mptObjects = doFetchLedgerObjects(mptKeys, ledgerSequence, yield); - // need to filter out the objs that don't exist at the ledger seq because these MPT are in no particular time - // order. The MPToken at the specified ledger index may not exist. - std::vector filteredMpt; - std::copy_if(mptObjects.begin(), mptObjects.end(), std::back_inserter(filteredMpt), [](Blob mpt) { - return mpt.size() != 0; - }); + auto it = std::remove_if(mptObjects.begin(), mptObjects.end(), [](Blob mpt) { return mpt.size() == 0; }); + + mptObjects.erase(it, mptObjects.end()); + ASSERT(mptKeys.size() <= limit, "Number of keys can't exceed the limit"); if (mptKeys.size() == limit) - return {filteredMpt, cursor}; + return {mptObjects, cursor}; - return {filteredMpt, {}}; + return {mptObjects, {}}; } std::optional @@ -891,15 +889,11 @@ class BasicCassandraBackend : public BackendInterface { } void - writeMPTHolders(std::vector> const& data) override + writeMPTHolders(std::vector const& data) override { std::vector statements; - for (auto const& record : data) { - auto const mptId = record.first; - auto const holder = record.second; - - statements.push_back(schema_->insertMPTHolder.bind(mptId, holder)); - } + for (auto [mptId, holder] : data) + statements.push_back(schema_->insertMPTHolder.bind(std::move(mptId), std::move(holder))); executor_.write(std::move(statements)); } diff --git a/src/data/DBHelpers.hpp b/src/data/DBHelpers.hpp index 06f41bac7..61100de14 100644 --- a/src/data/DBHelpers.hpp +++ b/src/data/DBHelpers.hpp @@ -172,6 +172,14 @@ struct NFTsData { } }; +/** + * @brief Represents an MPT and holder pair + */ +struct MPTHolderData { + ripple::uint192 mptID; + ripple::AccountID holder; +}; + /** * @brief Check whether the supplied object is an offer. * diff --git a/src/data/cassandra/Schema.hpp b/src/data/cassandra/Schema.hpp index 8d9c6e7e8..db16c0ce4 100644 --- a/src/data/cassandra/Schema.hpp +++ b/src/data/cassandra/Schema.hpp @@ -287,7 +287,7 @@ class Schema { ( mpt_id blob, holder blob, - PRIMARY KEY (mpt_id, holder) + PRIMARY KEY (mpt_id, holder) ) WITH CLUSTERING ORDER BY (holder ASC) AND default_time_to_live = {} diff --git a/src/etl/MPTHelpers.cpp b/src/etl/MPTHelpers.cpp index 2c862efd2..dd7126330 100644 --- a/src/etl/MPTHelpers.cpp +++ b/src/etl/MPTHelpers.cpp @@ -36,7 +36,7 @@ namespace etl { * @param txMeta Transaction metadata * @return MPT and holder account pair */ -static std::optional> +static std::optional getMPTokenAuthorize(ripple::TxMeta const& txMeta) { for (ripple::STObject const& node : txMeta.getNodes()) { @@ -45,13 +45,13 @@ getMPTokenAuthorize(ripple::TxMeta const& txMeta) if (node.getFName() == ripple::sfCreatedNode) { auto const& newMPT = node.peekAtField(ripple::sfNewFields).downcast(); - return std::make_pair(newMPT[ripple::sfMPTokenIssuanceID], newMPT.getAccountID(ripple::sfAccount)); + return MPTHolderData{newMPT[ripple::sfMPTokenIssuanceID], newMPT.getAccountID(ripple::sfAccount)}; } } return {}; } -std::optional> +std::optional getMPTHolderFromTx(ripple::TxMeta const& txMeta, ripple::STTx const& sttx) { if (txMeta.getResultTER() != ripple::tesSUCCESS || sttx.getTxnType() != ripple::TxType::ttMPTOKEN_AUTHORIZE) @@ -60,7 +60,7 @@ getMPTHolderFromTx(ripple::TxMeta const& txMeta, ripple::STTx const& sttx) return getMPTokenAuthorize(txMeta); } -std::optional> +std::optional getMPTHolderFromObj(std::string const& key, std::string const& blob) { ripple::STLedgerEntry const sle = @@ -72,7 +72,7 @@ getMPTHolderFromObj(std::string const& key, std::string const& blob) auto const mptIssuanceID = sle[ripple::sfMPTokenIssuanceID]; auto const holder = sle.getAccountID(ripple::sfAccount); - return std::make_pair(mptIssuanceID, holder); + return MPTHolderData{mptIssuanceID, holder}; } } // namespace etl diff --git a/src/etl/MPTHelpers.hpp b/src/etl/MPTHelpers.hpp index 13da51772..2aabb99d7 100644 --- a/src/etl/MPTHelpers.hpp +++ b/src/etl/MPTHelpers.hpp @@ -34,7 +34,7 @@ namespace etl { * @param sttx The transaction * @return The MPTIssuanceID and holder pair as a optional */ -std::optional> +std::optional getMPTHolderFromTx(ripple::TxMeta const& txMeta, ripple::STTx const& sttx); /** @@ -44,7 +44,7 @@ getMPTHolderFromTx(ripple::TxMeta const& txMeta, ripple::STTx const& sttx); * @param blob Object data as blob * @return The MPTIssuanceID and holder pair as a optional */ -std::optional> +std::optional getMPTHolderFromObj(std::string const& key, std::string const& blob); } // namespace etl diff --git a/src/etl/impl/LedgerLoader.hpp b/src/etl/impl/LedgerLoader.hpp index 462bd57a2..a7db07d34 100644 --- a/src/etl/impl/LedgerLoader.hpp +++ b/src/etl/impl/LedgerLoader.hpp @@ -56,7 +56,7 @@ struct FormattedTransactionsData { std::vector accountTxData; std::vector nfTokenTxData; std::vector nfTokensData; - std::vector> mptHoldersData; + std::vector mptHoldersData; }; namespace etl::impl { @@ -262,7 +262,7 @@ class LedgerLoader { backend_->writeAccountTransactions(std::move(insertTxResult.accountTxData)); backend_->writeNFTs(insertTxResult.nfTokensData); backend_->writeNFTTransactions(insertTxResult.nfTokenTxData); - backend_->writeMPTHolders(std::move(insertTxResult.mptHoldersData)); + backend_->writeMPTHolders(insertTxResult.mptHoldersData); } backend_->finishWrites(sequence); diff --git a/src/etl/impl/Transformer.hpp b/src/etl/impl/Transformer.hpp index 9c32e808d..5f64a8731 100644 --- a/src/etl/impl/Transformer.hpp +++ b/src/etl/impl/Transformer.hpp @@ -213,7 +213,7 @@ class Transformer { backend_->writeAccountTransactions(std::move(insertTxResultOp->accountTxData)); backend_->writeNFTs(insertTxResultOp->nfTokensData); backend_->writeNFTTransactions(insertTxResultOp->nfTokenTxData); - backend_->writeMPTHolders(std::move(insertTxResultOp->mptHoldersData)); + backend_->writeMPTHolders(insertTxResultOp->mptHoldersData); auto [success, duration] = ::util::timed>([&]() { return backend_->finishWrites(lgrInfo.seq); }); diff --git a/src/rpc/handlers/LedgerEntry.cpp b/src/rpc/handlers/LedgerEntry.cpp index d103d08b4..008572147 100644 --- a/src/rpc/handlers/LedgerEntry.cpp +++ b/src/rpc/handlers/LedgerEntry.cpp @@ -143,9 +143,11 @@ LedgerEntryHandler::process(LedgerEntryHandler::Input input, Context const& ctx) auto const mptIssuanceID = ripple::uint192{std::string_view(*(input.mptIssuance))}; key = ripple::keylet::mptIssuance(mptIssuanceID).key; } else if (input.mptoken) { - auto const holder = ripple::parseBase58(input.mptoken->at(JS(account)).as_string().c_str()); + auto const holder = + ripple::parseBase58(boost::json::value_to(input.mptoken->at(JS(account)))); auto const mptIssuanceID = - ripple::uint192{std::string_view(input.mptoken->at(JS(mpt_issuance_id)).as_string().c_str())}; + ripple::uint192{std::string_view(boost::json::value_to(input.mptoken->at(JS(mpt_issuance_id)))) + }; key = ripple::keylet::mptoken(mptIssuanceID, *holder).key; } else { // Must specify 1 of the following fields to indicate what type diff --git a/src/rpc/handlers/MPTHolders.cpp b/src/rpc/handlers/MPTHolders.cpp index 7c471096e..718000012 100644 --- a/src/rpc/handlers/MPTHolders.cpp +++ b/src/rpc/handlers/MPTHolders.cpp @@ -55,14 +55,11 @@ MPTHoldersHandler::process(MPTHoldersHandler::Input input, Context const& ctx) c return Error{*status}; auto const lgrInfo = std::get(lgrInfoOrStatus); - auto const limit = input.limit.value_or(MPTHoldersHandler::LIMIT_DEFAULT); - auto const mptID = ripple::uint192{input.mptID.c_str()}; auto const issuanceLedgerObject = sharedPtrBackend_->fetchLedgerObject(ripple::keylet::mptIssuance(mptID).key, lgrInfo.seq, ctx.yield); - if (!issuanceLedgerObject) return Error{Status{RippledError::rpcOBJECT_NOT_FOUND, "objectNotFound"}}; @@ -71,9 +68,7 @@ MPTHoldersHandler::process(MPTHoldersHandler::Input input, Context const& ctx) c cursor = ripple::AccountID{input.marker->c_str()}; auto const dbResponse = sharedPtrBackend_->fetchMPTHolders(mptID, limit, cursor, lgrInfo.seq, ctx.yield); - auto output = MPTHoldersHandler::Output{}; - output.mptID = to_string(mptID); output.limit = limit; output.ledgerIndex = lgrInfo.seq; diff --git a/unittests/rpc/handlers/AccountObjectsTests.cpp b/unittests/rpc/handlers/AccountObjectsTests.cpp index 6f42e00cc..3bd787087 100644 --- a/unittests/rpc/handlers/AccountObjectsTests.cpp +++ b/unittests/rpc/handlers/AccountObjectsTests.cpp @@ -1720,4 +1720,4 @@ TEST_F(RPCAccountObjectsHandlerTest, TypeFilterMPTokenType) ASSERT_EQ(accountObjects.size(), 1); EXPECT_EQ(accountObjects.front().at("LedgerEntryType").as_string(), "MPToken"); }); -} \ No newline at end of file +} diff --git a/unittests/rpc/handlers/LedgerDataTests.cpp b/unittests/rpc/handlers/LedgerDataTests.cpp index 139741ca9..79f34e7aa 100644 --- a/unittests/rpc/handlers/LedgerDataTests.cpp +++ b/unittests/rpc/handlers/LedgerDataTests.cpp @@ -805,4 +805,4 @@ TEST_F(RPCLedgerDataHandlerTest, TypeFilterMPToken) auto const& objects = output->as_object().at("state").as_array(); EXPECT_EQ(objects.front().at("LedgerEntryType").as_string(), "MPToken"); }); -} \ No newline at end of file +} diff --git a/unittests/util/MockBackend.hpp b/unittests/util/MockBackend.hpp index 4aea50b2f..d420ecd95 100644 --- a/unittests/util/MockBackend.hpp +++ b/unittests/util/MockBackend.hpp @@ -192,12 +192,7 @@ struct MockBackend : public BackendInterface { MOCK_METHOD(bool, doFinishWrites, (), (override)); - MOCK_METHOD( - void, - writeMPTHolders, - ((std::vector> const&)), - (override) - ); + MOCK_METHOD(void, writeMPTHolders, ((std::vector const&)), (override)); MOCK_METHOD( MPTHoldersAndCursor, diff --git a/unittests/util/TestObject.cpp b/unittests/util/TestObject.cpp index 72376ef13..dc5f0b050 100644 --- a/unittests/util/TestObject.cpp +++ b/unittests/util/TestObject.cpp @@ -1045,4 +1045,4 @@ CreateMPTokenObject( mptoken.setFieldU64(ripple::sfLockedAmount, lockedAmount); return mptoken; -} \ No newline at end of file +} From 5df20682f8b7b9812f3935a5c6c40957d6ae46f5 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Thu, 28 Mar 2024 12:03:36 -0400 Subject: [PATCH 57/64] predicate const ref --- src/data/CassandraBackend.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/CassandraBackend.hpp b/src/data/CassandraBackend.hpp index 11d25688b..4b6d990b0 100644 --- a/src/data/CassandraBackend.hpp +++ b/src/data/CassandraBackend.hpp @@ -580,7 +580,7 @@ class BasicCassandraBackend : public BackendInterface { auto mptObjects = doFetchLedgerObjects(mptKeys, ledgerSequence, yield); - auto it = std::remove_if(mptObjects.begin(), mptObjects.end(), [](Blob mpt) { return mpt.size() == 0; }); + auto it = std::remove_if(mptObjects.begin(), mptObjects.end(), [](Blob const& mpt) { return mpt.size() == 0; }); mptObjects.erase(it, mptObjects.end()); From 703500f1560eb7b80203a551a4b7aac695cc8e2a Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Mon, 8 Jul 2024 11:32:16 -0400 Subject: [PATCH 58/64] clang --- src/rpc/handlers/LedgerEntry.hpp | 18 ++++++++++-------- tests/common/util/TestObject.cpp | 1 - tests/unit/rpc/handlers/LedgerDataTests.cpp | 6 ++++-- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/rpc/handlers/LedgerEntry.hpp b/src/rpc/handlers/LedgerEntry.hpp index 6128d70f1..4ca90eb5c 100644 --- a/src/rpc/handlers/LedgerEntry.hpp +++ b/src/rpc/handlers/LedgerEntry.hpp @@ -315,14 +315,16 @@ class LedgerEntryHandler { }}}, {JS(mpt_issuance), validation::CustomValidators::Uint192HexStringValidator}, {JS(mptoken), - validation::Type{}, - meta::IfType{malformedRequestHexStringValidator}, - meta::IfType{ - meta::Section{ - {JS(account), validation::Required{}, validation::CustomValidators::AccountBase58Validator}, - {JS(mpt_issuance_id), validation::Required{}, validation::CustomValidators::Uint192HexStringValidator}, - }, - }}, + validation::Type{}, + meta::IfType{malformedRequestHexStringValidator}, + meta::IfType{ + meta::Section{ + {JS(account), validation::Required{}, validation::CustomValidators::AccountBase58Validator}, + {JS(mpt_issuance_id), + validation::Required{}, + validation::CustomValidators::Uint192HexStringValidator}, + }, + }}, {JS(ledger), check::Deprecated{}}, }; diff --git a/tests/common/util/TestObject.cpp b/tests/common/util/TestObject.cpp index e9bdb0c70..ab85ffbe3 100644 --- a/tests/common/util/TestObject.cpp +++ b/tests/common/util/TestObject.cpp @@ -1137,7 +1137,6 @@ CreateMPTokenObject( mptoken.setFieldU64(ripple::sfLockedAmount, lockedAmount); return mptoken; - } ripple::STObject diff --git a/tests/unit/rpc/handlers/LedgerDataTests.cpp b/tests/unit/rpc/handlers/LedgerDataTests.cpp index da34f60ee..b29d2eb12 100644 --- a/tests/unit/rpc/handlers/LedgerDataTests.cpp +++ b/tests/unit/rpc/handlers/LedgerDataTests.cpp @@ -734,7 +734,8 @@ TEST_F(RPCLedgerDataHandlerTest, TypeFilterMPTIssuance) backend->setRange(RANGEMIN, RANGEMAX); EXPECT_CALL(*backend, fetchLedgerBySequence).Times(1); - ON_CALL(*backend, fetchLedgerBySequence(RANGEMAX, _)).WillByDefault(Return(CreateLedgerHeader(LEDGERHASH, RANGEMAX))); + ON_CALL(*backend, fetchLedgerBySequence(RANGEMAX, _)) + .WillByDefault(Return(CreateLedgerHeader(LEDGERHASH, RANGEMAX))); std::vector bbs; EXPECT_CALL(*backend, doFetchSuccessorKey).Times(1); @@ -777,7 +778,8 @@ TEST_F(RPCLedgerDataHandlerTest, TypeFilterMPToken) backend->setRange(RANGEMIN, RANGEMAX); EXPECT_CALL(*backend, fetchLedgerBySequence).Times(1); - ON_CALL(*backend, fetchLedgerBySequence(RANGEMAX, _)).WillByDefault(Return(CreateLedgerHeader(LEDGERHASH, RANGEMAX))); + ON_CALL(*backend, fetchLedgerBySequence(RANGEMAX, _)) + .WillByDefault(Return(CreateLedgerHeader(LEDGERHASH, RANGEMAX))); std::vector bbs; EXPECT_CALL(*backend, doFetchSuccessorKey).Times(1); From 8b8cc9b9d89036aa71110a5f5b5f42ff9d6032b4 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Mon, 8 Jul 2024 12:15:24 -0400 Subject: [PATCH 59/64] add MPT amendment --- src/data/AmendmentCenter.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/data/AmendmentCenter.hpp b/src/data/AmendmentCenter.hpp index 2e2951685..675425f0e 100644 --- a/src/data/AmendmentCenter.hpp +++ b/src/data/AmendmentCenter.hpp @@ -121,6 +121,7 @@ struct Amendments { REGISTER(NFTokenMintOffer); REGISTER(fixReducedOffersV2); REGISTER(fixEnforceNFTokenTrustline); + REGISTER(MPTokensV1); // Obsolete but supported by libxrpl REGISTER(CryptoConditionsSuite); From 1a03ae015951162023ab0e6d4891eb558dac8fa2 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Thu, 5 Sep 2024 13:40:58 -0400 Subject: [PATCH 60/64] make compile --- conanfile.py | 2 +- src/rpc/RPCHelpers.cpp | 3 ++- src/rpc/RPCHelpers.hpp | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/conanfile.py b/conanfile.py index a8b895d92..96d1c33ab 100644 --- a/conanfile.py +++ b/conanfile.py @@ -28,7 +28,7 @@ class Clio(ConanFile): 'protobuf/3.21.9', 'grpc/1.50.1', 'openssl/1.1.1u', - 'xrpl-mpt/2.3.0-b1', + 'xrpl-mpt/2.3.0-b2', 'libbacktrace/cci.20210118' ] diff --git a/src/rpc/RPCHelpers.cpp b/src/rpc/RPCHelpers.cpp index a7250f68e..62e6447c6 100644 --- a/src/rpc/RPCHelpers.cpp +++ b/src/rpc/RPCHelpers.cpp @@ -64,6 +64,7 @@ #include #include #include +#include #include #include #include @@ -134,7 +135,7 @@ parseAccountCursor(std::optional jsonCursor) return AccountCursor({cursorIndex, startHint}); } -std::optional +std::optional getDeliveredAmount( std::shared_ptr const& txn, std::shared_ptr const& meta, diff --git a/src/rpc/RPCHelpers.hpp b/src/rpc/RPCHelpers.hpp index 0e2f53ec0..1ecba927f 100644 --- a/src/rpc/RPCHelpers.hpp +++ b/src/rpc/RPCHelpers.hpp @@ -54,6 +54,7 @@ #include #include #include +#include #include #include #include @@ -729,7 +730,7 @@ canHaveDeliveredAmount( * @param date The date of the ledger * @return The delivered amount or std::nullopt if not available */ -std::optional +std::optional getDeliveredAmount( std::shared_ptr const& txn, std::shared_ptr const& meta, From d877e5d60bacb659ff9923dfaf1d22cd9d0bb1ec Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Tue, 8 Oct 2024 11:09:07 -0400 Subject: [PATCH 61/64] update clio to compile with latest rippled --- conanfile.py | 2 +- src/rpc/RPCHelpers.cpp | 11 +++++------ src/rpc/RPCHelpers.hpp | 3 +-- src/rpc/handlers/MPTHolders.cpp | 8 ++------ tests/common/util/TestObject.cpp | 10 +--------- tests/common/util/TestObject.hpp | 7 +------ tests/unit/rpc/handlers/AccountObjectsTests.cpp | 4 ++-- tests/unit/rpc/handlers/LedgerDataTests.cpp | 4 ++-- tests/unit/rpc/handlers/LedgerEntryTests.cpp | 12 ++++++------ tests/unit/rpc/handlers/MPTHoldersTests.cpp | 3 +-- 10 files changed, 22 insertions(+), 42 deletions(-) diff --git a/conanfile.py b/conanfile.py index 96d1c33ab..11cd764de 100644 --- a/conanfile.py +++ b/conanfile.py @@ -28,7 +28,7 @@ class Clio(ConanFile): 'protobuf/3.21.9', 'grpc/1.50.1', 'openssl/1.1.1u', - 'xrpl-mpt/2.3.0-b2', + 'xrpl-mpt/2.3.0-b4', 'libbacktrace/cci.20210118' ] diff --git a/src/rpc/RPCHelpers.cpp b/src/rpc/RPCHelpers.cpp index 957cd6c29..e4a82a7f1 100644 --- a/src/rpc/RPCHelpers.cpp +++ b/src/rpc/RPCHelpers.cpp @@ -66,7 +66,6 @@ #include #include #include -#include #include #include #include @@ -137,7 +136,7 @@ parseAccountCursor(std::optional jsonCursor) return AccountCursor({cursorIndex, startHint}); } -std::optional +std::optional getDeliveredAmount( std::shared_ptr const& txn, std::shared_ptr const& meta, @@ -333,7 +332,7 @@ getMPTIssuanceID(std::shared_ptr const& meta) continue; auto const& mptNode = node.peekAtField(ripple::sfNewFields).downcast(); - return ripple::getMptID(mptNode[ripple::sfIssuer], mptNode[ripple::sfSequence]); + return ripple::makeMptID(mptNode[ripple::sfSequence], mptNode[ripple::sfIssuer]); } return {}; @@ -420,7 +419,7 @@ toJson(ripple::SLE const& sle) // if object type if mpt issuance, inject synthetic mpt id if (sle.getType() == ripple::ltMPTOKEN_ISSUANCE) value.as_object()[JS(mpt_issuance_id)] = - ripple::to_string(ripple::getMptID(sle[ripple::sfIssuer], sle[ripple::sfSequence])); + ripple::to_string(ripple::makeMptID(sle[ripple::sfSequence], sle[ripple::sfIssuer])); return value.as_object(); } @@ -1018,7 +1017,7 @@ accountHolds( auto const blob = backend.fetchLedgerObject(key, sequence, yield); if (!blob) { - amount.clear({currency, issuer}); + amount.clear(ripple::Issue{currency, issuer}); return amount; } @@ -1026,7 +1025,7 @@ accountHolds( ripple::SLE const sle{it, key}; if (zeroIfFrozen && isFrozen(backend, sequence, account, currency, issuer, yield)) { - amount.clear(ripple::Issue(currency, issuer)); + amount.clear(ripple::Issue{currency, issuer}); } else { amount = sle.getFieldAmount(ripple::sfBalance); if (account > issuer) { diff --git a/src/rpc/RPCHelpers.hpp b/src/rpc/RPCHelpers.hpp index d2db3669b..1ffd52cb8 100644 --- a/src/rpc/RPCHelpers.hpp +++ b/src/rpc/RPCHelpers.hpp @@ -54,7 +54,6 @@ #include #include #include -#include #include #include #include @@ -740,7 +739,7 @@ canHaveDeliveredAmount( * @param date The date of the ledger * @return The delivered amount or std::nullopt if not available */ -std::optional +std::optional getDeliveredAmount( std::shared_ptr const& txn, std::shared_ptr const& meta, diff --git a/src/rpc/handlers/MPTHolders.cpp b/src/rpc/handlers/MPTHolders.cpp index a2348b4f7..326859d05 100644 --- a/src/rpc/handlers/MPTHolders.cpp +++ b/src/rpc/handlers/MPTHolders.cpp @@ -80,12 +80,8 @@ MPTHoldersHandler::process(MPTHoldersHandler::Input input, Context const& ctx) c mptJson[JS(account)] = toBase58(sle[ripple::sfAccount]); mptJson[JS(flags)] = sle.getFlags(); - mptJson["mpt_amount"] = toBoostJson(ripple::STUInt64{sle[ripple::sfMPTAmount]}.getJson(JsonOptions::none)); - - if (sle[ripple::sfLockedAmount]) - mptJson["locked_amount"] = - toBoostJson(ripple::STUInt64{sle[ripple::sfLockedAmount]}.getJson(JsonOptions::none)); - + mptJson["mpt_amount"] = + toBoostJson(ripple::STUInt64{ripple::sfMPTAmount, sle[ripple::sfMPTAmount]}.getJson(JsonOptions::none)); mptJson["mptoken_index"] = ripple::to_string(ripple::keylet::mptoken(mptID, sle[ripple::sfAccount]).key); output.mpts.push_back(mptJson); diff --git a/tests/common/util/TestObject.cpp b/tests/common/util/TestObject.cpp index 5aad3f68a..cc12b2df1 100644 --- a/tests/common/util/TestObject.cpp +++ b/tests/common/util/TestObject.cpp @@ -1124,12 +1124,7 @@ CreateMPTIssuanceObject(std::string_view accountId, std::uint32_t seq, std::stri } ripple::STObject -CreateMPTokenObject( - std::string_view accountId, - ripple::uint192 issuanceID, - std::uint64_t mptAmount, - std::uint64_t lockedAmount -) +CreateMPTokenObject(std::string_view accountId, ripple::uint192 issuanceID, std::uint64_t mptAmount) { ripple::STObject mptoken(ripple::sfLedgerEntry); mptoken.setAccountID(ripple::sfAccount, GetAccountIDWithString(accountId)); @@ -1143,9 +1138,6 @@ CreateMPTokenObject( if (mptAmount) mptoken.setFieldU64(ripple::sfMPTAmount, mptAmount); - if (lockedAmount) - mptoken.setFieldU64(ripple::sfLockedAmount, lockedAmount); - return mptoken; } diff --git a/tests/common/util/TestObject.hpp b/tests/common/util/TestObject.hpp index b77a37408..bf9bc107e 100644 --- a/tests/common/util/TestObject.hpp +++ b/tests/common/util/TestObject.hpp @@ -397,12 +397,7 @@ CreateLPTCurrency(std::string_view assetCurrency, std::string_view asset2Currenc CreateMPTIssuanceObject(std::string_view accountId, std::uint32_t seq, std::string_view metadata); [[nodiscard]] ripple::STObject -CreateMPTokenObject( - std::string_view accountId, - ripple::uint192 issuanceID, - std::uint64_t mptAmount = 1, - std::uint64_t lockedAmount = 0 -); +CreateMPTokenObject(std::string_view accountId, ripple::uint192 issuanceID, std::uint64_t mptAmount = 1); [[nodiscard]] ripple::STObject CreateOraclePriceData( diff --git a/tests/unit/rpc/handlers/AccountObjectsTests.cpp b/tests/unit/rpc/handlers/AccountObjectsTests.cpp index c4fedbaff..05f52455a 100644 --- a/tests/unit/rpc/handlers/AccountObjectsTests.cpp +++ b/tests/unit/rpc/handlers/AccountObjectsTests.cpp @@ -1671,7 +1671,7 @@ TEST_F(RPCAccountObjectsHandlerTest, TypeFilterMPTIssuanceType) // make sure mptID is synethetically parsed if object is mptIssuance EXPECT_EQ( accountObjects.front().at("mpt_issuance_id").as_string(), - ripple::to_string(ripple::getMptID(GetAccountIDWithString(ACCOUNT), 2)) + ripple::to_string(ripple::makeMptID(2, GetAccountIDWithString(ACCOUNT))) ); }); } @@ -1696,7 +1696,7 @@ TEST_F(RPCAccountObjectsHandlerTest, TypeFilterMPTokenType) std::vector bbs; // put 1 mpt issuance - auto const mptokenObject = CreateMPTokenObject(ACCOUNT, ripple::getMptID(GetAccountIDWithString(ACCOUNT), 2)); + auto const mptokenObject = CreateMPTokenObject(ACCOUNT, ripple::makeMptID(2, GetAccountIDWithString(ACCOUNT))); bbs.push_back(mptokenObject.getSerializer().peekData()); EXPECT_CALL(*backend, doFetchLedgerObjects).WillOnce(Return(bbs)); diff --git a/tests/unit/rpc/handlers/LedgerDataTests.cpp b/tests/unit/rpc/handlers/LedgerDataTests.cpp index b29d2eb12..1c7842b57 100644 --- a/tests/unit/rpc/handlers/LedgerDataTests.cpp +++ b/tests/unit/rpc/handlers/LedgerDataTests.cpp @@ -768,7 +768,7 @@ TEST_F(RPCLedgerDataHandlerTest, TypeFilterMPTIssuance) // make sure mptID is synethetically parsed if object is mptIssuance EXPECT_EQ( objects.front().at("mpt_issuance_id").as_string(), - ripple::to_string(ripple::getMptID(GetAccountIDWithString(ACCOUNT), 2)) + ripple::to_string(ripple::makeMptID(2, GetAccountIDWithString(ACCOUNT))) ); }); } @@ -785,7 +785,7 @@ TEST_F(RPCLedgerDataHandlerTest, TypeFilterMPToken) EXPECT_CALL(*backend, doFetchSuccessorKey).Times(1); ON_CALL(*backend, doFetchSuccessorKey(_, RANGEMAX, _)).WillByDefault(Return(ripple::uint256{INDEX2})); - auto const mptoken = CreateMPTokenObject(ACCOUNT, ripple::getMptID(GetAccountIDWithString(ACCOUNT), 2)); + auto const mptoken = CreateMPTokenObject(ACCOUNT, ripple::makeMptID(2, GetAccountIDWithString(ACCOUNT))); bbs.push_back(mptoken.getSerializer().peekData()); ON_CALL(*backend, doFetchLedgerObjects).WillByDefault(Return(bbs)); diff --git a/tests/unit/rpc/handlers/LedgerEntryTests.cpp b/tests/unit/rpc/handlers/LedgerEntryTests.cpp index abf20be73..be6d94abb 100644 --- a/tests/unit/rpc/handlers/LedgerEntryTests.cpp +++ b/tests/unit/rpc/handlers/LedgerEntryTests.cpp @@ -2433,9 +2433,9 @@ generateTestValuesForNormalPathTest() "binary": true, "mpt_issuance": "{}" }})", - ripple::to_string(ripple::getMptID(account1, 2)) + ripple::to_string(ripple::makeMptID(2, account1)) ), - ripple::keylet::mptIssuance(ripple::getMptID(account1, 2)).key, + ripple::keylet::mptIssuance(ripple::makeMptID(2, account1)).key, CreateMPTIssuanceObject(ACCOUNT, 2, "metadata") }, NormalPathTestBundle{ @@ -2448,7 +2448,7 @@ generateTestValuesForNormalPathTest() INDEX1 ), ripple::uint256{INDEX1}, - CreateMPTokenObject(ACCOUNT, ripple::getMptID(account1, 2)) + CreateMPTokenObject(ACCOUNT, ripple::makeMptID(2, account1)) }, NormalPathTestBundle{ "MPTokenViaObject", @@ -2461,10 +2461,10 @@ generateTestValuesForNormalPathTest() }} }})", ACCOUNT, - ripple::to_string(ripple::getMptID(account1, 2)) + ripple::to_string(ripple::makeMptID(2, account1)) ), - ripple::keylet::mptoken(ripple::getMptID(account1, 2), account1).key, - CreateMPTokenObject(ACCOUNT, ripple::getMptID(account1, 2)) + ripple::keylet::mptoken(ripple::makeMptID(2, account1), account1).key, + CreateMPTokenObject(ACCOUNT, ripple::makeMptID(2, account1)) }, }; } diff --git a/tests/unit/rpc/handlers/MPTHoldersTests.cpp b/tests/unit/rpc/handlers/MPTHoldersTests.cpp index 2b0f0f63a..984b71535 100644 --- a/tests/unit/rpc/handlers/MPTHoldersTests.cpp +++ b/tests/unit/rpc/handlers/MPTHoldersTests.cpp @@ -421,7 +421,6 @@ TEST_F(RPCMPTHoldersHandlerTest, CustomAmounts) "account": "rrnAZCqMahreZrKMcZU3t2DZ6yUndT4ubN", "flags": 0, "mpt_amount": "0", - "locked_amount": "1", "mptoken_index": "D137F2E5A5767A06CB7A8F060ADE442A30CFF95028E1AF4B8767E3A56877205A" }}], "validated": true @@ -435,7 +434,7 @@ TEST_F(RPCMPTHoldersHandlerTest, CustomAmounts) auto const issuanceKk = ripple::keylet::mptIssuance(ripple::uint192(MPTID)).key; ON_CALL(*backend, doFetchLedgerObject(issuanceKk, 30, _)).WillByDefault(Return(Blob{'f', 'a', 'k', 'e'})); - auto const mptoken = CreateMPTokenObject(HOLDER1_ACCOUNT, ripple::uint192(MPTID), 0, 1); + auto const mptoken = CreateMPTokenObject(HOLDER1_ACCOUNT, ripple::uint192(MPTID), 0); std::vector const mpts = {mptoken.getSerializer().peekData()}; ON_CALL(*backend, fetchMPTHolders).WillByDefault(Return(MPTHoldersAndCursor{mpts, {}})); EXPECT_CALL( From cce63dc4c30667a0283b29816887c5215527c531 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Wed, 9 Oct 2024 13:48:39 -0400 Subject: [PATCH 62/64] comments --- src/rpc/RPCHelpers.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rpc/RPCHelpers.cpp b/src/rpc/RPCHelpers.cpp index e4a82a7f1..486c6e391 100644 --- a/src/rpc/RPCHelpers.cpp +++ b/src/rpc/RPCHelpers.cpp @@ -348,8 +348,7 @@ getMPTIssuanceID(std::shared_ptr const& meta) static bool canHaveMPTIssuanceID(std::shared_ptr const& txn, std::shared_ptr const& meta) { - ripple::TxType const tt{txn->getTxnType()}; - if (tt != ripple::ttMPTOKEN_ISSUANCE_CREATE) + if (txn->getTxnType() != ripple::ttMPTOKEN_ISSUANCE_CREATE) return false; if (meta->getResultTER() != ripple::tesSUCCESS) @@ -373,6 +372,7 @@ insertMPTIssuanceID( return true; } + assert(false); return false; } From a9b14304275738532894b47babd02a1434a9ba0b Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Thu, 17 Oct 2024 14:05:08 -0400 Subject: [PATCH 63/64] fix API response discrepancies --- src/rpc/RPCHelpers.cpp | 6 --- src/rpc/handlers/LedgerEntry.hpp | 29 +++++++++--- tests/unit/rpc/handlers/LedgerEntryTests.cpp | 49 ++++++++++++++++++-- 3 files changed, 68 insertions(+), 16 deletions(-) diff --git a/src/rpc/RPCHelpers.cpp b/src/rpc/RPCHelpers.cpp index 486c6e391..3687933eb 100644 --- a/src/rpc/RPCHelpers.cpp +++ b/src/rpc/RPCHelpers.cpp @@ -415,12 +415,6 @@ toJson(ripple::SLE const& sle) value.as_object()["urlgravatar"] = str(boost::format("http://www.gravatar.com/avatar/%s") % md5); } } - - // if object type if mpt issuance, inject synthetic mpt id - if (sle.getType() == ripple::ltMPTOKEN_ISSUANCE) - value.as_object()[JS(mpt_issuance_id)] = - ripple::to_string(ripple::makeMptID(sle[ripple::sfSequence], sle[ripple::sfIssuer])); - return value.as_object(); } diff --git a/src/rpc/handlers/LedgerEntry.hpp b/src/rpc/handlers/LedgerEntry.hpp index 9dc2b11fc..dfb7991c7 100644 --- a/src/rpc/handlers/LedgerEntry.hpp +++ b/src/rpc/handlers/LedgerEntry.hpp @@ -318,16 +318,33 @@ class LedgerEntryHandler { }, meta::WithCustomError{modifiers::ToNumber{}, Status(ClioError::rpcMALFORMED_ORACLE_DOCUMENT_ID)}}, }}}, - {JS(mpt_issuance), validation::CustomValidators::Uint192HexStringValidator}, + {JS(mpt_issuance), + meta::WithCustomError{ + validation::CustomValidators::Uint192HexStringValidator, Status(ClioError::rpcMALFORMED_REQUEST) + }}, {JS(mptoken), - validation::Type{}, + meta::WithCustomError{ + validation::Type{}, Status(ClioError::rpcMALFORMED_REQUEST) + }, meta::IfType{malformedRequestHexStringValidator}, meta::IfType{ meta::Section{ - {JS(account), validation::Required{}, validation::CustomValidators::AccountBase58Validator}, - {JS(mpt_issuance_id), - validation::Required{}, - validation::CustomValidators::Uint192HexStringValidator}, + { + JS(account), + meta::WithCustomError{validation::Required{}, Status(ClioError::rpcMALFORMED_REQUEST)}, + meta::WithCustomError{ + validation::CustomValidators::AccountBase58Validator, + Status(ClioError::rpcMALFORMED_ADDRESS) + }, + }, + { + JS(mpt_issuance_id), + meta::WithCustomError{validation::Required{}, Status(ClioError::rpcMALFORMED_REQUEST)}, + meta::WithCustomError{ + validation::CustomValidators::Uint192HexStringValidator, + Status(ClioError::rpcMALFORMED_REQUEST) + }, + }, }, }}, {JS(ledger), check::Deprecated{}}, diff --git a/tests/unit/rpc/handlers/LedgerEntryTests.cpp b/tests/unit/rpc/handlers/LedgerEntryTests.cpp index be6d94abb..ad8520aa3 100644 --- a/tests/unit/rpc/handlers/LedgerEntryTests.cpp +++ b/tests/unit/rpc/handlers/LedgerEntryTests.cpp @@ -1764,8 +1764,16 @@ generateTestValuesForParametersTest() R"({ "mpt_issuance": "invalid" })", - "invalidParams", - "mpt_issuanceMalformed" + "malformedRequest", + "Malformed request." + }, + ParamTestCaseBundle{ + "InvalidMPTIssuanceType", + R"({ + "mpt_issuance": 0 + })", + "malformedRequest", + "Malformed request." }, ParamTestCaseBundle{ "InvalidMPTokenStringIndex", @@ -1775,6 +1783,16 @@ generateTestValuesForParametersTest() "malformedRequest", "Malformed request." }, + ParamTestCaseBundle{ + "InvalidMPTokenObject", + fmt::format( + R"({{ + "mptoken": {{}} + }})" + ), + "malformedRequest", + "Malformed request." + }, ParamTestCaseBundle{ "MissingMPTokenID", fmt::format( @@ -1785,8 +1803,31 @@ generateTestValuesForParametersTest() }})", ACCOUNT ), - "invalidParams", - "Required field \'mpt_issuance_id\' missing" + "malformedRequest", + "Malformed request." + }, + ParamTestCaseBundle{ + "InvalidMPTokenAccount", + fmt::format( + R"({{ + "mptoken": {{ + "mpt_issuance_id": "0000019315EABA24E6135A4B5CE2899E0DA791206413B33D", + "account": 1 + }} + }})" + ), + "malformedAddress", + "Malformed address." + }, + ParamTestCaseBundle{ + "InvalidMPTokenType", + fmt::format( + R"({{ + "mptoken": 0 + }})" + ), + "malformedRequest", + "Malformed request." }, }; } From 7bd4472338409de535461ad365ed8fe83a3b810c Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Thu, 17 Oct 2024 16:48:42 -0400 Subject: [PATCH 64/64] ledger entry test --- tests/unit/rpc/handlers/LedgerEntryTests.cpp | 50 ++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/tests/unit/rpc/handlers/LedgerEntryTests.cpp b/tests/unit/rpc/handlers/LedgerEntryTests.cpp index ad8520aa3..d39783d6a 100644 --- a/tests/unit/rpc/handlers/LedgerEntryTests.cpp +++ b/tests/unit/rpc/handlers/LedgerEntryTests.cpp @@ -3054,6 +3054,56 @@ TEST_F(RPCLedgerEntryTest, ObjectSeqNotExist) }); } +// this testcase will test the if response includes synthetic mpt_issuance_id +TEST_F(RPCLedgerEntryTest, SyntheticMPTIssuanceID) +{ + static auto constexpr OUT = R"({ + "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_index":30, + "validated":true, + "index":"FD7E7EFAE2A20E75850D0E0590B205E2F74DC472281768CD6E03988069816336", + "node":{ + "Flags":0, + "Issuer":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "LedgerEntryType":"MPTokenIssuance", + "MPTokenMetadata":"6D65746164617461", + "MaximumAmount":"0", + "OutstandingAmount":"0", + "OwnerNode":"0", + "PreviousTxnID":"0000000000000000000000000000000000000000000000000000000000000000", + "PreviousTxnLgrSeq":0, + "Sequence":2, + "index":"FD7E7EFAE2A20E75850D0E0590B205E2F74DC472281768CD6E03988069816336", + "mpt_issuance_id":"000000024B4E9C06F24296074F7BC48F92A97916C6DC5EA9" + } + })"; + + auto const mptId = ripple::makeMptID(2, GetAccountIDWithString(ACCOUNT)); + + backend->setRange(RANGEMIN, RANGEMAX); + // return valid ledgerHeader + auto const ledgerHeader = CreateLedgerHeader(LEDGERHASH, RANGEMAX); + EXPECT_CALL(*backend, fetchLedgerBySequence(RANGEMAX, _)).WillRepeatedly(Return(ledgerHeader)); + + // return valid ledger entry which can be deserialized + auto const ledgerEntry = CreateMPTIssuanceObject(ACCOUNT, 2, "metadata"); + EXPECT_CALL(*backend, doFetchLedgerObject(ripple::keylet::mptIssuance(mptId).key, RANGEMAX, _)) + .WillRepeatedly(Return(ledgerEntry.getSerializer().peekData())); + + runSpawn([&, this](auto yield) { + auto const handler = AnyHandler{LedgerEntryHandler{backend}}; + auto const req = json::parse(fmt::format( + R"({{ + "mpt_issuance": "{}" + }})", + ripple::to_string(mptId) + )); + auto const output = handler.process(req, Context{yield}); + ASSERT_TRUE(output); + EXPECT_EQ(*output.result, json::parse(OUT)); + }); +} + using RPCLedgerEntryDeathTest = RPCLedgerEntryTest; TEST_F(RPCLedgerEntryDeathTest, RangeNotAvailable)