Compare commits

...
Author SHA1 Message Date
Denis Angell b926e2e2f6 update tsh 2023-12-18 13:29:20 +01:00
Denis Angell 2876ee146d add nftoken 2023-12-15 14:34:52 +01:00
Denis Angell 94fe1f7b29 clang-format 2023-12-15 14:27:10 +01:00
Denis Angell 2d93c50bcc refactor tsh 2023-12-15 14:24:23 +01:00
Denis Angell a9f7bc81c3 Merge branch 'fixXahauV1' into fxv1-tsh 2023-12-15 12:30:21 +01:00
Denis Angell 10ae534300 clang-format 2023-12-15 12:28:25 +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 3b353e146f fixup for offer sequence 2023-12-12 16:50:41 +01:00
Denis Angell 7295577cdd fxv1-tsh 2023-12-12 16:17:52 +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
24 changed files with 1223 additions and 707 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 -38
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<
@@ -44,43 +45,6 @@ public:
};
using namespace ripple;
static const std::map<uint16_t, uint8_t> TSHAllowances = {
{ttPAYMENT, tshROLLBACK},
{ttESCROW_CREATE, tshROLLBACK},
{ttESCROW_FINISH, tshROLLBACK},
{ttACCOUNT_SET, tshNONE},
{ttESCROW_CANCEL, tshCOLLECT},
{ttREGULAR_KEY_SET, tshROLLBACK},
{ttOFFER_CREATE, tshCOLLECT},
{ttOFFER_CANCEL, tshNONE},
{ttTICKET_CREATE, tshNONE},
{ttSIGNER_LIST_SET, tshROLLBACK},
{ttPAYCHAN_CREATE, tshROLLBACK},
{ttPAYCHAN_FUND, tshCOLLECT},
{ttPAYCHAN_CLAIM, tshCOLLECT},
{ttCHECK_CREATE, tshROLLBACK},
{ttCHECK_CASH, tshROLLBACK},
{ttCHECK_CANCEL, tshCOLLECT},
{ttDEPOSIT_PREAUTH, tshROLLBACK},
{ttTRUST_SET, tshCOLLECT},
{ttACCOUNT_DELETE, tshROLLBACK},
{ttHOOK_SET, tshNONE},
{ttNFTOKEN_MINT, tshROLLBACK},
{ttNFTOKEN_BURN, tshCOLLECT},
{ttNFTOKEN_CREATE_OFFER, tshROLLBACK},
{ttNFTOKEN_CANCEL_OFFER, tshCOLLECT},
{ttNFTOKEN_ACCEPT_OFFER, tshROLLBACK},
{ttCLAIM_REWARD, tshROLLBACK},
{ttINVOKE, tshROLLBACK},
{ttURITOKEN_MINT, tshNONE},
{ttURITOKEN_BURN, tshROLLBACK},
{ttURITOKEN_BUY, tshROLLBACK},
{ttURITOKEN_CREATE_SELL_OFFER, tshROLLBACK},
{ttURITOKEN_CANCEL_SELL_OFFER, tshNONE},
{ttIMPORT, tshROLLBACK},
{ttGENESIS_MINT, tshCOLLECT},
};
std::vector<std::pair<AccountID, bool>>
getTransactionalStakeHolders(STTx const& tx, ReadView const& rv);
} // namespace hook
+115 -57
View File
@@ -21,6 +21,8 @@
using namespace ripple;
namespace hook {
using namespace ripple;
std::vector<std::pair<AccountID, bool>>
getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
{
@@ -38,19 +40,10 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
uint16_t tt = tx.getFieldU16(sfTransactionType);
uint8_t tsh = tshNONE;
if (auto const& found = hook::TSHAllowances.find(tt);
found != hook::TSHAllowances.end())
tsh = found->second;
else
return {};
std::map<AccountID, std::pair<int, bool>> tshEntries;
int upto = 0;
bool canRollback = tsh & tshROLLBACK;
auto const ADD_TSH = [&otxnAcc, &tshEntries, &upto](
const AccountID& acc_r, bool rb) {
if (acc_r != *otxnAcc)
@@ -71,11 +64,14 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
return rv.read(keylet::nftoffer(*id));
};
bool const tshSTRONG = true; // tshROLLBACK
bool const tshWEAK = false; // tshCOLLECT
switch (tt)
{
case ttIMPORT: {
if (tx.isFieldPresent(sfIssuer))
ADD_TSH(tx.getAccountID(sfIssuer), canRollback);
ADD_TSH(tx.getAccountID(sfIssuer), tshSTRONG);
break;
}
@@ -103,13 +99,13 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
else if (*otxnAcc == owner)
{
// the owner burns their token, and the issuer is a weak TSH
ADD_TSH(issuer, canRollback);
ADD_TSH(issuer, tshWEAK);
}
else
{
// the issuer burns the owner's token, and the owner is a weak
// TSH
ADD_TSH(owner, canRollback);
ADD_TSH(owner, tshWEAK);
}
break;
@@ -126,16 +122,16 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
auto const owner = ut->getAccountID(sfOwner);
if (owner != tx.getAccountID(sfAccount))
if (owner != *otxnAcc)
{
// current owner is a strong TSH
ADD_TSH(owner, canRollback);
// current owner is a TSH
ADD_TSH(owner, tshSTRONG);
}
// issuer is also a strong TSH if the burnable flag is set
// issuer is also a TSH if the burnable flag is set
auto const issuer = ut->getAccountID(sfIssuer);
if (issuer != owner)
ADD_TSH(issuer, ut->getFlags() & lsfBurnable);
ADD_TSH(issuer, (ut->getFlags() & lsfBurnable) ? tshSTRONG : tshWEAK);
break;
}
@@ -154,22 +150,30 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
// issuer is a strong TSH if the burnable flag is set
if (issuer != owner)
ADD_TSH(issuer, ut->getFlags() & lsfBurnable);
ADD_TSH(issuer, (ut->getFlags() & lsfBurnable) ? tshSTRONG : tshWEAK);
// destination is a strong tsh
if (tx.isFieldPresent(sfDestination))
ADD_TSH(tx.getAccountID(sfDestination), canRollback);
ADD_TSH(tx.getAccountID(sfDestination), tshSTRONG);
break;
}
// NFT
case ttNFTOKEN_MINT:
case ttCLAIM_REWARD: {
if (tx.isFieldPresent(sfIssuer))
ADD_TSH(tx.getAccountID(sfIssuer), canRollback);
case ttURITOKEN_CANCEL_SELL_OFFER: {
Keylet const id{ltURI_TOKEN, tx.getFieldH256(sfURITokenID)};
if (!rv.exists(id))
return {};
auto const ut = rv.read(id);
if (!ut || ut->getFieldU16(sfLedgerEntryType) != ltURI_TOKEN)
return {};
// destination is weak tsh
if (ut->isFieldPresent(sfDestination))
ADD_TSH(ut->getAccountID(sfDestination), tshWEAK);
break;
};
}
case ttNFTOKEN_BURN:
case ttNFTOKEN_CREATE_OFFER: {
@@ -190,7 +194,7 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
ADD_TSH(issuer, issuerCanRollback);
if (hasOwner)
ADD_TSH(owner, canRollback);
ADD_TSH(owner, tshWEAK);
break;
}
@@ -209,16 +213,16 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
if (bo)
{
ADD_TSH(bo->getAccountID(sfOwner), canRollback);
ADD_TSH(bo->getAccountID(sfOwner), tshSTRONG);
if (bo->isFieldPresent(sfDestination))
ADD_TSH(bo->getAccountID(sfDestination), canRollback);
ADD_TSH(bo->getAccountID(sfDestination), tshSTRONG);
}
if (so)
{
ADD_TSH(so->getAccountID(sfOwner), canRollback);
ADD_TSH(so->getAccountID(sfOwner), tshSTRONG);
if (so->isFieldPresent(sfDestination))
ADD_TSH(so->getAccountID(sfDestination), canRollback);
ADD_TSH(so->getAccountID(sfDestination), tshSTRONG);
}
break;
@@ -234,10 +238,10 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
auto const offer = getNFTOffer(offerID, rv);
if (offer)
{
ADD_TSH(offer->getAccountID(sfOwner), canRollback);
ADD_TSH(offer->getAccountID(sfOwner), tshWEAK);
if (offer->isFieldPresent(sfDestination))
ADD_TSH(
offer->getAccountID(sfDestination), canRollback);
offer->getAccountID(sfDestination), tshWEAK);
// issuer can't stop people canceling their offers, but can
// get weak executions
@@ -249,9 +253,14 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
break;
}
case ttNFTOKEN_MINT:
case ttCLAIM_REWARD: {
if (tx.isFieldPresent(sfIssuer))
ADD_TSH(tx.getAccountID(sfIssuer), tshSTRONG);
break;
};
// self transactions
case ttURITOKEN_MINT:
case ttURITOKEN_CANCEL_SELL_OFFER:
case ttACCOUNT_SET:
case ttOFFER_CANCEL:
case ttTICKET_CREATE:
@@ -264,18 +273,19 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
case ttREGULAR_KEY_SET: {
if (!tx.isFieldPresent(sfRegularKey))
return {};
ADD_TSH(tx.getAccountID(sfRegularKey), canRollback);
ADD_TSH(tx.getAccountID(sfRegularKey), tshSTRONG);
break;
}
case ttDEPOSIT_PREAUTH: {
if (!tx.isFieldPresent(sfAuthorize))
return {};
ADD_TSH(tx.getAccountID(sfAuthorize), canRollback);
ADD_TSH(tx.getAccountID(sfAuthorize), tshSTRONG);
break;
}
// simple two party transactions
case ttURITOKEN_MINT:
case ttPAYMENT:
case ttESCROW_CREATE:
case ttCHECK_CREATE:
@@ -283,7 +293,7 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
case ttPAYCHAN_CREATE:
case ttINVOKE: {
if (destAcc)
ADD_TSH(*destAcc, canRollback);
ADD_TSH(*destAcc, tshSTRONG);
break;
}
@@ -294,24 +304,32 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
auto const& lim = tx.getFieldAmount(sfLimitAmount);
AccountID const& issuer = lim.getIssuer();
ADD_TSH(issuer, canRollback);
ADD_TSH(issuer, tshWEAK);
break;
}
case ttESCROW_CANCEL:
case ttESCROW_FINISH: {
if (!tx.isFieldPresent(sfOwner) ||
!tx.isFieldPresent(sfOfferSequence))
if (!tx.isFieldPresent(sfOwner))
return {};
auto escrow = rv.read(keylet::escrow(
tx.getAccountID(sfOwner), tx.getFieldU32(sfOfferSequence)));
if (!tx.isFieldPresent(sfOfferSequence) &&
!tx.isFieldPresent(sfEscrowID))
return {};
std::optional<uint256> escrowID = tx[~sfEscrowID];
std::optional<std::uint32_t> offerSeq = tx[~sfOfferSequence];
Keylet k = escrowID
? Keylet(ltESCROW, *escrowID)
: keylet::escrow(tx.getAccountID(sfOwner), *offerSeq);
auto escrow = rv.read(k);
if (!escrow)
return {};
ADD_TSH(escrow->getAccountID(sfAccount), true);
ADD_TSH(escrow->getAccountID(sfDestination), canRollback);
ADD_TSH(escrow->getAccountID(sfAccount), tshWEAK);
ADD_TSH(escrow->getAccountID(sfDestination), tshWEAK);
break;
}
@@ -324,8 +342,8 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
if (!chan)
return {};
ADD_TSH(chan->getAccountID(sfAccount), true);
ADD_TSH(chan->getAccountID(sfDestination), canRollback);
ADD_TSH(chan->getAccountID(sfAccount), tshWEAK);
ADD_TSH(chan->getAccountID(sfDestination), tshWEAK);
break;
}
@@ -338,8 +356,8 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
if (!check)
return {};
ADD_TSH(check->getAccountID(sfAccount), true);
ADD_TSH(check->getAccountID(sfDestination), canRollback);
ADD_TSH(check->getAccountID(sfAccount), tshWEAK);
ADD_TSH(check->getAccountID(sfDestination), tshWEAK);
break;
}
@@ -349,7 +367,7 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
STArray const& signerEntries = tx.getFieldArray(sfSignerEntries);
for (auto const& entryObj : signerEntries)
if (entryObj.isFieldPresent(sfAccount))
ADD_TSH(entryObj.getAccountID(sfAccount), canRollback);
ADD_TSH(entryObj.getAccountID(sfAccount), tshSTRONG);
break;
}
@@ -361,7 +379,7 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
{
if (mint.isFieldPresent(sfDestination))
{
ADD_TSH(mint.getAccountID(sfDestination), canRollback);
ADD_TSH(mint.getAccountID(sfDestination), tshWEAK);
}
}
}
@@ -1287,7 +1305,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 +1330,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 +1364,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 +1399,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 +1539,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 +1668,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)
+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);
}
+27 -5
View File
@@ -1437,15 +1437,13 @@ Transactor::addWeakTSHFromSandbox(detail::ApplyViewBase const& pv)
TER
Transactor::doTSH(
bool strong, // only strong iff true, only weak iff false
std::vector<std::pair<AccountID, bool>> tsh,
hook::HookStateMap& stateMap,
std::vector<hook::HookResult>& results,
std::shared_ptr<STObject const> const& provisionalMeta)
{
auto& view = ctx_.view();
std::vector<std::pair<AccountID, bool>> tsh =
hook::getTransactionalStakeHolders(ctx_.tx, view);
// add the extra TSH marked out by the specific transactor (if applicable)
if (!strong)
for (auto& weakTsh : additionalWeakTSH_)
@@ -1463,22 +1461,37 @@ Transactor::doTSH(
// obviously we will never execute OTXN account
// as a TSH because they already had first execution
if (tshAccountID == account_)
{
JLOG(j_.trace()) << "doTSH: tshAccountID == account_";
continue;
}
if (alreadyProcessed.find(tshAccountID) != alreadyProcessed.end())
{
JLOG(j_.trace()) << "doTSH: alreadyProcessed.find(tshAccountID) != "
"alreadyProcessed.end()";
continue;
}
alreadyProcessed.emplace(tshAccountID);
// only process the relevant ones
if ((!canRollback && strong) || (canRollback && !strong))
{
JLOG(j_.trace()) << "doTSH: (!canRollback && strong) || "
"(canRollback && !strong)";
continue;
}
auto klTshHook = keylet::hook(tshAccountID);
auto tshHook = view.read(klTshHook);
if (!(tshHook && tshHook->isFieldPresent(sfHooks)))
{
JLOG(j_.trace())
<< "doTSH: !(tshHook && tshHook->isFieldPresent(sfHooks))";
continue;
}
// scoping here allows tshAcc to leave scope before
// hook execution, which is probably safer
@@ -1486,7 +1499,10 @@ Transactor::doTSH(
// check if the TSH exists and/or has any hooks
auto tshAcc = view.peek(keylet::account(tshAccountID));
if (!tshAcc)
{
JLOG(j_.trace()) << "doTSH: !tshAcc";
continue;
}
// compute and deduct fees for the TSH if applicable
XRPAmount tshFeeDrops =
@@ -1494,7 +1510,10 @@ Transactor::doTSH(
// no hooks to execute, skip tsh
if (tshFeeDrops == 0)
{
JLOG(j_.trace()) << "doTSH: tshFeeDrops == 0";
continue;
}
assert(tshFeeDrops >= beast::zero);
@@ -1698,6 +1717,9 @@ Transactor::operator()()
// application to the ledger
std::map<AccountID, std::set<uint256>> aawMap;
std::vector<std::pair<AccountID, bool>> tsh =
hook::getTransactionalStakeHolders(ctx_.tx, ctx_.view());
// Pre-application (Strong TSH) Hooks are executed here
// These TSH have the right to rollback.
// Weak TSH and callback are executed post-application.
@@ -1726,7 +1748,7 @@ Transactor::operator()()
// (who have the right to rollback the txn), any weak TSH will be
// executed after doApply has been successful (callback as well)
result = doTSH(true, stateMap, hookResults, {});
result = doTSH(true, tsh, stateMap, hookResults, {});
}
// write state if all chains executed successfully
@@ -1969,7 +1991,7 @@ Transactor::operator()()
hook::HookStateMap stateMap;
std::vector<hook::HookResult> weakResults;
doTSH(false, stateMap, weakResults, proMeta);
doTSH(false, tsh, stateMap, weakResults, proMeta);
// execute any hooks that nominated for 'again as weak'
for (auto const& [accID, hookHashes] : aawMap)
+1
View File
@@ -188,6 +188,7 @@ protected:
TER
doTSH(
bool strong, // only do strong TSH iff true, otheriwse only weak
std::vector<std::pair<AccountID, bool>> tsh,
hook::HookStateMap& stateMap,
std::vector<hook::HookResult>& result,
std::shared_ptr<STObject const> const& provisionalMeta);
+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);
}
}
}
}
+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);
}
};
File diff suppressed because it is too large. Load diff
+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(); //
+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
{