Compare commits

...

14 Commits

Author SHA1 Message Date
Denis Angell
9df80a90d0 fix weak tsh 2024-01-19 15:42:35 +01:00
RichardAH
f21d3e1e97 Merge pull request #260 from Xahau/emit_guard
Fix: EmittedTxn Reliability
2024-01-19 14:05:06 +01:00
Denis Angell
858055c811 clang-format 2024-01-19 11:07:22 +01:00
Denis Angell
5d66f17574 add test 2024-01-19 11:05:45 +01:00
Denis Angell
00328cb782 Merge branch 'dev' into emit_guard 2024-01-19 10:24:34 +01:00
Richard Holland
fdf7ea4174 dbg inject clang + permission check 2024-01-17 18:15:17 +00:00
Richard Holland
7877ed9704 debug txn injector 2024-01-17 18:07:05 +00:00
Richard Holland
17ccec9ac5 Add additional checks for emitted txns 2024-01-17 15:39:02 +00:00
RichardAH
de522ac4ae Merge pull request #255 from Xahau/candidate
Candidate/release/sync
2023-12-29 22:18:04 +01:00
RichardAH
74c83a9271 Merge branch 'release' into candidate 2023-12-29 21:56:05 +01:00
Wietse Wind
66ee96d456 Build on release after all 2023-12-29 15:43:33 +01:00
Wietse Wind
b476aea55b Do not auto build on release 2023-12-29 15:39:48 +01:00
RichardAH
4ad697069f Fix xahau v1audit (#250) 2023-12-27 14:53:38 +01:00
Denis Angell
1b9373e220 add array xpop path 2023-10-30 15:53:48 +01:00
17 changed files with 500 additions and 81 deletions

View File

@@ -30,6 +30,7 @@
#include <boost/circular_buffer.hpp>
#include <boost/intrusive/set.hpp>
#include <optional>
#include <vector>
namespace ripple {
@@ -56,7 +57,14 @@ class Config;
*/
class TxQ
{
private:
std::mutex debugTxInjectMutex;
std::vector<STTx> debugTxInjectQueue;
public:
void
debugTxInject(STTx const& txn);
/// Fee level for single-signed reference transaction.
static constexpr FeeLevel64 baseLevel{256};

View File

@@ -93,6 +93,13 @@ increase(FeeLevel64 level, std::uint32_t increasePercent)
//////////////////////////////////////////////////////////////////////////
void
TxQ::debugTxInject(STTx const& txn)
{
const std::lock_guard<std::mutex> _(debugTxInjectMutex);
debugTxInjectQueue.push_back(txn);
}
std::size_t
TxQ::FeeMetrics::update(
Application& app,
@@ -1444,6 +1451,32 @@ TxQ::accept(Application& app, OpenView& view)
auto const metricsSnapshot = feeMetrics_.getSnapshot();
// try to inject any debug txns waiting in the debug queue
{
std::unique_lock<std::mutex> trylock(
TxQ::debugTxInjectMutex, std::try_to_lock);
if (trylock.owns_lock() && !debugTxInjectQueue.empty())
{
// pop everything
for (STTx const& txn : debugTxInjectQueue)
{
auto txnHash = txn.getTransactionID();
app.getHashRouter().setFlags(txnHash, SF_EMITTED | SF_PRIVATE2);
auto const& emitted =
const_cast<ripple::STTx&>(txn).downcast<STObject>();
auto s = std::make_shared<ripple::Serializer>();
emitted.add(*s);
view.rawTxInsert(txnHash, std::move(s), nullptr);
ledgerChanged = true;
}
debugTxInjectQueue.clear();
}
}
// Inject emitted transactions if any
if (view.rules().enabled(featureHooks))
do

View File

@@ -437,11 +437,19 @@ EscrowFinish::preflight(PreflightContext const& ctx)
{
if (!ctx.tx.isFieldPresent(sfOfferSequence))
return temMALFORMED;
}
if (!ctx.tx.isFieldPresent(sfEscrowID) &&
!ctx.tx.isFieldPresent(sfOfferSequence))
return temMALFORMED;
if (ctx.tx.isFieldPresent(sfEscrowID) &&
ctx.tx.getFieldU32(sfOfferSequence) != 0)
return temMALFORMED;
}
else
{
if ((!ctx.tx.isFieldPresent(sfEscrowID) &&
!ctx.tx.isFieldPresent(sfOfferSequence)) ||
ctx.tx.isFieldPresent(sfEscrowID) &&
ctx.tx.isFieldPresent(sfOfferSequence))
return temMALFORMED;
}
return tesSUCCESS;
}
@@ -472,17 +480,6 @@ EscrowFinish::doApply()
bool const fixV1 = view().rules().enabled(fixXahauV1);
if (!fixV1)
{
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], *offerSequence);
@@ -723,11 +720,19 @@ EscrowCancel::preflight(PreflightContext const& ctx)
{
if (!ctx.tx.isFieldPresent(sfOfferSequence))
return temMALFORMED;
}
if (!ctx.tx.isFieldPresent(sfEscrowID) &&
!ctx.tx.isFieldPresent(sfOfferSequence))
return temMALFORMED;
if (ctx.tx.isFieldPresent(sfEscrowID) &&
ctx.tx.getFieldU32(sfOfferSequence) != 0)
return temMALFORMED;
}
else
{
if ((!ctx.tx.isFieldPresent(sfEscrowID) &&
!ctx.tx.isFieldPresent(sfOfferSequence)) ||
ctx.tx.isFieldPresent(sfEscrowID) &&
ctx.tx.isFieldPresent(sfOfferSequence))
return temMALFORMED;
}
return preflight2(ctx);
}
@@ -744,16 +749,6 @@ EscrowCancel::doApply()
std::optional<std::uint32_t> offerSequence = ctx_.tx[~sfOfferSequence];
bool const fixV1 = view().rules().enabled(fixXahauV1);
if (!fixV1)
{
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], *offerSequence);

View File

@@ -817,16 +817,17 @@ Import::preflight(PreflightContext const& ctx)
<< " validation count: " << validationCount;
// check if the validation count is adequate
auto hasInsufficientQuorum = [&ctx](int quorum, int validationCount) {
if (ctx.rules.enabled(fixXahauV1))
{
return quorum > validationCount;
}
else
{
return quorum >= validationCount;
}
};
auto hasInsufficientQuorum =
[&ctx](uint64_t quorum, uint64_t 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 "

View File

@@ -648,6 +648,43 @@ Transactor::checkPriorTxAndLastLedger(PreclaimContext const& ctx)
if (ctx.view.txExists(ctx.tx.getTransactionID()))
return tefALREADY;
if (hook::isEmittedTxn(ctx.tx) && ctx.view.rules().enabled(featureHooks) &&
ctx.view.rules().enabled(fixXahauV2))
{
// check if the emitted txn exists on ledger and is in the emission
// directory if not that's a re-apply so discard
auto const kl = keylet::emittedTxn(ctx.tx.getTransactionID());
auto const sleE = ctx.view.read(kl);
if (!sleE)
return tefNONDIR_EMIT;
// lookup the page
uint64_t const page = sleE->getFieldU64(sfOwnerNode);
auto node = ctx.view.read(keylet::page(keylet::emittedDir(), page));
if (!node)
{
JLOG(ctx.j.warn())
<< "applyTransaction: orphaned emitted txn detected. keylet="
<< to_string(kl.key);
// RH TODO: work out how to safely delete the object
return tefNONDIR_EMIT;
}
auto entries = node->getFieldV256(sfIndexes);
auto it = std::find(entries.begin(), entries.end(), kl.key);
if (entries.end() == it)
{
JLOG(ctx.j.warn()) << "applyTransaction: orphaned emitted txn "
"detected (2). keylet="
<< to_string(kl.key);
// RH TODO: work out how to safely delete the object
return tefNONDIR_EMIT;
}
}
return tesSUCCESS;
}
@@ -1437,14 +1474,15 @@ 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);
// 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)
@@ -1698,6 +1736,8 @@ 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 +1766,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 +2009,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)

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

View File

@@ -144,6 +144,8 @@ URIToken::preflight(PreflightContext const& ctx)
TER
URIToken::preclaim(PreclaimContext const& ctx)
{
bool const fixV1 = ctx.view.rules().enabled(fixXahauV1);
std::shared_ptr<SLE const> sleU;
uint32_t leFlags;
std::optional<AccountID> issuer;
@@ -180,6 +182,11 @@ URIToken::preclaim(PreclaimContext const& ctx)
AccountID const acc = ctx.tx.getAccountID(sfAccount);
uint16_t tt = ctx.tx.getFieldU16(sfTransactionType);
auto const sle =
ctx.view.read(keylet::account(ctx.tx.getAccountID(sfAccount)));
if (!sle)
return tefINTERNAL;
switch (tt)
{
case ttURITOKEN_MINT: {
@@ -228,24 +235,75 @@ URIToken::preclaim(PreclaimContext const& ctx)
if (purchaseAmount < saleAmount)
return tecINSUFFICIENT_PAYMENT;
if (purchaseAmount.native() && saleAmount->native())
if (fixV1)
{
// if it's an xrp sale/purchase then no trustline needed
if (purchaseAmount >
(sleOwner->getFieldAmount(sfBalance) - ctx.tx[sfFee]))
return tecINSUFFICIENT_FUNDS;
if (purchaseAmount.native() && saleAmount->native())
{
// native transfer
STAmount needed{ctx.view.fees().accountReserve(
sle->getFieldU32(sfOwnerCount) + 1)};
STAmount const fee = ctx.tx.getFieldAmount(sfFee).xrp();
if (needed + fee < needed)
return tecINTERNAL;
needed += fee;
if (needed + purchaseAmount < needed)
return tecINTERNAL;
needed += purchaseAmount;
if (needed > sle->getFieldAmount(sfBalance))
return tecINSUFFICIENT_FUNDS;
}
else if (purchaseAmount.native() || saleAmount->native())
{
// should not be able to happen
return tecINTERNAL;
}
else
{
// iou transfer
STAmount availableFunds{accountFunds(
ctx.view,
acc,
purchaseAmount,
fhZERO_IF_FROZEN,
ctx.j)};
if (purchaseAmount > availableFunds)
return tecINSUFFICIENT_FUNDS;
}
}
else
{
// old logic
// execution to here means it's an IOU sale
// check if the buyer has the right trustline with an adequate
// balance
STAmount availableFunds{accountFunds(
ctx.view, acc, purchaseAmount, fhZERO_IF_FROZEN, ctx.j)};
if (purchaseAmount > availableFunds)
return tecINSUFFICIENT_FUNDS;
if (purchaseAmount.native() && saleAmount->native())
{
// if it's an xrp sale/purchase then no trustline needed
if (purchaseAmount >
(sleOwner->getFieldAmount(sfBalance) - ctx.tx[sfFee]))
return tecINSUFFICIENT_FUNDS;
}
else
{
// iou
STAmount availableFunds{accountFunds(
ctx.view,
acc,
purchaseAmount,
fhZERO_IF_FROZEN,
ctx.j)};
if (purchaseAmount > availableFunds)
return tecINSUFFICIENT_FUNDS;
}
}
return tesSUCCESS;
}
@@ -412,17 +470,6 @@ URIToken::doApply()
}
case ttURITOKEN_BUY: {
if (account_ == *owner)
{
// this is a clear operation
sleU->makeFieldAbsent(sfAmount);
if (sleU->isFieldPresent(sfDestination))
sleU->makeFieldAbsent(sfDestination);
sb.update(sleU);
sb.apply(ctx_.rawView());
return tesSUCCESS;
}
STAmount const purchaseAmount = ctx_.tx.getFieldAmount(sfAmount);
// check if the seller has listed it at all
@@ -446,8 +493,22 @@ URIToken::doApply()
// if it's an xrp sale/purchase then no trustline needed
if (purchaseAmount.native())
{
if (purchaseAmount >
((*sleOwner)[sfBalance] - ctx_.tx[sfFee]))
STAmount needed{sb.fees().accountReserve(
sle->getFieldU32(sfOwnerCount) + 1)};
STAmount const fee = ctx_.tx.getFieldAmount(sfFee).xrp();
if (needed + fee < needed)
return tecINTERNAL;
needed += fee;
if (needed + purchaseAmount < needed)
return tecINTERNAL;
needed += purchaseAmount;
if (needed > mPriorBalance)
return tecINSUFFICIENT_FUNDS;
}
else

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 fixXahauV1;
extern uint256 const fixXahauV2;
} // namespace ripple

View File

@@ -183,6 +183,7 @@ enum TEFcodes : TERUnderlyingType {
tefNFTOKEN_IS_NOT_TRANSFERABLE,
tefPAST_IMPORT_SEQ,
tefPAST_IMPORT_VL_SEQ,
tefNONDIR_EMIT,
};
//------------------------------------------------------------------------------

View File

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

View File

@@ -115,6 +115,7 @@ transResults()
MAKE_ERROR(tefTOO_BIG, "Transaction affects too many items."),
MAKE_ERROR(tefNO_TICKET, "Ticket is not in ledger."),
MAKE_ERROR(tefNFTOKEN_IS_NOT_TRANSFERABLE, "The specified NFToken is not transferable."),
MAKE_ERROR(tefNONDIR_EMIT, "An emitted txn was injected into the ledger without a corresponding directory entry."),
MAKE_ERROR(telLOCAL_ERROR, "Local failure."),
MAKE_ERROR(telBAD_DOMAIN, "Domain too long."),

View File

@@ -353,11 +353,12 @@ JSS(ident); // in: AccountCurrencies, AccountInfo,
// OwnerInfo
JSS(ignore_default); // in: AccountLines
JSS(inLedger); // out: tx/Transaction
JSS(inbound); // out: PeerImp
JSS(index); // in: LedgerEntry, DownloadShard
// out: STLedgerEntry,
// LedgerEntry, TxHistory, LedgerData
JSS(info); // out: ServerInfo, ConsensusInfo, FetchInfo
JSS(in_queue);
JSS(inbound); // out: PeerImp
JSS(index); // in: LedgerEntry, DownloadShard
// out: STLedgerEntry,
// LedgerEntry, TxHistory, LedgerData
JSS(info); // out: ServerInfo, ConsensusInfo, FetchInfo
JSS(initial_sync_duration_us);
JSS(internal_command); // in: Internal
JSS(invalid_API_version); // out: Many, when a request has an invalid

View File

@@ -141,6 +141,8 @@ doCrawlShards(RPC::JsonContext&);
Json::Value
doStop(RPC::JsonContext&);
Json::Value
doInject(RPC::JsonContext&);
Json::Value
doSubmit(RPC::JsonContext&);
Json::Value
doSubmitMultiSigned(RPC::JsonContext&);

View File

@@ -20,6 +20,7 @@
#include <ripple/app/ledger/LedgerMaster.h>
#include <ripple/app/misc/HashRouter.h>
#include <ripple/app/misc/Transaction.h>
#include <ripple/app/misc/TxQ.h>
#include <ripple/app/tx/apply.h>
#include <ripple/net/RPCErr.h>
#include <ripple/protocol/ErrorCodes.h>
@@ -39,6 +40,51 @@ getFailHard(RPC::JsonContext const& context)
context.params["fail_hard"].asBool());
}
// {
// tx_blob: serialized tx
// }
// Only for debug use!!!!
Json::Value
doInject(RPC::JsonContext& context)
{
if (context.role != Role::ADMIN)
return RPC::make_error(
rpcNOT_SUPPORTED, "Signing is not supported by this server.");
if (context.role != Role::ADMIN)
return rpcError(rpcNO_PERMISSION);
Json::Value jvResult;
auto ret = strUnHex(context.params[jss::tx_blob].asString());
if (!ret || !ret->size())
return rpcError(rpcINVALID_PARAMS);
SerialIter sitTrans(makeSlice(*ret));
std::shared_ptr<STTx const> stpTrans;
try
{
stpTrans = std::make_shared<STTx const>(std::ref(sitTrans));
}
catch (std::exception& e)
{
jvResult[jss::error] = "invalidTransaction";
jvResult[jss::error_exception] = e.what();
jvResult[jss::in_queue] = false;
return jvResult;
}
context.app.getTxQ().debugTxInject(*stpTrans);
jvResult[jss::tx_json] = stpTrans->getJson(JsonOptions::none);
jvResult[jss::in_queue] = true;
return jvResult;
}
// {
// tx_json: <object>,
// secret: <secret>

View File

@@ -141,6 +141,7 @@ Handler const handlerArray[]{
{"ripple_path_find", byRef(&doRipplePathFind), Role::USER, NO_CONDITION},
{"sign", byRef(&doSign), Role::USER, NO_CONDITION},
{"sign_for", byRef(&doSignFor), Role::USER, NO_CONDITION},
{"inject", byRef(&doInject), Role::USER, NEEDS_CURRENT_LEDGER},
{"submit", byRef(&doSubmit), Role::USER, NEEDS_CURRENT_LEDGER},
{"submit_multisigned",
byRef(&doSubmitMultiSigned),

View File

@@ -18,6 +18,9 @@
*/
//==============================================================================
#include <ripple/app/misc/HashRouter.h>
#include <ripple/app/misc/TxQ.h>
#include <ripple/app/tx/apply.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/PayChan.h>
#include <ripple/protocol/jss.h>
@@ -4331,7 +4334,220 @@ private:
}
void
testWithFeats(FeatureBitset features)
testEmittedTxnReliability(FeatureBitset features)
{
testcase("emitted txn reliability");
using namespace test::jtx;
using namespace std::literals;
test::jtx::Env env{
*this,
network::makeNetworkConfig(21337, "10", "1000000", "200000"),
features};
auto const account = Account("alice");
auto const dest = Account("bob");
env.fund(XRP(1000), account, dest);
env.close();
auto setHook = [](test::jtx::Account const& account) {
std::string const createCodeHex =
"0061736D0100000001350860057F7F7F7F7F017E60017F017E60047F7F7F7F"
"017E60037F7F7E017E60027F7F017E60037F7F7F017E60027F7F017F600001"
"7E02CD010D03656E76057472616365000003656E760C6574786E5F72657365"
"727665000103656E760A7574696C5F6163636964000203656E760974726163"
"655F6E756D000303656E760C686F6F6B5F6163636F756E74000403656E760A"
"6F74786E5F6669656C64000503656E7608726F6C6C6261636B000303656E76"
"025F67000603656E7606616363657074000303656E760A6C65646765725F73"
"6571000703656E760C6574786E5F64657461696C73000403656E760D657478"
"6E5F6665655F62617365000403656E7604656D697400020303020101050301"
"0002062B077F0141C08C040B7F004180080B7F0041B40C0B7F004180080B7F"
"0041C08C040B7F0041000B7F0041010B070F02046362616B000D04686F6F6B"
"000E0AE1930002AC800001017F230041106B220124002001200036020C41F6"
"0B411A41B70A4119410010001A200141106A240042000BAE930002017F017C"
"230041B0056B22012400200120003602AC0541E40B411141840A4110410010"
"001A410110011A200120014190056A411441940A4123100237038805418C08"
"410320012903880510031A200141F0046A411410041A2001200141D0046A41"
"144181802010053E02CC0441E409411120013402CC0410031A20012802CC04"
"411448044041910C4123420110061A0B200141003602C804200141013602C8"
"04200141003602C4040340418180B48178411510071A4100210020012802C8"
"04047F20012802C4044114480520000B4101710440200120012802C4042001"
"41F0046A6A2D000020012802C404200141D0046A6A2D0000463602C8042001"
"20012802C40441016A3602C4040C010B0B20012802C804450440419B08411D"
"420210081A0B200120014190046A413041818018100537038804200142C084"
"3D370380040240200129038804420852044041D00A41CE0041D40841CD0041"
"0010001A0C010B419F0B41C40041A10941C300410010001A200120012D0090"
"04410776047E427E052001310097042001310090044291A2C480B001834238"
"862001310091044230867C2001310092044228867C2001310093044220867C"
"2001310094044218867C2001310095044210867C2001310096044208867C7C"
"0B3703F803419008410A20012903F80310031A20012903F80342A08D065504"
"402001027E20012903F803B94400000040E17A843FA2220299440000000000"
"00E0436304402002B00C010B428080808080808080807F0B370380040B0B41"
"F609410D20012903800410031A2001200141E0016A3602DC01200120012903"
"80043703B801200141003602B401200141003602B001200110093E02AC0120"
"0141C0016A411410041A200141003A00AB0120012802DC0141123A00002001"
"2802DC0120012D00AB014108763A000120012802DC0120012D00AB013A0002"
"200120012802DC0141036A3602DC0120014180808080783602A40120014102"
"3A00A30120012802DC0120012D00A301410F7141206A3A000020012802DC01"
"20012802A4014118763A000120012802DC0120012802A4014110763A000220"
"012802DC0120012802A4014108763A000320012802DC0120012802A4013A00"
"04200120012802DC0141056A3602DC01200120012802B00136029C01200141"
"033A009B0120012802DC0120012D009B01410F7141206A3A000020012802DC"
"01200128029C014118763A000120012802DC01200128029C014110763A0002"
"20012802DC01200128029C014108763A000320012802DC01200128029C013A"
"0004200120012802DC0141056A3602DC012001410036029401200141043A00"
"930120012802DC0120012D009301410F7141206A3A000020012802DC012001"
"280294014118763A000120012802DC012001280294014110763A0002200128"
"02DC012001280294014108763A000320012802DC012001280294013A000420"
"0120012802DC0141056A3602DC01200120012802B40136028C012001410E3A"
"008B0120012802DC0120012D008B01410F7141206A3A000020012802DC0120"
"0128028C014118763A000120012802DC01200128028C014110763A00022001"
"2802DC01200128028C014108763A000320012802DC01200128028C013A0004"
"200120012802DC0141056A3602DC01200120012802AC0141016A3602840120"
"01411A3A00830120012802DC0141203A000020012802DC0120012D0083013A"
"000120012802DC012001280284014118763A000220012802DC012001280284"
"014110763A000320012802DC012001280284014108763A000420012802DC01"
"2001280284013A0005200120012802DC0141066A3602DC01200120012802AC"
"0141056A36027C2001411B3A007B20012802DC0141203A000020012802DC01"
"20012D007B3A000120012802DC01200128027C4118763A000220012802DC01"
"200128027C4110763A000320012802DC01200128027C4108763A0004200128"
"02DC01200128027C3A0005200120012802DC0141066A3602DC01200141013A"
"007A200120012903B80137037020012802DC0120012D007A410F7141E0006A"
"3A000020012802DC012001290370423888423F8342407D3C000120012802DC"
"01200129037042308842FF01833C000220012802DC01200129037042288842"
"FF01833C000320012802DC01200129037042208842FF01833C000420012802"
"DC01200129037042188842FF01833C000520012802DC012001290370421088"
"42FF01833C000620012802DC01200129037042088842FF01833C0007200128"
"02DC01200129037042FF01833C0008200120012802DC0141096A3602DC0120"
"0120012802DC0136026C200141083A006B2001420037036020012802DC0120"
"012D006B410F7141E0006A3A000020012802DC012001290360423888423F83"
"42407D3C000120012802DC01200129036042308842FF01833C000220012802"
"DC01200129036042288842FF01833C000320012802DC012001290360422088"
"42FF01833C000420012802DC01200129036042188842FF01833C0005200128"
"02DC01200129036042108842FF01833C000620012802DC0120012903604208"
"8842FF01833C000720012802DC01200129036042FF01833C00082001200128"
"02DC0141096A3602DC0120012802DC0141F3003A000020012802DC0141213A"
"000120012802DC01420037030220012802DC01420037030A20012802DC0142"
"0037031220012802DC014200370319200120012802DC0141236A3602DC0120"
"0141013A005F20012802DC0120012D005F4180016A3A000020012802DC0141"
"143A000120012802DC0120012903C00137030220012802DC0120012903C801"
"37030A20012802DC0120012802D001360212200120012802DC0141166A3602"
"DC01200141033A005E20012802DC0120012D005E4180016A3A000020012802"
"DC0141143A000120012802DC0120012903900537030220012802DC01200129"
"03980537030A20012802DC0120012802A005360212200120012802DC014116"
"6A3602DC01200120012802DC01418E02100A3703502001200141E0016A418E"
"02100B370348200141083A004720012001290348370338200128026C20012D"
"0047410F7141E0006A3A0000200128026C2001290338423888423F8342407D"
"3C0001200128026C200129033842308842FF01833C0002200128026C200129"
"033842288842FF01833C0003200128026C200129033842208842FF01833C00"
"04200128026C200129033842188842FF01833C0005200128026C2001290338"
"42108842FF01833C0006200128026C200129033842088842FF01833C000720"
"0128026C200129033842FF01833C00082001200128026C41096A36026C2001"
"200141106A4120200141E0016A418E02100C370308418008410B2001290308"
"10031A41B808411C420010081A200141B0056A240042000B0BBB0401004180"
"080BB304656D69745F726573756C7400726574006F74786E5F64726F707300"
"436172626F6E3A20496E636F6D696E67207472616E73616374696F6E004361"
"72626F6E3A20456D6974746564207472616E73616374696F6E00436172626F"
"6E3A204E6F6E2D787270207472616E73616374696F6E206465746563746564"
"2C2073656E64696E672064656661756C7420313030302064726F707320746F"
"207266436172626F6E00436172626F6E3A20585250207472616E7361637469"
"6F6E2064657465637465642C20636F6D707574696E6720312520746F207365"
"6E6420746F207266436172626F6E006163636F756E745F6669656C645F6C65"
"6E0064726F70735F746F5F73656E6400436172626F6E3A2073746172746564"
"0072504D68375069396374363939695A5554576179744A556F48634A376367"
"797A694B00436172626F6E3A2063616C6C6261636B2063616C6C65642E0022"
"436172626F6E3A204E6F6E2D787270207472616E73616374696F6E20646574"
"65637465642C2073656E64696E672064656661756C7420313030302064726F"
"707320746F207266436172626F6E220022436172626F6E3A20585250207472"
"616E73616374696F6E2064657465637465642C20636F6D707574696E672031"
"2520746F2073656E6420746F207266436172626F6E220022436172626F6E3A"
"2073746172746564220022436172626F6E3A2063616C6C6261636B2063616C"
"6C65642E2200436172626F6E3A2073664163636F756E74206669656C64206D"
"697373696E67212121";
Json::Value jhv = hso(createCodeHex);
jhv[jss::HookOn] =
"fffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffbfff"
"ff";
Json::Value jv = ripple::test::jtx::hook(account, {{jhv}}, 0);
return jv;
};
env(setHook(account), HSFEE);
env.close();
// ttINVOKE
env(invoke::invoke(account), fee(XRP(1)), ter(tesSUCCESS));
env.close();
Blob txBlob;
auto meta = env.meta();
auto const hookExecutions = meta->getFieldArray(sfHookExecutions);
for (auto const& node : meta->getFieldArray(sfAffectedNodes))
{
SField const& metaType = node.getFName();
uint16_t nodeType = node.getFieldU16(sfLedgerEntryType);
if (metaType == sfCreatedNode && nodeType == ltEMITTED_TXN)
{
auto const& nf = const_cast<ripple::STObject&>(node)
.getField(sfNewFields)
.downcast<STObject>();
auto const& et = const_cast<ripple::STObject&>(nf)
.getField(sfEmittedTxn)
.downcast<STObject>();
txBlob = et.getSerializer().getData();
break;
}
}
env.close();
auto const preDest = env.balance(dest);
bool const withFix = env.current()->rules().enabled(fixXahauV2);
bool didApply;
TER terRes;
env.app().openLedger().modify([&](OpenView& view, beast::Journal j) {
auto const tx =
std::make_unique<STTx>(Slice{txBlob.data(), txBlob.size()});
std::tie(terRes, didApply) =
ripple::apply(env.app(), view, *tx, tapNONE, env.journal);
bool const applyResult = withFix ? false : true;
if (withFix)
{
BEAST_EXPECT(terRes == tefNONDIR_EMIT);
}
else
{
BEAST_EXPECT(terRes == tesSUCCESS);
}
BEAST_EXPECT(didApply == applyResult);
return didApply;
});
env.close();
auto const postDest = env.balance(dest);
auto const postValue = withFix ? XRP(0) : XRP(1);
BEAST_EXPECT(postDest == preDest + postValue);
for (size_t i = 0; i < 4; i++)
{
Json::Value params1;
params1[jss::tx_blob] = strHex(Slice{txBlob.data(), txBlob.size()});
auto const jrr1 = env.rpc("json", "inject", to_string(params1));
env.close();
}
auto const postDest1 = env.balance(dest);
auto const postValue1 = withFix ? XRP(0) : XRP(2);
BEAST_EXPECT(postDest1 == postDest + postValue1);
}
void
testTSH(FeatureBitset features)
{
testAccountSetTSH(features);
testAccountDeleteTSH(features);
@@ -4366,14 +4582,22 @@ private:
testURITokenCreateSellOfferTSH(features);
}
void
testEmittedTxn(FeatureBitset features)
{
testEmittedTxnReliability(features);
}
public:
void
run() override
{
using namespace test::jtx;
auto const sa = supported_amendments();
testWithFeats(sa - fixXahauV1);
testWithFeats(sa);
testTSH(sa - fixXahauV1);
testTSH(sa);
testEmittedTxn(sa - fixXahauV2);
testEmittedTxn(sa);
}
};

View File

@@ -454,7 +454,10 @@ struct URIToken_test : public beast::unit_test::suite
using namespace std::literals::chrono_literals;
// setup env
Env env{*this, features};
Env env{
*this, envconfig(), features, nullptr, beast::severities::kWarning
// beast::severities::kTrace
};
auto const alice = Account("alice");
auto const bob = Account("bob");
auto const carol = Account("carol");