mirror of
https://github.com/Xahau/xahaud.git
synced 2026-06-04 17:26:39 +00:00
423 lines
12 KiB
C++
423 lines
12 KiB
C++
//------------------------------------------------------------------------------
|
|
/*
|
|
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 <ripple/app/tx/impl/SetRemarks.h>
|
|
#include <ripple/basics/Log.h>
|
|
#include <ripple/core/Config.h>
|
|
#include <ripple/ledger/View.h>
|
|
#include <ripple/protocol/Feature.h>
|
|
#include <ripple/protocol/Indexes.h>
|
|
#include <ripple/protocol/PublicKey.h>
|
|
#include <ripple/protocol/Quality.h>
|
|
#include <ripple/protocol/st.h>
|
|
|
|
namespace ripple {
|
|
|
|
TxConsequences
|
|
SetRemarks::makeTxConsequences(PreflightContext const& ctx)
|
|
{
|
|
return TxConsequences{ctx.tx, TxConsequences::normal};
|
|
}
|
|
|
|
NotTEC
|
|
SetRemarks::validateRemarks(STArray const& remarks, Journal& j)
|
|
{
|
|
std::set<Blob> already_seen;
|
|
|
|
if (remarks.empty() || remarks.size() > 32)
|
|
{
|
|
JLOG(j.warn()) << "Remakrs: Cannot set more than 32 remarks (or fewer "
|
|
"than 1) in a txn.";
|
|
return temMALFORMED;
|
|
}
|
|
|
|
for (auto const& remark : remarks)
|
|
{
|
|
if (remark.getFName() != sfRemark)
|
|
{
|
|
JLOG(j.warn()) << "Remarks: contained non-sfRemark field.";
|
|
return temMALFORMED;
|
|
}
|
|
|
|
// will be checked by template system, extra check for security
|
|
if (!remark.isFieldPresent(sfRemarkName))
|
|
return temMALFORMED;
|
|
|
|
Blob const& name = remark.getFieldVL(sfRemarkName);
|
|
if (already_seen.find(name) != already_seen.end())
|
|
{
|
|
JLOG(j.warn()) << "Remarks: duplicate RemarkName entry.";
|
|
return temMALFORMED;
|
|
}
|
|
if (name.size() == 0 || name.size() > 256)
|
|
{
|
|
JLOG(j.warn()) << "Remarks: RemarkName cannot be empty or larger "
|
|
"than 256 chars.";
|
|
return temMALFORMED;
|
|
}
|
|
|
|
already_seen.emplace(name);
|
|
|
|
uint32_t flags =
|
|
remark.isFieldPresent(sfFlags) ? remark.getFieldU32(sfFlags) : 0;
|
|
if (flags != 0 && flags != tfImmutable)
|
|
{
|
|
JLOG(j.warn()) << "Remarks: Flags must be either tfImmutable or 0";
|
|
return temMALFORMED;
|
|
}
|
|
|
|
if (!remark.isFieldPresent(sfRemarkValue))
|
|
{
|
|
if (flags & tfImmutable)
|
|
{
|
|
JLOG(j.warn())
|
|
<< "Remarks: A remark deletion cannot be marked immutable.";
|
|
return temMALFORMED;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
Blob const& val = remark.getFieldVL(sfRemarkValue);
|
|
if (val.size() == 0 || val.size() > 256)
|
|
{
|
|
JLOG(j.warn()) << "Remarks: RemarkValue cannot be empty or larger "
|
|
"than 256 chars.";
|
|
return temMALFORMED;
|
|
}
|
|
}
|
|
|
|
return tesSUCCESS;
|
|
}
|
|
|
|
NotTEC
|
|
SetRemarks::preflight(PreflightContext const& ctx)
|
|
{
|
|
if (!ctx.rules.enabled(featureRemarks))
|
|
return temDISABLED;
|
|
|
|
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
|
|
return ret;
|
|
|
|
auto& tx = ctx.tx;
|
|
auto& j = ctx.j;
|
|
|
|
if (tx.isFieldPresent(sfFlags) && tx.getFieldU32(sfFlags) != 0)
|
|
{
|
|
JLOG(j.warn()) << "Remarks: sfFlags != 0 (there are no valid flags for "
|
|
"ttREMARKS_SET.)";
|
|
return temMALFORMED;
|
|
}
|
|
|
|
auto const& remarks = tx.getFieldArray(sfRemarks);
|
|
if (NotTEC result = validateRemarks(remarks, ctx.j); !isTesSuccess(result))
|
|
return result;
|
|
|
|
return preflight2(ctx);
|
|
}
|
|
|
|
template <typename T>
|
|
inline std::optional<AccountID>
|
|
getRemarksIssuer(T const& sleO)
|
|
{
|
|
std::optional<AccountID> issuer;
|
|
|
|
// check if it's an allowable object type
|
|
uint16_t lt = sleO->getFieldU16(sfLedgerEntryType);
|
|
|
|
switch (lt)
|
|
{
|
|
case ltACCOUNT_ROOT:
|
|
case ltOFFER:
|
|
case ltESCROW:
|
|
case ltTICKET:
|
|
case ltPAYCHAN:
|
|
case ltCHECK:
|
|
case ltDEPOSIT_PREAUTH: {
|
|
issuer = sleO->getAccountID(sfAccount);
|
|
break;
|
|
}
|
|
|
|
case ltNFTOKEN_OFFER: {
|
|
issuer = sleO->getAccountID(sfOwner);
|
|
break;
|
|
}
|
|
|
|
case ltURI_TOKEN: {
|
|
issuer = sleO->getAccountID(sfIssuer);
|
|
break;
|
|
}
|
|
|
|
case ltRIPPLE_STATE: {
|
|
// remarks can only be attached to a trustline by the issuer
|
|
AccountID lowAcc = sleO->getFieldAmount(sfLowLimit).getIssuer();
|
|
AccountID highAcc = sleO->getFieldAmount(sfHighLimit).getIssuer();
|
|
|
|
STAmount bal = sleO->getFieldAmount(sfBalance);
|
|
|
|
if (bal < beast::zero)
|
|
{
|
|
// low account is issuer
|
|
issuer = lowAcc;
|
|
break;
|
|
}
|
|
|
|
if (bal > beast::zero)
|
|
{
|
|
// high acccount is issuer
|
|
issuer = highAcc;
|
|
break;
|
|
}
|
|
|
|
// if the balance is zero we'll look for the side in default state
|
|
// and assume this is the issuer
|
|
uint32_t flags = sleO->getFieldU32(sfFlags);
|
|
bool const highReserve = (flags & lsfHighReserve);
|
|
bool const lowReserve = (flags & lsfLowReserve);
|
|
|
|
if (!highReserve && !lowReserve)
|
|
{
|
|
// error state
|
|
// do nothing, fallthru.
|
|
}
|
|
else if (highReserve && lowReserve)
|
|
{
|
|
// in this edge case we don't know who is the issuer, because
|
|
// there isn't a clear issuer. do nothing, fallthru.
|
|
}
|
|
else
|
|
{
|
|
issuer = (highReserve ? highAcc : lowAcc);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return issuer;
|
|
}
|
|
|
|
TER
|
|
SetRemarks::preclaim(PreclaimContext const& ctx)
|
|
{
|
|
if (!ctx.view.rules().enabled(featureRemarks))
|
|
return temDISABLED;
|
|
|
|
auto const id = ctx.tx[sfAccount];
|
|
|
|
auto const sle = ctx.view.read(keylet::account(id));
|
|
if (!sle)
|
|
return terNO_ACCOUNT;
|
|
|
|
auto const objID = ctx.tx[sfObjectID];
|
|
auto const sleO = ctx.view.read(keylet::unchecked(objID));
|
|
if (!sleO)
|
|
return tecNO_TARGET;
|
|
|
|
std::optional<AccountID> issuer = getRemarksIssuer(sleO);
|
|
|
|
if (!issuer || *issuer != id)
|
|
return tecNO_PERMISSION;
|
|
|
|
// sanity check the remarks merge between txn and obj
|
|
auto const& remarksTxn = ctx.tx.getFieldArray(sfRemarks);
|
|
|
|
std::map<Blob, std::pair<Blob, bool>> keys;
|
|
if (sleO->isFieldPresent(sfRemarks))
|
|
{
|
|
auto const& remarksObj = sleO->getFieldArray(sfRemarks);
|
|
|
|
// map the remark name to its value and whether it's immutable
|
|
for (auto const& remark : remarksObj)
|
|
keys.emplace(std::make_pair(
|
|
remark.getFieldVL(sfRemarkName),
|
|
std::make_pair(
|
|
remark.getFieldVL(sfRemarkValue),
|
|
remark.isFieldPresent(sfFlags) &&
|
|
remark.getFieldU32(sfFlags) & tfImmutable)));
|
|
}
|
|
|
|
int64_t count = keys.size();
|
|
|
|
for (auto const& remark : remarksTxn)
|
|
{
|
|
std::optional<Blob> valTxn;
|
|
if (remark.isFieldPresent(sfRemarkValue))
|
|
valTxn = remark.getFieldVL(sfRemarkValue);
|
|
bool const isDeletion = !valTxn;
|
|
|
|
Blob name = remark.getFieldVL(sfRemarkName);
|
|
if (keys.find(name) == keys.end())
|
|
{
|
|
// new remark
|
|
if (isDeletion)
|
|
{
|
|
// this could have been an error but deleting something
|
|
// that doesn't exist is traditionally not an error in xrpl
|
|
continue;
|
|
}
|
|
|
|
++count;
|
|
continue;
|
|
}
|
|
|
|
auto const& [valObj, immutable] = keys[name];
|
|
|
|
// even if it's immutable, if we don't mutate it that's a noop so just
|
|
// pass it
|
|
if (valTxn && *valTxn == valObj)
|
|
continue;
|
|
|
|
if (immutable)
|
|
{
|
|
JLOG(ctx.j.warn())
|
|
<< "Remarks: attempt to mutate an immutable remark.";
|
|
return tecIMMUTABLE;
|
|
}
|
|
|
|
if (isDeletion)
|
|
{
|
|
if (--count < 0)
|
|
{
|
|
JLOG(ctx.j.warn()) << "Remarks: insane remarks accounting.";
|
|
return tecCLAIM;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (count > 32)
|
|
{
|
|
JLOG(ctx.j.warn()) << "Remarks: an object may have at most 32 remarks.";
|
|
return tecTOO_MANY_REMARKS;
|
|
}
|
|
|
|
return tesSUCCESS;
|
|
}
|
|
|
|
TER
|
|
SetRemarks::doApply()
|
|
{
|
|
auto j = ctx_.journal;
|
|
Sandbox sb(&ctx_.view());
|
|
|
|
auto const sle = sb.peek(keylet::account(account_));
|
|
if (!sle)
|
|
return tefINTERNAL;
|
|
|
|
auto const objID = ctx_.tx[sfObjectID];
|
|
auto sleO = sb.peek(keylet::unchecked(objID));
|
|
if (!sleO)
|
|
return tecNO_TARGET;
|
|
|
|
std::optional<AccountID> issuer = getRemarksIssuer(sleO);
|
|
|
|
if (!issuer || *issuer != account_)
|
|
return tecNO_PERMISSION;
|
|
|
|
auto const& remarksTxn = ctx_.tx.getFieldArray(sfRemarks);
|
|
|
|
std::map<Blob, std::pair<Blob, bool>> remarksMap;
|
|
|
|
if (sleO->isFieldPresent(sfRemarks))
|
|
{
|
|
auto const& remarksObj = sleO->getFieldArray(sfRemarks);
|
|
for (auto const& remark : remarksObj)
|
|
{
|
|
uint32_t flags = remark.isFieldPresent(sfFlags)
|
|
? remark.getFieldU32(sfFlags)
|
|
: 0;
|
|
bool const immutable = (flags & tfImmutable) != 0;
|
|
remarksMap[remark.getFieldVL(sfRemarkName)] = {
|
|
remark.getFieldVL(sfRemarkValue),
|
|
remark.isFieldPresent(sfFlags) && immutable};
|
|
}
|
|
}
|
|
|
|
for (auto const& remark : remarksTxn)
|
|
{
|
|
std::optional<Blob> val;
|
|
if (remark.isFieldPresent(sfRemarkValue))
|
|
val = remark.getFieldVL(sfRemarkValue);
|
|
Blob name = remark.getFieldVL(sfRemarkName);
|
|
|
|
bool const isDeletion = !val;
|
|
uint32_t flags =
|
|
remark.isFieldPresent(sfFlags) ? remark.getFieldU32(sfFlags) : 0;
|
|
bool const setImmutable = (flags & tfImmutable) != 0;
|
|
|
|
if (isDeletion)
|
|
{
|
|
if (remarksMap.find(name) != remarksMap.end())
|
|
remarksMap.erase(name);
|
|
continue;
|
|
}
|
|
|
|
if (remarksMap.find(name) == remarksMap.end())
|
|
{
|
|
remarksMap[name] = std::make_pair(*val, setImmutable);
|
|
continue;
|
|
}
|
|
|
|
remarksMap[name].first = *val;
|
|
if (!remarksMap[name].second)
|
|
remarksMap[name].second = setImmutable;
|
|
}
|
|
|
|
// canonically order
|
|
std::vector<Blob> keys;
|
|
for (auto const& [k, _] : remarksMap)
|
|
keys.push_back(k);
|
|
|
|
std::sort(keys.begin(), keys.end());
|
|
|
|
STArray newRemarks{sfRemarks, static_cast<int>(keys.size())};
|
|
for (auto const& k : keys)
|
|
{
|
|
STObject remark{sfRemark};
|
|
|
|
remark.setFieldVL(sfRemarkName, k);
|
|
remark.setFieldVL(sfRemarkValue, remarksMap[k].first);
|
|
if (remarksMap[k].second & tfImmutable)
|
|
remark.setFieldU32(sfFlags, lsfImmutable);
|
|
|
|
newRemarks.push_back(std::move(remark));
|
|
}
|
|
|
|
if (newRemarks.size() > 32)
|
|
return tecINTERNAL;
|
|
|
|
if (newRemarks.empty() && sleO->isFieldPresent(sfRemarks))
|
|
sleO->makeFieldAbsent(sfRemarks);
|
|
else
|
|
sleO->setFieldArray(sfRemarks, std::move(newRemarks));
|
|
|
|
sb.update(sleO);
|
|
|
|
return tesSUCCESS;
|
|
}
|
|
|
|
XRPAmount
|
|
SetRemarks::calculateBaseFee(ReadView const& view, STTx const& tx)
|
|
{
|
|
// RH TODO: transaction fee needs to charge for remarks, in particular
|
|
// because they are not ownercounted.
|
|
auto fee = Transactor::calculateBaseFee(view, tx);
|
|
return fee;
|
|
}
|
|
|
|
} // namespace ripple
|