implement noripple_check

This commit is contained in:
CJ Cobb
2021-09-13 15:58:42 -04:00
parent f18c8f67e2
commit 8e877ac87c
7 changed files with 347 additions and 1 deletions

View File

@@ -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

View File

@@ -30,6 +30,9 @@ doAccountOffers(Context const& context);
Result
doGatewayBalances(Context const& context);
Result
doNoRippleCheck(Context const& context);
// channels methods
Result

View File

@@ -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

View File

@@ -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);

View File

@@ -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;

View File

@@ -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

View 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