From 1150626511fc748c2e63996037ea6d90a9c375bb Mon Sep 17 00:00:00 2001 From: Ed Hennis Date: Wed, 30 Oct 2024 18:08:45 -0400 Subject: [PATCH 1/7] test: Retry all RPC commands to fix MacOS CI jobs * Follow up to #5120 (23991c9), which added a retry for submit commands. It improved MacOS test reliability, but other tests are failing now. * Retry all failed RPC connections / commands in unit tests. --- include/xrpl/protocol/TxFlags.h | 2 +- src/test/app/MultiSign_test.cpp | 1 + src/test/jtx/Env.h | 49 ++++++++++++++++++- src/test/jtx/impl/Env.cpp | 66 ++++++++++++++++++-------- src/test/rpc/LedgerRPC_test.cpp | 35 +++++++++++--- src/test/rpc/LedgerRequestRPC_test.cpp | 3 +- src/test/rpc/ValidatorInfo_test.cpp | 3 +- src/test/rpc/ValidatorRPC_test.cpp | 3 +- 8 files changed, 130 insertions(+), 32 deletions(-) diff --git a/include/xrpl/protocol/TxFlags.h b/include/xrpl/protocol/TxFlags.h index 4894f48a7f9..c83f2a964a8 100644 --- a/include/xrpl/protocol/TxFlags.h +++ b/include/xrpl/protocol/TxFlags.h @@ -134,7 +134,7 @@ constexpr std::uint32_t const tfTrustLine = 0x00000004; constexpr std::uint32_t const tfTransferable = 0x00000008; // MPTokenIssuanceCreate flags: -// NOTE - there is intentionally no flag here for lsfMPTLocked, which this transaction cannot mutate. +// NOTE - there is intentionally no flag here for lsfMPTLocked, which this transaction cannot mutate. constexpr std::uint32_t const tfMPTCanLock = lsfMPTCanLock; constexpr std::uint32_t const tfMPTRequireAuth = lsfMPTRequireAuth; constexpr std::uint32_t const tfMPTCanEscrow = lsfMPTCanEscrow; diff --git a/src/test/app/MultiSign_test.cpp b/src/test/app/MultiSign_test.cpp index 77d85d9011b..94d3d3ee11d 100644 --- a/src/test/app/MultiSign_test.cpp +++ b/src/test/app/MultiSign_test.cpp @@ -644,6 +644,7 @@ class MultiSign_test : public beast::unit_test::suite Json::Value jv_submit; jv_submit[jss::tx_json] = jrr[jss::tx_json]; jrr = env.rpc( + env.expectInternalRPCError, "json", "submit_multisigned", to_string(jv_submit))[jss::result]; diff --git a/src/test/jtx/Env.h b/src/test/jtx/Env.h index d90d2bc1228..e971cecc166 100644 --- a/src/test/jtx/Env.h +++ b/src/test/jtx/Env.h @@ -292,6 +292,17 @@ class Env The command is examined and used to build the correct JSON as per the arguments. */ + using rpcCallback = std::function; + const rpcCallback useDefaultCallback{}; + const rpcCallback expectInternalRPCError = [](Json::Value const&) { + return true; + }; + rpcCallback + getInternalFailureCallback(bool shouldFail) + { + return shouldFail ? expectInternalRPCError : useDefaultCallback; + } + template Json::Value rpc(unsigned apiVersion, @@ -303,12 +314,23 @@ class Env Json::Value rpc(unsigned apiVersion, std::string const& cmd, Args&&... args); + template + Json::Value + rpc(rpcCallback cb, + std::unordered_map const& headers, + std::string const& cmd, + Args&&... args); + template Json::Value rpc(std::unordered_map const& headers, std::string const& cmd, Args&&... args); + template + Json::Value + rpc(rpcCallback cb, std::string const& cmd, Args&&... args); + template Json::Value rpc(std::string const& cmd, Args&&... args); @@ -514,7 +536,7 @@ class Env /** Gets the TER result and `didApply` flag from a RPC Json result object. */ static ParsedResult - parseResult(Json::Value const& jr); + parseResult(Json::Value const& jr, bool checkTER); /** Submit an existing JTx. This calls postconditions. @@ -696,6 +718,7 @@ class Env Json::Value do_rpc( + rpcCallback cb, unsigned apiVersion, std::vector const& args, std::unordered_map const& headers = {}); @@ -746,6 +769,7 @@ Env::rpc( Args&&... args) { return do_rpc( + useDefaultCallback, apiVersion, std::vector{cmd, std::forward(args)...}, headers); @@ -765,16 +789,39 @@ Env::rpc(unsigned apiVersion, std::string const& cmd, Args&&... args) template Json::Value Env::rpc( + rpcCallback cb, std::unordered_map const& headers, std::string const& cmd, Args&&... args) { return do_rpc( + cb, RPC::apiCommandLineVersion, std::vector{cmd, std::forward(args)...}, headers); } +template +Json::Value +Env::rpc( + std::unordered_map const& headers, + std::string const& cmd, + Args&&... args) +{ + return rpc(useDefaultCallback, headers, cmd, std::forward(args)...); +} + +template +Json::Value +Env::rpc(rpcCallback cb, std::string const& cmd, Args&&... args) +{ + return rpc( + cb, + std::unordered_map(), + cmd, + std::forward(args)...); +} + template Json::Value Env::rpc(std::string const& cmd, Args&&... args) diff --git a/src/test/jtx/impl/Env.cpp b/src/test/jtx/impl/Env.cpp index ef5a2124e24..ba1c697ce2b 100644 --- a/src/test/jtx/impl/Env.cpp +++ b/src/test/jtx/impl/Env.cpp @@ -273,7 +273,7 @@ Env::trust(STAmount const& amount, Account const& account) } Env::ParsedResult -Env::parseResult(Json::Value const& jr) +Env::parseResult(Json::Value const& jr, bool checkTER) { auto error = [](ParsedResult& parsed, Json::Value const& object) { // Use an error code that is not used anywhere in the transaction @@ -296,14 +296,19 @@ Env::parseResult(Json::Value const& jr) if (jr.isObject() && jr.isMember(jss::result)) { auto const& result = jr[jss::result]; - if (result.isMember(jss::engine_result_code)) + if (checkTER && result.isMember(jss::engine_result_code)) { parsed.ter = TER::fromInt(result[jss::engine_result_code].asInt()); parsed.rpcCode.emplace(rpcSUCCESS); } + else if (!checkTER && !result.isMember(jss::error)) + parsed.rpcCode.emplace(rpcSUCCESS); else error(parsed, result); } + else if ( + jr.isObject() && jr.isMember(jss::error) && jr[jss::error].isObject()) + error(parsed, jr[jss::error]); else error(parsed, jr); @@ -317,24 +322,20 @@ Env::submit(JTx const& jt) auto const jr = [&]() { if (jt.stx) { - // We shouldn't need to retry, but it fixes the test on macOS for - // the moment. - int retries = 3; - do - { - txid_ = jt.stx->getTransactionID(); - Serializer s; - jt.stx->add(s); - auto const jr = rpc("submit", strHex(s.slice())); - - parsedResult = parseResult(jr); + txid_ = jt.stx->getTransactionID(); + Serializer s; + jt.stx->add(s); + auto const cb = [&](Json::Value const& jr) { + parsedResult = parseResult(jr, true); test.expect(parsedResult.ter, "ter uninitialized!"); ter_ = parsedResult.ter.value_or(telENV_RPC_FAILED); - if (ter_ != telENV_RPC_FAILED || + return ( + ter_ != telENV_RPC_FAILED || parsedResult.rpcCode != rpcINTERNAL || - jt.ter == telENV_RPC_FAILED || --retries <= 0) - return jr; - } while (true); + jt.ter == telENV_RPC_FAILED); + }; + // rpc() will call cb(), which does all the parsing + return rpc(cb, "submit", strHex(s.slice())); } else { @@ -379,7 +380,7 @@ Env::sign_and_submit(JTx const& jt, Json::Value params) if (!txid_.parseHex(jr[jss::result][jss::tx_json][jss::hash].asString())) txid_.zero(); - ParsedResult const parsedResult = parseResult(jr); + ParsedResult const parsedResult = parseResult(jr, true); test.expect(parsedResult.ter, "ter uninitialized!"); ter_ = parsedResult.ter.value_or(telENV_RPC_FAILED); @@ -562,12 +563,37 @@ Env::ust(JTx const& jt) Json::Value Env::do_rpc( + rpcCallback cb, unsigned apiVersion, std::vector const& args, std::unordered_map const& headers) { - return rpcClient(args, app().config(), app().logs(), apiVersion, headers) - .second; + // We shouldn't need to retry, but it fixes the test on macOS for + // the moment. + if (!cb) + { + static auto const defaultCallback = [](Json::Value const& jr) { + auto const parsedResult = parseResult(jr, false); + return parsedResult.rpcCode != rpcINTERNAL; + }; + cb = defaultCallback; + } + int retries = 3; + do + { + auto const ret = + rpcClient(args, app().config(), app().logs(), apiVersion, headers) + .second; + if (cb(ret) || --retries <= 0) + return ret; + std::stringstream ss; + for (auto const& arg : args) + { + ss << arg << ", "; + } + test.log << "RPC failed: " << ss.str() << " -> " << to_string(ret) + << std::endl; + } while (true); } void diff --git a/src/test/rpc/LedgerRPC_test.cpp b/src/test/rpc/LedgerRPC_test.cpp index 792da88b5bc..37ed82985db 100644 --- a/src/test/rpc/LedgerRPC_test.cpp +++ b/src/test/rpc/LedgerRPC_test.cpp @@ -1638,7 +1638,10 @@ class LedgerRPC_test : public beast::unit_test::suite }); Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; + env.getInternalFailureCallback(apiVersion < 2u), + "json", + "ledger_entry", + to_string(jvParams))[jss::result]; if (apiVersion < 2u) checkErrorValue(jrr, "internal", "Internal error."); @@ -1675,7 +1678,10 @@ class LedgerRPC_test : public beast::unit_test::suite }); Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; + env.getInternalFailureCallback(apiVersion < 2u), + "json", + "ledger_entry", + to_string(jvParams))[jss::result]; if (apiVersion < 2u) checkErrorValue(jrr, "internal", "Internal error."); @@ -1691,7 +1697,10 @@ class LedgerRPC_test : public beast::unit_test::suite }); Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; + env.getInternalFailureCallback(apiVersion < 2u), + "json", + "ledger_entry", + to_string(jvParams))[jss::result]; if (apiVersion < 2u) checkErrorValue(jrr, "internal", "Internal error."); @@ -1707,7 +1716,10 @@ class LedgerRPC_test : public beast::unit_test::suite }); Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; + env.getInternalFailureCallback(apiVersion < 2u), + "json", + "ledger_entry", + to_string(jvParams))[jss::result]; if (apiVersion < 2u) checkErrorValue(jrr, "internal", "Internal error."); @@ -1723,7 +1735,10 @@ class LedgerRPC_test : public beast::unit_test::suite }); Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; + env.getInternalFailureCallback(apiVersion < 2u), + "json", + "ledger_entry", + to_string(jvParams))[jss::result]; if (apiVersion < 2u) checkErrorValue(jrr, "internal", "Internal error."); @@ -1746,7 +1761,10 @@ class LedgerRPC_test : public beast::unit_test::suite }); Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; + env.getInternalFailureCallback(apiVersion < 2u), + "json", + "ledger_entry", + to_string(jvParams))[jss::result]; if (apiVersion < 2u) checkErrorValue(jrr, "internal", "Internal error."); @@ -1762,7 +1780,10 @@ class LedgerRPC_test : public beast::unit_test::suite }); Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; + env.getInternalFailureCallback(apiVersion < 2u), + "json", + "ledger_entry", + to_string(jvParams))[jss::result]; if (apiVersion < 2u) checkErrorValue(jrr, "internal", "Internal error."); diff --git a/src/test/rpc/LedgerRequestRPC_test.cpp b/src/test/rpc/LedgerRequestRPC_test.cpp index 8922cd38386..689447df817 100644 --- a/src/test/rpc/LedgerRequestRPC_test.cpp +++ b/src/test/rpc/LedgerRequestRPC_test.cpp @@ -347,7 +347,8 @@ class LedgerRequestRPC_test : public beast::unit_test::suite auto const USD = gw["USD"]; env.fund(XRP(100000), gw); - auto const result = env.rpc("ledger_request", "1")[jss::result]; + auto const result = env.rpc( + env.expectInternalRPCError, "ledger_request", "1")[jss::result]; // The current HTTP/S ServerHandler returns an HTTP 403 error code here // rather than a noPermission JSON error. The JSONRPCClient just eats // that error and returns an null result. diff --git a/src/test/rpc/ValidatorInfo_test.cpp b/src/test/rpc/ValidatorInfo_test.cpp index 603a0ad9d23..1b94e4d9812 100644 --- a/src/test/rpc/ValidatorInfo_test.cpp +++ b/src/test/rpc/ValidatorInfo_test.cpp @@ -50,7 +50,8 @@ class ValidatorInfo_test : public beast::unit_test::suite { using namespace test::jtx; Env env{*this, envconfig(no_admin)}; - auto const info = env.rpc("validator_info")[jss::result]; + auto const info = + env.rpc(env.expectInternalRPCError, "validator_info")[jss::result]; BEAST_EXPECT(info.isNull()); } diff --git a/src/test/rpc/ValidatorRPC_test.cpp b/src/test/rpc/ValidatorRPC_test.cpp index 2bd4b69c37b..0c842d54dc2 100644 --- a/src/test/rpc/ValidatorRPC_test.cpp +++ b/src/test/rpc/ValidatorRPC_test.cpp @@ -49,7 +49,8 @@ class ValidatorRPC_test : public beast::unit_test::suite for (std::string cmd : {"validators", "validator_list_sites"}) { Env env{*this, isAdmin ? envconfig() : envconfig(no_admin)}; - auto const jrr = env.rpc(cmd)[jss::result]; + auto const jrr = env.rpc( + env.getInternalFailureCallback(!isAdmin), cmd)[jss::result]; if (isAdmin) { BEAST_EXPECT(!jrr.isMember(jss::error)); From 205fe37aa76fec44bc9837443ff5f4b3a02811f9 Mon Sep 17 00:00:00 2001 From: Ed Hennis Date: Tue, 5 Nov 2024 19:12:45 -0500 Subject: [PATCH 2/7] Also handle timeout exceptions --- src/test/jtx/impl/Env.cpp | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/test/jtx/impl/Env.cpp b/src/test/jtx/impl/Env.cpp index ba1c697ce2b..a2619a496cd 100644 --- a/src/test/jtx/impl/Env.cpp +++ b/src/test/jtx/impl/Env.cpp @@ -581,18 +581,33 @@ Env::do_rpc( int retries = 3; do { - auto const ret = - rpcClient(args, app().config(), app().logs(), apiVersion, headers) - .second; - if (cb(ret) || --retries <= 0) - return ret; + std::string retString; + try + { + auto const ret = + rpcClient( + args, app().config(), app().logs(), apiVersion, headers) + .second; + if (cb(ret) || --retries <= 0) + return ret; + test.log << "RPC failure: "; + retString = to_string(ret); + } + catch (std::exception const& e) + { + using namespace std::string_literals; + // TODO: Narrow down the exceptions that can be retried + if (--retries <= 0) + throw; + test.log << "RPC exception: "; + retString = e.what(); + } std::stringstream ss; for (auto const& arg : args) { ss << arg << ", "; } - test.log << "RPC failed: " << ss.str() << " -> " << to_string(ret) - << std::endl; + test.log << ss.str() << " -> " << retString << std::endl; } while (true); } From 63768ce88fdd8c89a7e9cb1a173bc1df2ff8c38a Mon Sep 17 00:00:00 2001 From: Ed Hennis Date: Tue, 12 Nov 2024 17:26:44 -0500 Subject: [PATCH 3/7] [FOLD] Review feedback from @thejohnfreeman: * Rename the RpcCallback type, and the default variables of that type * Move the "default callback" out of the retry logic, and into the variable definition. * Get rid of the checkTER parameter to parseResult. --- src/test/app/MultiSign_test.cpp | 2 +- src/test/jtx/Env.h | 52 ++++++++++++++++---------- src/test/jtx/impl/Env.cpp | 26 ++++++------- src/test/rpc/LedgerRPC_test.cpp | 14 +++---- src/test/rpc/LedgerRequestRPC_test.cpp | 4 +- src/test/rpc/ValidatorInfo_test.cpp | 2 +- src/test/rpc/ValidatorRPC_test.cpp | 2 +- 7 files changed, 56 insertions(+), 46 deletions(-) diff --git a/src/test/app/MultiSign_test.cpp b/src/test/app/MultiSign_test.cpp index 94d3d3ee11d..b50c6e41d4b 100644 --- a/src/test/app/MultiSign_test.cpp +++ b/src/test/app/MultiSign_test.cpp @@ -644,7 +644,7 @@ class MultiSign_test : public beast::unit_test::suite Json::Value jv_submit; jv_submit[jss::tx_json] = jrr[jss::tx_json]; jrr = env.rpc( - env.expectInternalRPCError, + env.rejectNever, "json", "submit_multisigned", to_string(jv_submit))[jss::result]; diff --git a/src/test/jtx/Env.h b/src/test/jtx/Env.h index e971cecc166..e6cfe505884 100644 --- a/src/test/jtx/Env.h +++ b/src/test/jtx/Env.h @@ -287,22 +287,31 @@ class Env return *bundle_.client; } + /// Given the return value of an RPC call, returns true if it is acceptable. + using RpcCallback = std::function; + /// Used as the default callback - any result is acceptable, except an + /// internal error, which usually indicates a timeout. + const RpcCallback rejectInternalError = [](Json::Value const& jr) { + auto const parsedResult = parseResult(jr); + return parsedResult.rpcCode != rpcINTERNAL; + }; + /// Used as the callback when any result is acceptable, particularly when an + /// internal error is expected. + const RpcCallback rejectNever = [](Json::Value const&) { return true; }; + /// Returns the appropriate callback when the decision of what is acceptible + /// is made at run time. For example, when conditional on API version + /// number. + RpcCallback + rejectInternalErrorIf(bool shouldFail) + { + return shouldFail ? rejectNever : rejectInternalError; + } + /** Execute an RPC command. The command is examined and used to build the correct JSON as per the arguments. */ - using rpcCallback = std::function; - const rpcCallback useDefaultCallback{}; - const rpcCallback expectInternalRPCError = [](Json::Value const&) { - return true; - }; - rpcCallback - getInternalFailureCallback(bool shouldFail) - { - return shouldFail ? expectInternalRPCError : useDefaultCallback; - } - template Json::Value rpc(unsigned apiVersion, @@ -316,7 +325,7 @@ class Env template Json::Value - rpc(rpcCallback cb, + rpc(RpcCallback cb, std::unordered_map const& headers, std::string const& cmd, Args&&... args); @@ -329,7 +338,7 @@ class Env template Json::Value - rpc(rpcCallback cb, std::string const& cmd, Args&&... args); + rpc(RpcCallback cb, std::string const& cmd, Args&&... args); template Json::Value @@ -533,10 +542,13 @@ class Env jtx::required(args...)(*this); } - /** Gets the TER result and `didApply` flag from a RPC Json result object. + /** Parses the Json result of an RPC request. + * + * Includes RPC result status, transaction engine result (TER) if + * appropriate, and error information, if any. */ static ParsedResult - parseResult(Json::Value const& jr, bool checkTER); + parseResult(Json::Value const& jr); /** Submit an existing JTx. This calls postconditions. @@ -718,7 +730,7 @@ class Env Json::Value do_rpc( - rpcCallback cb, + RpcCallback cb, unsigned apiVersion, std::vector const& args, std::unordered_map const& headers = {}); @@ -769,7 +781,7 @@ Env::rpc( Args&&... args) { return do_rpc( - useDefaultCallback, + rejectInternalError, apiVersion, std::vector{cmd, std::forward(args)...}, headers); @@ -789,7 +801,7 @@ Env::rpc(unsigned apiVersion, std::string const& cmd, Args&&... args) template Json::Value Env::rpc( - rpcCallback cb, + RpcCallback cb, std::unordered_map const& headers, std::string const& cmd, Args&&... args) @@ -808,12 +820,12 @@ Env::rpc( std::string const& cmd, Args&&... args) { - return rpc(useDefaultCallback, headers, cmd, std::forward(args)...); + return rpc(rejectInternalError, headers, cmd, std::forward(args)...); } template Json::Value -Env::rpc(rpcCallback cb, std::string const& cmd, Args&&... args) +Env::rpc(RpcCallback cb, std::string const& cmd, Args&&... args) { return rpc( cb, diff --git a/src/test/jtx/impl/Env.cpp b/src/test/jtx/impl/Env.cpp index a2619a496cd..e5fd72285a3 100644 --- a/src/test/jtx/impl/Env.cpp +++ b/src/test/jtx/impl/Env.cpp @@ -273,7 +273,7 @@ Env::trust(STAmount const& amount, Account const& account) } Env::ParsedResult -Env::parseResult(Json::Value const& jr, bool checkTER) +Env::parseResult(Json::Value const& jr) { auto error = [](ParsedResult& parsed, Json::Value const& object) { // Use an error code that is not used anywhere in the transaction @@ -296,12 +296,16 @@ Env::parseResult(Json::Value const& jr, bool checkTER) if (jr.isObject() && jr.isMember(jss::result)) { auto const& result = jr[jss::result]; - if (checkTER && result.isMember(jss::engine_result_code)) + if (result.isMember(jss::engine_result_code)) { parsed.ter = TER::fromInt(result[jss::engine_result_code].asInt()); parsed.rpcCode.emplace(rpcSUCCESS); } - else if (!checkTER && !result.isMember(jss::error)) + else if ( + !result.isMember(jss::error) && !result.isMember(jss::error_code) && + !result.isMember(jss::error_message) && + !result.isMember(jss::error_exception)) + // parsed.ter remains unseated parsed.rpcCode.emplace(rpcSUCCESS); else error(parsed, result); @@ -326,7 +330,7 @@ Env::submit(JTx const& jt) Serializer s; jt.stx->add(s); auto const cb = [&](Json::Value const& jr) { - parsedResult = parseResult(jr, true); + parsedResult = parseResult(jr); test.expect(parsedResult.ter, "ter uninitialized!"); ter_ = parsedResult.ter.value_or(telENV_RPC_FAILED); return ( @@ -380,7 +384,7 @@ Env::sign_and_submit(JTx const& jt, Json::Value params) if (!txid_.parseHex(jr[jss::result][jss::tx_json][jss::hash].asString())) txid_.zero(); - ParsedResult const parsedResult = parseResult(jr, true); + ParsedResult const parsedResult = parseResult(jr); test.expect(parsedResult.ter, "ter uninitialized!"); ter_ = parsedResult.ter.value_or(telENV_RPC_FAILED); @@ -563,21 +567,15 @@ Env::ust(JTx const& jt) Json::Value Env::do_rpc( - rpcCallback cb, + RpcCallback cb, unsigned apiVersion, std::vector const& args, std::unordered_map const& headers) { // We shouldn't need to retry, but it fixes the test on macOS for // the moment. - if (!cb) - { - static auto const defaultCallback = [](Json::Value const& jr) { - auto const parsedResult = parseResult(jr, false); - return parsedResult.rpcCode != rpcINTERNAL; - }; - cb = defaultCallback; - } + if (!test.BEAST_EXPECT(cb)) + cb = rejectInternalError; int retries = 3; do { diff --git a/src/test/rpc/LedgerRPC_test.cpp b/src/test/rpc/LedgerRPC_test.cpp index a4a704e1157..897ab6919e1 100644 --- a/src/test/rpc/LedgerRPC_test.cpp +++ b/src/test/rpc/LedgerRPC_test.cpp @@ -2132,7 +2132,7 @@ class LedgerRPC_test : public beast::unit_test::suite }); Json::Value const jrr = env.rpc( - env.getInternalFailureCallback(apiVersion < 2u), + env.rejectInternalErrorIf(apiVersion < 2u), "json", "ledger_entry", to_string(jvParams))[jss::result]; @@ -2172,7 +2172,7 @@ class LedgerRPC_test : public beast::unit_test::suite }); Json::Value const jrr = env.rpc( - env.getInternalFailureCallback(apiVersion < 2u), + env.rejectInternalErrorIf(apiVersion < 2u), "json", "ledger_entry", to_string(jvParams))[jss::result]; @@ -2191,7 +2191,7 @@ class LedgerRPC_test : public beast::unit_test::suite }); Json::Value const jrr = env.rpc( - env.getInternalFailureCallback(apiVersion < 2u), + env.rejectInternalErrorIf(apiVersion < 2u), "json", "ledger_entry", to_string(jvParams))[jss::result]; @@ -2210,7 +2210,7 @@ class LedgerRPC_test : public beast::unit_test::suite }); Json::Value const jrr = env.rpc( - env.getInternalFailureCallback(apiVersion < 2u), + env.rejectInternalErrorIf(apiVersion < 2u), "json", "ledger_entry", to_string(jvParams))[jss::result]; @@ -2229,7 +2229,7 @@ class LedgerRPC_test : public beast::unit_test::suite }); Json::Value const jrr = env.rpc( - env.getInternalFailureCallback(apiVersion < 2u), + env.rejectInternalErrorIf(apiVersion < 2u), "json", "ledger_entry", to_string(jvParams))[jss::result]; @@ -2255,7 +2255,7 @@ class LedgerRPC_test : public beast::unit_test::suite }); Json::Value const jrr = env.rpc( - env.getInternalFailureCallback(apiVersion < 2u), + env.rejectInternalErrorIf(apiVersion < 2u), "json", "ledger_entry", to_string(jvParams))[jss::result]; @@ -2274,7 +2274,7 @@ class LedgerRPC_test : public beast::unit_test::suite }); Json::Value const jrr = env.rpc( - env.getInternalFailureCallback(apiVersion < 2u), + env.rejectInternalErrorIf(apiVersion < 2u), "json", "ledger_entry", to_string(jvParams))[jss::result]; diff --git a/src/test/rpc/LedgerRequestRPC_test.cpp b/src/test/rpc/LedgerRequestRPC_test.cpp index 689447df817..c9f375e435a 100644 --- a/src/test/rpc/LedgerRequestRPC_test.cpp +++ b/src/test/rpc/LedgerRequestRPC_test.cpp @@ -347,8 +347,8 @@ class LedgerRequestRPC_test : public beast::unit_test::suite auto const USD = gw["USD"]; env.fund(XRP(100000), gw); - auto const result = env.rpc( - env.expectInternalRPCError, "ledger_request", "1")[jss::result]; + auto const result = + env.rpc(env.rejectNever, "ledger_request", "1")[jss::result]; // The current HTTP/S ServerHandler returns an HTTP 403 error code here // rather than a noPermission JSON error. The JSONRPCClient just eats // that error and returns an null result. diff --git a/src/test/rpc/ValidatorInfo_test.cpp b/src/test/rpc/ValidatorInfo_test.cpp index 1b94e4d9812..7fa8b23f9a7 100644 --- a/src/test/rpc/ValidatorInfo_test.cpp +++ b/src/test/rpc/ValidatorInfo_test.cpp @@ -51,7 +51,7 @@ class ValidatorInfo_test : public beast::unit_test::suite using namespace test::jtx; Env env{*this, envconfig(no_admin)}; auto const info = - env.rpc(env.expectInternalRPCError, "validator_info")[jss::result]; + env.rpc(env.rejectNever, "validator_info")[jss::result]; BEAST_EXPECT(info.isNull()); } diff --git a/src/test/rpc/ValidatorRPC_test.cpp b/src/test/rpc/ValidatorRPC_test.cpp index 0c842d54dc2..97081888dfc 100644 --- a/src/test/rpc/ValidatorRPC_test.cpp +++ b/src/test/rpc/ValidatorRPC_test.cpp @@ -50,7 +50,7 @@ class ValidatorRPC_test : public beast::unit_test::suite { Env env{*this, isAdmin ? envconfig() : envconfig(no_admin)}; auto const jrr = env.rpc( - env.getInternalFailureCallback(!isAdmin), cmd)[jss::result]; + env.rejectInternalErrorIf(!isAdmin), cmd)[jss::result]; if (isAdmin) { BEAST_EXPECT(!jrr.isMember(jss::error)); From e66d2e1f990754ab8a49295ae4270481aa12d782 Mon Sep 17 00:00:00 2001 From: Ed Hennis Date: Mon, 4 Nov 2024 19:09:53 -0500 Subject: [PATCH 4/7] Add MacOS CI configurations: Debug, unity --- .github/workflows/macos.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index ab4be74fbf4..9d23b6dfa9a 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -19,6 +19,7 @@ jobs: test: strategy: + fail-fast: false matrix: platform: - macos @@ -26,6 +27,10 @@ jobs: - Ninja configuration: - Release + - Debug + cmake-args: + - + - "-Dunity=ON" runs-on: [self-hosted, macOS] env: # The `build` action requires these variables. @@ -66,6 +71,7 @@ jobs: with: generator: ${{ matrix.generator }} configuration: ${{ matrix.configuration }} + cmake-args: ${{ matrix.cmake-args }} - name: test run: | ${build_dir}/rippled --unittest From 315b7ddb161b4a11f5b6707df18d7c337510ce3d Mon Sep 17 00:00:00 2001 From: Ed Hennis Date: Thu, 7 Nov 2024 19:05:33 -0500 Subject: [PATCH 5/7] Cycle through more test ports to avoid slow close collisions: * Add a lot more retries across test network operations. * Add some network error logging details. --- include/xrpl/server/detail/Door.h | 5 +-- src/test/jtx/Env.h | 16 +++++++++ src/test/jtx/impl/Env.cpp | 48 +++++++++++++++++++++++++++ src/test/jtx/impl/JSONRPCClient.cpp | 26 +++++++++++++-- src/test/jtx/impl/WSClient.cpp | 20 +++++++++-- src/test/jtx/impl/envconfig.cpp | 26 +++++++++++++-- src/test/rpc/Subscribe_test.cpp | 2 ++ src/test/server/ServerStatus_test.cpp | 10 ++++-- src/test/unit_test/multi_runner.cpp | 4 +-- 9 files changed, 144 insertions(+), 13 deletions(-) diff --git a/include/xrpl/server/detail/Door.h b/include/xrpl/server/detail/Door.h index 20bdf421e38..7a42c8b5bf0 100644 --- a/include/xrpl/server/detail/Door.h +++ b/include/xrpl/server/detail/Door.h @@ -264,8 +264,9 @@ Door::reOpen() acceptor_.bind(local_address, ec); if (ec) { - JLOG(j_.error()) << "Bind port '" << port_.name - << "' failed:" << ec.message(); + JLOG(j_.error()) << "Bind port '" << port_.name << "', (" + << local_address << ")" + << "failed:" << ec.message(); Throw(); } diff --git a/src/test/jtx/Env.h b/src/test/jtx/Env.h index e6cfe505884..ac9ea31e0a9 100644 --- a/src/test/jtx/Env.h +++ b/src/test/jtx/Env.h @@ -344,6 +344,22 @@ class Env Json::Value rpc(std::string const& cmd, Args&&... args); + void + retry(std::function cb, std::string const& context); + + static void + retry( + std::function cb, + std::string const& context, + std::chrono::milliseconds delay); + + static void + retry( + std::function cb, + std::string const& context, + beast::unit_test::suite* test, + std::chrono::milliseconds delay); + /** Returns the current ledger. This is a non-modifiable snapshot of the diff --git a/src/test/jtx/impl/Env.cpp b/src/test/jtx/impl/Env.cpp index e5fd72285a3..0b02d56e7e6 100644 --- a/src/test/jtx/impl/Env.cpp +++ b/src/test/jtx/impl/Env.cpp @@ -606,6 +606,54 @@ Env::do_rpc( ss << arg << ", "; } test.log << ss.str() << " -> " << retString << std::endl; + using namespace std::chrono_literals; + std::this_thread::sleep_for(100ms); + } while (true); +} + +void +Env::retry(std::function cb, std::string const& context) +{ + using namespace std::chrono_literals; + retry(cb, context, &test, 100ms); +} + +void +Env::retry( + std::function cb, + std::string const& context, + std::chrono::milliseconds delay) +{ + retry(cb, context, nullptr, delay); +} + +void +Env::retry( + std::function cb, + std::string const& context, + beast::unit_test::suite* test, + std::chrono::milliseconds delay) +{ + int retries = 3; + do + { + try + { + return cb(); + } + catch (std::exception const& e) + { + if (--retries <= 0) + throw; + if (test) + test->log << "Retry exception(" << context << "): " << e.what() + << std::endl; + // TODO remove + else + std::cout << "Retry exception(" << context << "): " << e.what() + << std::endl; + std::this_thread::sleep_for(delay); + } } while (true); } diff --git a/src/test/jtx/impl/JSONRPCClient.cpp b/src/test/jtx/impl/JSONRPCClient.cpp index 20f2149e4a0..8dddaaf01b3 100644 --- a/src/test/jtx/impl/JSONRPCClient.cpp +++ b/src/test/jtx/impl/JSONRPCClient.cpp @@ -17,6 +17,8 @@ */ //============================================================================== #include + +#include #include #include #include @@ -129,10 +131,20 @@ class JSONRPCClient : public AbstractClient req.body() = to_string(jr); } req.prepare_payload(); - write(stream_, req); + + using namespace std::chrono_literals; + using namespace ripple::test::jtx; + + Env::retry( + [&]() { write(stream_, req); }, + "JSONRPCClient::invoke write", + 10ms); response res; - read(stream_, bin_, res); + Env::retry( + [&]() { read(stream_, bin_, res); }, + "JSONRPCClient::invoke read", + 10ms); Json::Reader jr; Json::Value jv; @@ -154,7 +166,15 @@ class JSONRPCClient : public AbstractClient std::unique_ptr makeJSONRPCClient(Config const& cfg, unsigned rpc_version) { - return std::make_unique(cfg, rpc_version); + using namespace std::chrono_literals; + using namespace ripple::test::jtx; + + std::unique_ptr ret; + Env::retry( + [&]() { ret = std::make_unique(cfg, rpc_version); }, + "makeJSONRPCClient", + 250ms); + return ret; } } // namespace test diff --git a/src/test/jtx/impl/WSClient.cpp b/src/test/jtx/impl/WSClient.cpp index 185d0ea5dba..97e97185021 100644 --- a/src/test/jtx/impl/WSClient.cpp +++ b/src/test/jtx/impl/WSClient.cpp @@ -173,6 +173,8 @@ class WSClientImpl : public WSClient using namespace std::chrono_literals; { + using namespace ripple::test::jtx; + Json::Value jp; if (params) jp = params; @@ -186,7 +188,10 @@ class WSClientImpl : public WSClient else jp[jss::command] = cmd; auto const s = to_string(jp); - ws_.write_some(true, buffer(s)); + Env::retry( + [&]() { ws_.write_some(true, buffer(s)); }, + "WSClient::invoke write_some", + 100ms); } auto jv = findMsg(5s, [&](Json::Value const& jval) { @@ -303,7 +308,18 @@ makeWSClient( unsigned rpc_version, std::unordered_map const& headers) { - return std::make_unique(cfg, v2, rpc_version, headers); + using namespace std::chrono_literals; + using namespace ripple::test::jtx; + + std::unique_ptr ret; + + Env::retry( + [&]() { + ret = std::make_unique(cfg, v2, rpc_version, headers); + }, + "makeWSClient", + 250ms); + return ret; } } // namespace test diff --git a/src/test/jtx/impl/envconfig.cpp b/src/test/jtx/impl/envconfig.cpp index c9788a6d70f..64a38323821 100644 --- a/src/test/jtx/impl/envconfig.cpp +++ b/src/test/jtx/impl/envconfig.cpp @@ -26,10 +26,29 @@ namespace ripple { namespace test { int port_base = 8000; +int port_start = port_base; +int port_increment = 4; +int port_reserve = 24; +int port_max = port_base + port_reserve; void -incPorts(int times) +incPorts(int sets, int times) { - port_base += (4 * times); + port_increment *= sets; + port_base += (port_reserve * times); + port_reserve = std::max(port_reserve, port_increment * 2); + if (port_base + port_reserve > 65535) + { + // This should only happen if the runner uses + // (65535 - 8100) / 25 ~= 2297 threads. + // If the runner uses so many jobs that it overflows, wrap + // around and hope for the best. Even then, it won't collide + // unless the runner uses (65535 - 1024) / 25 ~= 2580 threads. + // If this ever becomes an issue, just decrease the + // port_reserve or something. + port_base = (port_base % 65535) + 1024; + } + port_start = port_base; + port_max = port_base + port_reserve; } std::atomic envUseIPv4{false}; @@ -37,6 +56,9 @@ std::atomic envUseIPv4{false}; void setupConfigForUnitTests(Config& cfg) { + port_base += port_increment; + if (port_base + port_increment >= port_max) + port_base = port_start; std::string const port_peer = std::to_string(port_base); std::string port_rpc = std::to_string(port_base + 1); std::string port_ws = std::to_string(port_base + 2); diff --git a/src/test/rpc/Subscribe_test.cpp b/src/test/rpc/Subscribe_test.cpp index f1cb2f9a135..4b83b4e47f2 100644 --- a/src/test/rpc/Subscribe_test.cpp +++ b/src/test/rpc/Subscribe_test.cpp @@ -1284,6 +1284,8 @@ class Subscribe_test : public beast::unit_test::suite auto wscShort = makeWSClient(env.app().config()); auto jv = wscShort->invoke("subscribe", request); IdxHashVec vec2; + if (!BEAST_EXPECT(jv)) + return; if (!BEAST_EXPECT(getTxHash(*wscShort, vec2, count).first)) return; if (!BEAST_EXPECT(hashCompare(vec1, vec2, true))) diff --git a/src/test/server/ServerStatus_test.cpp b/src/test/server/ServerStatus_test.cpp index 8aa7bf19f30..46f828fa8ff 100644 --- a/src/test/server/ServerStatus_test.cpp +++ b/src/test/server/ServerStatus_test.cpp @@ -485,12 +485,18 @@ class ServerStatus_test : public beast::unit_test::suite, async_connect(sock, it, yield[ec]); if (!BEAST_EXPECTS(!ec, ec.message())) return; - async_write(sock, boost::asio::buffer(req_string), yield[ec]); + env.retry( + [&]() { + async_write(sock, boost::asio::buffer(req_string), yield[ec]); + }, + "ServerStatus testTruncatedWSUpgrade async_write"); if (!BEAST_EXPECTS(!ec, ec.message())) return; // since we've sent an incomplete request, the server will // keep trying to read until it gives up (by timeout) - async_read(sock, sb, resp, yield[ec]); + env.retry( + [&]() { async_read(sock, sb, resp, yield[ec]); }, + "ServerStatus testTruncatedWSUpgrade async_read"); BEAST_EXPECT(ec); } diff --git a/src/test/unit_test/multi_runner.cpp b/src/test/unit_test/multi_runner.cpp index 60487cadfb8..57c4e8279bc 100644 --- a/src/test/unit_test/multi_runner.cpp +++ b/src/test/unit_test/multi_runner.cpp @@ -33,7 +33,7 @@ namespace ripple { namespace test { extern void -incPorts(int times); +incPorts(int sets, int times); namespace detail { @@ -511,7 +511,7 @@ multi_runner_child::multi_runner_child( , print_log_{!quiet || print_log} { // incPort twice (2*jobIndex_) because some tests need two envs - test::incPorts(2 * job_index_); + test::incPorts(2, job_index_); if (num_jobs_ > 1) { From 7f74c46152ac08e8e195a605cb68ef44af4e8361 Mon Sep 17 00:00:00 2001 From: Ed Hennis Date: Thu, 7 Nov 2024 19:07:57 -0500 Subject: [PATCH 6/7] Run MacOS tests with multiple jobs --- .github/actions/dependencies/action.yml | 18 ++++++++++++++++++ .github/workflows/macos.yml | 14 +++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/.github/actions/dependencies/action.yml b/.github/actions/dependencies/action.yml index 50e2999018a..9a6bd444303 100644 --- a/.github/actions/dependencies/action.yml +++ b/.github/actions/dependencies/action.yml @@ -24,6 +24,11 @@ runs: # Do not quote the URL. An empty string will be accepted (with # a non-fatal warning), but a missing argument will not. conan remote add ripple ${{ env.CONAN_URL }} --insert 0 + if [[ "${RUNNER_OS}" = "macOS" ]] + then + conan profile update 'env.CXXFLAGS="-DBOOST_ASIO_DISABLE_CONCEPTS"' default + conan profile update 'conf.tools.build:cxxflags+=["-DBOOST_ASIO_DISABLE_CONCEPTS"]' default + fi - name: try to authenticate to Ripple Conan remote id: remote shell: bash @@ -47,6 +52,19 @@ runs: run: | mkdir ${build_dir} cd ${build_dir} + # This fixes some dependency build issues, especially on MacOS + if [[ "${RUNNER_OS}" = "macOS" && \ + "${{ steps.binaries.outputs.missing }}" =~ "boost" ]] + then + conan install \ + --output-folder . \ + --build boost \ + --options tests=True \ + --options xrpld=True \ + --settings build_type=${{ inputs.configuration }} \ + .. || \ + rm -rfv ~/.conan + fi conan install \ --output-folder . \ --build missing \ diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 9d23b6dfa9a..749ca6c4455 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -46,6 +46,9 @@ jobs: - name: install Ninja if: matrix.generator == 'Ninja' run: brew install ninja + - name: install nproc + run: | + brew install coreutils - name: check environment run: | env | sort @@ -53,6 +56,9 @@ jobs: python --version conan --version cmake --version + nproc --version + echo -n "nproc returns: " + nproc - name: configure Conan run : | conan profile new default --detect || true @@ -74,4 +80,10 @@ jobs: cmake-args: ${{ matrix.cmake-args }} - name: test run: | - ${build_dir}/rippled --unittest + n=$(nproc) + if [[ $n -gt 2 ]] + then + : $[ n/=2 ] + fi + echo "Using $n test jobs" + ${build_dir}/rippled --unittest --unittest-jobs $n From 34d918ac714ca3b9d555e7ec0846642e79fc909a Mon Sep 17 00:00:00 2001 From: Ed Hennis Date: Thu, 14 Nov 2024 16:41:54 -0500 Subject: [PATCH 7/7] Include endpoint info in retry messages --- src/test/jtx/impl/JSONRPCClient.cpp | 60 ++++++++++++++++------------- src/test/jtx/impl/WSClient.cpp | 60 ++++++++++++++++------------- 2 files changed, 66 insertions(+), 54 deletions(-) diff --git a/src/test/jtx/impl/JSONRPCClient.cpp b/src/test/jtx/impl/JSONRPCClient.cpp index 8dddaaf01b3..a8119ff9749 100644 --- a/src/test/jtx/impl/JSONRPCClient.cpp +++ b/src/test/jtx/impl/JSONRPCClient.cpp @@ -36,30 +36,6 @@ namespace test { class JSONRPCClient : public AbstractClient { - static boost::asio::ip::tcp::endpoint - getEndpoint(BasicConfig const& cfg) - { - auto& log = std::cerr; - ParsedPort common; - parse_Port(common, cfg["server"], log); - for (auto const& name : cfg.section("server").values()) - { - if (!cfg.exists(name)) - continue; - ParsedPort pp; - parse_Port(pp, cfg[name], log); - if (pp.protocol.count("http") == 0) - continue; - using namespace boost::asio::ip; - if (pp.ip && pp.ip->is_unspecified()) - *pp.ip = pp.ip->is_v6() ? address{address_v6::loopback()} - : address{address_v4::loopback()}; - return {*pp.ip, *pp.port}; - } - Throw("Missing HTTP port"); - return {}; // Silence compiler control paths return value warning - } - template static std::string buffer_string(ConstBufferSequence const& b) @@ -72,6 +48,7 @@ class JSONRPCClient : public AbstractClient } boost::asio::ip::tcp::endpoint ep_; + std::string endpointLabel_; boost::asio::io_service ios_; boost::asio::ip::tcp::socket stream_; boost::beast::multi_buffer bin_; @@ -83,6 +60,9 @@ class JSONRPCClient : public AbstractClient : ep_(getEndpoint(cfg)), stream_(ios_), rpc_version_(rpc_version) { stream_.connect(ep_); + std::stringstream ss; + ss << ep_; + endpointLabel_ = ss.str(); } ~JSONRPCClient() override @@ -91,6 +71,30 @@ class JSONRPCClient : public AbstractClient // stream_.close(); } + static boost::asio::ip::tcp::endpoint + getEndpoint(BasicConfig const& cfg) + { + auto& log = std::cerr; + ParsedPort common; + parse_Port(common, cfg["server"], log); + for (auto const& name : cfg.section("server").values()) + { + if (!cfg.exists(name)) + continue; + ParsedPort pp; + parse_Port(pp, cfg[name], log); + if (pp.protocol.count("http") == 0) + continue; + using namespace boost::asio::ip; + if (pp.ip && pp.ip->is_unspecified()) + *pp.ip = pp.ip->is_v6() ? address{address_v6::loopback()} + : address{address_v4::loopback()}; + return {*pp.ip, *pp.port}; + } + Throw("Missing HTTP port"); + return {}; // Silence compiler control paths return value warning + } + /* Return value is an Object type with up to three keys: status @@ -137,13 +141,13 @@ class JSONRPCClient : public AbstractClient Env::retry( [&]() { write(stream_, req); }, - "JSONRPCClient::invoke write", + "JSONRPCClient::invoke write " + endpointLabel_, 10ms); response res; Env::retry( [&]() { read(stream_, bin_, res); }, - "JSONRPCClient::invoke read", + "JSONRPCClient::invoke read " + endpointLabel_, 10ms); Json::Reader jr; @@ -170,9 +174,11 @@ makeJSONRPCClient(Config const& cfg, unsigned rpc_version) using namespace ripple::test::jtx; std::unique_ptr ret; + std::stringstream endpoint; + endpoint << JSONRPCClient::getEndpoint(cfg); Env::retry( [&]() { ret = std::make_unique(cfg, rpc_version); }, - "makeJSONRPCClient", + "makeJSONRPCClient " + endpoint.str(), 250ms); return ret; } diff --git a/src/test/jtx/impl/WSClient.cpp b/src/test/jtx/impl/WSClient.cpp index 97e97185021..6ff5d3aba61 100644 --- a/src/test/jtx/impl/WSClient.cpp +++ b/src/test/jtx/impl/WSClient.cpp @@ -50,31 +50,6 @@ class WSClientImpl : public WSClient } }; - static boost::asio::ip::tcp::endpoint - getEndpoint(BasicConfig const& cfg, bool v2) - { - auto& log = std::cerr; - ParsedPort common; - parse_Port(common, cfg["server"], log); - auto const ps = v2 ? "ws2" : "ws"; - for (auto const& name : cfg.section("server").values()) - { - if (!cfg.exists(name)) - continue; - ParsedPort pp; - parse_Port(pp, cfg[name], log); - if (pp.protocol.count(ps) == 0) - continue; - using namespace boost::asio::ip; - if (pp.ip && pp.ip->is_unspecified()) - *pp.ip = pp.ip->is_v6() ? address{address_v6::loopback()} - : address{address_v4::loopback()}; - return {*pp.ip, *pp.port}; - } - Throw("Missing WebSocket port"); - return {}; // Silence compiler control paths return value warning - } - template static std::string buffer_string(ConstBuffers const& b) @@ -94,6 +69,7 @@ class WSClientImpl : public WSClient boost::asio::ip::tcp::socket stream_; boost::beast::websocket::stream ws_; boost::beast::multi_buffer rb_; + std::string endpointLabel_; bool peerClosed_ = false; @@ -153,6 +129,9 @@ class WSClientImpl : public WSClient rb_, strand_.wrap(std::bind( &WSClientImpl::on_read_msg, this, std::placeholders::_1))); + std::stringstream ss; + ss << ep; + endpointLabel_ = ss.str(); } catch (std::exception&) { @@ -166,6 +145,31 @@ class WSClientImpl : public WSClient cleanup(); } + static boost::asio::ip::tcp::endpoint + getEndpoint(BasicConfig const& cfg, bool v2) + { + auto& log = std::cerr; + ParsedPort common; + parse_Port(common, cfg["server"], log); + auto const ps = v2 ? "ws2" : "ws"; + for (auto const& name : cfg.section("server").values()) + { + if (!cfg.exists(name)) + continue; + ParsedPort pp; + parse_Port(pp, cfg[name], log); + if (pp.protocol.count(ps) == 0) + continue; + using namespace boost::asio::ip; + if (pp.ip && pp.ip->is_unspecified()) + *pp.ip = pp.ip->is_v6() ? address{address_v6::loopback()} + : address{address_v4::loopback()}; + return {*pp.ip, *pp.port}; + } + Throw("Missing WebSocket port"); + return {}; // Silence compiler control paths return value warning + } + Json::Value invoke(std::string const& cmd, Json::Value const& params) override { @@ -190,7 +194,7 @@ class WSClientImpl : public WSClient auto const s = to_string(jp); Env::retry( [&]() { ws_.write_some(true, buffer(s)); }, - "WSClient::invoke write_some", + "WSClient::invoke write_some " + endpointLabel_, 100ms); } @@ -312,12 +316,14 @@ makeWSClient( using namespace ripple::test::jtx; std::unique_ptr ret; + std::stringstream endpoint; + endpoint << WSClientImpl::getEndpoint(cfg, v2); Env::retry( [&]() { ret = std::make_unique(cfg, v2, rpc_version, headers); }, - "makeWSClient", + "makeWSClient " + endpoint.str(), 250ms); return ret; }