mirror of
https://github.com/XRPLF/clio.git
synced 2025-11-26 14:45:52 +00:00
implement noripple_check
This commit is contained in:
@@ -85,6 +85,7 @@ target_sources(clio PRIVATE
|
||||
src/rpc/handlers/AccountOffers.cpp
|
||||
src/rpc/handlers/AccountObjects.cpp
|
||||
src/rpc/handlers/GatewayBalances.cpp
|
||||
src/rpc/handlers/NoRippleCheck.cpp
|
||||
# Ledger
|
||||
src/rpc/handlers/Ledger.cpp
|
||||
src/rpc/handlers/LedgerData.cpp
|
||||
|
||||
@@ -30,6 +30,9 @@ doAccountOffers(Context const& context);
|
||||
Result
|
||||
doGatewayBalances(Context const& context);
|
||||
|
||||
Result
|
||||
doNoRippleCheck(Context const& context);
|
||||
|
||||
// channels methods
|
||||
|
||||
Result
|
||||
|
||||
@@ -106,6 +106,7 @@ static std::unordered_map<std::string, std::function<Result(Context const&)>>
|
||||
{"account_offers", &doAccountOffers},
|
||||
{"account_tx", &doAccountTx},
|
||||
{"gateway_balances", &doGatewayBalances},
|
||||
{"noripple_check", &doNoRippleCheck},
|
||||
{"book_offers", &doBookOffers},
|
||||
{"channel_authorize", &doChannelAuthorize},
|
||||
{"channel_verify", &doChannelVerify},
|
||||
@@ -169,6 +170,23 @@ buildResponse(Context const& ctx)
|
||||
|
||||
auto method = handlerTable[ctx.method];
|
||||
|
||||
try
|
||||
{
|
||||
return method(ctx);
|
||||
}
|
||||
catch (InvalidParamsError const& err)
|
||||
{
|
||||
return Status{Error::rpcINVALID_PARAMS, err.what()};
|
||||
}
|
||||
catch (AccountNotFoundError const& err)
|
||||
{
|
||||
return Status{Error::rpcACT_NOT_FOUND, err.what()};
|
||||
}
|
||||
catch (std::exception const& err)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(error)
|
||||
<< __func__ << " caught exception : " << err.what();
|
||||
return Status{Error::rpcINTERNAL, err.what()};
|
||||
}
|
||||
}
|
||||
} // namespace RPC
|
||||
|
||||
@@ -85,6 +85,35 @@ static Status OK;
|
||||
|
||||
using Result = std::variant<Status, boost::json::object>;
|
||||
|
||||
class InvalidParamsError : public std::exception
|
||||
{
|
||||
std::string msg;
|
||||
|
||||
public:
|
||||
InvalidParamsError(std::string const& msg) : msg(msg)
|
||||
{
|
||||
}
|
||||
|
||||
const char*
|
||||
what() const throw() override
|
||||
{
|
||||
return msg.c_str();
|
||||
}
|
||||
};
|
||||
class AccountNotFoundError : public std::exception
|
||||
{
|
||||
std::string account;
|
||||
|
||||
public:
|
||||
AccountNotFoundError(std::string const& acct) : account(acct)
|
||||
{
|
||||
}
|
||||
const char*
|
||||
what() const throw() override
|
||||
{
|
||||
return account.c_str();
|
||||
}
|
||||
};
|
||||
void
|
||||
inject_error(Error err, boost::json::object& json);
|
||||
|
||||
|
||||
@@ -3,6 +3,96 @@
|
||||
#include <rpc/RPCHelpers.h>
|
||||
namespace RPC {
|
||||
|
||||
std::optional<bool>
|
||||
getBool(boost::json::object const& request, std::string const& field)
|
||||
{
|
||||
if (!request.contains(field))
|
||||
return {};
|
||||
else if (request.at(field).is_bool())
|
||||
return request.at(field).as_bool();
|
||||
else
|
||||
throw InvalidParamsError("Invalid field " + field + ", not bool.");
|
||||
}
|
||||
bool
|
||||
getBool(
|
||||
boost::json::object const& request,
|
||||
std::string const& field,
|
||||
bool dfault)
|
||||
{
|
||||
if (auto res = getBool(request, field))
|
||||
return *res;
|
||||
else
|
||||
return dfault;
|
||||
}
|
||||
bool
|
||||
getRequiredBool(boost::json::object const& request, std::string const& field)
|
||||
{
|
||||
if (auto res = getBool(request, field))
|
||||
return *res;
|
||||
else
|
||||
throw InvalidParamsError("Missing field " + field);
|
||||
}
|
||||
std::optional<uint32_t>
|
||||
getUInt(boost::json::object const& request, std::string const& field)
|
||||
{
|
||||
if (!request.contains(field))
|
||||
return {};
|
||||
else if (request.at(field).is_uint64())
|
||||
return request.at(field).as_uint64();
|
||||
else if (request.at(field).is_int64())
|
||||
return request.at(field).as_int64();
|
||||
else
|
||||
throw InvalidParamsError("Invalid field " + field + ", not uint.");
|
||||
}
|
||||
uint32_t
|
||||
getUInt(
|
||||
boost::json::object const& request,
|
||||
std::string const& field,
|
||||
uint32_t dfault)
|
||||
{
|
||||
if (auto res = getUInt(request, field))
|
||||
return *res;
|
||||
else
|
||||
return dfault;
|
||||
}
|
||||
uint32_t
|
||||
getRequiredUInt(boost::json::object const& request, std::string const& field)
|
||||
{
|
||||
if (auto res = getUInt(request, field))
|
||||
return *res;
|
||||
else
|
||||
throw InvalidParamsError("Missing field " + field);
|
||||
}
|
||||
std::optional<std::string>
|
||||
getString(boost::json::object const& request, std::string const& field)
|
||||
{
|
||||
if (!request.contains(field))
|
||||
return {};
|
||||
else if (request.at(field).is_string())
|
||||
return request.at(field).as_string().c_str();
|
||||
else
|
||||
throw InvalidParamsError("Invalid field " + field + ", not string.");
|
||||
}
|
||||
std::string
|
||||
getRequiredString(boost::json::object const& request, std::string const& field)
|
||||
{
|
||||
if (auto res = getString(request, field))
|
||||
return *res;
|
||||
else
|
||||
throw InvalidParamsError("Missing field " + field);
|
||||
}
|
||||
std::string
|
||||
getString(
|
||||
boost::json::object const& request,
|
||||
std::string const& field,
|
||||
std::string dfault)
|
||||
{
|
||||
if (auto res = getString(request, field))
|
||||
return *res;
|
||||
else
|
||||
return dfault;
|
||||
}
|
||||
|
||||
std::optional<ripple::STAmount>
|
||||
getDeliveredAmount(
|
||||
std::shared_ptr<ripple::STTx const> const& txn,
|
||||
@@ -318,6 +408,9 @@ traverseOwnedNodes(
|
||||
ripple::uint256 const& cursor,
|
||||
std::function<bool(ripple::SLE)> atOwnedNode)
|
||||
{
|
||||
if (!backend.fetchLedgerObject(
|
||||
ripple::keylet::account(accountID).key, sequence))
|
||||
throw AccountNotFoundError(ripple::toBase58(accountID));
|
||||
auto const rootIndex = ripple::keylet::ownerDir(accountID);
|
||||
auto currentIndex = rootIndex;
|
||||
|
||||
|
||||
@@ -141,5 +141,40 @@ parseBook(boost::json::object const& request);
|
||||
std::variant<Status, ripple::AccountID>
|
||||
parseTaker(boost::json::value const& request);
|
||||
|
||||
std::optional<uint32_t>
|
||||
getUInt(boost::json::object const& request, std::string const& field);
|
||||
|
||||
uint32_t
|
||||
getUInt(
|
||||
boost::json::object const& request,
|
||||
std::string const& field,
|
||||
uint32_t dfault);
|
||||
|
||||
uint32_t
|
||||
getRequiredUInt(boost::json::object const& request, std::string const& field);
|
||||
|
||||
std::optional<bool>
|
||||
getBool(boost::json::object const& request, std::string const& field);
|
||||
|
||||
bool
|
||||
getBool(
|
||||
boost::json::object const& request,
|
||||
std::string const& field,
|
||||
bool dfault);
|
||||
|
||||
bool
|
||||
getRequiredBool(boost::json::object const& request, std::string const& field);
|
||||
|
||||
std::optional<std::string>
|
||||
getString(boost::json::object const& request, std::string const& field);
|
||||
|
||||
std::string
|
||||
getRequiredString(boost::json::object const& request, std::string const& field);
|
||||
|
||||
std::string
|
||||
getString(
|
||||
boost::json::object const& request,
|
||||
std::string const& field,
|
||||
std::string dfault);
|
||||
} // namespace RPC
|
||||
#endif
|
||||
|
||||
167
src/rpc/handlers/NoRippleCheck.cpp
Normal file
167
src/rpc/handlers/NoRippleCheck.cpp
Normal file
@@ -0,0 +1,167 @@
|
||||
#include <ripple/protocol/TxFlags.h>
|
||||
#include <rpc/RPCHelpers.h>
|
||||
|
||||
namespace RPC {
|
||||
|
||||
boost::json::object
|
||||
getBaseTx(
|
||||
ripple::AccountID const& accountID,
|
||||
std::uint32_t accountSeq,
|
||||
ripple::Fees const& fees)
|
||||
{
|
||||
boost::json::object tx;
|
||||
tx["Sequence"] = accountSeq;
|
||||
tx["Account"] = ripple::toBase58(accountID);
|
||||
tx["Fee"] = RPC::toBoostJson(fees.units.jsonClipped());
|
||||
return tx;
|
||||
}
|
||||
|
||||
Result
|
||||
doNoRippleCheck(Context const& context)
|
||||
{
|
||||
auto const& request = context.params;
|
||||
|
||||
auto accountID =
|
||||
accountFromStringStrict(getRequiredString(request, "account"));
|
||||
|
||||
if (!accountID)
|
||||
return Status{Error::rpcINVALID_PARAMS, "malformedAccount"};
|
||||
|
||||
std::string role = getRequiredString(request, "role");
|
||||
bool roleGateway = false;
|
||||
{
|
||||
if (role == "gateway")
|
||||
roleGateway = true;
|
||||
else if (role != "user")
|
||||
return Status{Error::rpcINVALID_PARAMS, "role field is invalid"};
|
||||
}
|
||||
|
||||
size_t limit = getUInt(request, "limit", 300);
|
||||
|
||||
bool includeTxs = getBool(request, "transactions", false);
|
||||
|
||||
auto v = ledgerInfoFromRequest(context);
|
||||
if (auto status = std::get_if<Status>(&v))
|
||||
return *status;
|
||||
|
||||
auto lgrInfo = std::get<ripple::LedgerInfo>(v);
|
||||
std::optional<ripple::Fees> fees =
|
||||
includeTxs ? context.backend->fetchFees(lgrInfo.seq) : std::nullopt;
|
||||
|
||||
boost::json::array transactions;
|
||||
|
||||
auto keylet = ripple::keylet::account(*accountID);
|
||||
auto accountObj =
|
||||
context.backend->fetchLedgerObject(keylet.key, lgrInfo.seq);
|
||||
if (!accountObj)
|
||||
throw AccountNotFoundError(ripple::toBase58(*accountID));
|
||||
|
||||
ripple::SerialIter it{accountObj->data(), accountObj->size()};
|
||||
ripple::SLE sle{it, keylet.key};
|
||||
|
||||
std::uint32_t accountSeq = sle.getFieldU32(ripple::sfSequence);
|
||||
|
||||
boost::json::array problems;
|
||||
bool bDefaultRipple =
|
||||
sle.getFieldU32(ripple::sfFlags) & ripple::lsfDefaultRipple;
|
||||
if (bDefaultRipple & !roleGateway)
|
||||
{
|
||||
problems.push_back(
|
||||
"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)
|
||||
{
|
||||
problems.push_back(
|
||||
"You should immediately set your default ripple flag");
|
||||
if (includeTxs)
|
||||
{
|
||||
auto tx = getBaseTx(*accountID, accountSeq++, *fees);
|
||||
tx["TransactionType"] = "AccountSet";
|
||||
tx["SetFlag"] = 8;
|
||||
transactions.push_back(tx);
|
||||
}
|
||||
}
|
||||
|
||||
traverseOwnedNodes(
|
||||
*context.backend,
|
||||
*accountID,
|
||||
lgrInfo.seq,
|
||||
{},
|
||||
[roleGateway,
|
||||
includeTxs,
|
||||
&fees,
|
||||
&transactions,
|
||||
&accountSeq,
|
||||
&limit,
|
||||
&accountID,
|
||||
&problems](auto const& ownedItem) {
|
||||
if (ownedItem.getType() == ripple::ltRIPPLE_STATE)
|
||||
{
|
||||
bool const bLow = accountID ==
|
||||
ownedItem.getFieldAmount(ripple::sfLowLimit).getIssuer();
|
||||
|
||||
bool const bNoRipple = ownedItem.getFieldU32(ripple::sfFlags) &
|
||||
(bLow ? ripple::lsfLowNoRipple : ripple::lsfHighNoRipple);
|
||||
|
||||
std::string problem;
|
||||
bool needFix = false;
|
||||
if (bNoRipple & roleGateway)
|
||||
{
|
||||
problem = "You should clear the no ripple flag on your ";
|
||||
needFix = true;
|
||||
}
|
||||
else if (!bNoRipple & !roleGateway)
|
||||
{
|
||||
problem =
|
||||
"You should probably set the no ripple flag on "
|
||||
"your ";
|
||||
needFix = true;
|
||||
}
|
||||
if (needFix)
|
||||
{
|
||||
ripple::AccountID peer =
|
||||
ownedItem
|
||||
.getFieldAmount(
|
||||
bLow ? ripple::sfHighLimit : ripple::sfLowLimit)
|
||||
.getIssuer();
|
||||
ripple::STAmount peerLimit = ownedItem.getFieldAmount(
|
||||
bLow ? ripple::sfHighLimit : ripple::sfLowLimit);
|
||||
problem += to_string(peerLimit.getCurrency());
|
||||
problem += " line to ";
|
||||
problem += to_string(peerLimit.getIssuer());
|
||||
problems.emplace_back(problem);
|
||||
if (includeTxs)
|
||||
{
|
||||
ripple::STAmount limitAmount(ownedItem.getFieldAmount(
|
||||
bLow ? ripple::sfLowLimit : ripple::sfHighLimit));
|
||||
limitAmount.setIssuer(peer);
|
||||
auto tx = getBaseTx(*accountID, accountSeq++, *fees);
|
||||
tx["TransactionType"] = "TrustSet";
|
||||
tx["LimitAmount"] = RPC::toBoostJson(
|
||||
limitAmount.getJson(ripple::JsonOptions::none));
|
||||
tx["Flags"] = bNoRipple ? ripple::tfClearNoRipple
|
||||
: ripple::tfSetNoRipple;
|
||||
transactions.push_back(tx);
|
||||
}
|
||||
|
||||
if (limit-- == 0)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
boost::json::object response;
|
||||
response["ledger_index"] = lgrInfo.seq;
|
||||
response["ledger_hash"] = ripple::strHex(lgrInfo.hash);
|
||||
response["problems"] = std::move(problems);
|
||||
if (includeTxs)
|
||||
response["transactions"] = std::move(transactions);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
} // namespace RPC
|
||||
Reference in New Issue
Block a user