diff --git a/API-CHANGELOG.md b/API-CHANGELOG.md index c7a31d27fa4..46623961e3f 100644 --- a/API-CHANGELOG.md +++ b/API-CHANGELOG.md @@ -22,6 +22,12 @@ API version 2 is available in `rippled` version 2.0.0 and later. See [API-VERSIO This version is supported by all `rippled` versions. For WebSocket and HTTP JSON-RPC requests, it is currently the default API version used when no `api_version` is specified. +## Unreleased + +### Additions and bugfixes in unreleased version + +- `noripple_check`: The `transactions` field is no longer included in the response if the response is an error or if there are no `problems`. + ## XRP Ledger server version 3.1.0 [Version 3.1.0](https://github.com/XRPLF/rippled/releases/tag/3.1.0) was released on Jan 27, 2026. diff --git a/include/xrpl/protocol/jss.h b/include/xrpl/protocol/jss.h index 0264f625eb2..573ba4cc699 100644 --- a/include/xrpl/protocol/jss.h +++ b/include/xrpl/protocol/jss.h @@ -280,6 +280,7 @@ JSS(frozen_balances); // out: GatewayBalances JSS(full); // in: LedgerClearer, handlers/Ledger JSS(full_reply); // out: PathFind JSS(fullbelow_size); // out: GetCounts +JSS(gateway); // in: noripple_check JSS(git); // out: server_info JSS(good); // out: RPCVersion JSS(hash); // out: NetworkOPs, InboundLedger, @@ -483,6 +484,7 @@ JSS(ports); // out: NetworkOPs JSS(previous); // out: Reservations JSS(previous_ledger); // out: LedgerPropose JSS(price); // out: amm_info, AuctionSlot +JSS(problems); // out: noripple_check JSS(proof); // in: BookOffers JSS(propose_seq); // out: LedgerPropose JSS(proposers); // out: NetworkOPs, LedgerConsensus @@ -660,6 +662,7 @@ JSS(url); // in/out: Subscribe, Unsubscribe JSS(url_password); // in: Subscribe JSS(url_username); // in: Subscribe JSS(urlgravatar); // +JSS(user); // in: noripple_check JSS(username); // in: Subscribe JSS(validated); // out: NetworkOPs, RPCHelpers, AccountTx* // Tx diff --git a/src/test/rpc/NoRippleCheck_test.cpp b/src/test/rpc/NoRippleCheck_test.cpp index c436c1d7b17..2ea68475c1f 100644 --- a/src/test/rpc/NoRippleCheck_test.cpp +++ b/src/test/rpc/NoRippleCheck_test.cpp @@ -104,13 +104,16 @@ class NoRippleCheck_test : public beast::unit_test::suite { // passing an account private key will cause // parsing as a seed to fail + // also, `transactions` is not included Json::Value params; params[jss::account] = toBase58(TokenType::NodePrivate, alice.sk()); params[jss::role] = "user"; params[jss::ledger] = "current"; + params[jss::transactions] = true; auto const result = env.rpc("json", "noripple_check", to_string(params))[jss::result]; BEAST_EXPECT(result[jss::error] == "actMalformed"); BEAST_EXPECT(result[jss::error_message] == "Account malformed."); + BEAST_EXPECT(!result.isMember(jss::transactions)); } { @@ -176,6 +179,7 @@ class NoRippleCheck_test : public beast::unit_test::suite if (!BEAST_EXPECT(pa.isArray())) return; + BEAST_EXPECT(!result.isMember(jss::transactions)); if (problems) { if (!BEAST_EXPECT(pa.size() == 2)) @@ -201,12 +205,12 @@ class NoRippleCheck_test : public beast::unit_test::suite // time. params[jss::transactions] = true; result = env.rpc("json", "noripple_check", to_string(params))[jss::result]; - if (!BEAST_EXPECT(result[jss::transactions].isArray())) - return; auto const txs = result[jss::transactions]; if (problems) { + if (!BEAST_EXPECT(result[jss::transactions].isArray())) + return; if (!BEAST_EXPECT(txs.size() == (user ? 1 : 2))) return; diff --git a/src/xrpld/rpc/handlers/NoRippleCheck.cpp b/src/xrpld/rpc/handlers/NoRippleCheck.cpp index e17a437efc8..6d6ab729423 100644 --- a/src/xrpld/rpc/handlers/NoRippleCheck.cpp +++ b/src/xrpld/rpc/handlers/NoRippleCheck.cpp @@ -22,12 +22,13 @@ fillTransaction( std::uint32_t& sequence, ReadView const& ledger) { - txArray["Sequence"] = Json::UInt(sequence++); - txArray["Account"] = toBase58(accountID); + txArray[jss::Sequence] = Json::UInt(sequence++); + txArray[jss::Account] = toBase58(accountID); auto& fees = ledger.fees(); // Convert the reference transaction cost in fee units to drops // scaled to represent the current fee load. - txArray["Fee"] = scaleFeeLoad(fees.base, context.app.getFeeTrack(), fees, false).jsonClipped(); + txArray[jss::Fee] = + scaleFeeLoad(fees.base, context.app.getFeeTrack(), fees, false).jsonClipped(); } // { @@ -42,32 +43,40 @@ Json::Value doNoRippleCheck(RPC::JsonContext& context) { auto const& params(context.params); - if (!params.isMember(jss::account)) - return RPC::missing_field_error("account"); - if (!params.isMember("role")) - return RPC::missing_field_error("role"); + // check account param + if (!params.isMember(jss::account)) + return RPC::missing_field_error(jss::account); if (!params[jss::account].isString()) return RPC::invalid_field_error(jss::account); + auto id = parseBase58(params[jss::account].asString()); + if (!id) + { + return rpcError(rpcACT_MALFORMED); + } + auto const accountID{std::move(id.value())}; + + // check role param + if (!params.isMember(jss::role)) + return RPC::missing_field_error(jss::role); + bool roleGateway = false; { - std::string const role = params["role"].asString(); - if (role == "gateway") + std::string const role = params[jss::role].asString(); + if (role == jss::gateway) roleGateway = true; - else if (role != "user") - return RPC::invalid_field_error("role"); + else if (role != jss::user) + return RPC::invalid_field_error(jss::role); } + // check limit param unsigned int limit; if (auto err = readLimitField(limit, RPC::Tuning::noRippleCheck, context)) return *err; - bool transactions = false; - if (params.isMember(jss::transactions)) - transactions = params["transactions"].asBool(); - + // check transactions param // The document[https://xrpl.org/noripple_check.html#noripple_check] states // that transactions params is a boolean value, however, assigning any // string value works. Do not allow this. This check is for api Version 2 @@ -78,47 +87,43 @@ doNoRippleCheck(RPC::JsonContext& context) return RPC::invalid_field_error(jss::transactions); } + bool transactions = false; + if (params.isMember(jss::transactions)) + transactions = params[jss::transactions].asBool(); + + // lookup ledger via params std::shared_ptr ledger; auto result = RPC::lookupLedger(ledger, context); if (!ledger) return result; - Json::Value dummy; - Json::Value& jvTransactions = - transactions ? (result[jss::transactions] = Json::arrayValue) : dummy; - - auto id = parseBase58(params[jss::account].asString()); - if (!id) - { - RPC::inject_error(rpcACT_MALFORMED, result); - return result; - } - auto const accountID{std::move(id.value())}; auto const sle = ledger->read(keylet::account(accountID)); if (!sle) return rpcError(rpcACT_NOT_FOUND); std::uint32_t seq = sle->getFieldU32(sfSequence); - Json::Value& problems = (result["problems"] = Json::arrayValue); + Json::Value& problems = (result[jss::problems] = Json::arrayValue); + + bool defaultRipple = sle->getFieldU32(sfFlags) & lsfDefaultRipple; - bool bDefaultRipple = sle->getFieldU32(sfFlags) & lsfDefaultRipple; + Json::Value jvTransactions = Json::arrayValue; - if (bDefaultRipple & !roleGateway) + if (defaultRipple & !roleGateway) { problems.append( "You appear to have set your default ripple flag even though you " "are not a gateway. This is not recommended unless you are " "experimenting"); } - else if (roleGateway & !bDefaultRipple) + else if (roleGateway & !defaultRipple) { problems.append("You should immediately set your default ripple flag"); if (transactions) { Json::Value& tx = jvTransactions.append(Json::objectValue); - tx["TransactionType"] = jss::AccountSet; - tx["SetFlag"] = 8; + tx[jss::TransactionType] = jss::AccountSet; + tx[jss::SetFlag] = 8; fillTransaction(context, tx, accountID, seq, *ledger); } } @@ -127,19 +132,19 @@ doNoRippleCheck(RPC::JsonContext& context) *ledger, accountID, uint256(), 0, limit, [&](std::shared_ptr const& ownedItem) { if (ownedItem->getType() == ltRIPPLE_STATE) { - bool const bLow = accountID == ownedItem->getFieldAmount(sfLowLimit).getIssuer(); + bool const low = accountID == ownedItem->getFieldAmount(sfLowLimit).getIssuer(); - bool const bNoRipple = - ownedItem->getFieldU32(sfFlags) & (bLow ? lsfLowNoRipple : lsfHighNoRipple); + bool const noRipple = + ownedItem->getFieldU32(sfFlags) & (low ? lsfLowNoRipple : lsfHighNoRipple); std::string problem; bool needFix = false; - if (bNoRipple & roleGateway) + if (noRipple && roleGateway) { problem = "You should clear the no ripple flag on your "; needFix = true; } - else if (!roleGateway & !bNoRipple) + else if (!roleGateway && !noRipple) { problem = "You should probably set the no ripple flag on your "; needFix = true; @@ -147,22 +152,24 @@ doNoRippleCheck(RPC::JsonContext& context) if (needFix) { AccountID peer = - ownedItem->getFieldAmount(bLow ? sfHighLimit : sfLowLimit).getIssuer(); - STAmount peerLimit = ownedItem->getFieldAmount(bLow ? sfHighLimit : sfLowLimit); + ownedItem->getFieldAmount(low ? sfHighLimit : sfLowLimit).getIssuer(); + STAmount peerLimit = ownedItem->getFieldAmount(low ? sfHighLimit : sfLowLimit); problem += to_string(peerLimit.getCurrency()); problem += " line to "; problem += to_string(peerLimit.getIssuer()); problems.append(problem); - STAmount limitAmount( - ownedItem->getFieldAmount(bLow ? sfLowLimit : sfHighLimit)); + STAmount limitAmount(ownedItem->getFieldAmount(low ? sfLowLimit : sfHighLimit)); limitAmount.setIssuer(peer); - Json::Value& tx = jvTransactions.append(Json::objectValue); - tx["TransactionType"] = jss::TrustSet; - tx["LimitAmount"] = limitAmount.getJson(JsonOptions::none); - tx["Flags"] = bNoRipple ? tfClearNoRipple : tfSetNoRipple; - fillTransaction(context, tx, accountID, seq, *ledger); + if (transactions) + { + Json::Value& tx = jvTransactions.append(Json::objectValue); + tx[jss::TransactionType] = jss::TrustSet; + tx[jss::LimitAmount] = limitAmount.getJson(JsonOptions::none); + tx[jss::Flags] = noRipple ? tfClearNoRipple : tfSetNoRipple; + fillTransaction(context, tx, accountID, seq, *ledger); + } return true; } @@ -170,6 +177,8 @@ doNoRippleCheck(RPC::JsonContext& context) return false; }); + if (transactions) + result[jss::transactions] = std::move(jvTransactions); return result; }