diff --git a/Builds/CMake/RippledCore.cmake b/Builds/CMake/RippledCore.cmake index c3fd51bc7..cc3441af5 100644 --- a/Builds/CMake/RippledCore.cmake +++ b/Builds/CMake/RippledCore.cmake @@ -983,6 +983,7 @@ if (tests) src/test/rpc/AccountLinesRPC_test.cpp src/test/rpc/AccountObjects_test.cpp src/test/rpc/AccountOffers_test.cpp + src/test/rpc/AccountNamespace_test.cpp src/test/rpc/AccountSet_test.cpp src/test/rpc/AccountTx_test.cpp src/test/rpc/AmendmentBlocked_test.cpp diff --git a/Builds/levelization/results/loops.txt b/Builds/levelization/results/loops.txt index 917cd45b4..209e0e1fa 100644 --- a/Builds/levelization/results/loops.txt +++ b/Builds/levelization/results/loops.txt @@ -52,6 +52,9 @@ Loop: ripple.overlay ripple.rpc Loop: test.app test.jtx test.app > test.jtx +Loop: test.app test.rpc + test.rpc == test.app + Loop: test.jtx test.toplevel test.toplevel > test.jtx diff --git a/Builds/levelization/results/ordering.txt b/Builds/levelization/results/ordering.txt index ddb0f6083..12df1a86e 100644 --- a/Builds/levelization/results/ordering.txt +++ b/Builds/levelization/results/ordering.txt @@ -90,7 +90,6 @@ test.app > ripple.overlay test.app > ripple.protocol test.app > ripple.resource test.app > ripple.rpc -test.app > test.rpc test.app > test.toplevel test.app > test.unit_test test.basics > ripple.basics diff --git a/src/ripple/protocol/impl/ErrorCodes.cpp b/src/ripple/protocol/impl/ErrorCodes.cpp index a1844890c..bc31b21b8 100644 --- a/src/ripple/protocol/impl/ErrorCodes.cpp +++ b/src/ripple/protocol/impl/ErrorCodes.cpp @@ -108,6 +108,7 @@ constexpr static ErrorInfo unorderedErrorInfos[]{ {rpcSTREAM_MALFORMED, "malformedStream", "Stream malformed.", 400}, {rpcTOO_BUSY, "tooBusy", "The server is too busy to help you now.", 503}, {rpcTXN_NOT_FOUND, "txnNotFound", "Transaction not found.", 404}, + {rpcNAMESPACE_NOT_FOUND, "namespaceNotFound", "Namespace not found.", 404}, {rpcUNKNOWN_COMMAND, "unknownCmd", "Unknown method.", 405}}; // clang-format on diff --git a/src/ripple/protocol/jss.h b/src/ripple/protocol/jss.h index b5ed722e2..40626801d 100644 --- a/src/ripple/protocol/jss.h +++ b/src/ripple/protocol/jss.h @@ -161,6 +161,7 @@ JSS(account_data); // out: AccountInfo JSS(account_flags); // out: AccountInfo JSS(account_hash); // out: LedgerToJson JSS(account_id); // out: WalletPropose +JSS(account_namespace); // out: AccountNamespace JSS(account_nfts); // out: AccountNFTs JSS(account_objects); // out: AccountObjects JSS(account_root); // in: LedgerEntry @@ -353,6 +354,7 @@ JSS(id); // websocket. JSS(ident); // in: AccountCurrencies, AccountInfo, // OwnerInfo JSS(ignore_default); // in: AccountLines +JSS(import_vlseq); // in: LedgerEntry JSS(inLedger); // out: tx/Transaction JSS(in_queue); JSS(inbound); // out: PeerImp @@ -699,6 +701,7 @@ JSS(TRANSACTION_FLAGS_INDICIES); // out: RPC server_definitions JSS(type_hex); // out: STPathSet JSS(unl); // out: UnlList JSS(unlimited); // out: Connection.h +JSS(unl_report); // in: LedgerEntry JSS(uptime); // out: GetCounts JSS(uri); // out: ValidatorSites JSS(uri_token); // in: LedgerEntry diff --git a/src/ripple/rpc/handlers/AccountInfo.cpp b/src/ripple/rpc/handlers/AccountInfo.cpp index 524777e38..5b85fc257 100644 --- a/src/ripple/rpc/handlers/AccountInfo.cpp +++ b/src/ripple/rpc/handlers/AccountInfo.cpp @@ -78,7 +78,7 @@ doAccountInfo(RPC::JsonContext& context) return jvAccepted; static constexpr std:: - array, 9> + array, 10> lsFlags{ {{"defaultRipple", lsfDefaultRipple}, {"depositAuth", lsfDepositAuth}, @@ -88,6 +88,7 @@ doAccountInfo(RPC::JsonContext& context) {"noFreeze", lsfNoFreeze}, {"passwordSpent", lsfPasswordSpent}, {"requireAuthorization", lsfRequireAuth}, + {"tshCollect", lsfTshCollect}, {"requireDestinationTag", lsfRequireDestTag}}}; static constexpr std:: diff --git a/src/ripple/rpc/handlers/AccountNamespace.cpp b/src/ripple/rpc/handlers/AccountNamespace.cpp index 625fa15e9..c7c969867 100644 --- a/src/ripple/rpc/handlers/AccountNamespace.cpp +++ b/src/ripple/rpc/handlers/AccountNamespace.cpp @@ -42,7 +42,6 @@ namespace ripple { namespace_id: ledger_hash: // optional ledger_index: // optional - type: // optional, defaults to all account objects types limit: // optional marker: // optional, resume previous query } diff --git a/src/ripple/rpc/handlers/LedgerEntry.cpp b/src/ripple/rpc/handlers/LedgerEntry.cpp index a6e718e39..aa5ffb02a 100644 --- a/src/ripple/rpc/handlers/LedgerEntry.cpp +++ b/src/ripple/rpc/handlers/LedgerEntry.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -231,6 +232,41 @@ doLedgerEntry(RPC::JsonContext& context) uNodeIndex = keylet::emittedTxn(uNodeIndex).key; } } + else if (context.params.isMember(jss::import_vlseq)) + { + expectedType = ltIMPORT_VLSEQ; + if (!context.params[jss::import_vlseq].isObject()) + { + if (!uNodeIndex.parseHex( + context.params[jss::import_vlseq].asString())) + { + uNodeIndex = beast::zero; + jvResult[jss::error] = "malformedRequest"; + } + } + else if ( + !context.params[jss::import_vlseq].isMember(jss::public_key) || + !context.params[jss::import_vlseq][jss::public_key].isString()) + { + jvResult[jss::error] = "malformedRequest"; + } + else + { + auto const pkHex = strUnHex( + context.params[jss::import_vlseq][jss::public_key].asString()); + auto const pkSlice = makeSlice(*pkHex); + if (!publicKeyType(pkSlice)) + { + uNodeIndex = beast::zero; + jvResult[jss::error] = "malformedRequest"; + } + else + { + auto const pk = PublicKey(pkSlice); + uNodeIndex = keylet::import_vlseq(pk).key; + } + } + } else if (context.params.isMember(jss::offer)) { expectedType = ltOFFER; @@ -277,11 +313,32 @@ doLedgerEntry(RPC::JsonContext& context) { expectedType = ltURI_TOKEN; - if (!uNodeIndex.parseHex(context.params[jss::uri_token].asString())) + if (!context.params[jss::uri_token].isObject()) + { + if (!uNodeIndex.parseHex(context.params[jss::uri_token].asString())) + { + uNodeIndex = beast::zero; + jvResult[jss::error] = "malformedRequest"; + } + } + else if ( + !context.params[jss::uri_token].isMember(jss::account) || + !context.params[jss::uri_token].isMember(jss::uri)) { - uNodeIndex = beast::zero; jvResult[jss::error] = "malformedRequest"; } + else + { + auto const id = parseBase58( + context.params[jss::uri_token][jss::account].asString()); + auto const strUri = + context.params[jss::uri_token][jss::uri].asString(); + Blob raw = Blob(strUri.begin(), strUri.end()); + if (!id) + jvResult[jss::error] = "malformedAddress"; + else + uNodeIndex = keylet::uritoken(*id, raw).key; + } } else if (context.params.isMember(jss::ripple_state)) { diff --git a/src/ripple/rpc/impl/RPCHelpers.cpp b/src/ripple/rpc/impl/RPCHelpers.cpp index 8a3195571..26d279dbd 100644 --- a/src/ripple/rpc/impl/RPCHelpers.cpp +++ b/src/ripple/rpc/impl/RPCHelpers.cpp @@ -1067,7 +1067,7 @@ chooseLedgerEntryType(Json::Value const& params) std::pair result{RPC::Status::OK, ltANY}; if (params.isMember(jss::type)) { - static constexpr std::array, 19> + static constexpr std::array, 22> types{ {{jss::account, ltACCOUNT_ROOT}, {jss::amendments, ltAMENDMENTS}, @@ -1075,11 +1075,13 @@ chooseLedgerEntryType(Json::Value const& params) {jss::deposit_preauth, ltDEPOSIT_PREAUTH}, {jss::directory, ltDIR_NODE}, {jss::escrow, ltESCROW}, + {jss::emitted_txn, ltEMITTED_TXN}, {jss::hook, ltHOOK}, {jss::hook_definition, ltHOOK_DEFINITION}, {jss::hook_state, ltHOOK_STATE}, {jss::fee, ltFEE_SETTINGS}, {jss::hashes, ltLEDGER_HASHES}, + {jss::import_vlseq, ltIMPORT_VLSEQ}, {jss::offer, ltOFFER}, {jss::payment_channel, ltPAYCHAN}, {jss::uri_token, ltURI_TOKEN}, @@ -1087,7 +1089,8 @@ chooseLedgerEntryType(Json::Value const& params) {jss::state, ltRIPPLE_STATE}, {jss::ticket, ltTICKET}, {jss::nft_offer, ltNFTOKEN_OFFER}, - {jss::nft_page, ltNFTOKEN_PAGE}}}; + {jss::nft_page, ltNFTOKEN_PAGE}, + {jss::unl_report, ltUNL_REPORT}}}; auto const& p = params[jss::type]; if (!p.isString()) diff --git a/src/test/app/Import_test.cpp b/src/test/app/Import_test.cpp index d85a0420b..c115bcc4d 100644 --- a/src/test/app/Import_test.cpp +++ b/src/test/app/Import_test.cpp @@ -84,28 +84,6 @@ class Import_test : public beast::unit_test::suite return 0; } - STTx - createUNLReportTx( - LedgerIndex seq, - PublicKey const& importKey, - PublicKey const& valKey) - { - auto fill = [&](auto& obj) { - obj.setFieldU32(sfLedgerSequence, seq); - obj.set(([&]() { - auto inner = std::make_unique(sfActiveValidator); - inner->setFieldVL(sfPublicKey, valKey); - return inner; - })()); - obj.set(([&]() { - auto inner = std::make_unique(sfImportVLKey); - inner->setFieldVL(sfPublicKey, importKey); - return inner; - })()); - }; - return STTx(ttUNL_REPORT, fill); - } - bool hasUNLReport(jtx::Env const& env) { @@ -5320,7 +5298,7 @@ class Import_test : public beast::unit_test::suite // insert a ttUNL_REPORT pseudo into the open ledger env.app().openLedger().modify( [&](OpenView& view, beast::Journal j) -> bool { - STTx tx = createUNLReportTx( + STTx tx = unl::createUNLReportTx( env.current()->seq() + 1, ivlKeys[0], vlKeys[0]); uint256 txID = tx.getTransactionID(); auto s = std::make_shared(); diff --git a/src/test/app/XahauGenesis_test.cpp b/src/test/app/XahauGenesis_test.cpp index 23ec46a60..3c409a36a 100644 --- a/src/test/app/XahauGenesis_test.cpp +++ b/src/test/app/XahauGenesis_test.cpp @@ -3598,28 +3598,6 @@ struct XahauGenesis_test : public beast::unit_test::suite return slep != nullptr; } - STTx - createUNLReportTx( - LedgerIndex seq, - PublicKey const& importKey, - PublicKey const& valKey) - { - auto fill = [&](auto& obj) { - obj.setFieldU32(sfLedgerSequence, seq); - obj.set(([&]() { - auto inner = std::make_unique(sfActiveValidator); - inner->setFieldVL(sfPublicKey, valKey); - return inner; - })()); - obj.set(([&]() { - auto inner = std::make_unique(sfImportVLKey); - inner->setFieldVL(sfPublicKey, importKey); - return inner; - })()); - }; - return STTx(ttUNL_REPORT, fill); - } - void activateUNLReport( jtx::Env& env, @@ -3631,7 +3609,7 @@ struct XahauGenesis_test : public beast::unit_test::suite { env.app().openLedger().modify( [&](OpenView& view, beast::Journal j) -> bool { - STTx tx = createUNLReportTx( + STTx tx = unl::createUNLReportTx( env.current()->seq() + 1, ivlKeys[0], vlKey); uint256 txID = tx.getTransactionID(); auto s = std::make_shared(); diff --git a/src/test/jtx/impl/unl.cpp b/src/test/jtx/impl/unl.cpp index 41cbd50b0..54d628cff 100644 --- a/src/test/jtx/impl/unl.cpp +++ b/src/test/jtx/impl/unl.cpp @@ -90,6 +90,28 @@ createTx(bool disabling, LedgerIndex seq, PublicKey const& txKey) return STTx(ttUNL_MODIFY, fill); } +STTx +createUNLReportTx( + LedgerIndex seq, + PublicKey const& importKey, + PublicKey const& valKey) +{ + auto fill = [&](auto& obj) { + obj.setFieldU32(sfLedgerSequence, seq); + obj.set(([&]() { + auto inner = std::make_unique(sfActiveValidator); + inner->setFieldVL(sfPublicKey, valKey); + return inner; + })()); + obj.set(([&]() { + auto inner = std::make_unique(sfImportVLKey); + inner->setFieldVL(sfPublicKey, importKey); + return inner; + })()); + }; + return STTx(ttUNL_REPORT, fill); +} + } // namespace unl } // namespace test } // namespace ripple diff --git a/src/test/jtx/unl.h b/src/test/jtx/unl.h index 68a30cc6b..2e0bae354 100644 --- a/src/test/jtx/unl.h +++ b/src/test/jtx/unl.h @@ -78,6 +78,20 @@ countTx(std::shared_ptr const& txSet); STTx createTx(bool disabling, LedgerIndex seq, PublicKey const& txKey); +/** + * Create ttUNL_REPORT Tx + * + * @param seq current ledger seq + * @param importKey the public key of the import network + * @param txKey the public key of the validator + * @return the ttUNL_REPORT Tx + */ +STTx +createUNLReportTx( + LedgerIndex seq, + PublicKey const& importKey, + PublicKey const& valKey); + } // namespace unl } // namespace test diff --git a/src/test/rpc/AccountInfo_test.cpp b/src/test/rpc/AccountInfo_test.cpp index 78aa9e646..5c9e32328 100644 --- a/src/test/rpc/AccountInfo_test.cpp +++ b/src/test/rpc/AccountInfo_test.cpp @@ -516,7 +516,7 @@ class AccountInfo_test : public beast::unit_test::suite }; static constexpr std:: - array, 7> + array, 8> asFlags{ {{"defaultRipple", asfDefaultRipple}, {"depositAuth", asfDepositAuth}, @@ -524,6 +524,7 @@ class AccountInfo_test : public beast::unit_test::suite {"globalFreeze", asfGlobalFreeze}, {"noFreeze", asfNoFreeze}, {"requireAuthorization", asfRequireAuth}, + {"tshCollect", asfTshCollect}, {"requireDestinationTag", asfRequireDest}}}; for (auto& asf : asFlags) diff --git a/src/test/rpc/AccountNamespace_test.cpp b/src/test/rpc/AccountNamespace_test.cpp new file mode 100644 index 000000000..b63b52e68 --- /dev/null +++ b/src/test/rpc/AccountNamespace_test.cpp @@ -0,0 +1,250 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 XRPL-Labs + + Permission to use, copy, modify, and/or 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 + +namespace ripple { +namespace test { + +class AccountNamespace_test : public beast::unit_test::suite +{ +public: + void + testErrors(FeatureBitset features) + { + testcase("error cases"); + + using namespace jtx; + Env env(*this); + + Account const alice{"alice"}; + Account const bob{"bob"}; + Account const carol{"carol"}; + env.fund(XRP(1000), alice, bob); + env.close(); + + auto const ns = uint256::fromVoid( + (std::array{ + 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, + 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, + 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, + 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU}) + .data()); + + { + // account_namespace with no account. + auto const info = env.rpc("json", "account_namespace", "{ }"); + BEAST_EXPECT( + info[jss::result][jss::error_message] == + "Missing field 'account'."); + } + { + // account_namespace missing filed namespace_id. + Json::Value params; + params[jss::account] = alice.human(); + auto const info = + env.rpc("json", "account_namespace", to_string(params)); + BEAST_EXPECT( + info[jss::result][jss::error_message] == + "Missing field 'namespace_id'."); + } + { + // account_namespace with a malformed account string. + Json::Value params; + params[jss::account] = + "n94JNrQYkDrpt62bbSR7nVEhdyAvcJXRAsjEkFYyqRkh9SUTYEqV"; + params[jss::namespace_id] = ""; + auto const info = + env.rpc("json", "account_namespace", to_string(params)); + BEAST_EXPECT( + info[jss::result][jss::error_message] == "Disallowed seed."); + } + { + // account_namespace with an invalid namespace_id. + Json::Value params; + params[jss::account] = alice.human(); + params[jss::namespace_id] = "DEADBEEF"; + auto const info = + env.rpc("json", "account_namespace", to_string(params)); + BEAST_EXPECT( + info[jss::result][jss::error_message] == "Invalid parameters."); + } + { + // account_namespace with an account that's not in the ledger. + Json::Value params; + params[jss::account] = carol.human(); + params[jss::namespace_id] = to_string(ns); + auto const info = + env.rpc("json", "account_namespace", to_string(params)); + BEAST_EXPECT( + info[jss::result][jss::error_message] == "Account not found."); + } + { + // account_namespace with a namespace that doesnt exist. + Json::Value params; + params[jss::account] = alice.human(); + params[jss::namespace_id] = to_string(ns); + auto const info = + env.rpc("json", "account_namespace", to_string(params)); + BEAST_EXPECT( + info[jss::result][jss::error_message] == + "Namespace not found."); + } + // test errors on marker + { + auto const key = uint256::fromVoid( + (std::array{ + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 'k', 'e', 'y', 0x00U}) + .data()); + + auto const ns = uint256::fromVoid( + (std::array{ + 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, + 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, + 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, + 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU}) + .data()); + + auto const nons = uint256::fromVoid( + (std::array{ + 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, + 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, + 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, + 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFFU}) + .data()); + + // Lambda to create a hook. + auto setHook = [](test::jtx::Account const& account) { + std::string const createCodeHex = + "0061736D01000000011B0460027F7F017F60047F7F7F7F017E60037F7F" + "7E01" + "7E60017F017E02270303656E76025F67000003656E760973746174655F" + "7365" + "74000103656E76066163636570740002030201030503010002062B077F" + "0141" + "9088040B7F004180080B7F00418A080B7F004180080B7F00419088040B" + "7F00" + "41000B7F0041010B07080104686F6F6B00030AE7800001E3800002017F" + "017E" + "230041106B220124002001200036020C41012200200010001A20014180" + "0828" + "0000360208200141046A410022002F0088083B01002001200028008408" + "3602" + "004100200020014106200141086A4104100110022102200141106A2400" + "2002" + "0B0B1001004180080B096B65790076616C7565"; + std::string ns_str = + "CAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECA" + "FECA" + "FE"; + Json::Value jv = + ripple::test::jtx::hook(account, {{hso(createCodeHex)}}, 0); + jv[jss::Hooks][0U][jss::Hook][jss::HookNamespace] = ns_str; + return jv; + }; + + env(setHook(bob), fee(XRP(1)), ter(tesSUCCESS)); + env.close(); + + env(pay(alice, bob, XRP(1)), fee(XRP(1)), ter(tesSUCCESS)); + env.close(); + + Json::Value params; + params[jss::account] = bob.human(); + params[jss::namespace_id] = to_string(ns); + params[jss::limit] = 1; + auto resp = env.rpc("json", "account_namespace", to_string(params)); + + auto resume_marker = resp[jss::result][jss::marker]; + std::string mark = to_string(resume_marker); + params[jss::marker] = 10; + resp = env.rpc("json", "account_namespace", to_string(params)); + BEAST_EXPECT( + resp[jss::result][jss::error_message] == + "Invalid field 'marker', not string."); + + params[jss::marker] = "This is a string with no comma"; + resp = env.rpc("json", "account_namespace", to_string(params)); + BEAST_EXPECT( + resp[jss::result][jss::error_message] == + "Invalid field 'marker'."); + + params[jss::marker] = "This string has a comma, but is not hex"; + resp = env.rpc("json", "account_namespace", to_string(params)); + BEAST_EXPECT( + resp[jss::result][jss::error_message] == + "Invalid field 'marker'."); + + params[jss::marker] = std::string(&mark[1U], 64); + resp = env.rpc("json", "account_namespace", to_string(params)); + BEAST_EXPECT( + resp[jss::result][jss::error_message] == + "Invalid field 'marker'."); + + params[jss::marker] = std::string(&mark[1U], 65); + resp = env.rpc("json", "account_namespace", to_string(params)); + BEAST_EXPECT( + resp[jss::result][jss::error_message] == + "Invalid field 'marker'."); + + params[jss::marker] = std::string(&mark[1U], 65) + "not hex"; + resp = env.rpc("json", "account_namespace", to_string(params)); + BEAST_EXPECT( + resp[jss::result][jss::error_message] == + "Invalid field 'marker'."); + + params[jss::marker] = std::string(&mark[1U], 128); + resp = env.rpc("json", "account_namespace", to_string(params)); + BEAST_EXPECT( + resp[jss::result][jss::error_message] == + "Invalid field 'marker'."); + } + + // test error on limit -ve + { + Account const bob{"bob"}; + Json::Value params; + params[jss::account] = bob.human(); + params[jss::namespace_id] = to_string(ns); + params[jss::limit] = -1; + auto resp = env.rpc("json", "account_namespace", to_string(params)); + BEAST_EXPECT( + resp[jss::result][jss::error_message] == + "Invalid field 'limit', not unsigned integer."); + } + } + + void + run() override + { + using namespace test::jtx; + FeatureBitset const all{supported_amendments()}; + testErrors(all); + } +}; + +BEAST_DEFINE_TESTSUITE(AccountNamespace, rpc, ripple); + +} // namespace test +} // namespace ripple diff --git a/src/test/rpc/AccountObjects_test.cpp b/src/test/rpc/AccountObjects_test.cpp index 2714d2c35..8a784b884 100644 --- a/src/test/rpc/AccountObjects_test.cpp +++ b/src/test/rpc/AccountObjects_test.cpp @@ -34,19 +34,19 @@ namespace test { static char const* bobs_account_objects[] = { R"json({ "Account" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK", - "BookDirectory" : "50AD0A9E54D2B381288D535EB724E4275FFBF41580D28A925D038D7EA4C68000", + "BookDirectory" : "B025997A323F5C3E03DDF1334471F5984ABDE31C59D463525D038D7EA4C68000", "BookNode" : "0", "Flags" : 65536, "LedgerEntryType" : "Offer", "OwnerNode" : "0", - "Sequence" : 6, + "Sequence" : 4, "TakerGets" : { "currency" : "USD", - "issuer" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK", + "issuer" : "r32rQHyesiTtdWFU7UJVtff4nCR5SHCbJW", "value" : "1" }, "TakerPays" : "100000000", - "index" : "29665262716C19830E26AEEC0916E476FC7D8EF195FF3B4F06829E64F82A3B3E" + "index" : "A984D036A0E562433A8377CA57D1A1E056E58C0D04818F8DFD3A1AA3F217DD82" })json", R"json({ "Balance" : { @@ -94,19 +94,19 @@ static char const* bobs_account_objects[] = { })json", R"json({ "Account" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK", - "BookDirectory" : "B025997A323F5C3E03DDF1334471F5984ABDE31C59D463525D038D7EA4C68000", + "BookDirectory" : "50AD0A9E54D2B381288D535EB724E4275FFBF41580D28A925D038D7EA4C68000", "BookNode" : "0", "Flags" : 65536, "LedgerEntryType" : "Offer", "OwnerNode" : "0", - "Sequence" : 7, + "Sequence" : 3, "TakerGets" : { "currency" : "USD", - "issuer" : "r32rQHyesiTtdWFU7UJVtff4nCR5SHCbJW", + "issuer" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK", "value" : "1" }, "TakerPays" : "100000000", - "index" : "F03ABE26CB8C5F4AFB31A86590BD25C64C5756FCE5CE9704C27AFE291A4A29A1" + "index" : "E11029302EE744401427793A4F37BCB18F698D55C96851BEC5ABBD6242CF03D7" })json"}; class AccountObjects_test : public beast::unit_test::suite @@ -328,9 +328,7 @@ class AccountObjects_test : public beast::unit_test::suite aobj.removeMember("PreviousTxnID"); aobj.removeMember("PreviousTxnLgrSeq"); - BEAST_EXPECT(aobj == bobj[i]); - params[jss::marker] = resp[jss::result][jss::marker]; } } @@ -734,7 +732,7 @@ class AccountObjects_test : public beast::unit_test::suite auto const& ticket = resp[jss::result][jss::account_objects][0u]; BEAST_EXPECT(ticket[sfAccount.jsonName] == gw.human()); BEAST_EXPECT(ticket[sfLedgerEntryType.jsonName] == jss::Ticket); - BEAST_EXPECT(ticket[sfTicketSequence.jsonName].asUInt() == 13); + BEAST_EXPECT(ticket[sfTicketSequence.jsonName].asUInt() == 10); } { // Create a uri token. @@ -856,7 +854,7 @@ class AccountObjects_test : public beast::unit_test::suite run() override { using namespace jtx; - FeatureBitset const all{supported_amendments() - featureXahauGenesis}; + FeatureBitset const all{supported_amendments()}; testErrors(all); testUnsteppedThenStepped(all); testUnsteppedThenSteppedWithNFTs(all); diff --git a/src/test/rpc/AccountOffers_test.cpp b/src/test/rpc/AccountOffers_test.cpp index 58b908f73..79e0ddd72 100644 --- a/src/test/rpc/AccountOffers_test.cpp +++ b/src/test/rpc/AccountOffers_test.cpp @@ -119,23 +119,23 @@ class AccountOffers_test : public beast::unit_test::suite BEAST_EXPECT(jroOuter[0u][jss::quality] == "100000000"); BEAST_EXPECT(jroOuter[0u][jss::taker_gets][jss::currency] == "USD"); BEAST_EXPECT( - jroOuter[0u][jss::taker_gets][jss::issuer] == gw.human()); - BEAST_EXPECT(jroOuter[0u][jss::taker_gets][jss::value] == "2"); - BEAST_EXPECT(jroOuter[0u][jss::taker_pays] == "200000000"); + jroOuter[0u][jss::taker_gets][jss::issuer] == bob.human()); + BEAST_EXPECT(jroOuter[0u][jss::taker_gets][jss::value] == "1"); + BEAST_EXPECT(jroOuter[0u][jss::taker_pays] == "100000000"); - BEAST_EXPECT(jroOuter[1u][jss::quality] == "100000000"); + BEAST_EXPECT(jroOuter[1u][jss::quality] == "5000000"); BEAST_EXPECT(jroOuter[1u][jss::taker_gets][jss::currency] == "USD"); BEAST_EXPECT( - jroOuter[1u][jss::taker_gets][jss::issuer] == bob.human()); - BEAST_EXPECT(jroOuter[1u][jss::taker_gets][jss::value] == "1"); - BEAST_EXPECT(jroOuter[1u][jss::taker_pays] == "100000000"); + jroOuter[1u][jss::taker_gets][jss::issuer] == gw.human()); + BEAST_EXPECT(jroOuter[1u][jss::taker_gets][jss::value] == "6"); + BEAST_EXPECT(jroOuter[1u][jss::taker_pays] == "30000000"); - BEAST_EXPECT(jroOuter[2u][jss::quality] == "5000000"); + BEAST_EXPECT(jroOuter[2u][jss::quality] == "100000000"); BEAST_EXPECT(jroOuter[2u][jss::taker_gets][jss::currency] == "USD"); BEAST_EXPECT( jroOuter[2u][jss::taker_gets][jss::issuer] == gw.human()); - BEAST_EXPECT(jroOuter[2u][jss::taker_gets][jss::value] == "6"); - BEAST_EXPECT(jroOuter[2u][jss::taker_pays] == "30000000"); + BEAST_EXPECT(jroOuter[2u][jss::taker_gets][jss::value] == "2"); + BEAST_EXPECT(jroOuter[2u][jss::taker_pays] == "200000000"); } { @@ -327,7 +327,7 @@ class AccountOffers_test : public beast::unit_test::suite run() override { using namespace jtx; - FeatureBitset const all{supported_amendments() - featureXahauGenesis}; + FeatureBitset const all{supported_amendments()}; testSequential(all, true); testSequential(all, false); testBadInput(all); diff --git a/src/test/rpc/LedgerData_test.cpp b/src/test/rpc/LedgerData_test.cpp index 5e354c881..1c5366891 100644 --- a/src/test/rpc/LedgerData_test.cpp +++ b/src/test/rpc/LedgerData_test.cpp @@ -17,8 +17,11 @@ */ //============================================================================== +#include #include +#include #include +#include #include namespace ripple { @@ -308,11 +311,16 @@ class LedgerData_test : public beast::unit_test::suite // Put a bunch of different LedgerEntryTypes into a ledger using namespace test::jtx; using namespace std::chrono; - Env env{*this, envconfig(validator, "")}; + std::vector const keys = { + "ED74D4036C6591A4BDF9C54CEFA39B996A5DCE5F86D11FDA1874481CE9D5A1CDC" + "1"}; + Env env{*this, network::makeNetworkVLConfig(21337, keys)}; + + Account const alice{"alice"}; Account const gw{"gateway"}; auto const USD = gw["USD"]; - env.fund(XRP(100000), gw); + env.fund(XRP(100000), gw, alice); auto makeRequest = [&env](Json::StaticString const& type) { Json::Value jvParams; @@ -335,6 +343,10 @@ class LedgerData_test : public beast::unit_test::suite jss::ticket, jss::escrow, jss::payment_channel, + jss::hook, + jss::hook_definition, + jss::hook_state, + jss::uri_token, jss::deposit_preauth}) { auto const jrr = makeRequest(type); @@ -399,6 +411,12 @@ class LedgerData_test : public beast::unit_test::suite env.close(); } + { + std::string const uri(maxTokenURILength, '?'); + env(uritoken::mint(Account{"bob2"}, uri)); + env.close(); + } + { Json::Value jv; jv[jss::TransactionType] = jss::PaymentChannelCreate; @@ -414,6 +432,63 @@ class LedgerData_test : public beast::unit_test::suite env(jv); } + { + auto const master = Account("masterpassphrase"); + env(noop(master), fee(10'000'000'000), ter(tesSUCCESS)); + env.close(); + env(import::import( + alice, import::loadXpop(test::ImportTCAccountSet::w_seed)), + fee(10 * 10), + ter(tesSUCCESS)); + env.close(); + } + + { + // ADD UNL REPORT + std::vector const _ivlKeys = { + "ED74D4036C6591A4BDF9C54CEFA39B996A5DCE5F86D11FDA1874481CE9D5A1" + "CDC1", + "ED74D4036C6591A4BDF9C54CEFA39B996A5DCE5F86D11FDA1874481CE9D5A1" + "CDC2", + }; + + std::vector ivlKeys; + for (auto const& strPk : _ivlKeys) + { + auto pkHex = strUnHex(strPk); + ivlKeys.emplace_back(makeSlice(*pkHex)); + } + + std::vector const _vlKeys = { + "ED8E43A943A174190BA2FAE91F44AC6E2D1D8202EFDCC2EA3DBB39814576D6" + "90F7", + "ED45D1840EE724BE327ABE9146503D5848EFD5F38B6D5FEDE71E80ACCE5E6E" + "738B"}; + + std::vector vlKeys; + for (auto const& strPk : _vlKeys) + { + auto pkHex = strUnHex(strPk); + vlKeys.emplace_back(makeSlice(*pkHex)); + } + + // insert a ttUNL_REPORT pseudo into the open ledger + env.app().openLedger().modify( + [&](OpenView& view, beast::Journal j) -> bool { + STTx tx = test::unl::createUNLReportTx( + env.current()->seq() + 1, ivlKeys[0], vlKeys[0]); + uint256 txID = tx.getTransactionID(); + auto s = std::make_shared(); + tx.add(*s); + env.app().getHashRouter().setFlags(txID, SF_PRIVATE2); + view.rawTxInsert(txID, std::move(s), nullptr); + return true; + }); + + // close the ledger + env.close(); + } + env(check::create("bob6", "bob7", XRP(100))); // bob9 DepositPreauths bob4 and bob8. @@ -425,18 +500,26 @@ class LedgerData_test : public beast::unit_test::suite { // jvParams[jss::type] = "account"; auto const jrr = makeRequest(jss::account); - BEAST_EXPECT(checkArraySize(jrr[jss::state], 12)); + BEAST_EXPECT(checkArraySize(jrr[jss::state], 13)); for (auto const& j : jrr[jss::state]) BEAST_EXPECT(j["LedgerEntryType"] == jss::AccountRoot); } { // jvParams[jss::type] = "amendments"; auto const jrr = makeRequest(jss::amendments); - BEAST_EXPECT(checkArraySize(jrr[jss::state], 1)); + BEAST_EXPECT(checkArraySize(jrr[jss::state], 0)); for (auto const& j : jrr[jss::state]) BEAST_EXPECT(j["LedgerEntryType"] == jss::Amendments); } + { // jvParams[jss::type] = "unl_report"; + auto const jrr = makeRequest(jss::unl_report); + BEAST_EXPECT(checkArraySize(jrr[jss::state], 1)); + + for (auto const& j : jrr[jss::state]) + BEAST_EXPECT(j["LedgerEntryType"] == jss::UNLReport); + } + { // jvParams[jss::type] = "check"; auto const jrr = makeRequest(jss::check); BEAST_EXPECT(checkArraySize(jrr[jss::state], 1)); @@ -465,6 +548,13 @@ class LedgerData_test : public beast::unit_test::suite BEAST_EXPECT(j["LedgerEntryType"] == jss::LedgerHashes); } + { // jvParams[jss::type] = "import_vlseq"; + auto const jrr = makeRequest(jss::import_vlseq); + BEAST_EXPECT(checkArraySize(jrr[jss::state], 1)); + for (auto const& j : jrr[jss::state]) + BEAST_EXPECT(j["LedgerEntryType"] == jss::ImportVLSequence); + } + { // jvParams[jss::type] = "offer"; auto const jrr = makeRequest(jss::offer); BEAST_EXPECT(checkArraySize(jrr[jss::state], 1)); @@ -521,6 +611,13 @@ class LedgerData_test : public beast::unit_test::suite BEAST_EXPECT(j["LedgerEntryType"] == jss::HookState); } + { // jvParams[jss::type] = "uri_token"; + auto const jrr = makeRequest(jss::uri_token); + BEAST_EXPECT(checkArraySize(jrr[jss::state], 1)); + for (auto const& j : jrr[jss::state]) + BEAST_EXPECT(j["LedgerEntryType"] == jss::URIToken); + } + { // jvParams[jss::type] = "payment_channel"; auto const jrr = makeRequest(jss::payment_channel); BEAST_EXPECT(checkArraySize(jrr[jss::state], 1)); diff --git a/src/test/rpc/LedgerRPC_test.cpp b/src/test/rpc/LedgerRPC_test.cpp index 7eb4e4484..de141311b 100644 --- a/src/test/rpc/LedgerRPC_test.cpp +++ b/src/test/rpc/LedgerRPC_test.cpp @@ -1707,6 +1707,39 @@ class LedgerRPC_test : public beast::unit_test::suite BEAST_EXPECT(jrr[jss::node][sfURI.jsonName] == strHex(uri)); BEAST_EXPECT(jrr[jss::node][sfFlags.jsonName] == lsfBurnable); } + { + // Request the uritoken using its account and uri. + Json::Value jvParams; + jvParams[jss::uri_token] = Json::objectValue; + jvParams[jss::uri_token][jss::account] = alice.human(); + jvParams[jss::uri_token][jss::uri] = uri; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT(jrr[jss::node][sfOwner.jsonName] == alice.human()); + BEAST_EXPECT(jrr[jss::node][sfURI.jsonName] == strHex(uri)); + BEAST_EXPECT(jrr[jss::node][sfFlags.jsonName] == lsfBurnable); + } + { + // Malformed uritoken object. Missing account member. + Json::Value jvParams; + jvParams[jss::uri_token] = Json::objectValue; + jvParams[jss::uri_token][jss::uri] = uri; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // Malformed uritoken object. Missing seq member. + Json::Value jvParams; + jvParams[jss::uri_token] = Json::objectValue; + jvParams[jss::uri_token][jss::account] = env.master.human(); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } { // Request an index that is not a uritoken. Json::Value jvParams; @@ -1718,6 +1751,93 @@ class LedgerRPC_test : public beast::unit_test::suite } } + void + testLedgerEntryImportVLSeq() + { + testcase("ledger_entry Request ImportVLSeq"); + using namespace test::jtx; + + std::vector const keys = { + "ED74D4036C6591A4BDF9C54CEFA39B996A5DCE5F86D11FDA1874481CE9D5A1CDC" + "1"}; + Env env{*this, network::makeNetworkVLConfig(21337, keys)}; + + Account const alice{"alice"}; + env.fund(XRP(10000), alice); + env.close(); + + auto const master = Account("masterpassphrase"); + env(noop(master), fee(10'000'000'000), ter(tesSUCCESS)); + env.close(); + env(import::import( + alice, import::loadXpop(test::ImportTCAccountSet::w_seed)), + fee(10 * 10), + ter(tesSUCCESS)); + env.close(); + + std::string const ledgerHash{to_string(env.closed()->info().hash)}; + + auto const pk = PublicKey(makeSlice(*strUnHex(keys[0u]))); + uint256 const importvlIndex{keylet::import_vlseq(pk).key}; + { + // Request the import vl using its index. + Json::Value jvParams; + jvParams[jss::import_vlseq] = to_string(importvlIndex); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT(jrr[jss::node][sfPublicKey.jsonName] == keys[0u]); + } + { + // Request the import vl using its public key. + Json::Value jvParams; + jvParams[jss::import_vlseq] = Json::objectValue; + jvParams[jss::import_vlseq][jss::public_key] = keys[0u]; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT(jrr[jss::node][sfPublicKey.jsonName] == keys[0u]); + } + { + // Malformed import vl object. Missing public key. + Json::Value jvParams; + jvParams[jss::import_vlseq] = Json::objectValue; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // Malformed import vl object. Bad public key. + Json::Value jvParams; + jvParams[jss::import_vlseq] = Json::objectValue; + jvParams[jss::import_vlseq][jss::public_key] = 1; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // Malformed import vl object. Bad public key. + Json::Value jvParams; + jvParams[jss::import_vlseq] = Json::objectValue; + jvParams[jss::import_vlseq][jss::public_key] = "DEADBEEF"; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // Request an index that is not a uritoken. + Json::Value jvParams; + jvParams[jss::import_vlseq] = ledgerHash; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "entryNotFound", ""); + } + } + void testLedgerEntryUnknownOption() { @@ -2242,6 +2362,7 @@ class LedgerRPC_test : public beast::unit_test::suite testLedgerEntryRippleState(); testLedgerEntryTicket(); testLedgerEntryURIToken(); + testLedgerEntryImportVLSeq(); testLedgerEntryUnknownOption(); testLookupLedger(); testNoQueue();