Compare commits

..

8 Commits

Author SHA1 Message Date
Richard Holland
738a4c1e87 move attestation blocking logic to applySteps, add error code 2023-12-03 22:29:12 +00:00
Richard Holland
df9ed403ad bug fixes 2023-12-03 21:39:55 +00:00
Richard Holland
deb4b34b9b add last ledger seq enforcement, accid attestations 2023-12-03 16:19:18 +00:00
Richard Holland
d0baeab32f attestation consensus stuff. compiling not tested 2023-12-03 15:38:05 +00:00
Richard Holland
72016c3e0e bug fixes 2023-12-03 11:17:29 +00:00
Richard Holland
54211fb63b start of featureAttestations 2023-12-03 10:51:08 +00:00
Denis Angell
27f43ba9ee TSH Tests (#222) 2023-12-03 10:13:05 +01:00
Wietse Wind
7881b7f69f Build on most cores (nproc / 3 » nproc / 1.337) (#226)
Use those resources! 🎉🚀
2023-12-01 12:47:50 +01:00
40 changed files with 5592 additions and 2446 deletions

View File

@@ -455,7 +455,7 @@ target_sources (rippled PRIVATE
src/ripple/app/tx/impl/GenesisMint.cpp
src/ripple/app/tx/impl/Import.cpp
src/ripple/app/tx/impl/Invoke.cpp
src/ripple/app/tx/impl/Remit.cpp
src/ripple/app/tx/impl/Attest.cpp
src/ripple/app/tx/impl/SetSignerList.cpp
src/ripple/app/tx/impl/SetTrust.cpp
src/ripple/app/tx/impl/SignerEntries.cpp
@@ -741,7 +741,6 @@ if (tests)
src/test/app/RCLCensorshipDetector_test.cpp
src/test/app/RCLValidations_test.cpp
src/test/app/Regression_test.cpp
src/test/app/Remit_test.cpp
src/test/app/SHAMapStore_test.cpp
src/test/app/SetAuth_test.cpp
src/test/app/SetRegularKey_test.cpp
@@ -757,6 +756,7 @@ if (tests)
src/test/app/ValidatorList_test.cpp
src/test/app/ValidatorSite_test.cpp
src/test/app/SetHook_test.cpp
src/test/app/SetHookTSH_test.cpp
src/test/app/Wildcard_test.cpp
src/test/app/XahauGenesis_test.cpp
src/test/app/tx/apply_test.cpp
@@ -890,7 +890,6 @@ if (tests)
src/test/jtx/impl/rate.cpp
src/test/jtx/impl/regkey.cpp
src/test/jtx/impl/reward.cpp
src/test/jtx/impl/remit.cpp
src/test/jtx/impl/sendmax.cpp
src/test/jtx/impl/seq.cpp
src/test/jtx/impl/sig.cpp
@@ -899,6 +898,7 @@ if (tests)
src/test/jtx/impl/token.cpp
src/test/jtx/impl/trust.cpp
src/test/jtx/impl/txflags.cpp
src/test/jtx/impl/unl.cpp
src/test/jtx/impl/uritoken.cpp
src/test/jtx/impl/utility.cpp

View File

@@ -0,0 +1,182 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
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/Attest.h>
#include <ripple/basics/Log.h>
#include <ripple/ledger/View.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/Indexes.h>
namespace ripple {
TxConsequences
Attest::makeTxConsequences(PreflightContext const& ctx)
{
return TxConsequences{ctx.tx, TxConsequences::normal};
}
NotTEC
Attest::preflight(PreflightContext const& ctx)
{
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
return ret;
auto const flags = ctx.tx.getFlags();
if (flags & tfAttestMask)
return temINVALID_FLAG;
bool const hasTxnID = ctx.tx.isFieldPresent(sfAttestedTxnID);
bool const hasAccID = ctx.tx.isFieldPresent(sfAttestedAccID);
if ((hasTxnID && hasAccID) || (!hasTxnID && !hasAccID))
{
JLOG(ctx.j.warn())
<< "Attest: must specify exactly one of: AttestedTxnID, AttestedAccID";
return temMALFORMED;
}
return preflight2(ctx);
}
TER
Attest::preclaim(PreclaimContext const& ctx)
{
if (!ctx.view.rules().enabled(featureAttestations))
return temDISABLED;
auto const id = ctx.tx[sfAccount];
auto const sle = ctx.view.read(keylet::account(id));
if (!sle)
return terNO_ACCOUNT;
Keylet kl =
ctx.tx.isFieldPresent(sfAttestedTxnID)
? keylet::attestationTxn(id, ctx.tx.getFieldH256(sfAttestedTxnID))
: keylet::attestationAcc(id, ctx.tx.getAccountID(sfAttestedAccID));
uint32_t flags = ctx.tx.getFlags();
bool const isDelete = (flags & tfDeleteAttestation) == tfDeleteAttestation;
bool const exists = ctx.view.exists(kl);
if (exists && !isDelete)
return tecDUPLICATE;
else if (!exists && isDelete)
return tecNO_ENTRY;
return tesSUCCESS;
}
TER
Attest::doApply()
{
auto j = ctx_.app.journal("View");
auto const sle = view().peek(keylet::account(account_));
if (!sle)
return tefINTERNAL;
Keylet kl =
ctx_.tx.isFieldPresent(sfAttestedTxnID)
? keylet::attestationTxn(account_, ctx_.tx.getFieldH256(sfAttestedTxnID))
: keylet::attestationAcc(account_, ctx_.tx.getAccountID(sfAttestedAccID));
uint32_t flags = ctx_.tx.getFlags();
bool const isDelete = (flags & tfDeleteAttestation) == tfDeleteAttestation;
bool const exists = view().exists(kl);
// check for sufficient reserves
if (!isDelete)
{
STAmount const reserve{
view().fees().accountReserve(sle->getFieldU32(sfOwnerCount) + 1)};
STAmount const afterFee =
mPriorBalance - ctx_.tx.getFieldAmount(sfFee).xrp();
if (afterFee > mPriorBalance || afterFee < reserve)
return tecINSUFFICIENT_RESERVE;
}
// delete mode
if (exists && isDelete)
{
auto sleA = view().peek(kl);
AccountID owner = sleA->getAccountID(sfOwner);
auto const page = (*sleA)[sfOwnerNode];
if (!view().dirRemove(
keylet::ownerDir(owner), page, kl.key, true))
{
JLOG(j.fatal())
<< "Could not remove Attestation from owner directory";
return tefBAD_LEDGER;
}
view().erase(sleA);
adjustOwnerCount(view(), sle, -1, j);
return tesSUCCESS;
}
// create mode
else if (!exists && !isDelete)
{
auto sleA = std::make_shared<SLE>(kl);
sleA->setFieldU16(sfLedgerEntryType, ltATTESTATION);
sleA->setAccountID(sfOwner, account_);
if (ctx_.tx.isFieldPresent(sfAttestedTxnID))
sleA->setFieldH256(sfAttestedTxnID, ctx_.tx.getFieldH256(sfAttestedTxnID));
else
sleA->setAccountID(sfAttestedAccID, ctx_.tx.getAccountID(sfAttestedAccID));
auto const page = view().dirInsert(
keylet::ownerDir(account_), kl, describeOwnerDir(account_));
JLOG(j_.trace())
<< "Adding Attestation to owner directory " << to_string(kl.key)
<< ": " << (page ? "success" : "failure");
if (!page)
return tecDIR_FULL;
sleA->setFieldU64(sfOwnerNode, *page);
view().insert(sleA);
adjustOwnerCount(view(), sle, 1, j);
return tesSUCCESS;
}
// everything else is invalid
else
return tecINTERNAL;
}
XRPAmount
Attest::calculateBaseFee(ReadView const& view, STTx const& tx)
{
return Transactor::calculateBaseFee(view, tx);
}
} // namespace ripple

View File

@@ -17,8 +17,8 @@
*/
//==============================================================================
#ifndef RIPPLE_TX_SIMPLE_PAYMENT_H_INCLUDED
#define RIPPLE_TX_SIMPLE_PAYMENT_H_INCLUDED
#ifndef RIPPLE_TX_ATTEST_H_INCLUDED
#define RIPPLE_TX_ATTEST_H_INCLUDED
#include <ripple/app/tx/impl/Transactor.h>
#include <ripple/basics/Log.h>
@@ -27,12 +27,12 @@
namespace ripple {
class Remit : public Transactor
class Attest : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Custom};
explicit Remit(ApplyContext& ctx) : Transactor(ctx)
explicit Attest(ApplyContext& ctx) : Transactor(ctx)
{
}
@@ -45,6 +45,9 @@ public:
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};

View File

@@ -492,6 +492,7 @@ LedgerEntryTypesMatch::visitEntry(
case ltURI_TOKEN:
case ltIMPORT_VLSEQ:
case ltUNL_REPORT:
case ltATTESTATION:
break;
default:
invalidTypeAdded_ = true;
@@ -597,8 +598,7 @@ ValidNewAccountRoot::finalize(
return false;
}
if ((tt == ttPAYMENT || tt == ttIMPORT || tt == ttGENESIS_MINT ||
tt == ttREMIT) &&
if ((tt == ttPAYMENT || tt == ttIMPORT || tt == ttGENESIS_MINT) &&
result == tesSUCCESS)
{
std::uint32_t const startingSeq{

View File

@@ -1,501 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
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/Remit.h>
#include <ripple/app/tx/impl/URIToken.h>
#include <ripple/basics/Log.h>
#include <ripple/ledger/View.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/Indexes.h>
namespace ripple {
TxConsequences
Remit::makeTxConsequences(PreflightContext const& ctx)
{
return TxConsequences{ctx.tx, TxConsequences::normal};
}
NotTEC
Remit::preflight(PreflightContext const& ctx)
{
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
return ret;
if (ctx.tx.getFlags() & tfUniversalMask)
{
// There are no flags (other than universal).
JLOG(ctx.j.warn()) << "Malformed transaction: Invalid flags set.";
return temINVALID_FLAG;
}
AccountID const dstID = ctx.tx.getAccountID(sfDestination);
AccountID const srcID = ctx.tx.getAccountID(sfAccount);
if (dstID == srcID)
{
JLOG(ctx.j.warn()) << "Malformed transaction: Remit to self.";
return temREDUNDANT;
}
// sanity check amounts
if (ctx.tx.isFieldPresent(sfAmounts))
{
std::map<Currency, std::set<AccountID>> already;
bool nativeAlready = false;
STArray const& sEntries(ctx.tx.getFieldArray(sfAmounts));
for (STObject const& sEntry : sEntries)
{
// Validate the AmountEntry.
if (sEntry.getFName() != sfAmountEntry)
{
JLOG(ctx.j.warn()) << "Malformed: Expected AmountEntry.";
return temMALFORMED;
}
STAmount const amount = sEntry.getFieldAmount(sfAmount);
if (!isLegalNet(amount) || amount.signum() <= 0)
{
JLOG(ctx.j.warn()) << "Malformed transaction: bad amount: "
<< amount.getFullText();
return temBAD_AMOUNT;
}
if (isBadCurrency(amount.getCurrency()))
{
JLOG(ctx.j.warn()) << "Malformed transaction: Bad currency.";
return temBAD_CURRENCY;
}
if (isXRP(amount))
{
if (nativeAlready)
{
JLOG(ctx.j.warn()) << "Malformed transaction: Native "
"Currency appears more than once.";
return temMALFORMED;
}
nativeAlready = true;
continue;
}
auto found = already.find(amount.getCurrency());
if (found == already.end())
{
already.emplace(
amount.getCurrency(),
std::set<AccountID>{amount.getIssuer()});
continue;
}
if (found->second.find(amount.getIssuer()) != found->second.end())
{
JLOG(ctx.j.warn()) << "Malformed transaction: Issued Currency "
"appears more than once.";
return temMALFORMED;
}
found->second.emplace(amount.getIssuer());
}
}
// sanity check minturitoken
if (ctx.tx.isFieldPresent(sfMintURIToken))
{
STObject const& mint = const_cast<ripple::STTx&>(ctx.tx)
.getField(sfMintURIToken)
.downcast<STObject>();
// RH TODO: iterate mint fields detect any that shouldnt be there
Blob const uri = mint.getFieldVL(sfURI);
if (uri.size() < 1 || uri.size() > 256)
{
JLOG(ctx.j.warn())
<< "Malformed transaction: URI was too short/long.";
return temMALFORMED;
}
if (!URIToken::validateUTF8(mint.getFieldVL(sfURI)))
{
JLOG(ctx.j.warn())
<< "Malformed transaction: Invalid UTF8 inside MintURIToken.";
return temMALFORMED;
}
if (mint.isFieldPresent(sfFlags))
{
if (mint.getFieldU32(sfFlags) & tfURITokenMintMask)
return temINVALID_FLAG;
}
}
// check uritokenids for duplicates
if (ctx.tx.isFieldPresent(sfURITokenIDs))
{
STVector256 ids = ctx.tx.getFieldV256(sfURITokenIDs);
std::sort(ids.begin(), ids.end());
if (std::adjacent_find(ids.begin(), ids.end()) != ids.end())
{
JLOG(ctx.j.warn())
<< "Malformed transaction: Duplicate URITokenID.";
return temMALFORMED;
}
}
return preflight2(ctx);
}
TER
Remit::doApply()
{
if (!view().rules().enabled(featureRemit) || !view().rules().enabled(featureURIToken))
return temDISABLED;
Sandbox sb(&ctx_.view());
beast::Journal const& j = ctx_.journal;
auto const srcAccID = ctx_.tx[sfAccount];
auto sleSrcAcc = sb.peek(keylet::account(srcAccID));
if (!sleSrcAcc)
return terNO_ACCOUNT;
XRPAmount const accountReserve{sb.fees().accountReserve(0)};
XRPAmount const objectReserve{sb.fees().accountReserve(1) - accountReserve};
// amount of native tokens we will transfer to cover reserves for the
// tls/acc/uritokens created, and native tokens listed in amounts
XRPAmount nativeRemit{0};
AccountID const dstAccID{ctx_.tx[sfDestination]};
auto sleDstAcc = sb.peek(keylet::account(dstAccID));
auto const flags = !sleDstAcc ? 0 : sleDstAcc->getFlags();
// Check if the destination has disallowed incoming
if (sb.rules().enabled(featureDisallowIncoming) &&
(flags & lsfDisallowIncomingRemit))
return tecNO_PERMISSION;
// the destination may require a dest tag
if ((flags & lsfRequireDestTag) &&
!ctx_.tx.isFieldPresent(sfDestinationTag))
{
JLOG(j.warn())
<< "Remit: DestinationTag required for this destination.";
return tecDST_TAG_NEEDED;
}
// if the destination doesn't exist, create it.
bool const createDst = !sleDstAcc;
if (createDst)
{
// sender will pay the reserve
nativeRemit += accountReserve;
// Create the account.
std::uint32_t const seqno{
sb.rules().enabled(featureXahauGenesis)
? sb.info().parentCloseTime.time_since_epoch().count()
: sb.rules().enabled(featureDeletableAccounts) ? sb.seq() : 1};
sleDstAcc = std::make_shared<SLE>(keylet::account(dstAccID));
sleDstAcc->setAccountID(sfAccount, dstAccID);
sleDstAcc->setFieldU32(sfSequence, seqno);
sleDstAcc->setFieldU32(sfOwnerCount, 0);
if (sb.exists(keylet::fees()) && sb.rules().enabled(featureXahauGenesis))
{
auto sleFees = sb.peek(keylet::fees());
uint64_t accIdx = sleFees->isFieldPresent(sfAccountCount)
? sleFees->getFieldU64(sfAccountCount)
: 0;
sleDstAcc->setFieldU64(sfAccountIndex, accIdx);
sleFees->setFieldU64(sfAccountCount, accIdx + 1);
sb.update(sleFees);
}
// we'll fix this up at the end
sleDstAcc->setFieldAmount(sfBalance, STAmount{XRPAmount{0}});
sb.insert(sleDstAcc);
}
// if theres a minted uritoken the sender pays for that
if (ctx_.tx.isFieldPresent(sfMintURIToken))
{
nativeRemit += objectReserve;
STObject const& mint = const_cast<ripple::STTx&>(ctx_.tx)
.getField(sfMintURIToken)
.downcast<STObject>();
Blob const& mintURI = mint.getFieldVL(sfURI);
std::optional<uint256> mintDigest;
if (mint.isFieldPresent(sfDigest))
mintDigest = mint.getFieldH256(sfDigest);
Keylet kl = keylet::uritoken(srcAccID, mintURI);
// check that it doesn't already exist
if (sb.exists(kl))
{
JLOG(j.trace()) << "Remit: tried to creat duplicate URIToken. Tx: "
<< ctx_.tx.getTransactionID();
return tecDUPLICATE;
}
auto sleMint = std::make_shared<SLE>(kl);
sleMint->setAccountID(sfOwner, dstAccID);
sleMint->setAccountID(sfIssuer, srcAccID);
sleMint->setFieldVL(sfURI, mintURI);
if (mint.isFieldPresent(sfDigest))
sleMint->setFieldH256(sfDigest, mint.getFieldH256(sfDigest));
sleMint->setFieldU32(
sfFlags,
mint.isFieldPresent(sfFlags) ? mint.getFieldU32(sfFlags) : 0);
auto const page = view().dirInsert(
keylet::ownerDir(dstAccID), kl, describeOwnerDir(dstAccID));
JLOG(j_.trace()) << "Adding URIToken to owner directory "
<< to_string(kl.key) << ": "
<< (page ? "success" : "failure");
if (!page)
return tecDIR_FULL;
sleMint->setFieldU64(sfOwnerNode, *page);
sb.insert(sleMint);
// ensure there is a deletion blocker against the issuer now
sleSrcAcc->setFieldU32(
sfFlags, sleSrcAcc->getFlags() | lsfURITokenIssuer);
adjustOwnerCount(sb, sleDstAcc, 1, j);
}
// iterate uritokens
if (ctx_.tx.isFieldPresent(sfURITokenIDs))
{
STVector256 ids = ctx_.tx.getFieldV256(sfURITokenIDs);
for (uint256 const klRaw : ids)
{
Keylet kl = keylet::unchecked(klRaw);
auto sleU = sb.peek(kl);
// does it exist
if (!sleU)
{
JLOG(j.warn()) << "Remit: one or more uritokens did not exist "
"on the source account.";
return tecUNFUNDED_PAYMENT;
}
// is it a uritoken?
if (sleU->getFieldU16(sfLedgerEntryType) != ltURI_TOKEN)
{
JLOG(j.warn()) << "Remit: one or more supplied URITokenIDs was "
"not actually a uritoken.";
return tecNO_ENTRY;
}
// is it our uritoken?
if (sleU->getAccountID(sfOwner) != srcAccID)
{
JLOG(j.warn()) << "Remit: one or more supplied URITokenIDs was "
"not owned by sender.";
return tecNO_PERMISSION;
}
// erase current sale offers, if any
if (sleU->isFieldPresent(sfAmount))
sleU->makeFieldAbsent(sfAmount);
if (sleU->isFieldPresent(sfDestination))
sleU->makeFieldAbsent(sfDestination);
// pay the reserve
nativeRemit += objectReserve;
// remove from sender dir
{
auto const page = (*sleU)[sfOwnerNode];
if (!sb.dirRemove(
keylet::ownerDir(srcAccID), page, kl.key, true))
{
JLOG(j.fatal())
<< "Could not remove URIToken from owner directory";
return tefBAD_LEDGER;
}
adjustOwnerCount(sb, sleSrcAcc, -1, j);
}
// add to dest dir
{
auto const page = sb.dirInsert(
keylet::ownerDir(dstAccID), kl, describeOwnerDir(dstAccID));
JLOG(j_.trace()) << "Adding URIToken to owner directory "
<< to_string(kl.key) << ": "
<< (page ? "success" : "failure");
if (!page)
return tecDIR_FULL;
sleU->setFieldU64(sfOwnerNode, *page);
adjustOwnerCount(sb, sleDstAcc, 1, j);
}
// change the owner
sleU->setAccountID(sfOwner, dstAccID);
sb.update(sleU);
}
}
// iterate trustlines
if (ctx_.tx.isFieldPresent(sfAmounts))
{
// process trustline remits
STArray const& sEntries(ctx_.tx.getFieldArray(sfAmounts));
for (STObject const& sEntry : sEntries)
{
STAmount const amount = sEntry.getFieldAmount(sfAmount);
if (isXRP(amount))
{
// since we have to pay for all the created objects including
// possibly the account itself this is paid right at the end,
// and only if there is balance enough to cover.
nativeRemit += amount.xrp();
continue;
}
AccountID const issuerAccID = amount.getIssuer();
// check permissions
if (issuerAccID == srcAccID || issuerAccID == dstAccID)
{
// no permission check needed when the issuer sends out or a
// subscriber sends back RH TODO: move this condition into
// trustTransferAllowed, guarded by an amendment
}
else if (TER canXfer = trustTransferAllowed(
sb,
std::vector<AccountID>{srcAccID, dstAccID},
amount.issue(),
j);
canXfer != tesSUCCESS)
return canXfer;
// compute the amount the source will need to send
// in remit the sender pays all transfer fees, so that
// the destination can always be assured they got the exact amount
// specified. therefore we need to compute the amount + transfer fee
auto const srcAmt =
issuerAccID != srcAccID && issuerAccID != dstAccID
? multiply(amount, transferRate(sb, issuerAccID))
: amount;
auto const dstAmt = amount;
STAmount availableFunds{
accountFunds(sb, srcAccID, srcAmt, fhZERO_IF_FROZEN, j)};
if (availableFunds < srcAmt)
return tecUNFUNDED_PAYMENT;
// if the target trustline doesn't exist we need to create it and
// pay its reserve
if (!sb.exists(keylet::line(
issuerAccID == dstAccID ? srcAccID : dstAccID,
issuerAccID,
amount.getCurrency())))
nativeRemit += objectReserve;
// action the transfer
STAmount sentAmt;
if (TER result =
rippleSend(sb, srcAccID, dstAccID, dstAmt, sentAmt, j);
result != tesSUCCESS)
return result;
if (sentAmt != srcAmt)
return tecINTERNAL;
}
}
if (nativeRemit < beast::zero)
return tecINTERNAL;
if (nativeRemit > beast::zero)
{
// ensure the account can cover the native remit
if (mSourceBalance < nativeRemit)
return tecUNFUNDED_PAYMENT;
// subtract the balance from the sender
{
STAmount bal = mSourceBalance;
bal -= nativeRemit;
if (bal < beast::zero || bal > mSourceBalance)
return tecINTERNAL;
sleSrcAcc->setFieldAmount(sfBalance, bal);
}
// add the balance to the destination
{
STAmount bal = sleDstAcc->getFieldAmount(sfBalance);
STAmount prior = bal;
bal += nativeRemit;
if (bal < beast::zero || bal < prior)
return tecINTERNAL;
sleDstAcc->setFieldAmount(sfBalance, bal);
}
}
// apply
sb.update(sleSrcAcc);
sb.update(sleDstAcc);
sb.apply(ctx_.rawView());
return tesSUCCESS;
}
XRPAmount
Remit::calculateBaseFee(ReadView const& view, STTx const& tx)
{
XRPAmount extraFee{0};
if (tx.isFieldPresent(sfBlob))
extraFee +=
XRPAmount{static_cast<XRPAmount>(tx.getFieldVL(sfBlob).size())};
// RH TODO: add fees
return Transactor::calculateBaseFee(view, tx) + extraFee;
}
} // namespace ripple

View File

@@ -577,14 +577,6 @@ SetAccount::doApply()
uFlagsOut |= lsfDisallowIncomingTrustline;
else if (uClearFlag == asfDisallowIncomingTrustline)
uFlagsOut &= ~lsfDisallowIncomingTrustline;
if (ctx_.view().rules().enabled(featureRemit))
{
if (uSetFlag == asfDisallowIncomingRemit)
uFlagsOut |= lsfDisallowIncomingRemit;
else if (uClearFlag == asfDisallowIncomingRemit)
uFlagsOut &= ~lsfDisallowIncomingRemit;
}
}
if (uFlagsIn != uFlagsOut)

View File

@@ -85,6 +85,54 @@ preflight0(PreflightContext const& ctx)
return temINVALID;
}
// don't allow attestations unless enabled
if (ctx.tx.isFieldPresent(sfAttesters))
{
if (!ctx.rules.enabled(featureAttestations))
return temDISABLED;
// make sure they can't spam millions of attesters
auto const& attesters = ctx.tx.getFieldArray(sfAttesters);
if (attesters.empty() || attesters.size() > 32)
{
JLOG(ctx.j.warn())
<< "applyTransaction: attesters array too big (max 32) or empty.";
return temMALFORMED;
}
// enforce that it must specify a last ledger seq
if (!ctx.tx.isFieldPresent(sfLastLedgerSequence))
{
JLOG(ctx.j.warn())
<< "applyTransaction: txns with attesters must specify a last ledger sequence <= cur + 256";
return temMALFORMED;
}
// sanity check entries
std::set<AccountID> used;
for (auto const& attester: attesters)
{
if (attester.getFName() != sfAttesterEntry)
{
JLOG(ctx.j.warn())
<< "applyTransaction: attesters array contained non AttesterEntry object.";
return temMALFORMED;
}
AccountID const& id = attester.getAccountID(sfAccount);
if (used.find(id) != used.end())
{
JLOG(ctx.j.warn())
<< "applyTransaction: attesters array contained duplicate attester ID.";
return temMALFORMED;
}
used.emplace(id);
}
}
return tesSUCCESS;
}
@@ -1928,6 +1976,62 @@ Transactor::operator()()
}
}
if (applied && ctx_.tx.isFieldPresent(sfAttesters) && view().rules().enabled(featureAttestations))
{
// delete used attestation objects
auto const& attesters = ctx_.tx.getFieldArray(sfAttesters);
auto const txid = ctx_.tx.getTransactionID();
auto const& j = ctx_.app.journal("View");
for (auto const& attester : attesters)
{
Keylet kl = keylet::attestationTxn(attester.getAccountID(sfAccount), txid);
if (!view().exists(kl))
{
JLOG(j.warn())
<< "Transactor: Warning!!! Attestation does not exist at end of attested txn "
<< txid;
continue;
}
// remove from dir
auto sleA = view().peek(kl);
if (!sleA->isFieldPresent(sfAttestedTxnID))
{
JLOG(j.warn())
<< "Transactor: Warning!!! Attestation is of the wrong type at end of attested txn "
<< txid;
continue;
}
AccountID owner = sleA->getAccountID(sfOwner);
auto sle = view().peek(keylet::account(owner));
if (!sle)
{
JLOG(j.warn())
<< "Transactor: Warning!!! Attester account does not exist at the end of attested txn "
<< txid;
continue;
}
auto const page = (*sleA)[sfOwnerNode];
if (!view().dirRemove(
keylet::ownerDir(owner), page, kl.key, true))
{
JLOG(j.warn())
<< "Could not remove Attestation from owner directory";
continue;
}
view().erase(sleA);
adjustOwnerCount(view(), sle, -1, j);
}
}
// Post-application (Weak TSH/AAW) Hooks are executed here.
// These TSH do not have the ability to rollback.
// The callback, if any, is also executed here.

View File

@@ -160,6 +160,7 @@ public:
{
// Most transactors do nothing
// after checkSeq/Fee/Sign.
return tesSUCCESS;
}
/////////////////////////////////////////////////////

View File

@@ -103,7 +103,57 @@ URIToken::preflight(PreflightContext const& ctx)
return temMALFORMED;
}
if (!validateUTF8(uri))
if (!([](std::vector<uint8_t> const& u) -> bool {
// this code is from
// https://www.cl.cam.ac.uk/~mgk25/ucs/utf8_check.c
uint8_t const* s = (uint8_t const*)u.data();
uint8_t const* end = s + u.size();
while (s < end)
{
if (*s < 0x80)
/* 0xxxxxxx */
s++;
else if ((s[0] & 0xe0) == 0xc0)
{
/* 110XXXXx 10xxxxxx */
if ((s[1] & 0xc0) != 0x80 ||
(s[0] & 0xfe) == 0xc0) /* overlong? */
return false;
else
s += 2;
}
else if ((s[0] & 0xf0) == 0xe0)
{
/* 1110XXXX 10Xxxxxx 10xxxxxx */
if ((s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80 ||
(s[0] == 0xe0 &&
(s[1] & 0xe0) == 0x80) || /* overlong? */
(s[0] == 0xed &&
(s[1] & 0xe0) == 0xa0) || /* surrogate? */
(s[0] == 0xef && s[1] == 0xbf &&
(s[2] & 0xfe) == 0xbe)) /* U+FFFE or U+FFFF? */
return false;
else
s += 3;
}
else if ((s[0] & 0xf8) == 0xf0)
{
/* 11110XXX 10XXxxxx 10xxxxxx 10xxxxxx */
if ((s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80 ||
(s[3] & 0xc0) != 0x80 ||
(s[0] == 0xf0 &&
(s[1] & 0xf0) == 0x80) || /* overlong? */
(s[0] == 0xf4 && s[1] > 0x8f) ||
s[0] > 0xf4) /* > U+10FFFF? */
return false;
else
s += 4;
}
else
return false;
}
return true;
})(uri))
{
JLOG(ctx.j.warn()) << "Malformed transaction. URI must be a "
"valid utf-8 string.";

View File

@@ -30,56 +30,6 @@ namespace ripple {
class URIToken : public Transactor
{
public:
bool inline static validateUTF8(std::vector<uint8_t> const& u)
{
// this code is from
// https://www.cl.cam.ac.uk/~mgk25/ucs/utf8_check.c
uint8_t const* s = (uint8_t const*)u.data();
uint8_t const* end = s + u.size();
while (s < end)
{
if (*s < 0x80)
/* 0xxxxxxx */
s++;
else if ((s[0] & 0xe0) == 0xc0)
{
/* 110XXXXx 10xxxxxx */
if ((s[1] & 0xc0) != 0x80 ||
(s[0] & 0xfe) == 0xc0) /* overlong? */
return false;
else
s += 2;
}
else if ((s[0] & 0xf0) == 0xe0)
{
/* 1110XXXX 10Xxxxxx 10xxxxxx */
if ((s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80 ||
(s[0] == 0xe0 && (s[1] & 0xe0) == 0x80) || /* overlong? */
(s[0] == 0xed && (s[1] & 0xe0) == 0xa0) || /* surrogate? */
(s[0] == 0xef && s[1] == 0xbf &&
(s[2] & 0xfe) == 0xbe)) /* U+FFFE or U+FFFF? */
return false;
else
s += 3;
}
else if ((s[0] & 0xf8) == 0xf0)
{
/* 11110XXX 10XXxxxx 10xxxxxx 10xxxxxx */
if ((s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80 ||
(s[3] & 0xc0) != 0x80 ||
(s[0] == 0xf0 && (s[1] & 0xf0) == 0x80) || /* overlong? */
(s[0] == 0xf4 && s[1] > 0x8f) ||
s[0] > 0xf4) /* > U+10FFFF? */
return false;
else
s += 4;
}
else
return false;
}
return true;
}
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit URIToken(ApplyContext& ctx) : Transactor(ctx)

View File

@@ -32,6 +32,7 @@
#include <ripple/app/tx/impl/GenesisMint.h>
#include <ripple/app/tx/impl/Import.h>
#include <ripple/app/tx/impl/Invoke.h>
#include <ripple/app/tx/impl/Attest.h>
#include <ripple/app/tx/impl/NFTokenAcceptOffer.h>
#include <ripple/app/tx/impl/NFTokenBurn.h>
#include <ripple/app/tx/impl/NFTokenCancelOffer.h>
@@ -164,8 +165,8 @@ invoke_preflight(PreflightContext const& ctx)
return invoke_preflight_helper<Import>(ctx);
case ttINVOKE:
return invoke_preflight_helper<Invoke>(ctx);
case ttREMIT:
return invoke_preflight_helper<Remit>(ctx);
case ttATTEST:
return invoke_preflight_helper<Attest>(ctx);
case ttURITOKEN_MINT:
case ttURITOKEN_BURN:
case ttURITOKEN_BUY:
@@ -211,6 +212,27 @@ invoke_preclaim(PreclaimContext const& ctx)
if (result != tesSUCCESS)
return result;
if (ctx.tx.isFieldPresent(sfAttesters) && ctx.view.rules().enabled(featureAttestations))
{
// check last ledger sequence
if (!ctx.tx.isFieldPresent(sfLastLedgerSequence))
return tecINTERNAL;
if (ctx.tx.getFieldU32(sfLastLedgerSequence) > ctx.view.seq() + 256)
return tecLAST_LEDGER_SEQ_TOO_HIGH;
// check if the required attestations are present on ledger
auto const& attesters = ctx.tx.getFieldArray(sfAttesters);
auto const txid = ctx.tx.getTransactionID();
// each required attestation must exist on the ledger to allow the txn through
// otherwise it gets marked retry
for (auto const& attester : attesters)
if (!ctx.view.exists(keylet::attestationTxn(attester.getAccountID(sfAccount), txid)))
return terAWAITING_ATTESTATION;
}
}
return T::preclaim(ctx);
@@ -285,8 +307,8 @@ invoke_preclaim(PreclaimContext const& ctx)
return invoke_preclaim<Import>(ctx);
case ttINVOKE:
return invoke_preclaim<Invoke>(ctx);
case ttREMIT:
return invoke_preclaim<Remit>(ctx);
case ttATTEST:
return invoke_preclaim<Attest>(ctx);
case ttURITOKEN_MINT:
case ttURITOKEN_BURN:
case ttURITOKEN_BUY:
@@ -368,8 +390,8 @@ invoke_calculateBaseFee(ReadView const& view, STTx const& tx)
return Import::calculateBaseFee(view, tx);
case ttINVOKE:
return Invoke::calculateBaseFee(view, tx);
case ttREMIT:
return Remit::calculateBaseFee(view, tx);
case ttATTEST:
return Attest::calculateBaseFee(view, tx);
case ttURITOKEN_MINT:
case ttURITOKEN_BURN:
case ttURITOKEN_BUY:
@@ -550,8 +572,8 @@ invoke_apply(ApplyContext& ctx)
Invoke p(ctx);
return p();
}
case ttREMIT: {
Remit p(ctx);
case ttATTEST: {
Attest p(ctx);
return p();
}
case ttURITOKEN_MINT:

View File

@@ -27,7 +27,6 @@
#include <ripple/ledger/OpenView.h>
#include <ripple/ledger/RawView.h>
#include <ripple/ledger/ReadView.h>
#include <ripple/ledger/Sandbox.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/Protocol.h>
#include <ripple/protocol/Rate.h>
@@ -389,18 +388,6 @@ rippleCredit(
bool bCheckIssuer,
beast::Journal j);
// Send regardless of limits.
// --> saAmount: Amount/currency/issuer to deliver to receiver.
// <-- saActual: Amount actually cost. Sender pays fees.
TER
rippleSend(
ApplyView& view,
AccountID const& uSenderID,
AccountID const& uReceiverID,
STAmount const& saAmount,
STAmount& saActual,
beast::Journal j);
[[nodiscard]] TER
accountSend(
ApplyView& view,
@@ -640,12 +627,12 @@ trustTransferAllowed(
{
static_assert(
std::is_same<V, ReadView const>::value ||
std::is_same<V, ApplyView>::value || std::is_same<V, Sandbox>::value);
std::is_same<V, ApplyView>::value);
typedef typename std::conditional<
std::is_same<V, ReadView const>::value,
std::shared_ptr<SLE const>,
std::shared_ptr<SLE>>::type SLEPtr;
std::is_same<V, ApplyView>::value,
std::shared_ptr<SLE>,
std::shared_ptr<SLE const>>::type SLEPtr;
if (isBadCurrency(issue.currency))
return tecNO_PERMISSION;

View File

@@ -1150,7 +1150,7 @@ rippleCredit(
// Send regardless of limits.
// --> saAmount: Amount/currency/issuer to deliver to receiver.
// <-- saActual: Amount actually cost. Sender pays fees.
TER
static TER
rippleSend(
ApplyView& view,
AccountID const& uSenderID,

View File

@@ -354,8 +354,7 @@ extern uint256 const featureImport;
extern uint256 const featureXahauGenesis;
extern uint256 const featureHooksUpdate1;
extern uint256 const fixURITokenV1;
extern uint256 const featureRemit;
extern uint256 const featureAttestations;
} // namespace ripple
#endif

View File

@@ -295,7 +295,13 @@ Keylet
import_vlseq(PublicKey const& key) noexcept;
Keylet
uritoken(AccountID const& issuer, Blob const& uri);
uritoken(AccountID const& issuer, Blob const& uri) noexcept;
Keylet
attestationTxn(AccountID const& issuer, uint256 const& txnid) noexcept;
Keylet
attestationAcc(AccountID const& issuer, AccountID const& issuee) noexcept;
} // namespace keylet

View File

@@ -52,6 +52,12 @@ namespace ripple {
// clang-format off
enum LedgerEntryType : std::uint16_t
{
/** A ledger object which describes an attested txn
\sa kelet::attestation
*/
ltATTESTATION = 0x0041,
/** A ledger object which describes an account.
\sa keylet::account
@@ -285,8 +291,6 @@ enum LedgerSpecificFlags {
0x20000000, // True, reject new trustlines (only if no issued assets)
lsfURITokenIssuer =
0x40000000, // True, has minted tokens in the past
lsfDisallowIncomingRemit = // True, no remits allowed to this account
0x80000000,
// ltOFFER
lsfPassive = 0x00010000,

View File

@@ -483,6 +483,7 @@ extern SF_UINT256 const sfURITokenID;
extern SF_UINT256 const sfGovernanceFlags;
extern SF_UINT256 const sfGovernanceMarks;
extern SF_UINT256 const sfEmittedTxnID;
extern SF_UINT256 const sfAttestedTxnID;
// currency amount (common)
extern SF_AMOUNT const sfAmount;
@@ -553,6 +554,7 @@ extern SF_ACCOUNT const sfEmitCallback;
extern SF_ACCOUNT const sfHookAccount;
extern SF_ACCOUNT const sfNFTokenMinter;
extern SF_ACCOUNT const sfInform;
extern SF_ACCOUNT const sfAttestedAccID;
// path set
extern SField const sfPaths;
@@ -594,6 +596,7 @@ extern SField const sfImportVLKey;
extern SField const sfHookEmission;
extern SField const sfMintURIToken;
extern SField const sfAmountEntry;
extern SField const sfAttesterEntry;
// array of objects (common)
// ARRAY/1 is reserved for end of array
@@ -622,6 +625,7 @@ extern SField const sfActiveValidators;
extern SField const sfImportVLKeys;
extern SField const sfHookEmissions;
extern SField const sfAmounts;
extern SField const sfAttesters;
//------------------------------------------------------------------------------

View File

@@ -223,8 +223,9 @@ enum TERcodes : TERUnderlyingType {
terQUEUED, // Transaction is being held in TxQ until fee drops
terPRE_TICKET, // Ticket is not yet in ledger but might be on its way
terNO_AMM, // RESERVED - AMM
terNO_HOOK // Transaction requires a non-existent hook definition
terNO_HOOK, // Transaction requires a non-existent hook definition
// (referenced by sfHookHash)
terAWAITING_ATTESTATION,
};
//------------------------------------------------------------------------------
@@ -337,6 +338,7 @@ enum TECcodes : TERUnderlyingType {
tecXCHAIN_PAYMENT_FAILED = 184, // RESERVED - XCHAIN
tecXCHAIN_SELF_COMMIT = 185, // RESERVED - XCHAIN
tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR = 186, // RESERVED - XCHAIN
tecLAST_LEDGER_SEQ_TOO_HIGH = 187,
tecLAST_POSSIBLE_ENTRY = 255,
};

View File

@@ -86,7 +86,6 @@ constexpr std::uint32_t asfDisallowIncomingNFTokenOffer = 12;
constexpr std::uint32_t asfDisallowIncomingCheck = 13;
constexpr std::uint32_t asfDisallowIncomingPayChan = 14;
constexpr std::uint32_t asfDisallowIncomingTrustline = 15;
constexpr std::uint32_t asfDisallowIncomingRemit = 16;
// OfferCreate flags:
constexpr std::uint32_t tfPassive = 0x00010000;
@@ -133,6 +132,10 @@ constexpr std::uint32_t const tfStrongTSH = 0x00008000;
constexpr std::uint32_t const tfNFTokenMintOldMask =
~(tfUniversal | tfBurnable | tfOnlyXRP | tfTrustLine | tfTransferable | tfStrongTSH);
// Attest flags:
constexpr std::uint32_t tfDeleteAttestation = 0x00000001;
constexpr std::uint32_t tfAttestMask = ~tfDeleteAttestation;
// Prior to fixRemoveNFTokenAutoTrustLine, transfer of an NFToken between
// accounts allowed a TrustLine to be added to the issuer of that token
// without explicit permission from that issuer. This was enabled by

View File

@@ -146,6 +146,9 @@ enum TxType : std::uint16_t
ttURITOKEN_CREATE_SELL_OFFER = 48,
ttURITOKEN_CANCEL_SELL_OFFER = 49,
/** This transaction acts as an attestation for another txn currently in the TxQ */
ttATTEST = 94,
/* A payment transactor that delivers only the exact amounts specified, creating accounts and TLs as needed
* that the sender pays for. */
ttREMIT = 95,

View File

@@ -460,7 +460,7 @@ REGISTER_FEATURE(Import, Supported::yes, VoteBehavior::De
REGISTER_FEATURE(XahauGenesis, Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FEATURE(HooksUpdate1, Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FIX (fixURITokenV1, Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FEATURE(Remit, Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FEATURE(Attestations, Supported::yes, VoteBehavior::DefaultNo);
// The following amendments are obsolete, but must remain supported
// because they could potentially get enabled.

View File

@@ -45,6 +45,8 @@ namespace ripple {
*/
enum class LedgerNameSpace : std::uint16_t {
ACCOUNT = 'a',
ATTESTATION_ACC = 'A',
ATTESTATION_TXN = 't',
DIR_NODE = 'd',
TRUST_LINE = 'r',
OFFER = 'o',
@@ -435,7 +437,7 @@ nft_sells(uint256 const& id) noexcept
}
Keylet
uritoken(AccountID const& issuer, Blob const& uri)
uritoken(AccountID const& issuer, Blob const& uri) noexcept
{
return {
ltURI_TOKEN,
@@ -443,6 +445,16 @@ uritoken(AccountID const& issuer, Blob const& uri)
LedgerNameSpace::URI_TOKEN, issuer, Slice{uri.data(), uri.size()})};
}
Keylet attestationTxn(AccountID const& issuer, uint256 const& txnid) noexcept
{
return {ltATTESTATION, indexHash(LedgerNameSpace::ATTESTATION_TXN, issuer, txnid)};
}
Keylet attestationAcc(AccountID const& issuer, AccountID const& issuee) noexcept
{
return {ltATTESTATION, indexHash(LedgerNameSpace::ATTESTATION_ACC, issuer, issuee)};
}
} // namespace keylet
} // namespace ripple

View File

@@ -156,6 +156,12 @@ InnerObjectFormats::InnerObjectFormats()
{sfDigest, soeOPTIONAL},
{sfFlags, soeOPTIONAL},
});
add(sfAttesterEntry.jsonName.c_str(),
sfAttesterEntry.getCode(),
{
{sfAccount, soeREQUIRED},
});
}
InnerObjectFormats const&

View File

@@ -363,6 +363,18 @@ LedgerFormats::LedgerFormats()
},
commonFields);
add(jss::Attestation,
ltATTESTATION,
{
{sfOwner, soeREQUIRED},
{sfOwnerNode, soeREQUIRED},
{sfAttestedTxnID, soeOPTIONAL},
{sfAttestedAccID, soeOPTIONAL},
{sfPreviousTxnID, soeREQUIRED},
{sfPreviousTxnLgrSeq, soeREQUIRED},
},
commonFields);
// clang-format on
}

View File

@@ -236,6 +236,7 @@ CONSTRUCT_TYPED_SFIELD(sfURITokenID, "URITokenID", UINT256,
CONSTRUCT_TYPED_SFIELD(sfGovernanceFlags, "GovernanceFlags", UINT256, 99);
CONSTRUCT_TYPED_SFIELD(sfGovernanceMarks, "GovernanceMarks", UINT256, 98);
CONSTRUCT_TYPED_SFIELD(sfEmittedTxnID, "EmittedTxnID", UINT256, 97);
CONSTRUCT_TYPED_SFIELD(sfAttestedTxnID, "AttestedTxnID", UINT256, 96);
// currency amount (common)
CONSTRUCT_TYPED_SFIELD(sfAmount, "Amount", AMOUNT, 1);
@@ -306,6 +307,7 @@ CONSTRUCT_TYPED_SFIELD(sfEmitCallback, "EmitCallback", ACCOUNT,
// account (uncommon)
CONSTRUCT_TYPED_SFIELD(sfHookAccount, "HookAccount", ACCOUNT, 16);
CONSTRUCT_TYPED_SFIELD(sfInform, "Inform", ACCOUNT, 99);
CONSTRUCT_TYPED_SFIELD(sfAttestedAccID, "AttestedAccID", ACCOUNT, 98);
// vector of 256-bit
CONSTRUCT_TYPED_SFIELD(sfIndexes, "Indexes", VECTOR256, 1, SField::sMD_Never);
@@ -348,8 +350,9 @@ CONSTRUCT_UNTYPED_SFIELD(sfGenesisMint, "GenesisMint", OBJECT,
CONSTRUCT_UNTYPED_SFIELD(sfActiveValidator, "ActiveValidator", OBJECT, 95);
CONSTRUCT_UNTYPED_SFIELD(sfImportVLKey, "ImportVLKey", OBJECT, 94);
CONSTRUCT_UNTYPED_SFIELD(sfHookEmission, "HookEmission", OBJECT, 93);
CONSTRUCT_UNTYPED_SFIELD(sfMintURIToken, "MintURIToken", OBJECT, 92);
CONSTRUCT_UNTYPED_SFIELD(sfMintURIToken, "MintURIToken", OBJECT, 92);
CONSTRUCT_UNTYPED_SFIELD(sfAmountEntry, "AmountEntry", OBJECT, 91);
CONSTRUCT_UNTYPED_SFIELD(sfAttesterEntry, "AttesterEntry", OBJECT, 90);
// array of objects
// ARRAY/1 is reserved for end of array
@@ -375,6 +378,7 @@ CONSTRUCT_UNTYPED_SFIELD(sfActiveValidators, "ActiveValidators", ARRAY,
CONSTRUCT_UNTYPED_SFIELD(sfImportVLKeys, "ImportVLKeys", ARRAY, 94);
CONSTRUCT_UNTYPED_SFIELD(sfHookEmissions, "HookEmissions", ARRAY, 93);
CONSTRUCT_UNTYPED_SFIELD(sfAmounts, "Amounts", ARRAY, 92);
CONSTRUCT_UNTYPED_SFIELD(sfAttesters, "Attesters", ARRAY, 91);
// clang-format on

View File

@@ -91,6 +91,7 @@ transResults()
MAKE_ERROR(tecHOOK_REJECTED, "Rejected by hook on sending or receiving account."),
MAKE_ERROR(tecREQUIRES_FLAG, "The transaction or part-thereof requires a flag that wasn't set."),
MAKE_ERROR(tecPRECISION_LOSS, "The amounts used by the transaction cannot interact."),
MAKE_ERROR(tecLAST_LEDGER_SEQ_TOO_HIGH, "The sfLastLedgerSequence was higher than seq + 256."),
MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."),
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),
MAKE_ERROR(tefBAD_AUTH, "Transaction's public key is not authorized."),
@@ -188,6 +189,7 @@ transResults()
MAKE_ERROR(terQUEUED, "Held until escalated fee drops."),
MAKE_ERROR(terPRE_TICKET, "Ticket is not yet in ledger."),
MAKE_ERROR(terNO_HOOK, "No hook with that hash exists on the ledger."),
MAKE_ERROR(terAWAITING_ATTESTATION, "Transaction is waiting for attesters to attest."),
MAKE_ERROR(tesSUCCESS, "The transaction was applied. Only final in a validated ledger."),
};

View File

@@ -44,6 +44,7 @@ TxFormats::TxFormats()
{sfNetworkID, soeOPTIONAL},
{sfHookParameters, soeOPTIONAL},
{sfOperationLimit, soeOPTIONAL},
{sfAttesters, soeOPTIONAL},
};
add(jss::AccountSet,
@@ -456,6 +457,15 @@ TxFormats::TxFormats()
{sfTicketSequence, soeOPTIONAL},
},
commonFields);
add(jss::Attest,
ttATTEST,
{
{sfAttestedTxnID, soeOPTIONAL},
{sfAttestedAccID, soeOPTIONAL},
{sfTicketSequence, soeOPTIONAL},
},
commonFields);
}
TxFormats const&

View File

@@ -50,6 +50,9 @@ JSS(AccountSet); // transaction type.
JSS(Amendments); // ledger type.
JSS(Amount); // in: TransactionSign; field.
JSS(Authorize); // field
JSS(Attest);
JSS(AttesterEntry);
JSS(Attestation);
JSS(Blob);
JSS(Check); // ledger type.
JSS(CheckCancel); // transaction type.

View File

@@ -422,11 +422,11 @@ doServerDefinitions(RPC::JsonContext& context)
uint32_t curLgrSeq = context.ledgerMaster.getValidatedLedger()->info().seq;
// static values used for cache
static thread_local uint32_t lastGenerated = 0; // last ledger seq it was generated
static thread_local Json::Value lastFeatures{
Json::objectValue}; // the actual features JSON last generated
static thread_local uint256 lastFeatureHash; // the hash of the features JSON last time
// it was generated
static uint32_t lastGenerated = 0; // last ledger seq it was generated
static Json::Value lastFeatures{
Json::objectValue}; // the actual features JSON last generated
static uint256 lastFeatureHash; // the hash of the features JSON last time
// it was generated
// if a flag ledger has passed since it was last generated, regenerate it,
// update the cache above

View File

@@ -5549,73 +5549,6 @@ class Import_test : public beast::unit_test::suite
}
}
// std::unique_ptr<Config>
// network::makeGenesisConfig(
// FeatureBitset features,
// uint32_t networkID,
// std::string fee,
// std::string a_res,
// std::string o_res,
// uint32_t ledgerID)
// {
// using namespace jtx;
// // IMPORT VL KEY
// std::vector<std::string> const keys = {
// "ED74D4036C6591A4BDF9C54CEFA39B996A"
// "5DCE5F86D11FDA1874481CE9D5A1CDC1"};
// Json::Value jsonValue;
// Json::Reader reader;
// reader.parse(ImportTCHalving::base_genesis, jsonValue);
// foreachFeature(features, [&](uint256 const& feature) {
// std::string featureName = featureToName(feature);
// std::optional<uint256> featureHash =
// getRegisteredFeature(featureName);
// if (featureHash.has_value())
// {
// std::string hashString = to_string(featureHash.value());
// jsonValue["ledger"]["accountState"][1]["Amendments"].append(
// hashString);
// }
// });
// jsonValue["ledger_current_index"] = ledgerID;
// jsonValue["ledger"]["ledger_index"] = to_string(ledgerID);
// jsonValue["ledger"]["seqNum"] = to_string(ledgerID);
// return envconfig([&](std::unique_ptr<Config> cfg) {
// cfg->NETWORK_ID = networkID;
// cfg->START_LEDGER = jsonValue.toStyledString();
// cfg->START_UP = Config::LOAD_JSON;
// Section config;
// config.append(
// {"reference_fee = " + fee,
// "account_reserve = " + a_res,
// "owner_reserve = " + o_res});
// auto setup = setup_FeeVote(config);
// cfg->FEES = setup;
// for (auto const& strPk : keys)
// {
// auto pkHex = strUnHex(strPk);
// if (!pkHex)
// Throw<std::runtime_error>(
// "Import VL Key '" + strPk + "' was not valid hex.");
// auto const pkType = publicKeyType(makeSlice(*pkHex));
// if (!pkType)
// Throw<std::runtime_error>(
// "Import VL Key '" + strPk +
// "' was not a valid key type.");
// cfg->IMPORT_VL_KEYS.emplace(strPk, makeSlice(*pkHex));
// }
// return cfg;
// });
// }
void
testHalving(FeatureBitset features)
{

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -42,52 +42,6 @@ namespace test {
* are put in their existing unit test files.
*/
/**
* Test the size of the negative UNL in a ledger,
* also test if the ledger has ToDisalbe and/or ToReEnable
*
* @param l the ledger
* @param size the expected negative UNL size
* @param hasToDisable if expect ToDisable in ledger
* @param hasToReEnable if expect ToDisable in ledger
* @return true if meet all three expectation
*/
bool inline negUnlSizeTest(
std::shared_ptr<ripple::Ledger const> const& l,
size_t size,
bool hasToDisable,
bool hasToReEnable)
{
bool sameSize = l->negativeUNL().size() == size;
bool sameToDisable =
(l->validatorToDisable() != std::nullopt) == hasToDisable;
bool sameToReEnable =
(l->validatorToReEnable() != std::nullopt) == hasToReEnable;
return sameSize && sameToDisable && sameToReEnable;
}
/**
* Try to apply a ttUNL_MODIFY Tx, and test the apply result
*
* @param env the test environment
* @param view the OpenView of the ledger
* @param tx the ttUNL_MODIFY Tx
* @param pass if the Tx should be applied successfully
* @return true if meet the expectation of apply result
*/
bool inline applyAndTestResult(
jtx::Env& env,
ripple::OpenView& view,
ripple::STTx const& tx,
bool pass)
{
auto res = apply(env.app(), view, tx, ApplyFlags::tapNONE, env.journal);
if (pass)
return res.first == tesSUCCESS;
else
return res.first == tefFAILURE || res.first == temDISABLED;
}
/**
* Verify the content of negative UNL entries (public key and ledger sequence)
* of a ledger
@@ -102,15 +56,6 @@ VerifyPubKeyAndSeq(
std::shared_ptr<Ledger const> const& l,
hash_map<PublicKey, std::uint32_t> nUnlLedgerSeq);
/**
* Count the number of Tx in a TxSet
*
* @param txSet the TxSet
* @return the number of Tx
*/
std::size_t
countTx(std::shared_ptr<SHAMap> const& txSet);
/**
* Create fake public keys
*
@@ -120,17 +65,6 @@ countTx(std::shared_ptr<SHAMap> const& txSet);
std::vector<PublicKey>
createPublicKeys(std::size_t n);
/**
* Create ttUNL_MODIFY Tx
*
* @param disabling disabling or re-enabling a validator
* @param seq current ledger seq
* @param txKey the public key of the validator
* @return the ttUNL_MODIFY Tx
*/
STTx
createTx(bool disabling, LedgerIndex seq, PublicKey const& txKey);
class NegativeUNL_test : public beast::unit_test::suite
{
/**
@@ -262,14 +196,16 @@ class NegativeUNL_test : public beast::unit_test::suite
l = std::make_shared<Ledger>(
*l, env.app().timeKeeper().closeTime());
auto txDisable_0 = createTx(true, l->seq(), publicKeys[0]);
auto txReEnable_1 = createTx(false, l->seq(), publicKeys[1]);
auto txDisable_0 = unl::createTx(true, l->seq(), publicKeys[0]);
auto txReEnable_1 = unl::createTx(false, l->seq(), publicKeys[1]);
OpenView accum(&*l);
BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_0, false));
BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_1, false));
BEAST_EXPECT(
unl::applyAndTestResult(env, accum, txDisable_0, false));
BEAST_EXPECT(
unl::applyAndTestResult(env, accum, txReEnable_1, false));
accum.apply(*l);
BEAST_EXPECT(negUnlSizeTest(l, 0, false, false));
BEAST_EXPECT(unl::negUnlSizeTest(l, 0, false, false));
}
{
@@ -283,18 +219,21 @@ class NegativeUNL_test : public beast::unit_test::suite
BEAST_EXPECT(l->isFlagLedger());
l->updateNegativeUNL();
auto txDisable_0 = createTx(true, l->seq(), publicKeys[0]);
auto txDisable_1 = createTx(true, l->seq(), publicKeys[1]);
auto txReEnable_2 = createTx(false, l->seq(), publicKeys[2]);
auto txDisable_0 = unl::createTx(true, l->seq(), publicKeys[0]);
auto txDisable_1 = unl::createTx(true, l->seq(), publicKeys[1]);
auto txReEnable_2 = unl::createTx(false, l->seq(), publicKeys[2]);
// can apply 1 and only 1 ToDisable Tx,
// cannot apply ToReEnable Tx, since negative UNL is empty
OpenView accum(&*l);
BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_0, true));
BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_1, false));
BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_2, false));
BEAST_EXPECT(
unl::applyAndTestResult(env, accum, txDisable_0, true));
BEAST_EXPECT(
unl::applyAndTestResult(env, accum, txDisable_1, false));
BEAST_EXPECT(
unl::applyAndTestResult(env, accum, txReEnable_2, false));
accum.apply(*l);
auto good_size = negUnlSizeTest(l, 0, true, false);
auto good_size = unl::negUnlSizeTest(l, 0, true, false);
BEAST_EXPECT(good_size);
if (good_size)
{
@@ -309,7 +248,7 @@ class NegativeUNL_test : public beast::unit_test::suite
//(3) ledgers before the next flag ledger
for (auto i = 0; i < 256; ++i)
{
auto good_size = negUnlSizeTest(l, 0, true, false);
auto good_size = unl::negUnlSizeTest(l, 0, true, false);
BEAST_EXPECT(good_size);
if (good_size)
BEAST_EXPECT(l->validatorToDisable() == publicKeys[0]);
@@ -321,7 +260,7 @@ class NegativeUNL_test : public beast::unit_test::suite
//(4) next flag ledger
// test if the ledger updated correctly
auto good_size = negUnlSizeTest(l, 1, false, false);
auto good_size = unl::negUnlSizeTest(l, 1, false, false);
BEAST_EXPECT(good_size);
if (good_size)
{
@@ -329,20 +268,25 @@ class NegativeUNL_test : public beast::unit_test::suite
nUnlLedgerSeq.emplace(publicKeys[0], l->seq());
}
auto txDisable_0 = createTx(true, l->seq(), publicKeys[0]);
auto txDisable_1 = createTx(true, l->seq(), publicKeys[1]);
auto txReEnable_0 = createTx(false, l->seq(), publicKeys[0]);
auto txReEnable_1 = createTx(false, l->seq(), publicKeys[1]);
auto txReEnable_2 = createTx(false, l->seq(), publicKeys[2]);
auto txDisable_0 = unl::createTx(true, l->seq(), publicKeys[0]);
auto txDisable_1 = unl::createTx(true, l->seq(), publicKeys[1]);
auto txReEnable_0 = unl::createTx(false, l->seq(), publicKeys[0]);
auto txReEnable_1 = unl::createTx(false, l->seq(), publicKeys[1]);
auto txReEnable_2 = unl::createTx(false, l->seq(), publicKeys[2]);
OpenView accum(&*l);
BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_0, false));
BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_1, true));
BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_1, false));
BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_2, false));
BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_0, true));
BEAST_EXPECT(
unl::applyAndTestResult(env, accum, txDisable_0, false));
BEAST_EXPECT(
unl::applyAndTestResult(env, accum, txDisable_1, true));
BEAST_EXPECT(
unl::applyAndTestResult(env, accum, txReEnable_1, false));
BEAST_EXPECT(
unl::applyAndTestResult(env, accum, txReEnable_2, false));
BEAST_EXPECT(
unl::applyAndTestResult(env, accum, txReEnable_0, true));
accum.apply(*l);
good_size = negUnlSizeTest(l, 1, true, true);
good_size = unl::negUnlSizeTest(l, 1, true, true);
BEAST_EXPECT(good_size);
if (good_size)
{
@@ -358,7 +302,7 @@ class NegativeUNL_test : public beast::unit_test::suite
//(5) ledgers before the next flag ledger
for (auto i = 0; i < 256; ++i)
{
auto good_size = negUnlSizeTest(l, 1, true, true);
auto good_size = unl::negUnlSizeTest(l, 1, true, true);
BEAST_EXPECT(good_size);
if (good_size)
{
@@ -374,19 +318,20 @@ class NegativeUNL_test : public beast::unit_test::suite
//(6) next flag ledger
// test if the ledger updated correctly
auto good_size = negUnlSizeTest(l, 1, false, false);
auto good_size = unl::negUnlSizeTest(l, 1, false, false);
BEAST_EXPECT(good_size);
if (good_size)
{
BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
}
auto txDisable_0 = createTx(true, l->seq(), publicKeys[0]);
auto txDisable_0 = unl::createTx(true, l->seq(), publicKeys[0]);
OpenView accum(&*l);
BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_0, true));
BEAST_EXPECT(
unl::applyAndTestResult(env, accum, txDisable_0, true));
accum.apply(*l);
good_size = negUnlSizeTest(l, 1, true, false);
good_size = unl::negUnlSizeTest(l, 1, true, false);
BEAST_EXPECT(good_size);
if (good_size)
{
@@ -402,7 +347,7 @@ class NegativeUNL_test : public beast::unit_test::suite
//(7) ledgers before the next flag ledger
for (auto i = 0; i < 256; ++i)
{
auto good_size = negUnlSizeTest(l, 1, true, false);
auto good_size = unl::negUnlSizeTest(l, 1, true, false);
BEAST_EXPECT(good_size);
if (good_size)
{
@@ -417,7 +362,7 @@ class NegativeUNL_test : public beast::unit_test::suite
//(8) next flag ledger
// test if the ledger updated correctly
auto good_size = negUnlSizeTest(l, 2, false, false);
auto good_size = unl::negUnlSizeTest(l, 2, false, false);
BEAST_EXPECT(good_size);
if (good_size)
{
@@ -427,16 +372,19 @@ class NegativeUNL_test : public beast::unit_test::suite
BEAST_EXPECT(VerifyPubKeyAndSeq(l, nUnlLedgerSeq));
}
auto txDisable_0 = createTx(true, l->seq(), publicKeys[0]);
auto txReEnable_0 = createTx(false, l->seq(), publicKeys[0]);
auto txReEnable_1 = createTx(false, l->seq(), publicKeys[1]);
auto txDisable_0 = unl::createTx(true, l->seq(), publicKeys[0]);
auto txReEnable_0 = unl::createTx(false, l->seq(), publicKeys[0]);
auto txReEnable_1 = unl::createTx(false, l->seq(), publicKeys[1]);
OpenView accum(&*l);
BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_0, true));
BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_1, false));
BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_0, false));
BEAST_EXPECT(
unl::applyAndTestResult(env, accum, txReEnable_0, true));
BEAST_EXPECT(
unl::applyAndTestResult(env, accum, txReEnable_1, false));
BEAST_EXPECT(
unl::applyAndTestResult(env, accum, txDisable_0, false));
accum.apply(*l);
good_size = negUnlSizeTest(l, 2, false, true);
good_size = unl::negUnlSizeTest(l, 2, false, true);
BEAST_EXPECT(good_size);
if (good_size)
{
@@ -451,7 +399,7 @@ class NegativeUNL_test : public beast::unit_test::suite
//(9) ledgers before the next flag ledger
for (auto i = 0; i < 256; ++i)
{
auto good_size = negUnlSizeTest(l, 2, false, true);
auto good_size = unl::negUnlSizeTest(l, 2, false, true);
BEAST_EXPECT(good_size);
if (good_size)
{
@@ -467,7 +415,7 @@ class NegativeUNL_test : public beast::unit_test::suite
//(10) next flag ledger
// test if the ledger updated correctly
auto good_size = negUnlSizeTest(l, 1, false, false);
auto good_size = unl::negUnlSizeTest(l, 1, false, false);
BEAST_EXPECT(good_size);
if (good_size)
{
@@ -476,12 +424,13 @@ class NegativeUNL_test : public beast::unit_test::suite
BEAST_EXPECT(VerifyPubKeyAndSeq(l, nUnlLedgerSeq));
}
auto txReEnable_1 = createTx(false, l->seq(), publicKeys[1]);
auto txReEnable_1 = unl::createTx(false, l->seq(), publicKeys[1]);
OpenView accum(&*l);
BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_1, true));
BEAST_EXPECT(
unl::applyAndTestResult(env, accum, txReEnable_1, true));
accum.apply(*l);
good_size = negUnlSizeTest(l, 1, false, true);
good_size = unl::negUnlSizeTest(l, 1, false, true);
BEAST_EXPECT(good_size);
if (good_size)
{
@@ -495,7 +444,7 @@ class NegativeUNL_test : public beast::unit_test::suite
//(11) ledgers before the next flag ledger
for (auto i = 0; i < 256; ++i)
{
auto good_size = negUnlSizeTest(l, 1, false, true);
auto good_size = unl::negUnlSizeTest(l, 1, false, true);
BEAST_EXPECT(good_size);
if (good_size)
{
@@ -509,14 +458,14 @@ class NegativeUNL_test : public beast::unit_test::suite
l->updateNegativeUNL();
//(12) next flag ledger
BEAST_EXPECT(negUnlSizeTest(l, 0, false, false));
BEAST_EXPECT(unl::negUnlSizeTest(l, 0, false, false));
}
{
//(13) ledgers before the next flag ledger
for (auto i = 0; i < 256; ++i)
{
BEAST_EXPECT(negUnlSizeTest(l, 0, false, false));
BEAST_EXPECT(unl::negUnlSizeTest(l, 0, false, false));
l = std::make_shared<Ledger>(
*l, env.app().timeKeeper().closeTime());
}
@@ -524,7 +473,7 @@ class NegativeUNL_test : public beast::unit_test::suite
l->updateNegativeUNL();
//(14) next flag ledger
BEAST_EXPECT(negUnlSizeTest(l, 0, false, false));
BEAST_EXPECT(unl::negUnlSizeTest(l, 0, false, false));
}
}
@@ -559,11 +508,11 @@ class NegativeUNLNoAmendment_test : public beast::unit_test::suite
*l, env.app().timeKeeper().closeTime());
}
BEAST_EXPECT(l->seq() == 256);
auto txDisable_0 = createTx(true, l->seq(), publicKeys[0]);
auto txDisable_0 = unl::createTx(true, l->seq(), publicKeys[0]);
OpenView accum(&*l);
BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_0, false));
BEAST_EXPECT(unl::applyAndTestResult(env, accum, txDisable_0, false));
accum.apply(*l);
BEAST_EXPECT(negUnlSizeTest(l, 0, false, false));
BEAST_EXPECT(unl::negUnlSizeTest(l, 0, false, false));
}
void
@@ -651,8 +600,8 @@ struct NetworkHistory
OpenView accum(&*l);
if (l->negativeUNL().size() < param.negUNLSize)
{
auto tx = createTx(true, l->seq(), UNLKeys[nidx]);
if (!applyAndTestResult(env, accum, tx, true))
auto tx = unl::createTx(true, l->seq(), UNLKeys[nidx]);
if (!unl::applyAndTestResult(env, accum, tx, true))
break;
++nidx;
}
@@ -660,15 +609,15 @@ struct NetworkHistory
{
if (param.hasToDisable)
{
auto tx = createTx(true, l->seq(), UNLKeys[nidx]);
if (!applyAndTestResult(env, accum, tx, true))
auto tx = unl::createTx(true, l->seq(), UNLKeys[nidx]);
if (!unl::applyAndTestResult(env, accum, tx, true))
break;
++nidx;
}
if (param.hasToReEnable)
{
auto tx = createTx(false, l->seq(), UNLKeys[0]);
if (!applyAndTestResult(env, accum, tx, true))
auto tx = unl::createTx(false, l->seq(), UNLKeys[0]);
if (!unl::applyAndTestResult(env, accum, tx, true))
break;
}
}
@@ -676,7 +625,7 @@ struct NetworkHistory
}
l->updateSkipList();
}
return negUnlSizeTest(
return unl::negUnlSizeTest(
l, param.negUNLSize, param.hasToDisable, param.hasToReEnable);
}
@@ -776,7 +725,7 @@ voteAndCheck(
vote.doVoting(
history.lastLedger(), history.UNLKeySet, history.validations, txSet);
return countTx(txSet) >= expect;
return unl::countTx(txSet) >= expect;
}
/**
@@ -799,11 +748,11 @@ class NegativeUNLVoteInternal_test : public beast::unit_test::suite
PublicKey toDisableKey;
PublicKey toReEnableKey;
LedgerIndex seq(1234);
BEAST_EXPECT(countTx(txSet) == 0);
BEAST_EXPECT(unl::countTx(txSet) == 0);
vote.addTx(seq, toDisableKey, NegativeUNLVote::ToDisable, txSet);
BEAST_EXPECT(countTx(txSet) == 1);
BEAST_EXPECT(unl::countTx(txSet) == 1);
vote.addTx(seq, toReEnableKey, NegativeUNLVote::ToReEnable, txSet);
BEAST_EXPECT(countTx(txSet) == 2);
BEAST_EXPECT(unl::countTx(txSet) == 2);
// content of a tx is implicitly tested after applied to a ledger
// in later test cases
}
@@ -1967,34 +1916,6 @@ VerifyPubKeyAndSeq(
return nUnlLedgerSeq.size() == 0;
}
std::size_t
countTx(std::shared_ptr<SHAMap> const& txSet)
{
/*uint64_t counter = 0;
if (txSet)
for (auto const& item : *txSet)
{
SerialIter sit(item.slice());
auto tx = std::make_shared<STTx
const>(SerialIter{sit.getSlice(sit.getVLDataLength())});
if (tx->getFieldU16(sfTransactionType) == ttUNL_MODIFY)
counter++;
}
*/
std::size_t count = 0;
for (auto i = txSet->begin(); i != txSet->end(); ++i)
{
// RH TODO: why does the above parse??
auto raw = i->slice();
if (raw[0] == 0x12U && raw[1] == 0 && raw[2] == 0x66U)
count++;
}
return count;
};
std::vector<PublicKey>
createPublicKeys(std::size_t n)
{
@@ -2011,16 +1932,5 @@ createPublicKeys(std::size_t n)
return keys;
}
STTx
createTx(bool disabling, LedgerIndex seq, PublicKey const& txKey)
{
auto fill = [&](auto& obj) {
obj.setFieldU8(sfUNLModifyDisabling, disabling ? 1 : 0);
obj.setFieldU32(sfLedgerSequence, seq);
obj.setFieldVL(sfUNLModifyValidator, txKey);
};
return STTx(ttUNL_MODIFY, fill);
}
} // namespace test
} // namespace ripple

View File

@@ -52,66 +52,20 @@ namespace test {
// */
// /**
// * Test the size of the negative UNL in a ledger,
// * also test if the ledger has ToDisalbe and/or ToReEnable
// * Try to apply a ttUNL_MODIFY Tx, and test the apply result
// *
// * @param l the ledger
// * @param size the expected negative UNL size
// * @param hasToDisable if expect ToDisable in ledger
// * @param hasToReEnable if expect ToDisable in ledger
// * @return true if meet all three expectation
// * @param env the test environment
// * @param view the OpenView of the ledger
// * @param tx the ttUNL_MODIFY Tx
// * @param pass if the Tx should be applied successfully
// * @return true if meet the expectation of apply result
// */
/*
bool
inline
negUnlSizeTest(
std::shared_ptr<ripple::Ledger const> const& l,
size_t size,
bool hasToDisable,
bool hasToReEnable)
{
bool sameSize = l->negativeUNL().size() == size;
bool sameToDisable =
(l->validatorToDisable() != std::nullopt) == hasToDisable;
bool sameToReEnable =
(l->validatorToReEnable() != std::nullopt) == hasToReEnable;
return sameSize && sameToDisable && sameToReEnable;
}
*/
/**
* Try to apply a ttUNL_MODIFY Tx, and test the apply result
*
* @param env the test environment
* @param view the OpenView of the ledger
* @param tx the ttUNL_MODIFY Tx
* @param pass if the Tx should be applied successfully
* @return true if meet the expectation of apply result
*/
/*
bool
inline
applyAndTestResult(jtx::Env& env, ripple::OpenView& view, ripple::STTx const&
tx, bool pass)
{
auto res = apply(env.app(), view, tx, ApplyFlags::tapNONE, env.journal);
if (pass)
return res.first == tesSUCCESS;
else
return res.first == tefFAILURE || res.first == temDISABLED;
}
*/
inline bool
applyAndTestUNLRResult(jtx::Env& env, OpenView& view, STTx const& tx, bool pass)
{
auto res = apply(env.app(), view, tx, ApplyFlags::tapNONE, env.journal);
if (pass)
return res.first == tesSUCCESS;
else
return res.first == tefFAILURE || res.first == temDISABLED ||
res.first == temMALFORMED ||
res.first == telIMPORT_VL_KEY_NOT_RECOGNISED;
}
applyAndTestUNLRResult(
jtx::Env& env,
OpenView& view,
STTx const& tx,
bool pass);
/**
* Verify the content of UNL Report entries (public key and ledger sequence)
@@ -139,9 +93,6 @@ countUNLRTx(std::shared_ptr<SHAMap> const& txSet);
std::vector<std::string> const keys = {
"ED74D4036C6591A4BDF9C54CEFA39B996A5DCE5F86D11FDA1874481CE9D5A1CDC1"};
std::unique_ptr<Config>
makeNetworkVLConfig(uint32_t networkID, std::vector<std::string> keys);
/**
* Verify if the UNL report exists
*
@@ -194,38 +145,6 @@ createUNLRTx(
PublicKey const& importKey,
PublicKey const& valKey);
/**
* Count the number of Tx in a TxSet
*
* @param txSet the TxSet
* @return the number of Tx
*/
inline std::size_t
countTx(std::shared_ptr<SHAMap> const& txSet);
/**
* Create ttUNL_MODIFY Tx
*
* @param disabling disabling or re-enabling a validator
* @param seq current ledger seq
* @param txKey the public key of the validator
* @return the ttUNL_MODIFY Tx
*/
inline STTx
createTx(bool disabling, LedgerIndex seq, PublicKey const& txKey);
/**
* Try to apply a ttUNL_MODIFY Tx, and test the apply result
*
* @param env the test environment
* @param view the OpenView of the ledger
* @param tx the ttUNL_MODIFY Tx
* @param pass if the Tx should be applied successfully
* @return true if meet the expectation of apply result
*/
inline bool
applyAndTestResult(jtx::Env& env, OpenView& view, STTx const& tx, bool pass);
class UNLReport_test : public beast::unit_test::suite
{
// Import VL Keys
@@ -374,7 +293,10 @@ class UNLReport_test : public beast::unit_test::suite
// telIMPORT_VL_KEY_NOT_RECOGNISED
{
test::jtx::Env env{
*this, makeNetworkVLConfig(21337, keys), features, nullptr};
*this,
jtx::network::makeNetworkVLConfig(21337, keys),
features,
nullptr};
auto l = std::make_shared<Ledger>(
create_genesis,
@@ -403,7 +325,10 @@ class UNLReport_test : public beast::unit_test::suite
// SUCCESS
{
test::jtx::Env env{
*this, makeNetworkVLConfig(21337, keys), features, nullptr};
*this,
jtx::network::makeNetworkVLConfig(21337, keys),
features,
nullptr};
auto l = std::make_shared<Ledger>(
create_genesis,
@@ -442,7 +367,10 @@ class UNLReport_test : public beast::unit_test::suite
using namespace jtx;
test::jtx::Env env{
*this, makeNetworkVLConfig(21337, keys), features, nullptr};
*this,
jtx::network::makeNetworkVLConfig(21337, keys),
features,
nullptr};
std::vector<PublicKey> ivlKeys;
for (auto const& strPk : _ivlKeys)
@@ -703,7 +631,8 @@ struct URNetworkHistory
URNetworkHistory(beast::unit_test::suite& suite, Parameter const& p)
: env(suite,
p.withVL ? makeNetworkVLConfig(21337, keys) : jtx::envconfig(),
p.withVL ? jtx::network::makeNetworkVLConfig(21337, keys)
: jtx::envconfig(),
jtx::supported_amendments() | featureNegativeUNL)
, param(p)
, validations(env.app().getValidations())
@@ -757,8 +686,8 @@ struct URNetworkHistory
OpenView accum(&*l);
if (l->negativeUNL().size() < param.negUNLSize)
{
auto tx = createTx(true, l->seq(), UNLKeys[nidx]);
if (!applyAndTestResult(env, accum, tx, true))
auto tx = unl::createTx(true, l->seq(), UNLKeys[nidx]);
if (!unl::applyAndTestResult(env, accum, tx, true))
break;
++nidx;
}
@@ -766,15 +695,15 @@ struct URNetworkHistory
{
if (param.hasToDisable)
{
auto tx = createTx(true, l->seq(), UNLKeys[nidx]);
if (!applyAndTestResult(env, accum, tx, true))
auto tx = unl::createTx(true, l->seq(), UNLKeys[nidx]);
if (!unl::applyAndTestResult(env, accum, tx, true))
break;
++nidx;
}
if (param.hasToReEnable)
{
auto tx = createTx(false, l->seq(), UNLKeys[0]);
if (!applyAndTestResult(env, accum, tx, true))
auto tx = unl::createTx(false, l->seq(), UNLKeys[0]);
if (!unl::applyAndTestResult(env, accum, tx, true))
break;
}
}
@@ -782,7 +711,7 @@ struct URNetworkHistory
}
l->updateSkipList();
}
return negUnlSizeTest(
return unl::negUnlSizeTest(
l, param.negUNLSize, param.hasToDisable, param.hasToReEnable);
}
@@ -883,7 +812,8 @@ voteAndCheckUNLR(
vote.doVoting(
history.lastLedger(), history.UNLKeySet, history.validations, txSet);
return countUNLRTx(txSet) == expectReport && countTx(txSet) >= expectModify;
return countUNLRTx(txSet) == expectReport &&
unl::countTx(txSet) >= expectModify;
}
/*
@@ -1264,6 +1194,18 @@ BEAST_DEFINE_TESTSUITE(UNLReportVoteNewValidator, consensus, ripple);
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
bool
applyAndTestUNLRResult(jtx::Env& env, OpenView& view, STTx const& tx, bool pass)
{
auto res = apply(env.app(), view, tx, ApplyFlags::tapNONE, env.journal);
if (pass)
return res.first == tesSUCCESS;
else
return res.first == tefFAILURE || res.first == temDISABLED ||
res.first == temMALFORMED ||
res.first == telIMPORT_VL_KEY_NOT_RECOGNISED;
}
bool
VerifyUNLRPubKeyAndSeq(
std::shared_ptr<Ledger const> const& l,
@@ -1314,38 +1256,6 @@ countUNLRTx(std::shared_ptr<SHAMap> const& txSet)
return count;
};
std::unique_ptr<Config>
makeNetworkVLConfig(uint32_t networkID, std::vector<std::string> keys)
{
using namespace jtx;
return envconfig([&](std::unique_ptr<Config> cfg) {
cfg->NETWORK_ID = networkID;
Section config;
config.append(
{"reference_fee = 10",
"account_reserve = 1000000",
"owner_reserve = 200000"});
auto setup = setup_FeeVote(config);
cfg->FEES = setup;
for (auto const& strPk : keys)
{
auto pkHex = strUnHex(strPk);
if (!pkHex)
Throw<std::runtime_error>(
"Import VL Key '" + strPk + "' was not valid hex.");
auto const pkType = publicKeyType(makeSlice(*pkHex));
if (!pkType)
Throw<std::runtime_error>(
"Import VL Key '" + strPk + "' was not a valid key type.");
cfg->IMPORT_VL_KEYS.emplace(strPk, makeSlice(*pkHex));
}
return cfg;
});
}
bool
hasUNLReport(jtx::Env const& env)
{
@@ -1413,45 +1323,5 @@ createUNLRTx(
return STTx(ttUNL_REPORT, fill);
}
/*
inline STTx
createTx(bool disabling, LedgerIndex seq, PublicKey const& txKey)
{
auto fill = [&](auto& obj) {
obj.setFieldU8(sfUNLModifyDisabling, disabling ? 1 : 0);
obj.setFieldU32(sfLedgerSequence, seq);
obj.setFieldVL(sfUNLModifyValidator, txKey);
};
return STTx(ttUNL_MODIFY, fill);
}
*/
/*
inline std::size_t
countTx(std::shared_ptr<SHAMap> const& txSet)
{
std::size_t count = 0;
for (auto i = txSet->begin(); i != txSet->end(); ++i)
{
auto raw = i->slice();
if (raw[0] == 0x12U && raw[1] == 0 && raw[2] == 0x66U)
count++;
}
return count;
};
*/
/*
inline bool
applyAndTestResult(jtx::Env& env, OpenView& view, STTx const& tx, bool pass)
{
auto res = apply(env.app(), view, tx, ApplyFlags::tapNONE, env.journal);
if (pass)
return res.first == tesSUCCESS;
else
return res.first == tefFAILURE || res.first == temDISABLED;
}
*/
} // namespace test
} // namespace ripple
} // namespace ripple

View File

@@ -60,7 +60,6 @@
#include <test/jtx/require.h>
#include <test/jtx/requires.h>
#include <test/jtx/reward.h>
#include <test/jtx/remit.h>
#include <test/jtx/sendmax.h>
#include <test/jtx/seq.h>
#include <test/jtx/sig.h>
@@ -71,6 +70,7 @@
#include <test/jtx/token.h>
#include <test/jtx/trust.h>
#include <test/jtx/txflags.h>
#include <test/jtx/unl.h>
#include <test/jtx/uritoken.h>
#include <test/jtx/utility.h>

View File

@@ -17,6 +17,7 @@
*/
//==============================================================================
#include <ripple/protocol/jss.h>
#include <test/jtx/Env.h>
#include <test/jtx/acctdelete.h>

View File

@@ -1,84 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2023 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/protocol/jss.h>
#include <test/jtx/remit.h>
namespace ripple {
namespace test {
namespace jtx {
namespace remit {
Json::Value
remit(jtx::Account const& account, jtx::Account const& dest)
{
using namespace jtx;
Json::Value jv;
jv[jss::TransactionType] = jss::Remit;
jv[jss::Account] = account.human();
jv[jss::Destination] = dest.human();
return jv;
}
void
amts::operator()(Env& env, JTx& jt) const
{
auto& ja = jt.jv[sfAmounts.getJsonName()];
for (std::size_t i = 0; i < amts_.size(); ++i)
{
ja[i][sfAmountEntry.jsonName] = Json::Value{};
ja[i][sfAmountEntry.jsonName][jss::Amount] =
amts_[i].getJson(JsonOptions::none);
}
jt.jv[sfAmounts.jsonName] = ja;
}
void
blob::operator()(Env& env, JTx& jt) const
{
jt.jv[sfBlob.jsonName] = blob_;
}
void
inform::operator()(Env& env, JTx& jt) const
{
jt.jv[sfInform.jsonName] = inform_.human();
}
void
token_ids::operator()(Env& env, JTx& jt) const
{
for (std::size_t i = 0; i < token_ids_.size(); ++i)
{
jt.jv[sfURITokenIDs.jsonName] = Json::arrayValue;
jt.jv[sfURITokenIDs.jsonName][i] = token_ids_[i];
}
}
void
uri::operator()(Env& env, JTx& jt) const
{
jt.jv[sfMintURIToken.jsonName] = Json::Value{};
jt.jv[sfMintURIToken.jsonName][sfURI.jsonName] = strHex(uri_);;
}
} // namespace remit
} // namespace jtx
} // namespace test
} // namespace ripple

95
src/test/jtx/impl/unl.cpp Normal file
View File

@@ -0,0 +1,95 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2023 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/apply.h>
#include <ripple/protocol/jss.h>
#include <test/jtx/unl.h>
namespace ripple {
namespace test {
namespace unl {
bool
negUnlSizeTest(
std::shared_ptr<Ledger const> const& l,
size_t size,
bool hasToDisable,
bool hasToReEnable)
{
bool sameSize = l->negativeUNL().size() == size;
bool sameToDisable =
(l->validatorToDisable() != std::nullopt) == hasToDisable;
bool sameToReEnable =
(l->validatorToReEnable() != std::nullopt) == hasToReEnable;
return sameSize && sameToDisable && sameToReEnable;
}
bool
applyAndTestResult(jtx::Env& env, OpenView& view, STTx const& tx, bool pass)
{
auto res = apply(env.app(), view, tx, ApplyFlags::tapNONE, env.journal);
if (pass)
return res.first == tesSUCCESS;
else
return res.first == tefFAILURE || res.first == temDISABLED;
}
std::size_t
countTx(std::shared_ptr<SHAMap> const& txSet)
{
/*uint64_t counter = 0;
if (txSet)
for (auto const& item : *txSet)
{
SerialIter sit(item.slice());
auto tx = std::make_shared<STTx
const>(SerialIter{sit.getSlice(sit.getVLDataLength())});
if (tx->getFieldU16(sfTransactionType) == ttUNL_MODIFY)
counter++;
}
*/
std::size_t count = 0;
for (auto i = txSet->begin(); i != txSet->end(); ++i)
{
// RH TODO: why does the above parse??
auto raw = i->slice();
if (raw[0] == 0x12U && raw[1] == 0 && raw[2] == 0x66U)
count++;
}
return count;
};
STTx
createTx(bool disabling, LedgerIndex seq, PublicKey const& txKey)
{
auto fill = [&](auto& obj) {
obj.setFieldU8(sfUNLModifyDisabling, disabling ? 1 : 0);
obj.setFieldU32(sfLedgerSequence, seq);
obj.setFieldVL(sfUNLModifyValidator, txKey);
};
return STTx(ttUNL_MODIFY, fill);
}
} // namespace unl
} // namespace test
} // namespace ripple

View File

@@ -1,118 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2023 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.
*/
//==============================================================================
#ifndef RIPPLE_TEST_JTX_REMIT_H_INCLUDED
#define RIPPLE_TEST_JTX_REMIT_H_INCLUDED
#include <ripple/protocol/STAmount.h>
#include <test/jtx/Account.h>
#include <test/jtx/Env.h>
namespace ripple {
namespace test {
namespace jtx {
namespace remit {
Json::Value
remit(jtx::Account const& account, jtx::Account const& dest);
/** Sets the optional Amount on a JTx. */
class amts
{
private:
std::vector<STAmount> amts_;
public:
explicit amts(std::vector<STAmount> const& amts) : amts_(amts)
{
}
void
operator()(Env&, JTx& jtx) const;
};
/** Set the optional "Blob" on a JTx */
class blob
{
private:
std::string blob_;
public:
explicit blob(std::string const& blob) : blob_(blob)
{
}
void
operator()(Env&, JTx& jtx) const;
};
/** Sets the optional "Inform" on a JTx. */
class inform
{
private:
jtx::Account inform_;
public:
explicit inform(jtx::Account const& inform) : inform_(inform)
{
}
void
operator()(Env&, JTx& jtx) const;
};
/** Sets the optional "URITokenIDs" on a JTx. */
class token_ids
{
private:
std::vector<std::string> token_ids_;
public:
explicit token_ids(std::vector<std::string> const& token_ids) : token_ids_(token_ids)
{
}
void
operator()(Env&, JTx& jtx) const;
};
/** Set the optional "sfMintURIToken" on a JTx */
class uri
{
private:
std::string uri_;
public:
explicit uri(std::string const& uri) : uri_(uri)
{
}
void
operator()(Env&, JTx& jtx) const;
};
} // namespace remit
} // namespace jtx
} // namespace test
} // namespace ripple
#endif // RIPPLE_TEST_JTX_REMIT_H_INCLUDED

86
src/test/jtx/unl.h Normal file
View File

@@ -0,0 +1,86 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2023 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.
*/
//==============================================================================
#ifndef RIPPLE_TEST_JTX_UNL_H_INCLUDED
#define RIPPLE_TEST_JTX_UNL_H_INCLUDED
#include <ripple/protocol/STAmount.h>
#include <test/jtx/Account.h>
#include <test/jtx/Env.h>
namespace ripple {
namespace test {
namespace unl {
/**
* Test the size of the negative UNL in a ledger,
* also test if the ledger has ToDisalbe and/or ToReEnable
*
* @param l the ledger
* @param size the expected negative UNL size
* @param hasToDisable if expect ToDisable in ledger
* @param hasToReEnable if expect ToDisable in ledger
* @return true if meet all three expectation
*/
bool
negUnlSizeTest(
std::shared_ptr<Ledger const> const& l,
size_t size,
bool hasToDisable,
bool hasToReEnable);
/**
* Try to apply a ttUNL_MODIFY Tx, and test the apply result
*
* @param env the test environment
* @param view the OpenView of the ledger
* @param tx the ttUNL_MODIFY Tx
* @param pass if the Tx should be applied successfully
* @return true if meet the expectation of apply result
*/
bool
applyAndTestResult(jtx::Env& env, OpenView& view, STTx const& tx, bool pass);
/**
* Count the number of Tx in a TxSet
*
* @param txSet the TxSet
* @return the number of Tx
*/
std::size_t
countTx(std::shared_ptr<SHAMap> const& txSet);
/**
* Create ttUNL_MODIFY Tx
*
* @param disabling disabling or re-enabling a validator
* @param seq current ledger seq
* @param txKey the public key of the validator
* @return the ttUNL_MODIFY Tx
*/
STTx
createTx(bool disabling, LedgerIndex seq, PublicKey const& txKey);
} // namespace unl
} // namespace test
} // namespace ripple
#endif // RIPPLE_TEST_JTX_UNL_H_INCLUDED