Compare commits

...
Author SHA1 Message Date
Richard Holland 3a6f5faacd clangf 2023-12-20 11:35:51 +00:00
Richard Holland 4eba415e9b . 2023-12-20 11:19:07 +00:00
Richard Holland 9d73b12713 . 2023-12-20 11:15:57 +00:00
Richard Holland d3d4f4dbad lite fixes for tsh issues 2023-12-20 11:14:20 +00:00
RichardAH dd3b0447d5 FXV1: allow duplicate entries in genesis mint transactor (#239) 2023-12-20 11:23:11 +01:00
Denis Angell bba014a27c Merge branch 'dev' into fixXahauV1 2023-12-19 20:47:53 +01:00
Denis Angell 617c896514 Merge branch 'dev' into fixXahauV1 2023-12-19 10:09:41 +01:00
Denis Angell 97ab309a09 FXV1: Namespace Limit (#220) 2023-12-15 12:19:25 +01:00
Denis Angell 6cb07b0000 FXV1: Fix Import Quorum (#235) 2023-12-14 14:46:59 +01:00
Denis Angell 049efbcdc0 FXV1: Patch Hooks OwnerDir (#236) 2023-12-14 14:45:29 +01:00
Denis Angell 9f449e97bc clang-format 2023-12-13 15:15:58 +01:00
Denis Angell 6561cd7f58 fix tsh tests (offer seq now optional) 2023-12-13 15:13:21 +01:00
Denis Angell 27b91f997c Merge branch 'dev' into fixXahauV1 2023-12-11 13:51:11 +01:00
Denis Angell 75b760debe FXV1: Optional Offer Sequence (#224) 2023-12-07 13:26:47 +01:00
Denis Angell 3f7e15893e FXV1: Meta Amount (#225)
ensure default-value amount fields are correctly recorded in metadata for created nodes (fixXahau1)
2023-12-03 10:27:06 +01:00
25 changed files with 1326 additions and 432 deletions

No files matched your search

+2
View File
@@ -45,5 +45,7 @@
#define INVALID_KEY -41
#define NOT_A_STRING -42
#define MEM_OVERLAP -43
#define TOO_MANY_STATE_MODIFICATIONS -44
#define TOO_MANY_NAMESPACES -45
#define HOOK_ERROR_CODES
#endif //HOOK_ERROR_CODES
+7
View File
@@ -54,6 +54,12 @@ maxHookChainLength(void)
return 10;
}
inline uint32_t
maxNamespaces(void)
{
return 256;
}
enum TSHFlags : uint8_t {
tshNONE = 0b000,
tshROLLBACK = 0b001,
@@ -313,6 +319,7 @@ enum hook_return_code : int64_t {
MEM_OVERLAP = -43, // one or more specified buffers are the same memory
TOO_MANY_STATE_MODIFICATIONS = -44, // more than 5000 modified state
// entires in the combined hook chains
TOO_MANY_NAMESPACES = -45
};
enum ExitType : uint8_t {
+2 -1
View File
@@ -29,8 +29,9 @@ isEmittedTxn(ripple::STTx const& tx);
// only upon tesSuccess for the otxn.
class HookStateMap : public std::map<
ripple::AccountID, // account that owns the state
std::pair<
std::tuple<
int64_t, // remaining available ownercount
int64_t, // total namespace count
std::map<
ripple::uint256, // namespace
std::map<
+125 -26
View File
@@ -71,6 +71,8 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
return rv.read(keylet::nftoffer(*id));
};
bool const fixV1 = rv.rules().enabled(fixXahauV1);
switch (tt)
{
case ttIMPORT: {
@@ -97,19 +99,36 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
// the burner is the issuer and not the owner of the token
if (issuer == owner)
{
// pass, already a TSH
}
else if (*otxnAcc == owner)
break;
// pass, already a TSH
// new logic
if (fixV1)
{
// the owner burns their token, and the issuer is a weak TSH
ADD_TSH(issuer, canRollback);
}
else
{
if (*otxnAcc == owner && rv.exists(keylet::account(issuer)))
ADD_TSH(issuer, false);
// the issuer burns the owner's token, and the owner is a weak
// TSH
ADD_TSH(owner, canRollback);
else if (rv.exists(keylet::account(owner)))
ADD_TSH(owner, false);
break;
}
// old logic
{
if (*otxnAcc == owner)
{
// the owner burns their token, and the issuer is a weak TSH
ADD_TSH(issuer, true);
}
else
{
// the issuer burns the owner's token, and the owner is a
// weak TSH
ADD_TSH(owner, true);
}
}
break;
@@ -300,19 +319,59 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
case ttESCROW_CANCEL:
case ttESCROW_FINISH: {
if (!tx.isFieldPresent(sfOwner) ||
!tx.isFieldPresent(sfOfferSequence))
return {};
// new logic
if (fixV1)
{
if (!tx.isFieldPresent(sfOwner))
return {};
auto escrow = rv.read(keylet::escrow(
tx.getAccountID(sfOwner), tx.getFieldU32(sfOfferSequence)));
AccountID const owner = tx.getAccountID(sfOwner);
if (!escrow)
return {};
bool const hasSeq = tx.isFieldPresent(sfOfferSequence);
bool const hasID = tx.isFieldPresent(sfEscrowID);
if (!hasSeq && !hasID)
return {};
ADD_TSH(escrow->getAccountID(sfAccount), true);
ADD_TSH(escrow->getAccountID(sfDestination), canRollback);
break;
Keylet kl = hasSeq
? keylet::escrow(owner, tx.getFieldU32(sfOfferSequence))
: Keylet(ltESCROW, tx.getFieldH256(sfEscrowID));
auto escrow = rv.read(kl);
if (!escrow ||
escrow->getFieldU16(sfLedgerEntryType) != ltESCROW)
return {};
// this should always be the same as owner, but defensively...
AccountID const src = escrow->getAccountID(sfAccount);
AccountID const dst = escrow->getAccountID(sfDestination);
// the source account is a strong transacitonal stakeholder for
// fin and can
ADD_TSH(src, true);
// the dest acc is a strong tsh for fin and weak for can
if (src != dst)
ADD_TSH(dst, tt == ttESCROW_FINISH);
break;
}
// old logic
{
if (!tx.isFieldPresent(sfOwner) ||
!tx.isFieldPresent(sfOfferSequence))
return {};
auto escrow = rv.read(keylet::escrow(
tx.getAccountID(sfOwner), tx.getFieldU32(sfOfferSequence)));
if (!escrow)
return {};
ADD_TSH(escrow->getAccountID(sfAccount), true);
ADD_TSH(escrow->getAccountID(sfDestination), canRollback);
break;
}
}
case ttPAYCHAN_FUND:
@@ -1287,7 +1346,7 @@ lookup_state_cache(
if (stateMap.find(acc) == stateMap.end())
return std::nullopt;
auto& stateMapAcc = stateMap[acc].second;
auto& stateMapAcc = std::get<2>(stateMap[acc]);
if (stateMapAcc.find(ns) == stateMapAcc.end())
return std::nullopt;
@@ -1312,18 +1371,22 @@ set_state_cache(
bool modified)
{
auto& stateMap = hookCtx.result.stateMap;
auto& view = hookCtx.applyCtx.view();
if (modified && stateMap.modified_entry_count >= max_state_modifications)
return TOO_MANY_STATE_MODIFICATIONS;
bool const createNamespace = view.rules().enabled(fixXahauV1) &&
!view.exists(keylet::hookStateDir(acc, ns));
if (stateMap.find(acc) == stateMap.end())
{
// if this is the first time this account has been interacted with
// we will compute how many available reserve positions there are
auto const& fees = hookCtx.applyCtx.view().fees();
auto const accSLE =
hookCtx.applyCtx.view().read(ripple::keylet::account(acc));
auto const accSLE = view.read(ripple::keylet::account(acc));
if (!accSLE)
return DOESNT_EXIST;
@@ -1342,15 +1405,32 @@ set_state_cache(
if (availableForReserves < 1 && modified)
return RESERVE_INSUFFICIENT;
int64_t namespaceCount = accSLE->isFieldPresent(sfHookNamespaces)
? accSLE->getFieldV256(sfHookNamespaces).size()
: 0;
if (createNamespace)
{
// overflow should never ever happen but check anyway
if (namespaceCount + 1 < namespaceCount)
return INTERNAL_ERROR;
if (++namespaceCount > hook::maxNamespaces())
return TOO_MANY_NAMESPACES;
}
stateMap.modified_entry_count++;
stateMap[acc] = {
availableForReserves - 1, {{ns, {{key, {modified, data}}}}}};
availableForReserves - 1,
namespaceCount,
{{ns, {{key, {modified, data}}}}}};
return 1;
}
auto& stateMapAcc = stateMap[acc].second;
auto& availableForReserves = stateMap[acc].first;
auto& availableForReserves = std::get<0>(stateMap[acc]);
auto& namespaceCount = std::get<1>(stateMap[acc]);
auto& stateMapAcc = std::get<2>(stateMap[acc]);
bool const canReserveNew = availableForReserves > 0;
if (stateMapAcc.find(ns) == stateMapAcc.end())
@@ -1360,6 +1440,18 @@ set_state_cache(
if (!canReserveNew)
return RESERVE_INSUFFICIENT;
if (createNamespace)
{
// overflow should never ever happen but check anyway
if (namespaceCount + 1 < namespaceCount)
return INTERNAL_ERROR;
if (namespaceCount + 1 > hook::maxNamespaces())
return TOO_MANY_NAMESPACES;
namespaceCount++;
}
availableForReserves--;
stateMap.modified_entry_count++;
}
@@ -1488,6 +1580,13 @@ DEFINE_HOOK_FUNCTION(
auto const key = make_state_key(
std::string_view{(const char*)(memory + kread_ptr), (size_t)kread_len});
if (view.rules().enabled(fixXahauV1))
{
auto const sleAccount = view.peek(hookCtx.result.accountKeylet);
if (!sleAccount)
return tefINTERNAL;
}
if (!key)
return INTERNAL_ERROR;
@@ -1610,7 +1709,7 @@ hook::finalizeHookState(
for (const auto& accEntry : stateMap)
{
const auto& acc = accEntry.first;
for (const auto& nsEntry : accEntry.second.second)
for (const auto& nsEntry : std::get<2>(accEntry.second))
{
const auto& ns = nsEntry.first;
for (const auto& cacheEntry : nsEntry.second)
+31 -30
View File
@@ -39,10 +39,26 @@ CancelOffer::preflight(PreflightContext const& ctx)
return temINVALID_FLAG;
}
if (!ctx.tx[sfOfferSequence])
if (ctx.rules.enabled(fixXahauV1))
{
JLOG(ctx.j.trace()) << "CancelOffer::preflight: missing sequence";
return temBAD_SEQUENCE;
if ((!ctx.tx.isFieldPresent(sfOfferSequence) &&
!ctx.tx.isFieldPresent(sfOfferID)) ||
(ctx.tx.isFieldPresent(sfOfferSequence) &&
ctx.tx.isFieldPresent(sfOfferID)))
{
JLOG(ctx.j.trace())
<< "CancelOffer::preflight: invalid sequence or offer id";
return temBAD_SEQUENCE;
}
}
else
{
if (!ctx.tx.isFieldPresent(sfOfferSequence) ||
ctx.tx[sfOfferSequence] == 0)
{
JLOG(ctx.j.trace()) << "CancelOffer::preflight: missing sequence";
return temBAD_SEQUENCE;
}
}
return preflight2(ctx);
@@ -54,29 +70,18 @@ TER
CancelOffer::preclaim(PreclaimContext const& ctx)
{
auto const id = ctx.tx[sfAccount];
auto const offerSequence = ctx.tx[sfOfferSequence];
auto const sle = ctx.view.read(keylet::account(id));
if (!sle)
return terNO_ACCOUNT;
bool hooksEnabled = ctx.view.rules().enabled(featureHooks);
auto const offerSequence = ctx.tx[~sfOfferSequence];
auto const offerID = ctx.tx[~sfOfferID];
if ((*sle)[sfSequence] <= offerSequence)
if (offerSequence && (*sle)[sfSequence] <= *offerSequence)
{
if (hooksEnabled && offerID && offerSequence == 0)
{
// pass, here the txn provides offerID instead of offerSequence
}
else
{
JLOG(ctx.j.trace())
<< "Malformed transaction: "
<< "Sequence " << offerSequence << " is invalid.";
return temBAD_SEQUENCE;
}
JLOG(ctx.j.trace()) << "Malformed transaction: "
<< "Sequence " << *offerSequence << " is invalid.";
return temBAD_SEQUENCE;
}
return tesSUCCESS;
@@ -87,36 +92,32 @@ CancelOffer::preclaim(PreclaimContext const& ctx)
TER
CancelOffer::doApply()
{
auto const offerSequence = ctx_.tx[sfOfferSequence];
auto const sle = view().read(keylet::account(account_));
if (!sle)
return tefINTERNAL;
auto const offerSequence = ctx_.tx[~sfOfferSequence];
auto const offerID = ctx_.tx[~sfOfferID];
bool hooksEnabled = view().rules().enabled(featureHooks);
std::optional<uint256> offerID;
if (hooksEnabled)
offerID = ctx_.tx[~sfOfferID];
Keylet cancel = hooksEnabled && offerID && offerSequence == 0
Keylet cancel = hooksEnabled && offerID && !offerSequence
? Keylet(ltOFFER, *offerID)
: keylet::offer(account_, offerSequence);
: keylet::offer(account_, *offerSequence);
if (auto sleOffer = view().peek(cancel))
{
if (offerID)
JLOG(j_.debug()) << "Trying to cancel offer :" << *offerID;
else
JLOG(j_.debug()) << "Trying to cancel offer #" << offerSequence;
JLOG(j_.debug()) << "Trying to cancel offer #" << *offerSequence;
return offerDelete(view(), sleOffer, ctx_.app.journal("View"));
}
if (offerID)
JLOG(j_.debug()) << "Offer :" << *offerID << " can't be found.";
else
JLOG(j_.debug()) << "Offer #" << offerSequence << " can't be found.";
JLOG(j_.debug()) << "Offer #" << *offerSequence << " can't be found.";
return tesSUCCESS;
}
+34 -10
View File
@@ -431,6 +431,10 @@ EscrowFinish::preflight(PreflightContext const& ctx)
}
}
if (!ctx.tx.isFieldPresent(sfEscrowID) &&
!ctx.tx.isFieldPresent(sfOfferSequence))
return temMALFORMED;
return tesSUCCESS;
}
@@ -456,13 +460,21 @@ EscrowFinish::doApply()
return temDISABLED;
std::optional<uint256> escrowID = ctx_.tx[~sfEscrowID];
std::optional<std::uint32_t> offerSequence = ctx_.tx[~sfOfferSequence];
if (escrowID && ctx_.tx[sfOfferSequence] != 0)
return temMALFORMED;
if (!view().rules().enabled(fixXahauV1))
{
if (escrowID && ctx_.tx[sfOfferSequence] != 0)
return temMALFORMED;
}
else
{
if (escrowID && offerSequence)
return temMALFORMED;
}
Keylet k = escrowID
? Keylet(ltESCROW, *escrowID)
: keylet::escrow(ctx_.tx[sfOwner], ctx_.tx[sfOfferSequence]);
Keylet k = escrowID ? Keylet(ltESCROW, *escrowID)
: keylet::escrow(ctx_.tx[sfOwner], *offerSequence);
auto const slep = ctx_.view().peek(k);
if (!slep)
@@ -692,6 +704,10 @@ EscrowCancel::preflight(PreflightContext const& ctx)
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
return ret;
if (!ctx.tx.isFieldPresent(sfEscrowID) &&
!ctx.tx.isFieldPresent(sfOfferSequence))
return temMALFORMED;
return preflight2(ctx);
}
@@ -704,13 +720,21 @@ EscrowCancel::doApply()
return temDISABLED;
std::optional<uint256> escrowID = ctx_.tx[~sfEscrowID];
std::optional<std::uint32_t> offerSequence = ctx_.tx[~sfOfferSequence];
if (escrowID && ctx_.tx[sfOfferSequence] != 0)
return temMALFORMED;
if (!view().rules().enabled(fixXahauV1))
{
if (escrowID && ctx_.tx[sfOfferSequence] != 0)
return temMALFORMED;
}
else
{
if (escrowID && offerSequence)
return temMALFORMED;
}
Keylet k = escrowID
? Keylet(ltESCROW, *escrowID)
: keylet::escrow(ctx_.tx[sfOwner], ctx_.tx[sfOfferSequence]);
Keylet k = escrowID ? Keylet(ltESCROW, *escrowID)
: keylet::escrow(ctx_.tx[sfOwner], *offerSequence);
auto const slep = ctx_.view().peek(k);
if (!slep)
+160 -28
View File
@@ -74,6 +74,8 @@ GenesisMint::preflight(PreflightContext const& ctx)
return temMALFORMED;
}
bool const allowDuplicates = ctx.rules.enabled(fixXahauV1);
std::unordered_set<AccountID> alreadySeen;
for (auto const& dest : dests)
{
@@ -137,6 +139,9 @@ GenesisMint::preflight(PreflightContext const& ctx)
return temMALFORMED;
}
if (allowDuplicates)
continue;
if (alreadySeen.find(accid) != alreadySeen.end())
{
JLOG(ctx.j.warn()) << "GenesisMint: duplicate in destinations.";
@@ -171,7 +176,114 @@ GenesisMint::doApply()
{
auto const& dests = ctx_.tx.getFieldArray(sfGenesisMints);
if (!view().rules().enabled(fixXahauV1))
{
STAmount dropsAdded{0};
for (auto const& dest : dests)
{
auto const amt = dest[~sfAmount];
if (amt && !isXRP(*amt))
{
JLOG(ctx_.journal.warn()) << "GenesisMint: Non-xrp amount.";
return tecINTERNAL;
}
auto const flags = dest[~sfGovernanceFlags];
auto const marks = dest[~sfGovernanceMarks];
auto const id = dest.getAccountID(sfDestination);
auto const k = keylet::account(id);
auto sle = view().peek(k);
bool const created = !sle;
if (created)
{
// Create the account.
std::uint32_t const seqno{
view().info().parentCloseTime.time_since_epoch().count()};
sle = std::make_shared<SLE>(k);
sle->setAccountID(sfAccount, id);
sle->setFieldU32(sfSequence, seqno);
if (amt)
{
sle->setFieldAmount(sfBalance, *amt);
dropsAdded += *amt;
}
else // give them 2 XRP if the account didn't exist, same as
// ttIMPORT
{
XRPAmount const initialXrp =
Import::computeStartingBonus(ctx_.view());
sle->setFieldAmount(sfBalance, initialXrp);
dropsAdded += initialXrp;
}
}
else if (amt)
{
// Credit the account
STAmount startBal = sle->getFieldAmount(sfBalance);
STAmount finalBal = startBal + *amt;
if (finalBal <= startBal)
{
JLOG(ctx_.journal.warn())
<< "GenesisMint: cannot credit " << dest
<< " due to balance overflow";
return tecINTERNAL;
}
sle->setFieldAmount(sfBalance, finalBal);
dropsAdded += *amt;
}
// set flags and marks as applicable
if (flags)
sle->setFieldH256(sfGovernanceFlags, *flags);
if (marks)
sle->setFieldH256(sfGovernanceMarks, *marks);
if (created)
view().insert(sle);
else
view().update(sle);
}
// update ledger header
if (dropsAdded < beast::zero ||
dropsAdded.xrp() + view().info().drops < view().info().drops)
{
JLOG(ctx_.journal.warn()) << "GenesisMint: dropsAdded overflowed\n";
return tecINTERNAL;
}
if (dropsAdded > beast::zero)
ctx_.rawView().rawDestroyXRP(-dropsAdded.xrp());
return tesSUCCESS;
}
// RH NOTE:
// As of fixXahauV1, duplicate accounts are allowed
// so we first do a summation loop then an actioning loop
// if amendment is not active then there's exactly one entry per
std::map<
AccountID, // account to credit
std::tuple<
XRPAmount, // amount to credit
std::optional<uint256>, // gov flags
std::optional<uint256>>> // gov marks
mints;
STAmount dropsAdded{0};
// sum loop
for (auto const& dest : dests)
{
auto const amt = dest[~sfAmount];
@@ -182,13 +294,54 @@ GenesisMint::doApply()
return tecINTERNAL;
}
XRPAmount toCredit = amt ? amt->xrp() : XRPAmount{0};
auto const flags = dest[~sfGovernanceFlags];
auto const marks = dest[~sfGovernanceMarks];
auto const id = dest.getAccountID(sfDestination);
auto const k = keylet::account(id);
auto sle = view().peek(k);
bool const firstOccurance = mints.find(id) == mints.end();
dropsAdded += toCredit;
// if flags / marks appear more than once we just take the first
// appearance
if (firstOccurance)
mints[id] = {toCredit, flags, marks};
else
{
// duplicated entries accumulate in the map
auto& [accTotal, f, m] = mints[id];
// detect overflow
if (accTotal + toCredit < accTotal)
{
JLOG(ctx_.journal.warn()) << "GenesisMint: cannot credit " << id
<< " due to balance overflow";
return tecINTERNAL;
}
accTotal += toCredit;
}
}
// check for ledger header overflow
if (dropsAdded < beast::zero ||
dropsAdded.xrp() + view().info().drops < view().info().drops)
{
JLOG(ctx_.journal.warn()) << "GenesisMint: dropsAdded overflowed\n";
return tecINTERNAL;
}
// action loop
for (auto const& [id, values] : mints)
{
auto const& [amt, flags, marks] = values;
auto const k = keylet::account(id);
auto sle = view().peek(k);
bool const created = !sle;
if (created)
@@ -202,34 +355,21 @@ GenesisMint::doApply()
sle->setFieldU32(sfSequence, seqno);
if (amt)
{
sle->setFieldAmount(sfBalance, *amt);
dropsAdded += *amt;
}
else // give them 2 XRP if the account didn't exist, same as
// ttIMPORT
{
XRPAmount const initialXrp =
Import::computeStartingBonus(ctx_.view());
sle->setFieldAmount(sfBalance, initialXrp);
dropsAdded += initialXrp;
}
sle->setFieldAmount(sfBalance, amt);
}
else if (amt)
else if (amt > beast::zero)
{
// Credit the account
STAmount startBal = sle->getFieldAmount(sfBalance);
STAmount finalBal = startBal + *amt;
STAmount finalBal = startBal + amt;
if (finalBal <= startBal)
{
JLOG(ctx_.journal.warn()) << "GenesisMint: cannot credit "
<< dest << " due to balance overflow";
JLOG(ctx_.journal.warn()) << "GenesisMint: cannot credit " << id
<< " due to balance overflow";
return tecINTERNAL;
}
sle->setFieldAmount(sfBalance, finalBal);
dropsAdded += *amt;
}
// set flags and marks as applicable
@@ -245,14 +385,6 @@ GenesisMint::doApply()
view().update(sle);
}
// update ledger header
if (dropsAdded < beast::zero ||
dropsAdded.xrp() + view().info().drops < view().info().drops)
{
JLOG(ctx_.journal.warn()) << "GenesisMint: dropsAdded overflowed\n";
return tecINTERNAL;
}
if (dropsAdded > beast::zero)
ctx_.rawView().rawDestroyXRP(-dropsAdded.xrp());
+11 -2
View File
@@ -710,7 +710,6 @@ Import::preflight(PreflightContext const& ctx)
{
auto const& data = (*xpop)[jss::validation][jss::data];
std::set<std::string> used_key;
for (const auto& key : data.getMemberNames())
{
auto nodepub = key;
@@ -818,7 +817,17 @@ Import::preflight(PreflightContext const& ctx)
<< " validation count: " << validationCount;
// check if the validation count is adequate
if (quorum >= validationCount)
auto hasInsufficientQuorum = [&ctx](int quorum, int validationCount) {
if (ctx.rules.enabled(fixXahauV1))
{
return quorum > validationCount;
}
else
{
return quorum >= validationCount;
}
};
if (hasInsufficientQuorum(quorum, validationCount))
{
JLOG(ctx.j.warn()) << "Import: xpop did not contain an 80% quorum for "
"the txn it purports to prove. "
+4
View File
@@ -1856,6 +1856,10 @@ SetHook::setHook()
else if (oldHookSLE && !newHooksEmpty)
{
// UPDATE ltHOOK
if (view().rules().enabled(fixXahauV1))
{
(*newHookSLE)[sfOwnerNode] = (*oldHookSLE)[sfOwnerNode];
}
view().erase(oldHookSLE);
view().insert(newHookSLE);
}
+7 -1
View File
@@ -130,6 +130,8 @@ ApplyStateTable::generateTxMeta(
if (!hookEmission.empty())
meta.setHookEmissions(STArray{hookEmission, sfHookEmissions});
bool const recordDefaultAmounts = to.rules().enabled(fixXahauV1);
Mods newMod;
for (auto& item : items_)
{
@@ -232,8 +234,12 @@ ApplyStateTable::generateTxMeta(
STObject news(sfNewFields);
for (auto const& obj : *curNode)
{
bool const shouldRecord =
(obj.getSType() == STI_AMOUNT && recordDefaultAmounts) ||
!obj.isDefault();
// save non-default values
if (!obj.isDefault() &&
if (shouldRecord &&
obj.getFName().shouldMeta(
SField::sMD_Create | SField::sMD_Always))
news.emplace_back(obj);
+2 -1
View File
@@ -74,7 +74,7 @@ namespace detail {
// Feature.cpp. Because it's only used to reserve storage, and determine how
// large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than
// the actual number of amendments. A LogicError on startup will verify this.
static constexpr std::size_t numFeatures = 66;
static constexpr std::size_t numFeatures = 67;
/** Amendments that this server supports and the default voting behavior.
Whether they are enabled depends on the Rules defined in the validated
@@ -354,6 +354,7 @@ extern uint256 const featureImport;
extern uint256 const featureXahauGenesis;
extern uint256 const featureHooksUpdate1;
extern uint256 const fixURITokenV1;
extern uint256 const fixXahauV1;
} // namespace ripple
+1
View File
@@ -460,6 +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_FIX (fixXahauV1, Supported::yes, VoteBehavior::DefaultNo);
// The following amendments are obsolete, but must remain supported
+3 -3
View File
@@ -88,7 +88,7 @@ TxFormats::TxFormats()
add(jss::OfferCancel,
ttOFFER_CANCEL,
{
{sfOfferSequence, soeREQUIRED},
{sfOfferSequence, soeOPTIONAL},
{sfOfferID, soeOPTIONAL}, // keylet as alternative to offerseq
{sfTicketSequence, soeOPTIONAL},
},
@@ -133,7 +133,7 @@ TxFormats::TxFormats()
ttESCROW_FINISH,
{
{sfOwner, soeREQUIRED},
{sfOfferSequence, soeREQUIRED},
{sfOfferSequence, soeOPTIONAL},
{sfEscrowID, soeOPTIONAL}, // keylet as alternative to offerseq
{sfFulfillment, soeOPTIONAL},
{sfCondition, soeOPTIONAL},
@@ -145,7 +145,7 @@ TxFormats::TxFormats()
ttESCROW_CANCEL,
{
{sfOwner, soeREQUIRED},
{sfOfferSequence, soeREQUIRED},
{sfOfferSequence, soeOPTIONAL},
{sfEscrowID, soeOPTIONAL}, // keylet as alternative to offerseq
{sfTicketSequence, soeOPTIONAL},
},
+191 -103
View File
@@ -4256,128 +4256,216 @@ struct Escrow_test : public beast::unit_test::suite
auto const gw = Account{"gateway"};
auto const USD = gw["USD"];
Env env{*this, features};
env.fund(XRP(10000), alice, bob, gw);
env.close();
env.trust(USD(1000000), alice);
env.trust(USD(1000000), bob);
env.close();
env(pay(gw, alice, USD(10000)));
env(pay(gw, bob, USD(10000)));
env.close();
// EscrowCancel - EscrowID
for (bool const withXahauV1 : {true, false})
{
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
env(escrow::create(alice, bob, USD(1000)),
escrow::finish_time(env.now() + 1s),
escrow::cancel_time(env.now() + 2s),
fee(1500));
auto const amend = withXahauV1 ? features : features - fixXahauV1;
Env env{*this, amend};
env.fund(XRP(10000), alice, bob, gw);
env.close();
env.trust(USD(1000000), alice);
env.trust(USD(1000000), bob);
env.close();
env(pay(gw, alice, USD(10000)));
env(pay(gw, bob, USD(10000)));
env.close();
env(escrow::cancel(bob, alice, 0),
escrow::escrow_id(escrowId),
fee(1500));
env.close();
// EscrowCancel - EscrowID
{
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
env(escrow::create(alice, bob, USD(1000)),
escrow::finish_time(env.now() + 1s),
escrow::cancel_time(env.now() + 2s),
fee(1500));
env.close();
auto const escrowLE = env.le(keylet::unchecked(escrowId));
BEAST_EXPECT(!escrowLE);
}
if (withXahauV1)
{
// withXahauV1 - no OfferSequence
env(escrow::cancel(bob, alice),
escrow::escrow_id(escrowId),
fee(1500),
ter(tesSUCCESS));
env.close();
}
else
{
// !withXahauV1 - OfferSequence == 0
env(escrow::cancel(bob, alice, 0),
escrow::escrow_id(escrowId),
fee(1500),
ter(tesSUCCESS));
env.close();
}
// EscrowCancel - no EscrowID or OfferSequence
{
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
env(escrow::create(alice, bob, USD(1000)),
escrow::finish_time(env.now() + 1s),
escrow::cancel_time(env.now() + 2s),
fee(1500));
env.close();
auto const escrowLE = env.le(keylet::unchecked(escrowId));
BEAST_EXPECT(!escrowLE);
}
Json::Value jv;
jv[jss::TransactionType] = jss::EscrowCancel;
jv[jss::Flags] = tfUniversal;
jv[jss::Account] = bob.human();
jv[sfOwner.jsonName] = alice.human();
env(jv, fee(1500), ter(temMALFORMED));
env.close();
// EscrowCancel - no EscrowID or OfferSequence
{
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
env(escrow::create(alice, bob, USD(1000)),
escrow::finish_time(env.now() + 1s),
escrow::cancel_time(env.now() + 2s),
fee(1500));
env.close();
auto const escrowLE = env.le(keylet::unchecked(escrowId));
BEAST_EXPECT(escrowLE);
}
env(escrow::cancel(bob, alice), fee(1500), ter(temMALFORMED));
env.close();
// EscrowCancel - EscrowID & OfferSequence
{
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
env(escrow::create(alice, bob, USD(1000)),
escrow::finish_time(env.now() + 1s),
escrow::cancel_time(env.now() + 2s),
fee(1500));
env.close();
auto const escrowLE = env.le(keylet::unchecked(escrowId));
BEAST_EXPECT(escrowLE);
}
env(escrow::cancel(bob, alice, 0),
escrow::escrow_id(escrowId),
fee(1500),
ter(tesSUCCESS));
env.close();
// EscrowCancel - EscrowID & OfferSequence
{
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
auto const seq = env.seq(alice);
env(escrow::create(alice, bob, USD(1000)),
escrow::finish_time(env.now() + 1s),
escrow::cancel_time(env.now() + 2s),
fee(1500));
env.close();
auto const escrowLE = env.le(keylet::unchecked(escrowId));
BEAST_EXPECT(!escrowLE);
}
env(escrow::cancel(bob, alice, seq),
escrow::escrow_id(escrowId),
fee(1500),
ter(temMALFORMED));
env.close();
// EscrowFinish - EscrowID
{
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
env(escrow::create(alice, bob, USD(1000)),
escrow::finish_time(env.now() + 1s),
fee(1500));
env.close(5s);
auto const escrowLE = env.le(keylet::unchecked(escrowId));
BEAST_EXPECT(escrowLE);
}
env(escrow::finish(bob, alice, 0),
escrow::escrow_id(escrowId),
fee(1500));
env.close();
// EscrowCancel - EscrowID & OfferSequence 0
{
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
env(escrow::create(alice, bob, USD(1000)),
escrow::finish_time(env.now() + 1s),
escrow::cancel_time(env.now() + 2s),
fee(1500));
env.close();
auto const escrowLE = env.le(keylet::unchecked(escrowId));
BEAST_EXPECT(!escrowLE);
}
if (withXahauV1)
{
// withXahauV1 - OfferSequence 0 == temMALFORMED
env(escrow::cancel(bob, alice, 0),
escrow::escrow_id(escrowId),
fee(1500),
ter(temMALFORMED));
env.close();
auto const escrowLE = env.le(keylet::unchecked(escrowId));
BEAST_EXPECT(escrowLE);
}
else
{
// withXahauV1 - OfferSequence 0 == tesSUCCESS
env(escrow::cancel(bob, alice, 0),
escrow::escrow_id(escrowId),
fee(1500),
ter(tesSUCCESS));
env.close();
auto const escrowLE = env.le(keylet::unchecked(escrowId));
BEAST_EXPECT(!escrowLE);
}
}
// EscrowFinish - no EscrowID or OfferSequence
{
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
env(escrow::create(alice, bob, USD(1000)),
escrow::finish_time(env.now() + 1s),
escrow::cancel_time(env.now() + 2s),
fee(1500));
env.close();
// EscrowFinish - EscrowID
{
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
env(escrow::create(alice, bob, USD(1000)),
escrow::finish_time(env.now() + 1s),
fee(1500));
env.close(5s);
Json::Value jv;
jv[jss::TransactionType] = jss::EscrowFinish;
jv[jss::Flags] = tfUniversal;
jv[jss::Account] = bob.human();
jv[sfOwner.jsonName] = alice.human();
env(jv, fee(1500), ter(temMALFORMED));
env.close();
if (withXahauV1)
{
// withXahauV1 - no OfferSequence
env(escrow::finish(bob, alice),
escrow::escrow_id(escrowId),
fee(1500),
ter(tesSUCCESS));
env.close();
}
else
{
// !withXahauV1 - OfferSequence == 0
env(escrow::finish(bob, alice, 0),
escrow::escrow_id(escrowId),
fee(1500),
ter(tesSUCCESS));
env.close();
}
auto const escrowLE = env.le(keylet::unchecked(escrowId));
BEAST_EXPECT(escrowLE);
}
auto const escrowLE = env.le(keylet::unchecked(escrowId));
BEAST_EXPECT(!escrowLE);
}
// EscrowFinish- EscrowID & OfferSequence
{
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
env(escrow::create(alice, bob, USD(1000)),
escrow::finish_time(env.now() + 1s),
fee(1500));
env.close(5s);
// EscrowFinish - no EscrowID or OfferSequence
{
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
env(escrow::create(alice, bob, USD(1000)),
escrow::finish_time(env.now() + 1s),
fee(1500));
env.close();
env(escrow::finish(bob, alice, 0),
escrow::escrow_id(escrowId),
fee(1500),
ter(tesSUCCESS));
env.close();
env(escrow::finish(bob, alice), fee(1500), ter(temMALFORMED));
env.close();
auto const escrowLE = env.le(keylet::unchecked(escrowId));
BEAST_EXPECT(!escrowLE);
auto const escrowLE = env.le(keylet::unchecked(escrowId));
BEAST_EXPECT(escrowLE);
}
// EscrowFinish- EscrowID & OfferSequence
{
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
auto const seq = env.seq(alice);
env(escrow::create(alice, bob, USD(1000)),
escrow::finish_time(env.now() + 1s),
fee(1500));
env.close(5s);
env(escrow::finish(bob, alice, seq),
escrow::escrow_id(escrowId),
fee(1500),
ter(temMALFORMED));
env.close();
auto const escrowLE = env.le(keylet::unchecked(escrowId));
BEAST_EXPECT(escrowLE);
}
// EscrowFinish- EscrowID & OfferSequence 0
{
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
env(escrow::create(alice, bob, USD(1000)),
escrow::finish_time(env.now() + 1s),
fee(1500));
env.close(5s);
if (withXahauV1)
{
// withXahauV1 - OfferSequence 0 == temMALFORMED
env(escrow::finish(bob, alice, 0),
escrow::escrow_id(escrowId),
fee(1500),
ter(temMALFORMED));
env.close();
auto const escrowLE = env.le(keylet::unchecked(escrowId));
BEAST_EXPECT(escrowLE);
}
else
{
// !withXahauV1 - OfferSequence 0 == tesSUCCESS
env(escrow::finish(bob, alice, 0),
escrow::escrow_id(escrowId),
fee(1500),
ter(tesSUCCESS));
env.close();
auto const escrowLE = env.le(keylet::unchecked(escrowId));
BEAST_EXPECT(!escrowLE);
}
}
}
}
+79 -50
View File
@@ -24,6 +24,34 @@ namespace ripple {
namespace test {
struct GenesisMint_test : public beast::unit_test::suite
{
void
validateEmittedTxn(jtx::Env& env, std::string result, uint64_t lineno)
{
// get the emitted txn id
Json::Value params;
params[jss::transaction] =
env.tx()->getJson(JsonOptions::none)[jss::hash];
auto const jrr = env.rpc("json", "tx", to_string(params));
auto const meta = jrr[jss::result][jss::meta];
auto const emissions = meta[sfHookEmissions.jsonName];
auto const emission = emissions[0u][sfHookEmission.jsonName];
auto const txId = emission[sfEmittedTxnID.jsonName];
env.close();
// verify emitted result
Json::Value params1;
params1[jss::transaction] = txId;
auto const jrr1 = env.rpc("json", "tx", to_string(params1));
auto const meta1 = jrr1[jss::result][jss::meta];
BEAST_EXPECT(meta1[sfTransactionResult.jsonName] == result);
if (meta1[sfTransactionResult.jsonName] != result)
{
std::cout << "validateEmittedTxn failed " << lineno
<< " expected: " << result
<< " result: " << meta1[sfTransactionResult.jsonName]
<< "\n";
}
}
void
testDisabled(FeatureBitset features)
{
@@ -151,7 +179,9 @@ struct GenesisMint_test : public beast::unit_test::suite
ter(tesSUCCESS));
env.close();
env.close();
// validate emitted txn
validateEmittedTxn(env, "tesSUCCESS", __LINE__);
{
auto acc = env.le(keylet::account(bob.id()));
@@ -190,7 +220,10 @@ struct GenesisMint_test : public beast::unit_test::suite
ter(tesSUCCESS));
env.close();
env.close();
// validate emitted txn
validateEmittedTxn(env, "tesSUCCESS", __LINE__);
env.close();
{
@@ -229,7 +262,9 @@ struct GenesisMint_test : public beast::unit_test::suite
fee(XRP(1)));
env.close();
env.close();
// validate emitted txn
validateEmittedTxn(env, "tesSUCCESS", __LINE__);
for (auto const& [acc, amt, _, __] : mints)
{
@@ -242,7 +277,9 @@ struct GenesisMint_test : public beast::unit_test::suite
fee(XRP(1)));
env.close();
env.close();
// validate emitted txn
validateEmittedTxn(env, "tesSUCCESS", __LINE__);
for (auto const& [acc, amt, _, __] : mints)
{
@@ -295,23 +332,11 @@ struct GenesisMint_test : public beast::unit_test::suite
env.close();
// get the emitted txn id
Json::Value params;
params[jss::transaction] =
env.tx()->getJson(JsonOptions::none)[jss::hash];
auto const jrr = env.rpc("json", "tx", to_string(params));
auto const meta = jrr[jss::result][jss::meta];
auto const emissions = meta[sfHookEmissions.jsonName];
auto const emission = emissions[0u][sfHookEmission.jsonName];
auto const txId = emission[sfEmittedTxnID.jsonName];
env.close();
// verify emitted result
Json::Value params1;
params1[jss::transaction] = txId;
auto const jrr1 = env.rpc("json", "tx", to_string(params1));
auto const meta1 = jrr1[jss::result][jss::meta];
BEAST_EXPECT(meta1[sfTransactionResult.jsonName] == "tecINTERNAL");
// validate emitted txn
auto const txResult = env.current()->rules().enabled(fixXahauV1)
? "tesSUCCESS"
: "tecINTERNAL";
validateEmittedTxn(env, txResult, __LINE__);
}
// missing an amount
@@ -354,7 +379,9 @@ struct GenesisMint_test : public beast::unit_test::suite
fee(XRP(1)));
env.close();
env.close();
// validate emitted txn
validateEmittedTxn(env, "tesSUCCESS", __LINE__);
}
// check that alice has the right balance, and Governance Flags set
@@ -378,7 +405,9 @@ struct GenesisMint_test : public beast::unit_test::suite
fee(XRP(1)));
env.close();
env.close();
// validate emitted txn
validateEmittedTxn(env, "tesSUCCESS", __LINE__);
}
// check that bob has the right balance, and Governance Marks set
@@ -403,7 +432,9 @@ struct GenesisMint_test : public beast::unit_test::suite
fee(XRP(1)));
env.close();
env.close();
// validate emitted txn
validateEmittedTxn(env, "tesSUCCESS", __LINE__);
}
// check
@@ -430,9 +461,12 @@ struct GenesisMint_test : public beast::unit_test::suite
XRP(0).value(),
std::nullopt,
std::nullopt}})),
fee(XRP(1)));
env.close();
fee(XRP(1)),
ter(tesSUCCESS));
env.close();
// validate emitted txn
validateEmittedTxn(env, "tesSUCCESS", __LINE__);
}
// check
@@ -500,7 +534,9 @@ struct GenesisMint_test : public beast::unit_test::suite
std::nullopt}})),
fee(XRP(1)));
env.close();
env.close();
// validate emitted txn
validateEmittedTxn(env, "tesSUCCESS", __LINE__);
}
// check
@@ -525,11 +561,19 @@ struct GenesisMint_test : public beast::unit_test::suite
fee(XRP(1)),
ter(tesSUCCESS));
env.close();
env.close();
// validate emitted txn
validateEmittedTxn(env, "tesSUCCESS", __LINE__);
}
auto const amtResult = env.current()->rules().enabled(fixXahauV1)
? 30000000ULL
: 10000000ULL;
// try to include the same destination twice
{
auto const txResult = env.current()->rules().enabled(fixXahauV1)
? ter(tesSUCCESS)
: ter(tecHOOK_REJECTED);
env(invoke::invoke(
invoker,
env.master,
@@ -544,7 +588,7 @@ struct GenesisMint_test : public beast::unit_test::suite
std::nullopt},
})),
fee(XRP(1)),
ter(tecHOOK_REJECTED));
txResult);
env.close();
env.close();
}
@@ -554,7 +598,7 @@ struct GenesisMint_test : public beast::unit_test::suite
auto const le = env.le(keylet::account(greg.id()));
BEAST_EXPECT(
!!le &&
le->getFieldAmount(sfBalance).xrp().drops() == 10000000ULL);
le->getFieldAmount(sfBalance).xrp().drops() == amtResult);
}
// trip the supply cap invariant
@@ -572,30 +616,14 @@ struct GenesisMint_test : public beast::unit_test::suite
fee(XRP(1)));
env.close();
// get the emitted txn id
Json::Value params;
params[jss::transaction] =
env.tx()->getJson(JsonOptions::none)[jss::hash];
auto const jrr = env.rpc("json", "tx", to_string(params));
auto const meta = jrr[jss::result][jss::meta];
auto const emissions = meta[sfHookEmissions.jsonName];
auto const emission = emissions[0u][sfHookEmission.jsonName];
auto const txId = emission[sfEmittedTxnID.jsonName];
env.close();
// verify emitted result
Json::Value params1;
params1[jss::transaction] = txId;
auto const jrr1 = env.rpc("json", "tx", to_string(params1));
auto const meta1 = jrr1[jss::result][jss::meta];
BEAST_EXPECT(
meta1[sfTransactionResult.jsonName] == "tecINVARIANT_FAILED");
// validate emitted txn
validateEmittedTxn(env, "tecINVARIANT_FAILED", __LINE__);
// check balance wasn't changed
auto const le = env.le(keylet::account(greg.id()));
BEAST_EXPECT(
!!le &&
le->getFieldAmount(sfBalance).xrp().drops() == 10000000ULL);
le->getFieldAmount(sfBalance).xrp().drops() == amtResult);
auto const postCoins = env.current()->info().drops;
BEAST_EXPECT(
@@ -664,10 +692,11 @@ public:
using namespace test::jtx;
auto const sa = supported_amendments();
testWithFeats(sa);
testWithFeats(sa - fixXahauV1);
}
};
BEAST_DEFINE_TESTSUITE(GenesisMint, app, ripple);
} // namespace test
} // namespace ripple
} // namespace ripple
+51 -59
View File
@@ -105,93 +105,85 @@ inline std::string ImportTCAccountSet::max = R"json({
})json";
inline std::string ImportTCAccountSet::w_seed = R"json({
"ledger": {
"acroot": "8D612AAAEA2C875978527851BB0727CE64B21D792E91B791FEC769C01410B405",
"close": 743212280,
"coins": "99999998999999832",
"acroot": "3975FE3ACF65A9BCC64F07F49B5AEC93E9176CB8DDFE7B5AEDCDC191EA3A8B8C",
"close": 754758421,
"coins": "99999998000000000",
"cres": 10,
"flags": 0,
"index": 21,
"pclose": 743212272,
"phash": "B94C4657B7529C72DDCE970A87E2EB9EE8EE12580ADFE6CF93B8672E4B289BBC",
"txroot": "0B7479863581D57CB97867293AA2DD17C2DA4005FC139082925AA5806D956389"
"index": 149,
"pclose": 754758420,
"phash": "8F84F7AB19B38FA4377B34E1234EA613A2A5FE522AF08F0B5E0B957C726A6B15",
"txroot": "7B4454B7542F049427CE7A5148BDC385E741F2239C19E71E1CC7E4231A383589"
},
"transaction": {
"blob": "12000322000000002400000014201B00000027201D0000535968400000003B9ACA0073210388935426E0D08083314842EDFBB2D517BD47699F9A4527318A8E10468C97C0527446304402205A21EE95C4EBAAC3BB6E438B86BE0FFF864D7EDAB0827F04621B3C5EB0F84B2002206A025D68D8112C87913F228ED47C7CDFE79E217186056876E3FE60C8D0D7BB5E8114AE123A8556F3CF91154711376AFB0F894F832B3D",
"meta": "201C00000002F8E51100612500000014550664762F06918B9DDF5D8933481466D394E8D137DE112C9EFC74AE00F5B1BD0D5692FA6A9FC8EA6018D5D16532D7795C91BFB0831355BDFDA177E86C8BF997985FE624000000146240000002540BE400E1E7220000000024000000152D00000000624000000218711A008114AE123A8556F3CF91154711376AFB0F894F832B3DE1E1F1031000",
"blob": "1200032200000000240000007D201B000000A7201D0000535968400000003B9ACA0073210388935426E0D08083314842EDFBB2D517BD47699F9A4527318A8E10468C97C05274473045022100BED5EC411AA84FF23B26D60673C1B42901E253C72DFD92B326DF11B94FAB653D022072B734E038F30D4693A392B1609DEFCE57508F6BB5A3FBD721CC55DDDE929BCD8114AE123A8556F3CF91154711376AFB0F894F832B3D",
"meta": "201C00000003F8E5110061250000009555248470440C362FE1DA9C45D43C68583632588F8D5FAACC0038A60F1C292109875692FA6A9FC8EA6018D5D16532D7795C91BFB0831355BDFDA177E86C8BF997985FE6240000007D624000000218711A00E1E72200000000240000007E2D000000016240000001DCD650008114AE123A8556F3CF91154711376AFB0F894F832B3DE1E1F1031000",
"proof": {
"children": {
"0": {
"children": {},
"hash": "509C0CFF6C808D0A97271FC67C694D95BF177AF4C47D86CD1182516BDDEB6025",
"key": "005D3A84B6EF5A5BF673019F4C583E812F2429D2E6524119498803BD4D82B616"
"children": {
"2": {
"children": {},
"hash": "B19EFD49679890EA362F3938AFFC6FCC3D4B57AAE0054F42237E51ADEFC00E2A",
"key": "02418867190CC2BBA46600E334985E6915C861DBB69E030D2939CFE1FC94D4CC"
},
"F": {
"children": {},
"hash": "48E8A4BAE146645DA9FB45AA126676BF94FA767DBE2FEB63D570175C5C6EABF6",
"key": "0F84DB191B9B813AC94EC51EA58B69EB5B5C606A69075A64068F911246DFEFD5"
}
},
"hash": "B9AC3B86E69AFE74C4AE62E2E2A82BBBC4578BE84C093A9E5ECF15FD6C85164D",
"key": "0000000000000000000000000000000000000000000000000000000000000000"
},
"1": {
"children": {},
"hash": "7049738FBCD2A02C6424D2D0AED1E686525E72CBEE7E1E46665153D547152BEC",
"key": "1B0C515B4719732CA8E192DCE7C853EC9FDCB555044C7DBF6304842D1131B3F8"
"hash": "8521D6C26199F9F6D54E9522385C44C52E2E3EF48560AE64D3D089BC268A1311",
"key": "1F7D6AC7280BA77EC0676E08111CA9FC552C48DA16662B49A197C2996A594574"
},
"4": {
"2": {
"children": {},
"hash": "DF2BD3EE2061F7B6BA108AFD2E0879BBC7A416F004EC31721B1E35327AC84CC0",
"key": "4636DAF80989AD827DB21924CCA58DA67188569631135A5E04E173B7142E2B4F"
"hash": "01404B9BC507C42FC466B3BCC056B544DFEEB51E5C3030F90CC63D092DA9EDFF",
"key": "248470440C362FE1DA9C45D43C68583632588F8D5FAACC0038A60F1C29210987"
},
"5": {
"children": {},
"hash": "E000CC0736630D66DAB573A2642E1BD0646DFB13A871B2CCFA6E4226F477D88C",
"key": "52C1D70065FC14B118011B14B3936FF71D97F9F1D21891BBBD2293B27AC6F8BF"
"hash": "8A05CC08E8238765E4B94C14E09C2AD218B81F7FDD0415D6FE7F57B9B626899B",
"key": "5BC558F8699A824BE7C09A56E1D0A0FACA6F44F57AB7BC0306C8F1C4870FCE1E"
},
"7": {
"children": {
"1": {
"children": {},
"hash": "4156BB37D4BBA887A646879B3585065B95157FA15A0A68DDCA87CC90B6A8E370",
"key": "71C688E6B6B99744C01D908E0C75D12D91090BB109E9E39AC08150A973A90810"
},
"2": {
"children": {},
"hash": "EA93162C432C54B8CEDA63184FA9F0ABC76C5CB547671AC4069A8EEF7CE1DD1A",
"key": "72092AC8B4A50C6E191650344AFC93FE6EDCD00AD4B7D929A524DCCA7DA06074"
}
},
"hash": "191973AFD8C1667F39C050554AC62D67311CD7DE1E406EE7DF8EB0407930D891",
"key": "7000000000000000000000000000000000000000000000000000000000000000"
},
"B": {
"children": {
"9": {
"children": {},
"hash": "5109702FDCFFAF311C0AD8E7D4BECE7FA350395A2EAF522A5A7D04337B136B31",
"key": "B9448E0093010B019347E2D0BF9C2EB2B8F529877DABFAF7DF4672CFFEC642D8"
},
"C": {
"children": {},
"hash": "E80D50B1E6475C8588059C966BBEF247A15D7C8DE0674F0D7122316F9D424BFC",
"key": "BCBB6F4F04F0B108630E5CB04E6C9927D2F2621FD83F5548F12D4E44233AC2AA"
}
},
"hash": "01664A1CB97086A0E222BB2FEEE8CD5222E9535064C366CF3DF76A80A17BD7B2",
"key": "B000000000000000000000000000000000000000000000000000000000000000"
},
"F": {
"C": {
"children": {},
"hash": "8C26E194C7B44E108EFF98082D19BB2B739C5F8E6BB0C281D9ECEE11F98C3770",
"key": "F4DCF93061C61D249A16934AF83C3EFCE51AD98469AFC65B260C2100683CB761"
"hash": "652BC7C2C0F3FBA842AD24BB92C9DBFC2ABBB47D11E5255BD10A5BF5A2F12DEF",
"key": "C041798921B80DB9F9B017A27514BE6942ED00CDE078E7E7E1C9F011E0DD9B3D"
},
"D": {
"children": {},
"hash": "D4772ADC693366E7ADC55437496A80E0073556CCC6F34F28A3CA22F27B8FC804",
"key": "D0F5762BD1D415558E1C825A0933E7A0B2AC648AB719CE224E25E6A1EAEFB51D"
}
},
"hash": "0B7479863581D57CB97867293AA2DD17C2DA4005FC139082925AA5806D956389",
"hash": "7B4454B7542F049427CE7A5148BDC385E741F2239C19E71E1CC7E4231A383589",
"key": "0000000000000000000000000000000000000000000000000000000000000000"
}
},
"validation": {
"data": {
"n94QWAYxKUHacmyFTnzK4bvqVcUfr6RwtaNxCM2cJRY59UHmz1Fr": "22800000012600000015292C4C84F53A29EC0A36EDAB6C61510AD4F33846A123D86FDAD30CBF175E217BA7B5394A5A761DA5C6B7A45D678DA8501720806024F4F741C65B2E44B005CA293120889A6BC5F1E179335E20384AB3C6D15019B94C4657B7529C72DDCE970A87E2EB9EE8EE12580ADFE6CF93B8672E4B289BBC732103FCA947A7F08B146457BEF95AF0CF7C3ABF0D09CD1DC02099F7185C37BB32807576473045022100BBBE6EDE0B2B61CD369E2188C8FBFACCB35CA2D166FD29D5E3D7B2195083E74302201FAA160136301A43E518B9424A0DA5DC1E7EF8B90DAE2FA87310047498514EB4",
"n9KqAeJTJEJaMZNN35SNrPDbs324rwjDPy6BFHjZ4oM4en4snKjf": "22800000012600000015292C4C84F53A434EDA585468FD92510AD4F33846A123D86FDAD30CBF175E217BA7B5394A5A761DA5C6B7A45D678DA8501720806024F4F741C65B2E44B005CA293120889A6BC5F1E179335E20384AB3C6D15019B94C4657B7529C72DDCE970A87E2EB9EE8EE12580ADFE6CF93B8672E4B289BBC732102AB4E3B7C53A4265C51952DD9D2CD2829219CDD4F55F63969E38C5C910F3C5F1C76473045022100A011F2C1F61E78CEF76007BB72CE076255027793052DCF7027B10C0C30BA07AC02202380E5FF8330592C594784C4752736D7177D121763D91795C3AD3FEB7AF3A0B5"
"n94J5LCRu9bBWrJiwRWujvuVECVPWbXFcQ2VN38qLD378F5pDSDM": "22800000012600000095292CFCB3163A74499C839A2536F051CAD3AEA30BDAC743D24F91EEF18A03DFB7EDA477194429C9132656ACF85867FA501775530CC549425FB587CBEE269283F976D923CBF68A9AD5C636DE22A96F33FBB350198F84F7AB19B38FA4377B34E1234EA613A2A5FE522AF08F0B5E0B957C726A6B15732103EF7BD71691FE8352670866E018ACD21BE011971500485150AFA1A66DC564667676473045022100CD612D4D66388BC6F5B55C6FC79024419354F7C8F64421E18A2F92F8F12C044E02201E95057C863044378B94BD80D17022CE0FC6FEAF26482FA5C8A02C061BFB0529",
"n94M7rLruFgtroHGaxjG81DYKssEfSk5kWK7aiyzcfYKrVVDfZfk": "22800000012600000095292CFCB3163A22A5D454FFFE67CD51CAD3AEA30BDAC743D24F91EEF18A03DFB7EDA477194429C9132656ACF85867FA501775530CC549425FB587CBEE269283F976D923CBF68A9AD5C636DE22A96F33FBB350198F84F7AB19B38FA4377B34E1234EA613A2A5FE522AF08F0B5E0B957C726A6B15732103F5BC074629DFF09DA827417F891FBDCB97B08BF4D4A17715C46D89BF36C8FC2876473045022100E2F7EB279A55545A2241E44BB440A3489612189D7F352FA7756F1C145D707C530220550206F6C48FB16D0C63CE4899BE5DF91DF330BA1CB47BB6310DAD0FC40A1461",
"n94U45VGVjD4Gp9aZpuAKJHRF5AJXiARzHmmcR38i4cB3zbHKSWZ": "22800000012600000095292CFCB3163AFC08FE942F7912AF51CAD3AEA30BDAC743D24F91EEF18A03DFB7EDA477194429C9132656ACF85867FA501775530CC549425FB587CBEE269283F976D923CBF68A9AD5C636DE22A96F33FBB350198F84F7AB19B38FA4377B34E1234EA613A2A5FE522AF08F0B5E0B957C726A6B15732103E1177E8CC69B97B21B6C25813CC545BEEA49B03FD668F1701EC29C7FB8E609467647304502210089AD42057DF50C0324F1DC0B4935E124C0C58554369AEE7869E603B8C4AF686102205521AB9889240487CE9ADFE14F5844030D4C2A01B23EF49E53F3C4619A02AF15",
"n9KZofcLaHcfujXR4a3E17jKSvyNp8SZHu9rWVjhB9mGFXxB2w4L": "22800000012600000095292CFCB3153ABFAC3C292F2B6FE551CAD3AEA30BDAC743D24F91EEF18A03DFB7EDA477194429C9132656ACF85867FA501775530CC549425FB587CBEE269283F976D923CBF68A9AD5C636DE22A96F33FBB350198F84F7AB19B38FA4377B34E1234EA613A2A5FE522AF08F0B5E0B957C726A6B1573210286A448918DA7F8BF7693552EF9E47AFC57C20D9E090132633D521D32889040C876473045022100D6A52670D463FD1994030A39CEA91E87BA26353B6384FD4218DDA8E3AC8B162E022011295AE9BDDBD41F9489FA3D7E8A36B40D946DC4B39FEA654A77EFEADD112AFA",
"n9KjgPCVkmyJdWAVBCVKERdz5Xt6x7dLA1Y7ZqayZzJqVnnG982j": "22800000012600000095292CFCB3173AE187F1490B28C1FA51CAD3AEA30BDAC743D24F91EEF18A03DFB7EDA477194429C9132656ACF85867FA501775530CC549425FB587CBEE269283F976D923CBF68A9AD5C636DE22A96F33FBB350198F84F7AB19B38FA4377B34E1234EA613A2A5FE522AF08F0B5E0B957C726A6B157321029D15B24621B84804E7360DA1CC35F36B8A2771A316A3B83008F85CB370738C2876473045022100A3ECFC40D87E280D7FEDC184BA6DBBE6E77CF6FF08FCF7AE2D4B4FE8CD98B3BF0220726B6A7E9A6AB574EA82A6B5DF628B23C1193A094AF4CC2F28695FFA49CF22BC",
"n9L7FECrrDnTTLRJHc7rVxyTsKnjLcQXea5uhTFWxKh3TqnSR2Rz": "22800000012600000095292CFCB3163A89DF43A5821D7C4851CAD3AEA30BDAC743D24F91EEF18A03DFB7EDA477194429C9132656ACF85867FA501775530CC549425FB587CBEE269283F976D923CBF68A9AD5C636DE22A96F33FBB350198F84F7AB19B38FA4377B34E1234EA613A2A5FE522AF08F0B5E0B957C726A6B15732102FF077FE93DDC6626CADE5DB5436B03DC4CE81B0387A2B56C078D5E413FE20DC576473045022100EFFD4EC7B5B003BC5CCF07BE6406EE2A218AA46C0FC51902B801EFBE45C8FB0102200FC2D21E57E785076AA6D2D749F5FBAC9B99FDD9259DD7B8F9EB2D058B434807",
"n9LCBA8uJWJ2moif8wbm7SBhuR8uenVgBm7VxKSX4VmzcQH6Mw8j": "22800000012600000095292CFCB3163A6C13F3590B29FCC651CAD3AEA30BDAC743D24F91EEF18A03DFB7EDA477194429C9132656ACF85867FA501775530CC549425FB587CBEE269283F976D923CBF68A9AD5C636DE22A96F33FBB350198F84F7AB19B38FA4377B34E1234EA613A2A5FE522AF08F0B5E0B957C726A6B157321031695B0FB0D95F9C41931F14F49841F44997E76607D1E1D4FC57729F20E6C66397646304402203FAD0DF9CE9AD6A9987229E2AF83E80DACF5D0BFD14CC6ED2036CEAD068BB88002206F50EEA8AF44ACAB94100B0A6061396559D60C1D0E532EEA991F3D6E2B88C4D8",
"n9LtFW3Hda5qBS8TUPP9NnNnxUYXbhiWSLaNsGXJKyEkEUiSLvtU": "22800000012600000095292CFCB3163AD3E2E9C72D6F8A9E51CAD3AEA30BDAC743D24F91EEF18A03DFB7EDA477194429C9132656ACF85867FA501775530CC549425FB587CBEE269283F976D923CBF68A9AD5C636DE22A96F33FBB350198F84F7AB19B38FA4377B34E1234EA613A2A5FE522AF08F0B5E0B957C726A6B157321033588B42423B7F797AF19ADDA19470D7F6C5DC384B26D3DA14D859345ADB496247646304402207A5ABA310A90FD46D7BB164172750AE752FA389C697BFACE75CE2E9F2842DC4402200A42A054462531CE381047C7FD47DB8F8AC83963F1E7F3091251437912F9BD06",
"n9MNStvsHZxrCYnShytCfKMCXBcq8wJjR6fLTS7L2Q8qoFVNioQ8": "22800000012600000095292CFCB3163AA0E5E217DB17866851CAD3AEA30BDAC743D24F91EEF18A03DFB7EDA477194429C9132656ACF85867FA501775530CC549425FB587CBEE269283F976D923CBF68A9AD5C636DE22A96F33FBB350198F84F7AB19B38FA4377B34E1234EA613A2A5FE522AF08F0B5E0B957C726A6B157321036219BE1721FF5F57EBCB1997EA568BA8BED880ED0CF1FD81F25C888EC8E66D67764630440220467BE8918BBF65D201B705D0C83B0DE347ABC7E9353CEA8AEBE9495BC78AEFC602206FFCB1A2BD08B1F3ED18E3A737C83D9AD6CE20E4B90FF61621650E61F2A7A05A",
"n9MTTavZe6EPqqyQ27pJbNWpfHw8ZNspVgznrwx5HWm7cdTjKQie": "22800000012600000095292CFCB3163A3E9A11AB5CFA712751CAD3AEA30BDAC743D24F91EEF18A03DFB7EDA477194429C9132656ACF85867FA501775530CC549425FB587CBEE269283F976D923CBF68A9AD5C636DE22A96F33FBB350198F84F7AB19B38FA4377B34E1234EA613A2A5FE522AF08F0B5E0B957C726A6B157321037FA072E183DC2822AF50EBC43280C9AADF0DFE3BCD57D2CB0DD1179034B07543764730450221008A3D649F423D4B3BB74B4943DD3B4841BA2EF281DC8D1F48C6D3A7E72966344302200C465A03A50F33BD04A9751F4261B96F8058710D18DFC7DCE09850C735FE8B12"
},
"unl": {
"blob": "eyJzZXF1ZW5jZSI6MSwiZXhwaXJhdGlvbiI6NzY3Nzg0NjQ1LCJ2YWxpZGF0b3JzIjpbeyJ2YWxpZGF0aW9uX3B1YmxpY19rZXkiOiJFRDExREMwN0E2REEzRDA3QzAxMkUxOUZGOUFDNjdBQ0U1MzlBMjk1MTQ1QzhEQTM5NjQzN0NBQ0FFQzM2NzA5RjYiLCJtYW5pZmVzdCI6IkpBQUFBQUZ4SWUwUjNBZW0yajBId0JMaG4vbXNaNnpsT2FLVkZGeU5vNVpEZkt5dXcyY0o5bk1oQXF0T08zeFRwQ1pjVVpVdDJkTE5LQ2tobk4xUFZmWTVhZU9NWEpFUFBGOGNka2N3UlFJaEFOYlBEZlZLUzdwSWFqejlOMlFNNEY4Q3hhd1dJcmF0c3QyQjd3ZWR5czFGQWlCR0s3L1YwRDRBMlp2VFJEQlVQb0JLUVhpQk83QUV3ckRiOFlvN2RUSTJTWEFTUU9lQTIrVGdGVlRwNzdlN3VFZGpYNU1BYk8yU0t2dk8zOW1POEFyQkNqemRPUElxVmIrWi9QMDJUTmYzNjUrYjVCOGc3V0xEY0x2VkNpZUxjemw4MHcwPSJ9LHsidmFsaWRhdGlvbl9wdWJsaWNfa2V5IjoiRURBMTY0RjRCMzZDMkQ3MzA0NjJENUY3NjJCRkEyODA4QUE1MDkyQUJDRUNFQkIyNzA4OTUyNUQxRDA1NEJFMzNCIiwibWFuaWZlc3QiOiJKQUFBQUFGeEllMmhaUFN6YkMxekJHTFY5Mksvb29DS3BRa3F2T3pyc25DSlVsMGRCVXZqTzNNaEEveXBSNmZ3aXhSa1Y3NzVXdkRQZkRxL0RRbk5IY0FnbWZjWVhEZTdNb0IxZGtjd1JRSWhBTjRldTFvSGV0bFVDUmZmZ2FaeTkvTWJrQ0prWlFZdlJoMFVJZWFCaVZodkFpQWhGWXZKTXBPeVFpNGxFdzdzM0pTVTBMRm5FckRWVlRnK3NYUVk0c3Zta25BU1FCSGswakIweG1yL1UwbnkyajNrRmVvdEJUVEExVzdXcHBKbXFvYmR4SXkyR0w0QXBRaEpPcm5aRy93bXZqeFJCNXV4Y05FNUdUR1NZekQ3azhhVEx3bz0ifV19",
"blob": "eyJzZXF1ZW5jZSI6MSwiZXhwaXJhdGlvbiI6NzU3MzQ5NjI0LCJ2YWxpZGF0b3JzIjpbeyJ2YWxpZGF0aW9uX3B1YmxpY19rZXkiOiJFREJBOUZGMzEzNjgyMzlGNDIxRjA0QkY2ODEzQkJGNzZDNkJCRDQ1NkMyRTEyNDg2MDY2MjdCOTkwRjA5MTc2ODkiLCJtYW5pZmVzdCI6IkpBQUFBRVJ4SWUyNm4vTVRhQ09mUWg4RXYyZ1R1L2RzYTcxRmJDNFNTR0JtSjdtUThKRjJpWE1oQTMrZ2N1R0QzQ2dpcjFEcnhES0F5YXJmRGY0N3pWZlN5dzNSRjVBMHNIVkRka1l3UkFJZ2J4bVdKWXoxMFI2RVh6OG1RRmxKazlMM1UwZkQxT2UzbDJYK3NVOGFqajBDSUNIb2RoNzRWaDJNL0oxRnlzS2NOUmp1YWpKaCtSN1dqeXVBNHFqWWwvSHVkeDEwWlhOMExYSnBjSEJzWlM1MmJtOWtaVEV1ZEhKaGJuTnBZUzVqYjNBU1FEcDJuUVVTbk9CYlVxMHpCU05wMzRhRktBWnRrWHRaS3BjejBlQitEVG12TXRGN3M0Y240ZW8veHhkZU5UbmxJK1FRVmloZUpLSkZKQlJ3NzZwdXdROD0ifSx7InZhbGlkYXRpb25fcHVibGljX2tleSI6IkVEQkJDRDNGRjdGRkVGOTc3REU0Mzg0QUMzODE0NTMwNEM1MUI5MTJDRUYxMTgwNjBGMzQzMTBGRjgwN0NDRUU3QiIsIm1hbmlmZXN0IjoiSkFBQUFEeHhJZTI3elQvMy8rK1hmZVE0U3NPQlJUQk1VYmtTenZFWUJnODBNUS80Qjh6dWUzTWhBMkladmhjaC8xOVg2OHNabCtwV2k2aSsySUR0RFBIOWdmSmNpSTdJNW0xbmRrY3dSUUloQUtMWnFVYjIyYWg0YmJzL3JtTU1VQjl4MXE3MDY0RzczcW5uSytmaU9UVHZBaUFNdUJ4QVFlcEw4a2owRVVlY3ZIT2dKOEo2TUFUdS9CVnFXVzk1alhRNUMzY2RkR1Z6ZEMxeWFYQndiR1V1ZG01dlpHVXlMblJ5WVc1emFXRXVZMjl3RWtCcXpTMmFmdm9XUGJOcWM2UGRxaGV4ZnNUVDBEdUJZd2ltN0c4Y0xvSzg1bktTTW1TQy9ZaGw1cXRTRzkzNWZnQUlqVm9YZXMrZS9Kd3FFU01xZ2ZZQiJ9LHsidmFsaWRhdGlvbl9wdWJsaWNfa2V5IjoiRUQ2N0ZFMkU1MTc1Njc2NjBCMTU0NUEzQjg0OUU2OThERTg5Q0Y4MTUxODg2MjIwMzAxNzc2OEUxMjJBMTM3N0JDIiwibWFuaWZlc3QiOiJKQUFBQUR4eEllMW4vaTVSZFdkbUN4VkZvN2hKNXBqZWljK0JVWWhpSURBWGRvNFNLaE4zdkhNaEEvVzhCMFlwMy9DZHFDZEJmNGtmdmN1WHNJdjAxS0YzRmNSdGliODJ5UHdvZGtZd1JBSWdZV3NQMVMwYzhvZkFlRWp1WjJ3a2NpM29idStLV0tuOWxqMmVqOEJ1YmxVQ0lHTGl2UmJxazRHUW9VWVk2bC9NS0hpeWkwbzBwbmtpSTFLRDV2UHRYRHRnZHgxMFpYTjBMWEpwY0hCc1pTNTJibTlrWlRNdWRISmhibk5wWVM1amIzQVNRSzdZZUtoYnQwNlc1QnpQODk1V09NT1p2SlJLakhaVGdtN0VqYUtjakNLRTQwWTQwaTczeTUreVFHQWpISFlrRGgydE5UdVExQ09HT1E2b2N1VEFpZ1U9In0seyJ2YWxpZGF0aW9uX3B1YmxpY19rZXkiOiJFRDc1OUJFRTREQzIxQzVBMDYzMEZCODdENDJFQ0MzRDk1OUE3RkI0QkY0RUM3RkYxMkRDOUZDQjFCNkFCNDE5MTUiLCJtYW5pZmVzdCI6IkpBQUFBRHB4SWUxMW0rNU53aHhhQmpEN2g5UXV6RDJWbW4rMHYwN0gveExjbjhzYmFyUVpGWE1oQW9ha1NKR05wL2kvZHBOVkx2bmtldnhYd2cyZUNRRXlZejFTSFRLSWtFRElka1l3UkFJZ0JUTHFROXFna3VrVzFwYTBwQ0xRVld2QnFua09NVkVPK3JQTEVoL2praHdDSUZsQURXb3RLc0JBQ0hyWW1EY3N0dXczYjRkdWV4V3dXY0FCdTdkNmR1T3FkeDEwWlhOMExYSnBjSEJzWlM1MmJtOWtaVFF1ZEhKaGJuTnBZUzVqYjNBU1FBN0UrdHVzMHZ0aDk4eGQwWVdLb2FVOVJTL1VUZVQ1RUcwSURzbW9NVHdrN014VTRGVktTL0dXOTUrMHJ0cG0xV0NYM1VRWThaN1pIZmtGMVlxenZRUT0ifSx7InZhbGlkYXRpb25fcHVibGljX2tleSI6IkVEMTFDQzhEQUZDREUxMTFGRDFCNTM4OUJFRDM1QjcwNkQzMUQwMDRGMjBGQUNERjEwQ0RGMTI2MjhDRkE0NUQ2RSIsIm1hbmlmZXN0IjoiSkFBQUFEcHhJZTBSekkydnplRVIvUnRUaWI3VFczQnRNZEFFOGcrczN4RE44U1lvejZSZGJuTWhBdjhIZitrOTNHWW15dDVkdFVOckE5eE02QnNEaDZLMWJBZU5Ya0UvNGczRmRrWXdSQUlnRS84cTI2R0lSaDlLd1ZvUjFLZVBnS3VGcUlyRjV5NnMrREUvaXNmWHJpSUNJR3hQdHZSUjNLZFI3d2haMzI3VXFpUXFvZnM3d2VVRzQ0dEpiZnhZWndCV2R4MTBaWE4wTFhKcGNIQnNaUzUyYm05a1pUVXVkSEpoYm5OcFlTNWpiM0FTUU5hRFNNSmdpU2kxNzRoazYrdnlBRlhzdUFYZkw0YnRFT3c5OVBSSVJwQVAvUGxRa1ZmMXpNdUJrLzJGNzdqai9BTzFnd09va3hjbEJHRmF5MC9jcGc4PSJ9LHsidmFsaWRhdGlvbl9wdWJsaWNfa2V5IjoiRUQ5MjI5NDFEMjQwMEM4M0I3QjU1QzE4QkZFRDRCNzA5QkI4QzAxRkNGMEE3RTlCRTQ5RTA5RDBCOUIyNTRDRTU1IiwibWFuaWZlc3QiOiJKQUFBQURweEllMlNLVUhTUUF5RHQ3VmNHTC90UzNDYnVNQWZ6d3ArbStTZUNkQzVzbFRPVlhNaEF4YVZzUHNObGZuRUdUSHhUMG1FSDBTWmZuWmdmUjRkVDhWM0tmSU9iR1k1ZGtjd1JRSWhBUDBkOHRSZ0JlczNmKzl5Y2Y4c0FNdEdLcnUxN05neGN2azJpRXJQTks5MUFpQTI5WFM4VzNYYVZ4bS9xS0pmSzc0T1JTVlJFL3MzUmFZbGJhMmZoT3JCTDNjZGRHVnpkQzF5YVhCd2JHVXVkbTV2WkdVMkxuUnlZVzV6YVdFdVkyOXdFa0F6V2RkcGxHMkVKUmo2c1ZabEdxTFN6RWhqWEQ3VXFKV1pNSC9IMUZKaW9sWFVBbk5VOXVidjRsWDE0WVFlWmpJbitLR2gwekoxek5KS2YvbGhsRnNKIn0seyJ2YWxpZGF0aW9uX3B1YmxpY19rZXkiOiJFRDY1OThGNjYzRTNDRjQwRDczREMxOUU4NzUwRUQ2N0I4NjAzNTREQjhGMkEzMTVENUZFN0REMDY1MkY0OUQyMUUiLCJtYW5pZmVzdCI6IkpBQUFBRHB4SWUxbG1QWmo0ODlBMXozQm5vZFE3V2U0WURWTnVQS2pGZFgrZmRCbEwwblNIbk1oQSs5NzF4YVIvb05TWndobTRCaXMwaHZnRVpjVkFFaFJVSytocG0zRlpHWjJka1l3UkFJZ1VKbmg3VTNQV3U5LzVKblFBcFpOdmRZanhFTmZkcHIzcFUwY05NZHJ1eXdDSUVLVG5XSzB4RnM0UWVIMFhFZkNSQTFJdjQrMlJzSFhJZHZyR0I3NlVxSmdkeDEwWlhOMExYSnBjSEJzWlM1MmJtOWtaVGN1ZEhKaGJuTnBZUzVqYjNBU1FOakRvVTRjL2ZkbDJONCtFbnR0TXBQeTV5QlhqRldEbXVYZThrZ0FTaERaZllpVGNYZU5LT3RzU2xzQW5PVGIxbllzZzdFbFZpZTB6bGM5dTdQTWVBRT0ifSx7InZhbGlkYXRpb25fcHVibGljX2tleSI6IkVEOTk5RkZGRUY1MUQ3MTJEN0Q0MjVCQTA4M0U3OUU2MDJGNzhBNDkyMDU0QThDMjM4RjRBOTQ0ODc2OTlFRDkwNyIsIm1hbmlmZXN0IjoiSkFBQUFEcHhJZTJabi8vdlVkY1MxOVFsdWdnK2VlWUM5NHBKSUZTb3dqajBxVVNIYVo3WkIzTWhBeldJdENRanQvZVhyeG10MmhsSERYOXNYY09Fc20wOW9VMkZrMFd0dEpZa2RrY3dSUUloQU9RMFRhbFlVYnE3aml0T2MybXMxQm5wUTdsWHJVc2x2WEJSbS9tRG1UZEVBaUE0KzdPSmhkN0pFSlV2QUhSYjByaG5GSkd3b3ZoWWNQNjBBTG81Zm8xaTJIY2RkR1Z6ZEMxeWFYQndiR1V1ZG01dlpHVTRMblJ5WVc1emFXRXVZMjl3RWtBVzR2TlRPMEU5ZEptRC93eFNmcG9qN0V6bUxMV1pFN1dTeWZuTFk0WTBTVEVLR09jK0FGZ251Ty9IekhhdnQ3aTJBUHJkckZhT3IySm1Zb2lIa1RVSiJ9LHsidmFsaWRhdGlvbl9wdWJsaWNfa2V5IjoiRURGNjVFNTAwNkE1M0FGNzU2MTdFQUUyOUM3MzNDNUYzNDFBOTQxNzNDMjkyM0Y2MjY5Mjk2M0QxQTJGMTQwNzYyIiwibWFuaWZlc3QiOiJKQUFBQURweEllMzJYbEFHcFRyM1ZoZnE0cHh6UEY4MEdwUVhQQ2tqOWlhU2xqMGFMeFFIWW5NaEFwMFZza1lodUVnRTV6WU5vY3cxODJ1S0ozR2Line truncated
"manifest": "JAAAAAFxIe101ANsZZGkvfnFTO+jm5lqXc5fhtEf2hh0SBzp1aHNwXMh7TN9+b62cZqTngaFYU5tbGpYHC8oYuI3G3vwj9OW2Z9gdkAnUjfY5zOEkhq31tU4338jcyUpVA5/VTsANFce7unDo+JeVoEhfuOb/Y8WA3Diu9XzuOD4U/ikfgf9SZOlOGcBcBJAw44PLjH+HUtEnwX45lIRmo0x5aINFMvZsBpE9QteSDBXKwYzLdnSW4e1bs21o+IILJIiIKU/+1Uxx0FRpQbMDA==",
"public_key": "ED74D4036C6591A4BDF9C54CEFA39B996A5DCE5F86D11FDA1874481CE9D5A1CDC1",
"signature": "77809A0938EA47F3A03BA0B71DD2C6879416F06574F6B5DC0041C9D806D3D5971282EF99D9EF911F8F37B56DA7016679744EF042C7A98ED425EFFFC6CC8AEA05",
"signature": "B7619B4AD763E7E59715C8710E1836558F9B0B3F216F30A7922CCE288434AFDA8C12993D716CE750E603F8A6B21EFB53448758F2D90F3FE5DFD1063350F7040C",
"version": 1
}
}
+30 -13
View File
@@ -1682,6 +1682,36 @@ class Import_test : public beast::unit_test::suite
ter(temDISABLED));
}
// temMALFORMED - Import: xpop did not contain an 80% quorum for the txn
// it purports to prove.
for (bool const withXahauV1 : {true, false})
{
auto const amend = withXahauV1 ? features : features - fixXahauV1;
test::jtx::Env env{
*this, network::makeNetworkVLConfig(21337, keys), amend};
auto const alice = Account("alice");
env.fund(XRP(1000), alice);
env.close();
Json::Value tmpXpop = import::loadXpop(ImportTCAccountSet::w_seed);
Json::Value valData = tmpXpop[jss::validation][jss::data];
valData["n9MNStvsHZxrCYnShytCfKMCXBcq8wJjR6fLTS7L2Q8qoFVNioQ8"] =
"";
valData["n9MTTavZe6EPqqyQ27pJbNWpfHw8ZNspVgznrwx5HWm7cdTjKQie"] =
"";
if (withXahauV1)
{
valData
["n94J5LCRu9bBWrJiwRWujvuVECVPWbXFcQ2VN38qLD378F5pDSDM"] =
"";
}
tmpXpop[jss::validation][jss::data] = valData;
auto const txResult =
withXahauV1 ? ter(temMALFORMED) : ter(temMALFORMED);
env(import::import(alice, tmpXpop), txResult);
}
test::jtx::Env env{*this, network::makeNetworkVLConfig(21337, keys)};
auto const feeDrops = env.current()->fees().base;
@@ -2573,19 +2603,6 @@ class Import_test : public beast::unit_test::suite
// parsed
// DA: Catch All
// temMALFORMED - Import: xpop did not contain an 80% quorum for the txn
// it purports to prove.
{
Json::Value tmpXpop = import::loadXpop(ImportTCAccountSet::w_seed);
Json::Value valData;
valData["n94at1vSdHSBEun25yT4ZfgqD1tVQNsx1nqRZG3T6ygbuvwgcMZN"] =
"";
valData["n9KXYzdZD8YpsNiChtMjP6yhvQAhkkh5XeSTbvYyV1waF8wkNnBT"] =
"";
tmpXpop[jss::validation][jss::data] = valData;
env(import::import(alice, tmpXpop), ter(temMALFORMED));
}
// temMALFORMED - Import: xpop inner txn did not contain a sequence
// number or fee No Sequence
{
+129 -68
View File
@@ -5113,10 +5113,11 @@ public:
using namespace jtx;
// OfferCreate - OfferID
// OfferCreate
for (bool const withXahauV1 : {true, false})
{
Env env{*this, features};
auto const amend = withXahauV1 ? features : features - fixXahauV1;
Env env{*this, amend};
auto const gw = Account{"gateway"};
auto const alice = Account{"alice"};
auto const USD = gw["USD"];
@@ -5130,58 +5131,80 @@ public:
env(pay(gw, alice, USD(200)));
env.close();
uint256 const offerId{getOfferIndex(alice, env.seq(alice))};
env(offer(alice, XRP(50), USD(50)));
env.close();
// sequence id - no offer id
{
std::uint32_t const offerSeqId{env.seq(alice)};
env(offer(alice, XRP(50), USD(50)));
env.close();
auto offers = sortedOffersOnAccount(env, alice);
BEAST_EXPECT(offers.size() == 1);
auto tx = offer(alice, XRP(50), USD(50));
tx[sfOfferSequence.jsonName] = offerSeqId;
env(tx, ter(tesSUCCESS));
env.close();
auto const offerLE = env.le(keylet::offer(alice, offerSeqId));
BEAST_EXPECT(!offerLE);
}
auto tx = offer(alice, XRP(50), USD(50));
tx[sfOfferID.jsonName] = to_string(offerId);
env(tx, ter(tesSUCCESS));
env.close();
// no sequence id - offer id
{
uint256 const offerId{getOfferIndex(alice, env.seq(alice))};
env(offer(alice, XRP(50), USD(50)));
env.close();
offers = sortedOffersOnAccount(env, alice);
BEAST_EXPECT(offers.size() == 1);
}
auto tx = offer(alice, XRP(50), USD(50));
env(tx, offer_id(offerId), ter(tesSUCCESS));
env.close();
auto const offerLE = env.le(keylet::unchecked(offerId));
BEAST_EXPECT(!offerLE);
}
// OfferCreate - OfferSequence
{
Env env{*this, features};
auto const gw = Account{"gateway"};
auto const alice = Account{"alice"};
auto const USD = gw["USD"];
// no offer id or offer sequence
{
uint256 const offerId{getOfferIndex(alice, env.seq(alice))};
env(offer(alice, XRP(50), USD(50)));
env.close();
env.fund(XRP(10000), gw, alice);
env.close();
env(offer(alice, XRP(50), USD(50)));
env.close();
auto const offerLE = env.le(keylet::unchecked(offerId));
BEAST_EXPECT(offerLE);
}
env(trust(alice, USD(1000)));
env.close();
// offer id and offer sequence
{
uint256 const offerId{getOfferIndex(alice, env.seq(alice))};
std::uint32_t const offerSeqId{env.seq(alice)};
env(offer(alice, XRP(50), USD(50)));
env.close();
env(pay(gw, alice, USD(200)));
env.close();
auto tx = offer(alice, XRP(50), USD(50));
tx[sfOfferSequence.jsonName] = offerSeqId;
env(tx, offer_id(offerId), ter(temBAD_SEQUENCE));
env.close();
auto const offerLE = env.le(keylet::unchecked(offerId));
BEAST_EXPECT(offerLE);
}
// OfferCreate - OfferID
std::uint32_t const offerSeqId{env.seq(alice)};
env(offer(alice, XRP(50), USD(50)));
env.close();
// offer id and offer sequence 0
{
uint256 const offerId{getOfferIndex(alice, env.seq(alice))};
env(offer(alice, XRP(50), USD(50)));
env.close();
auto offers = sortedOffersOnAccount(env, alice);
BEAST_EXPECT(offers.size() == 1);
auto tx = offer(alice, XRP(50), USD(50));
tx[sfOfferSequence.jsonName] = offerSeqId;
env(tx, ter(tesSUCCESS));
env.close();
offers = sortedOffersOnAccount(env, alice);
BEAST_EXPECT(offers.size() == 1);
auto tx = offer(alice, XRP(50), USD(50));
tx[sfOfferSequence.jsonName] = 0;
env(tx, offer_id(offerId), ter(temBAD_SEQUENCE));
env.close();
auto const offerLE = env.le(keylet::unchecked(offerId));
BEAST_EXPECT(offerLE);
}
}
// OfferCancel
for (bool const withXahauV1 : {true, false})
{
Env env{*this, features};
auto const amend = withXahauV1 ? features : features - fixXahauV1;
Env env{*this, amend};
auto const gw = Account{"gateway"};
auto const alice = Account{"alice"};
auto const USD = gw["USD"];
@@ -5203,36 +5226,46 @@ public:
env(offer_cancel(alice, offerSeqId), ter(tesSUCCESS));
env.close();
auto const offers = sortedOffersOnAccount(env, alice);
BEAST_EXPECT(offers.size() == 0);
auto const offerLE = env.le(keylet::offer(alice, offerSeqId));
BEAST_EXPECT(!offerLE);
}
// offer id (offer sequence required)
// no sequence id - offer id
{
uint256 const offerId{getOfferIndex(alice, env.seq(alice))};
env(offer(alice, XRP(50), USD(50)));
env.close();
env(offer_cancel(alice, 0),
offer_id(offerId),
ter(temBAD_SEQUENCE));
env.close();
auto const offers = sortedOffersOnAccount(env, alice);
BEAST_EXPECT(offers.size() == 1);
if (withXahauV1)
{
env(offer_cancel(alice),
offer_id(offerId),
ter(tesSUCCESS));
env.close();
auto const offerLE = env.le(keylet::unchecked(offerId));
BEAST_EXPECT(!offerLE);
}
else
{
env(offer_cancel(alice),
offer_id(offerId),
ter(temBAD_SEQUENCE));
env.close();
auto const offerLE = env.le(keylet::unchecked(offerId));
BEAST_EXPECT(offerLE);
}
}
// no offer id or offer sequence
{
uint256 const offerId{getOfferIndex(alice, env.seq(alice))};
env(offer(alice, XRP(50), USD(50)));
env.close();
Json::Value jv;
jv[jss::Account] = alice.human();
jv[jss::TransactionType] = jss::OfferCancel;
env(jv, ter(temMALFORMED));
env(offer_cancel(alice), ter(temBAD_SEQUENCE));
env.close();
auto const offers = sortedOffersOnAccount(env, alice);
BEAST_EXPECT(offers.size() == 2);
auto const offerLE = env.le(keylet::unchecked(offerId));
BEAST_EXPECT(offerLE);
}
// both offer id and offer sequence
@@ -5242,12 +5275,39 @@ public:
env(offer(alice, XRP(50), USD(50)));
env.close();
env(offer_cancel(alice, offerSeqId),
offer_id(offerId),
ter(tesSUCCESS));
if (withXahauV1)
{
env(offer_cancel(alice, offerSeqId),
offer_id(offerId),
ter(temBAD_SEQUENCE));
env.close();
auto const offerLE = env.le(keylet::unchecked(offerId));
BEAST_EXPECT(offerLE);
}
else
{
env(offer_cancel(alice, offerSeqId),
offer_id(offerId),
ter(tesSUCCESS));
env.close();
auto const offerLE = env.le(keylet::unchecked(offerId));
BEAST_EXPECT(!offerLE);
}
}
// both offer id and offer sequence 0
{
uint256 const offerId{getOfferIndex(alice, env.seq(alice))};
std::uint32_t const offerSeqId{env.seq(alice)};
env(offer(alice, XRP(50), USD(50)));
env.close();
auto const offers = sortedOffersOnAccount(env, alice);
BEAST_EXPECT(offers.size() == 2);
env(offer_cancel(alice, 0),
offer_id(offerId),
ter(temBAD_SEQUENCE));
env.close();
auto const offerLE = env.le(keylet::unchecked(offerId));
BEAST_EXPECT(offerLE);
}
}
}
@@ -5344,12 +5404,13 @@ public:
FeatureBitset const rmSmallIncreasedQOffers{fixRmSmallIncreasedQOffers};
FeatureBitset const immediateOfferKilled{featureImmediateOfferKilled};
testAll(all - takerDryOffer - immediateOfferKilled);
testAll(all - flowCross - takerDryOffer - immediateOfferKilled);
testAll(all - flowCross - immediateOfferKilled);
testAll(all - rmSmallIncreasedQOffers - immediateOfferKilled);
testAll(all);
testFalseAssert();
// testAll(all - takerDryOffer - immediateOfferKilled);
// testAll(all - flowCross - takerDryOffer - immediateOfferKilled);
// testAll(all - flowCross - immediateOfferKilled);
// testAll(all - rmSmallIncreasedQOffers - immediateOfferKilled);
// testAll(all);
// testFalseAssert();
testOfferID(all);
}
};
+82 -32
View File
@@ -1048,10 +1048,17 @@ private:
env.close();
// cancel escrow
env(escrow::cancel(account, account, 0),
escrow::escrow_id(escrowId),
fee(XRP(1)),
ter(tesSUCCESS));
Json::Value tx;
if (!env.current()->rules().enabled(fixXahauV1))
{
tx = escrow::cancel(account, account, 0);
}
else
{
tx = escrow::cancel(account, account);
}
env(tx, escrow::escrow_id(escrowId), fee(XRP(1)), ter(tesSUCCESS));
env.close();
// verify tsh hook triggered
@@ -1103,10 +1110,16 @@ private:
env.close();
// cancel escrow
env(escrow::cancel(account, account, 0),
escrow::escrow_id(escrowId),
fee(XRP(1)),
ter(tesSUCCESS));
Json::Value tx;
if (!env.current()->rules().enabled(fixXahauV1))
{
tx = escrow::cancel(account, account, 0);
}
else
{
tx = escrow::cancel(account, account);
}
env(tx, escrow::escrow_id(escrowId), fee(XRP(1)), ter(tesSUCCESS));
env.close();
// verify tsh hook triggered
@@ -1156,10 +1169,16 @@ private:
env.close();
// cancel escrow
env(escrow::cancel(dest, account, 0),
escrow::escrow_id(escrowId),
fee(XRP(1)),
ter(tesSUCCESS));
Json::Value tx;
if (!env.current()->rules().enabled(fixXahauV1))
{
tx = escrow::cancel(dest, account, 0);
}
else
{
tx = escrow::cancel(dest, account);
}
env(tx, escrow::escrow_id(escrowId), fee(XRP(1)), ter(tesSUCCESS));
env.close();
// verify tsh hook triggered
@@ -1211,10 +1230,16 @@ private:
env.close();
// cancel escrow
env(escrow::cancel(dest, account, 0),
escrow::escrow_id(escrowId),
fee(XRP(1)),
ter(tesSUCCESS));
Json::Value tx;
if (!env.current()->rules().enabled(fixXahauV1))
{
tx = escrow::cancel(dest, account, 0);
}
else
{
tx = escrow::cancel(dest, account);
}
env(tx, escrow::escrow_id(escrowId), fee(XRP(1)), ter(tesSUCCESS));
env.close();
// verify tsh hook triggered
@@ -1565,10 +1590,16 @@ private:
env.close();
// finish escrow
env(escrow::finish(account, account, 0),
escrow::escrow_id(escrowId),
fee(XRP(1)),
ter(tesSUCCESS));
Json::Value tx;
if (!env.current()->rules().enabled(fixXahauV1))
{
tx = escrow::finish(account, account, 0);
}
else
{
tx = escrow::finish(account, account);
}
env(tx, escrow::escrow_id(escrowId), fee(XRP(1)), ter(tesSUCCESS));
env.close();
// verify tsh hook triggered
@@ -1617,10 +1648,16 @@ private:
env.close();
// finish escrow
env(escrow::finish(account, account, 0),
escrow::escrow_id(escrowId),
fee(XRP(1)),
ter(tesSUCCESS));
Json::Value tx;
if (!env.current()->rules().enabled(fixXahauV1))
{
tx = escrow::finish(account, account, 0);
}
else
{
tx = escrow::finish(account, account);
}
env(tx, escrow::escrow_id(escrowId), fee(XRP(1)), ter(tesSUCCESS));
env.close();
// verify tsh hook triggered
@@ -1663,10 +1700,16 @@ private:
env.close();
// finish escrow
env(escrow::finish(dest, account, 0),
escrow::escrow_id(escrowId),
fee(XRP(1)),
ter(tesSUCCESS));
Json::Value tx;
if (!env.current()->rules().enabled(fixXahauV1))
{
tx = escrow::finish(dest, account, 0);
}
else
{
tx = escrow::finish(dest, account);
}
env(tx, escrow::escrow_id(escrowId), fee(XRP(1)), ter(tesSUCCESS));
env.close();
// verify tsh hook triggered
@@ -1715,10 +1758,16 @@ private:
env.close();
// finish escrow
env(escrow::finish(dest, account, 0),
escrow::escrow_id(escrowId),
fee(XRP(1)),
ter(tesSUCCESS));
Json::Value tx;
if (!env.current()->rules().enabled(fixXahauV1))
{
tx = escrow::finish(dest, account, 0);
}
else
{
tx = escrow::finish(dest, account);
}
env(tx, escrow::escrow_id(escrowId), fee(XRP(1)), ter(tesSUCCESS));
env.close();
// verify tsh hook triggered
@@ -4806,6 +4855,7 @@ public:
using namespace test::jtx;
auto const sa = supported_amendments();
testWithFeats(sa);
testWithFeats(sa - fixXahauV1);
}
};
+197 -5
View File
@@ -89,6 +89,67 @@ public:
// fee unit tests, the rest of the time we want to ignore it.
#define HSFEE fee(100'000'000)
#define M(m) memo(m, "", "")
void
testHooksOwnerDir()
{
testcase("Test owner directory");
using namespace jtx;
for (bool const withXahauV1 : {true, false})
{
auto const amend = withXahauV1
? supported_amendments()
: supported_amendments() - fixXahauV1;
Env env{*this, amend};
auto const alice = Account{"alice"};
auto const gw = Account{"gateway"};
auto const USD = gw["USD"];
env.fund(XRP(10000), alice, gw);
env.close();
env.trust(USD(100000), alice);
env.close();
env(pay(gw, alice, USD(10000)));
for (int i = 1; i < 34; ++i)
{
std::string const uri(i, '?');
env(uritoken::mint(alice, uri));
}
env.close();
env(ripple::test::jtx::hook(
alice, {{hso(accept_wasm, overrideFlag)}}, 0),
HSFEE,
ter(tesSUCCESS));
env.close();
env(ripple::test::jtx::hook(
alice, {{hso(accept_wasm, overrideFlag)}}, 0),
HSFEE,
ter(tesSUCCESS));
env.close();
// delete hook
Json::Value jv;
jv[jss::Account] = alice.human();
jv[jss::TransactionType] = jss::SetHook;
jv[jss::Flags] = 0;
jv[jss::Hooks] = Json::Value{Json::arrayValue};
Json::Value iv;
iv[jss::Flags] = 1;
iv[jss::CreateCode] = "";
jv[jss::Hooks][0U][jss::Hook] = iv;
auto const txResult =
withXahauV1 ? ter(tesSUCCESS) : ter(tefBAD_LEDGER);
env(jv, HSFEE, txResult);
env.close();
}
}
void
testHooksDisabled()
{
@@ -100,7 +161,8 @@ public:
// RH TODO: does it matter that passing malformed txn here gives back
// temMALFORMED (and not disabled)?
env(ripple::test::jtx::hook(alice, {{hso(accept_wasm)}}, 0),
env(ripple::test::jtx::hook(
alice, {{hso(accept_wasm, overrideFlag)}}, 0),
M("Hooks Disabled"),
HSFEE,
ter(temDISABLED));
@@ -7764,6 +7826,134 @@ public:
}
}
void
test_state_foreign_set_max()
{
testcase("Test state_foreign_set max");
using namespace jtx;
static const std::vector<uint8_t> ns_maxHook = {
0x00U, 0x61U, 0x73U, 0x6dU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U,
0x36U, 0x07U, 0x60U, 0x02U, 0x7fU, 0x7fU, 0x01U, 0x7fU, 0x60U,
0x02U, 0x7fU, 0x7fU, 0x01U, 0x7eU, 0x60U, 0x03U, 0x7fU, 0x7fU,
0x7eU, 0x01U, 0x7eU, 0x60U, 0x04U, 0x7fU, 0x7fU, 0x7fU, 0x7fU,
0x01U, 0x7eU, 0x60U, 0x05U, 0x7fU, 0x7fU, 0x7fU, 0x7fU, 0x7fU,
0x01U, 0x7eU, 0x60U, 0x08U, 0x7fU, 0x7fU, 0x7fU, 0x7fU, 0x7fU,
0x7fU, 0x7fU, 0x7fU, 0x01U, 0x7eU, 0x60U, 0x01U, 0x7fU, 0x01U,
0x7eU, 0x02U, 0x79U, 0x08U, 0x03U, 0x65U, 0x6eU, 0x76U, 0x02U,
0x5fU, 0x67U, 0x00U, 0x00U, 0x03U, 0x65U, 0x6eU, 0x76U, 0x0cU,
0x68U, 0x6fU, 0x6fU, 0x6bU, 0x5fU, 0x61U, 0x63U, 0x63U, 0x6fU,
0x75U, 0x6eU, 0x74U, 0x00U, 0x01U, 0x03U, 0x65U, 0x6eU, 0x76U,
0x08U, 0x72U, 0x6fU, 0x6cU, 0x6cU, 0x62U, 0x61U, 0x63U, 0x6bU,
0x00U, 0x02U, 0x03U, 0x65U, 0x6eU, 0x76U, 0x05U, 0x73U, 0x74U,
0x61U, 0x74U, 0x65U, 0x00U, 0x03U, 0x03U, 0x65U, 0x6eU, 0x76U,
0x05U, 0x74U, 0x72U, 0x61U, 0x63U, 0x65U, 0x00U, 0x04U, 0x03U,
0x65U, 0x6eU, 0x76U, 0x11U, 0x73U, 0x74U, 0x61U, 0x74U, 0x65U,
0x5fU, 0x66U, 0x6fU, 0x72U, 0x65U, 0x69U, 0x67U, 0x6eU, 0x5fU,
0x73U, 0x65U, 0x74U, 0x00U, 0x05U, 0x03U, 0x65U, 0x6eU, 0x76U,
0x09U, 0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x5fU, 0x73U, 0x65U,
0x74U, 0x00U, 0x03U, 0x03U, 0x65U, 0x6eU, 0x76U, 0x06U, 0x61U,
0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x00U, 0x02U, 0x03U, 0x02U,
0x01U, 0x06U, 0x05U, 0x03U, 0x01U, 0x00U, 0x02U, 0x06U, 0x2bU,
0x07U, 0x7fU, 0x01U, 0x41U, 0xc0U, 0x88U, 0x04U, 0x0bU, 0x7fU,
0x00U, 0x41U, 0x80U, 0x08U, 0x0bU, 0x7fU, 0x00U, 0x41U, 0xb2U,
0x08U, 0x0bU, 0x7fU, 0x00U, 0x41U, 0x80U, 0x08U, 0x0bU, 0x7fU,
0x00U, 0x41U, 0xc0U, 0x88U, 0x04U, 0x0bU, 0x7fU, 0x00U, 0x41U,
0x00U, 0x0bU, 0x7fU, 0x00U, 0x41U, 0x01U, 0x0bU, 0x07U, 0x08U,
0x01U, 0x04U, 0x68U, 0x6fU, 0x6fU, 0x6bU, 0x00U, 0x08U, 0x0aU,
0xd4U, 0x81U, 0x00U, 0x01U, 0xd0U, 0x81U, 0x00U, 0x02U, 0x02U,
0x7fU, 0x01U, 0x7eU, 0x23U, 0x00U, 0x41U, 0xe0U, 0x00U, 0x6bU,
0x22U, 0x01U, 0x24U, 0x00U, 0x20U, 0x01U, 0x20U, 0x00U, 0x36U,
0x02U, 0x5cU, 0x41U, 0x01U, 0x41U, 0x01U, 0x10U, 0x00U, 0x1aU,
0x20U, 0x01U, 0x41U, 0x40U, 0x6bU, 0x41U, 0x14U, 0x10U, 0x01U,
0x42U, 0x14U, 0x52U, 0x04U, 0x40U, 0x41U, 0x00U, 0x41U, 0x00U,
0x42U, 0x0cU, 0x10U, 0x02U, 0x1aU, 0x0bU, 0x20U, 0x01U, 0x41U,
0x38U, 0x6aU, 0x41U, 0x08U, 0x20U, 0x01U, 0x41U, 0x40U, 0x6bU,
0x22U, 0x00U, 0x41U, 0x14U, 0x10U, 0x03U, 0x1aU, 0x20U, 0x01U,
0x20U, 0x01U, 0x29U, 0x03U, 0x38U, 0x42U, 0x01U, 0x7cU, 0x37U,
0x03U, 0x38U, 0x20U, 0x01U, 0x41U, 0xabU, 0x01U, 0x3aU, 0x00U,
0x19U, 0x20U, 0x01U, 0x20U, 0x01U, 0x29U, 0x03U, 0x38U, 0x3cU,
0x00U, 0x1aU, 0x41U, 0x80U, 0x08U, 0x41U, 0x02U, 0x20U, 0x01U,
0x41U, 0x10U, 0x6aU, 0x22U, 0x02U, 0x41U, 0x20U, 0x41U, 0x01U,
0x10U, 0x04U, 0x1aU, 0x20U, 0x01U, 0x41U, 0xa9U, 0x08U, 0x41U,
0x09U, 0x41U, 0xa4U, 0x08U, 0x41U, 0x05U, 0x20U, 0x02U, 0x41U,
0x20U, 0x20U, 0x00U, 0x41U, 0x14U, 0x10U, 0x05U, 0x37U, 0x03U,
0x08U, 0x20U, 0x01U, 0x29U, 0x03U, 0x08U, 0x42U, 0x00U, 0x53U,
0x04U, 0x40U, 0x41U, 0x83U, 0x08U, 0x41U, 0x21U, 0x20U, 0x01U,
0x29U, 0x03U, 0x08U, 0x10U, 0x02U, 0x1aU, 0x0bU, 0x20U, 0x01U,
0x41U, 0x38U, 0x6aU, 0x41U, 0x08U, 0x20U, 0x01U, 0x41U, 0x40U,
0x6bU, 0x41U, 0x14U, 0x10U, 0x06U, 0x1aU, 0x41U, 0x00U, 0x41U,
0x00U, 0x20U, 0x01U, 0x29U, 0x03U, 0x08U, 0x10U, 0x07U, 0x20U,
0x01U, 0x41U, 0xe0U, 0x00U, 0x6aU, 0x24U, 0x00U, 0x0bU, 0x0bU,
0x38U, 0x01U, 0x00U, 0x41U, 0x80U, 0x08U, 0x0bU, 0x31U, 0x6eU,
0x73U, 0x00U, 0x6eU, 0x73U, 0x5fU, 0x6dU, 0x61U, 0x78U, 0x2eU,
0x63U, 0x3aU, 0x20U, 0x4dU, 0x61U, 0x78U, 0x20U, 0x4eU, 0x61U,
0x6dU, 0x65U, 0x73U, 0x70U, 0x61U, 0x63U, 0x65U, 0x73U, 0x20U,
0x52U, 0x65U, 0x61U, 0x63U, 0x68U, 0x65U, 0x64U, 0x00U, 0x6bU,
0x65U, 0x79U, 0x32U, 0x00U, 0x63U, 0x6fU, 0x6eU, 0x74U, 0x65U,
0x6eU, 0x74U, 0x32U};
// fixXahauV1
{
for (bool const withfixXahauV1 : {true, false})
{
auto const amend = withfixXahauV1
? supported_amendments()
: supported_amendments() - fixXahauV1;
Env env{*this, amend};
// Env env{*this, envconfig(), amend, nullptr,
// // beast::severities::kWarning
// beast::severities::kTrace
// };
auto const bob = Account{"bob"};
auto const alice = Account{"alice"};
env.fund(XRP(10000000), alice);
env.fund(XRP(10000000), bob);
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hso(ns_maxHook, overrideFlag)}}, 0),
M("set state_foreign_set_max"),
HSFEE);
env.close();
// invoke the hook
for (uint32_t i = 0; i < 255; ++i)
{
env(pay(bob, alice, XRP(1)),
M("test state_foreign_set_max"),
fee(XRP(1)));
}
auto const txResult =
withfixXahauV1 ? ter(tecHOOK_REJECTED) : ter(tesSUCCESS);
env(pay(bob, alice, XRP(1)),
M("test state_foreign_set_max"),
fee(XRP(1)),
ter(txResult));
env.close();
// verify hook result
std::string const hookResult = withfixXahauV1
? "800000000000002d" // TOO_MANY_NAMESPACES / -45
: "9";
Json::Value params;
params[jss::transaction] =
env.tx()->getJson(JsonOptions::none)[jss::hash];
auto const jrr = env.rpc("json", "tx", to_string(params));
auto const meta = jrr[jss::result][jss::meta];
auto const executions = meta[sfHookExecutions.jsonName];
auto const execution = executions[0u][sfHookExecution.jsonName];
BEAST_EXPECT(
execution[sfHookReturnCode.jsonName] == hookResult);
}
}
}
void
test_state_foreign_set()
{
@@ -11437,6 +11627,7 @@ public:
void
run() override
{
testHooksOwnerDir();
testHooksDisabled();
testTxStructure();
testInferHookSetOperation();
@@ -11520,10 +11711,11 @@ public:
test_slot_subfield(); //
test_slot_type(); //
test_state(); //
test_state_foreign(); //
test_state_foreign_set(); //
test_state_set(); //
test_state(); //
test_state_foreign(); //
test_state_foreign_set(); //
test_state_foreign_set_max(); //
test_state_set(); //
test_sto_emplace(); //
test_sto_erase(); //
+137
View File
@@ -4245,6 +4245,142 @@ struct XahauGenesis_test : public beast::unit_test::suite
env, user, preLedger, preLedger + 1, postUser, preTime));
}
void
testValidL1WithUNLReport(FeatureBitset features)
{
using namespace jtx;
using namespace std::chrono_literals;
testcase("test claim reward valid for L1 with unl report");
for (bool const withXahauV1 : {true, false})
{
FeatureBitset _features = features - featureXahauGenesis;
auto const amend = withXahauV1 ? _features : _features - fixXahauV1;
Env env{*this, envconfig(), amend};
double const rateDrops = 0.00333333333 * 1'000'000;
STAmount const feesXRP = XRP(1);
// setup governance
Account const alice = Account("alice");
Account const bob = Account("bob");
Account const carol = Account("carol");
Account const david = Account("david");
Account const edward = Account("edward");
env.fund(XRP(10000), alice, bob, carol, david, edward);
env.close();
std::vector<AccountID> initial_members_ids{
alice.id(), bob.id(), carol.id(), david.id(), edward.id()};
setupGov(env, initial_members_ids);
// update reward delay
{
// this will be the new reward delay
// 100
std::vector<uint8_t> vote_data{
0x00U, 0x80U, 0xC6U, 0xA4U, 0x7EU, 0x8DU, 0x03U, 0x55U};
updateTopic(
env, alice, bob, carol, david, edward, 'R', 'D', vote_data);
}
// setup unl report
std::vector<std::string> const _ivlKeys = {
"ED74D4036C6591A4BDF9C54CEFA39B996A5DCE5F86D11FDA1874481CE9D5A1"
"CDC1",
};
std::vector<PublicKey> ivlKeys;
for (auto const& strPk : _ivlKeys)
{
auto pkHex = strUnHex(strPk);
ivlKeys.emplace_back(makeSlice(*pkHex));
}
std::vector<std::string> const _vlKeys = {
"0388935426E0D08083314842EDFBB2D517BD47699F9A4527318A8E10468C97"
"C05"
"2",
"02691AC5AE1C4C333AE5DF8A93BDC495F0EEBFC6DB0DA7EB6EF808F3AFC006"
"E3F"
"E",
"028949021029D5CC87E78BCF053AFEC0CAFD15108EC119EAAFEC466F5C0954"
"07B"
"F",
"027BAEF0CB02EA8B95F50DF4BC16C740B17B50C85F3757AA06A5DB6ADE0ED9"
"210"
"6",
"0318E0D644F3D2911D7B7E1B0B17684E7E625A6C36AECCE851BD16A4AD628B"
"213"
"6"};
std::vector<PublicKey> vlKeys;
for (auto const& strPk : _vlKeys)
{
auto pkHex = strUnHex(strPk);
vlKeys.emplace_back(makeSlice(*pkHex));
}
// activate unl report
activateUNLReport(env, ivlKeys, vlKeys);
// validate unl report
BEAST_EXPECT(hasUNLReport(env) == true);
// opt in claim reward
env(claimReward(alice, env.master), fee(feesXRP), ter(tesSUCCESS));
env.close();
// close ledgers
validateTime(lastClose(env), 90);
for (int i = 0; i < 5; ++i)
{
env.close(10s);
}
validateTime(lastClose(env), 180);
// define pre close values
STAmount const preAlice = env.balance(alice);
STAmount const preBob = env.balance(bob);
STAmount const preCarol = env.balance(carol);
STAmount const preDavid = env.balance(david);
STAmount const preEdward = env.balance(edward);
NetClock::time_point const preTime = lastClose(env);
std::uint32_t const preLedger = env.current()->seq();
auto const [acct, acctSle] =
accountKeyAndSle(*env.current(), alice);
// claim reward
auto const txResult =
withXahauV1 ? ter(tesSUCCESS) : ter(tecHOOK_REJECTED);
env(claimReward(alice, env.master), fee(feesXRP), txResult);
env.close();
// trigger emitted txn
env.close();
// calculate rewards
STAmount const netReward =
rewardUserAmount(*acctSle, preLedger, rateDrops);
STAmount const l1Reward =
withXahauV1 ? rewardL1Amount(netReward, 20) : STAmount(0);
// validate govern rewards
BEAST_EXPECT(env.balance(bob) == preBob + l1Reward);
BEAST_EXPECT(env.balance(carol) == preCarol + l1Reward);
BEAST_EXPECT(env.balance(david) == preDavid + l1Reward);
BEAST_EXPECT(env.balance(edward) == preEdward + l1Reward);
// validate account fields
STAmount const postAlice = preAlice + netReward + l1Reward;
bool const boolResult = withXahauV1 ? true : false;
BEAST_EXPECT(
expectAccountFields(
env, alice, preLedger, preLedger + 1, postAlice, preTime) ==
boolResult);
}
}
void
testOptInOptOut(FeatureBitset features)
{
@@ -4873,6 +5009,7 @@ struct XahauGenesis_test : public beast::unit_test::suite
testInvalidBeforeTime(features);
testValidWithoutUNLReport(features);
testValidWithUNLReport(features);
testValidL1WithUNLReport(features);
testOptInOptOut(features);
testValidLowBalance(features);
testValidElapsed1(features);
+6
View File
@@ -43,12 +43,18 @@ finish(
jtx::Account const& from,
std::uint32_t seq);
Json::Value
finish(jtx::Account const& account, jtx::Account const& from);
Json::Value
cancel(
jtx::Account const& account,
jtx::Account const& from,
std::uint32_t seq);
Json::Value
cancel(jtx::Account const& account, jtx::Account const& from);
/** Set the "FinishAfter" time tag on a JTx */
class finish_time
{
+22
View File
@@ -84,6 +84,17 @@ finish(jtx::Account const& account, jtx::Account const& from, std::uint32_t seq)
return jv;
}
Json::Value
finish(jtx::Account const& account, jtx::Account const& from)
{
Json::Value jv;
jv[jss::TransactionType] = jss::EscrowFinish;
jv[jss::Flags] = tfUniversal;
jv[jss::Account] = account.human();
jv[sfOwner.jsonName] = from.human();
return jv;
}
Json::Value
cancel(jtx::Account const& account, jtx::Account const& from, std::uint32_t seq)
{
@@ -96,6 +107,17 @@ cancel(jtx::Account const& account, jtx::Account const& from, std::uint32_t seq)
return jv;
}
Json::Value
cancel(jtx::Account const& account, jtx::Account const& from)
{
Json::Value jv;
jv[jss::TransactionType] = jss::EscrowCancel;
jv[jss::Flags] = tfUniversal;
jv[jss::Account] = account.human();
jv[sfOwner.jsonName] = from.human();
return jv;
}
} // namespace escrow
} // namespace jtx
+9
View File
@@ -51,6 +51,15 @@ offer_cancel(Account const& account, std::uint32_t offerSeq)
return jv;
}
Json::Value
offer_cancel(Account const& account)
{
Json::Value jv;
jv[jss::Account] = account.human();
jv[jss::TransactionType] = jss::OfferCancel;
return jv;
}
void
offer_id::operator()(Env& env, JTx& jt) const
{
+4
View File
@@ -40,6 +40,10 @@ offer(
Json::Value
offer_cancel(Account const& account, std::uint32_t offerSeq);
/** Cancel an offer. */
Json::Value
offer_cancel(Account const& account);
/** Sets the optional "OfferID" on a JTx. */
class offer_id
{