mirror of
https://github.com/Xahau/xahaud.git
synced 2026-07-31 19:10:10 +00:00
refactor: separate ExportSign transactor from Change
Move ttEXPORT_SIGN handling to dedicated ExportSign transactor class, following the same pattern as ttENTROPY/Entropy from the RNG feature. UVTxns (signed validator transactions) should not be mixed with pseudo-transactions in the Change transactor. - Create ExportSign.h/cpp with preflight, preclaim, doApply - Route ttEXPORT_SIGN through ExportSign in applySteps.cpp - Remove UVTx branches from Change transactor - Add documentation markers to View.h for inUNLReport functions
This commit is contained in:
@@ -462,6 +462,7 @@ target_sources (rippled PRIVATE
|
||||
src/ripple/app/tx/impl/DeleteAccount.cpp
|
||||
src/ripple/app/tx/impl/DepositPreauth.cpp
|
||||
src/ripple/app/tx/impl/Escrow.cpp
|
||||
src/ripple/app/tx/impl/ExportSign.cpp
|
||||
src/ripple/app/tx/impl/GenesisMint.cpp
|
||||
src/ripple/app/tx/impl/Import.cpp
|
||||
src/ripple/app/tx/impl/InvariantCheck.cpp
|
||||
@@ -744,6 +745,7 @@ if (tests)
|
||||
src/test/app/Discrepancy_test.cpp
|
||||
src/test/app/DNS_test.cpp
|
||||
src/test/app/Escrow_test.cpp
|
||||
src/test/app/Export_test.cpp
|
||||
src/test/app/FeeVote_test.cpp
|
||||
src/test/app/Flow_test.cpp
|
||||
src/test/app/Freeze_test.cpp
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
#include <ripple/app/misc/TxQ.h>
|
||||
#include <ripple/app/misc/ValidatorKeys.h>
|
||||
#include <ripple/app/misc/ValidatorList.h>
|
||||
#include <ripple/app/tx/impl/Change.h>
|
||||
#include <ripple/app/tx/impl/ExportSign.h>
|
||||
#include <ripple/basics/random.h>
|
||||
#include <ripple/beast/core/LexicalCast.h>
|
||||
#include <ripple/consensus/LedgerTiming.h>
|
||||
|
||||
@@ -351,8 +351,10 @@ enum hook_return_code : int64_t {
|
||||
TOO_MANY_STATE_MODIFICATIONS = -44, // more than 5000 modified state
|
||||
// entires in the combined hook chains
|
||||
TOO_MANY_NAMESPACES = -45,
|
||||
//@@start export-error-codes
|
||||
EXPORT_FAILURE = -46,
|
||||
TOO_MANY_EXPORTED_TXN = -47,
|
||||
//@@end export-error-codes
|
||||
|
||||
};
|
||||
|
||||
@@ -367,7 +369,9 @@ const uint16_t max_state_modifications = 256;
|
||||
const uint8_t max_slots = 255;
|
||||
const uint8_t max_nonce = 255;
|
||||
const uint8_t max_emit = 255;
|
||||
//@@start max-export
|
||||
const uint8_t max_export = 4;
|
||||
//@@end max-export
|
||||
const uint8_t max_params = 16;
|
||||
const double fee_base_multiplier = 1.1f;
|
||||
|
||||
@@ -473,12 +477,14 @@ static const APIWhitelist import_whitelist_1{
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
//@@start import-whitelist-2
|
||||
static const APIWhitelist import_whitelist_2{
|
||||
// clang-format off
|
||||
HOOK_API_DEFINITION(I64, xport, (I32, I32)),
|
||||
HOOK_API_DEFINITION(I64, xport_reserve, (I32)),
|
||||
// clang-format on
|
||||
};
|
||||
//@@end import-whitelist-2
|
||||
|
||||
#undef HOOK_API_DEFINITION
|
||||
#undef I32
|
||||
|
||||
@@ -1034,6 +1034,7 @@ validateGuards(
|
||||
{
|
||||
// PASS, this is a version 1 api
|
||||
}
|
||||
//@@start guard-export-whitelist
|
||||
else if (
|
||||
rulesVersion & 0x04U &&
|
||||
hook_api::import_whitelist_2.find(import_name) !=
|
||||
@@ -1041,6 +1042,7 @@ validateGuards(
|
||||
{
|
||||
// PASS, this is an export api
|
||||
}
|
||||
//@@end guard-export-whitelist
|
||||
else
|
||||
{
|
||||
GUARDLOG(hook::log::IMPORT_ILLEGAL)
|
||||
|
||||
@@ -406,6 +406,7 @@ DECLARE_HOOK_FUNCTION(
|
||||
uint32_t slot_no_tx,
|
||||
uint32_t slot_no_meta);
|
||||
|
||||
//@@start declare-xport
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
xport,
|
||||
@@ -414,6 +415,7 @@ DECLARE_HOOK_FUNCTION(
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len);
|
||||
DECLARE_HOOK_FUNCTION(int64_t, xport_reserve, uint32_t count);
|
||||
//@@end declare-xport
|
||||
/*
|
||||
DECLARE_HOOK_FUNCTION(int64_t, str_find, uint32_t hread_ptr,
|
||||
uint32_t hread_len, uint32_t nread_ptr, uint32_t nread_len, uint32_t mode,
|
||||
@@ -493,7 +495,9 @@ struct HookResult
|
||||
|
||||
std::queue<std::shared_ptr<ripple::Transaction>>
|
||||
emittedTxn{}; // etx stored here until accept/rollback
|
||||
//@@start hook-result-exported-txn
|
||||
std::queue<std::shared_ptr<ripple::Transaction>> exportedTxn{};
|
||||
//@@end hook-result-exported-txn
|
||||
HookStateMap& stateMap;
|
||||
uint16_t changedStateCount = 0;
|
||||
std::map<
|
||||
@@ -550,7 +554,9 @@ struct HookContext
|
||||
uint16_t ledger_nonce_counter{0};
|
||||
int64_t expected_etxn_count{-1}; // make this a 64bit int so the uint32
|
||||
// from the hookapi cant overflow it
|
||||
//@@start hook-context-export-count
|
||||
int64_t expected_export_count{-1};
|
||||
//@@end hook-context-export-count
|
||||
std::map<ripple::uint256, bool> nonce_used{};
|
||||
uint32_t generation =
|
||||
0; // used for caching, only generated when txn_generation is called
|
||||
@@ -887,8 +893,10 @@ public:
|
||||
ADD_HOOK_FUNCTION(meta_slot, ctx);
|
||||
ADD_HOOK_FUNCTION(xpop_slot, ctx);
|
||||
|
||||
//@@start add-hook-xport
|
||||
ADD_HOOK_FUNCTION(xport, ctx);
|
||||
ADD_HOOK_FUNCTION(xport_reserve, ctx);
|
||||
//@@end add-hook-xport
|
||||
|
||||
/*
|
||||
ADD_HOOK_FUNCTION(str_find, ctx);
|
||||
|
||||
@@ -2028,6 +2028,7 @@ hook::finalizeHookResult(
|
||||
}
|
||||
}
|
||||
|
||||
//@@start finalize-export
|
||||
DBG_PRINTF("exported txn count: %d\n", hookResult.exportedTxn.size());
|
||||
for (; hookResult.exportedTxn.size() > 0; hookResult.exportedTxn.pop())
|
||||
{
|
||||
@@ -2077,6 +2078,7 @@ hook::finalizeHookResult(
|
||||
}
|
||||
}
|
||||
}
|
||||
//@@end finalize-export
|
||||
}
|
||||
|
||||
bool const fixV2 = applyCtx.view().rules().enabled(fixXahauV2);
|
||||
@@ -3943,6 +3945,7 @@ DEFINE_HOOK_FUNCTION(int64_t, etxn_reserve, uint32_t count)
|
||||
HOOK_TEARDOWN();
|
||||
}
|
||||
|
||||
//@@start xport-reserve
|
||||
DEFINE_HOOK_FUNCTION(int64_t, xport_reserve, uint32_t count)
|
||||
{
|
||||
HOOK_SETUP(); // populates memory_ctx, memory, memory_length, applyCtx,
|
||||
@@ -3963,6 +3966,7 @@ DEFINE_HOOK_FUNCTION(int64_t, xport_reserve, uint32_t count)
|
||||
|
||||
HOOK_TEARDOWN();
|
||||
}
|
||||
//@@end xport-reserve
|
||||
|
||||
// Compute the burden of an emitted transaction based on a number of factors
|
||||
DEFINE_HOOK_FUNCNARG(int64_t, etxn_burden)
|
||||
@@ -6233,6 +6237,7 @@ DEFINE_HOOK_FUNCTION(
|
||||
HOOK_TEARDOWN();
|
||||
}
|
||||
|
||||
//@@start xport
|
||||
DEFINE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
xport,
|
||||
@@ -6319,6 +6324,7 @@ DEFINE_HOOK_FUNCTION(
|
||||
return result;
|
||||
HOOK_TEARDOWN();
|
||||
}
|
||||
//@@end xport
|
||||
/*
|
||||
|
||||
DEFINE_HOOK_FUNCTION(
|
||||
|
||||
@@ -599,6 +599,7 @@ public:
|
||||
return validatorKeys_.publicKey;
|
||||
}
|
||||
|
||||
//@@start app-impl-validator-keys
|
||||
SecretKey const&
|
||||
getValidationSecretKey() const override
|
||||
{
|
||||
@@ -610,6 +611,7 @@ public:
|
||||
{
|
||||
return validatorKeys_;
|
||||
}
|
||||
//@@end app-impl-validator-keys
|
||||
|
||||
NetworkOPs&
|
||||
getOPs() override
|
||||
|
||||
@@ -241,11 +241,13 @@ public:
|
||||
virtual PublicKey const&
|
||||
getValidationPublicKey() const = 0;
|
||||
|
||||
//@@start app-validator-keys
|
||||
virtual SecretKey const&
|
||||
getValidationSecretKey() const = 0;
|
||||
|
||||
virtual ValidatorKeys const&
|
||||
getValidatorKeys() const = 0;
|
||||
//@@end app-validator-keys
|
||||
virtual Resource::Manager&
|
||||
getResourceManager() = 0;
|
||||
virtual PathRequests&
|
||||
|
||||
@@ -1541,6 +1541,7 @@ TxQ::accept(Application& app, OpenView& view)
|
||||
}
|
||||
}
|
||||
|
||||
//@@start txq-inject-export
|
||||
// Inject exported transactions/signatures, if any
|
||||
if (view.rules().enabled(featureExport))
|
||||
{
|
||||
@@ -1723,6 +1724,7 @@ TxQ::accept(Application& app, OpenView& view)
|
||||
|
||||
} while (0);
|
||||
}
|
||||
//@@end txq-inject-export
|
||||
|
||||
// Inject emitted transactions if any
|
||||
if (view.rules().enabled(featureHooks))
|
||||
@@ -2115,8 +2117,10 @@ TxQ::tryDirectApply(
|
||||
const bool isFirstImport = !sleAccount &&
|
||||
view.rules().enabled(featureImport) && tx->getTxnType() == ttIMPORT;
|
||||
|
||||
//@@start txq-uvtx-handling
|
||||
// UVTxns don't require an account
|
||||
const bool accRequired = !(isFirstImport || isUVTx(*tx));
|
||||
//@@end txq-uvtx-handling
|
||||
|
||||
// Don't attempt to direct apply if the account is not in the ledger.
|
||||
if (!sleAccount && accRequired)
|
||||
|
||||
@@ -23,17 +23,14 @@
|
||||
#include <ripple/app/main/Application.h>
|
||||
#include <ripple/app/misc/AmendmentTable.h>
|
||||
#include <ripple/app/misc/NetworkOPs.h>
|
||||
#include <ripple/app/misc/ValidatorKeys.h>
|
||||
#include <ripple/app/tx/impl/Change.h>
|
||||
#include <ripple/app/tx/impl/SetSignerList.h>
|
||||
#include <ripple/app/tx/impl/XahauGenesis.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/ledger/OpenView.h>
|
||||
#include <ripple/ledger/Sandbox.h>
|
||||
#include <ripple/protocol/AccountID.h>
|
||||
#include <ripple/protocol/Feature.h>
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
#include <ripple/protocol/Sign.h>
|
||||
#include <ripple/protocol/TxFlags.h>
|
||||
#include <string_view>
|
||||
|
||||
@@ -46,57 +43,34 @@ Change::preflight(PreflightContext const& ctx)
|
||||
if (!isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
// ttEXPORT_SIGN is a UVTx (UNL Validator Transaction), not a pseudo-tx.
|
||||
// It has a real account, signature, and goes through normal validation.
|
||||
bool const isUVTx = ctx.tx.getTxnType() == ttEXPORT_SIGN;
|
||||
|
||||
if (!isUVTx)
|
||||
auto account = ctx.tx.getAccountID(sfAccount);
|
||||
if (account != beast::zero)
|
||||
{
|
||||
auto account = ctx.tx.getAccountID(sfAccount);
|
||||
if (account != beast::zero)
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Change: Bad source id";
|
||||
return temBAD_SRC_ACCOUNT;
|
||||
}
|
||||
|
||||
// No point in going any further if the transaction fee is malformed.
|
||||
auto const fee = ctx.tx.getFieldAmount(sfFee);
|
||||
if (!fee.native() || fee != beast::zero)
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Change: invalid fee";
|
||||
return temBAD_FEE;
|
||||
}
|
||||
|
||||
if (!ctx.tx.getSigningPubKey().empty() ||
|
||||
!ctx.tx.getSignature().empty() || ctx.tx.isFieldPresent(sfSigners))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Change: Bad signature";
|
||||
return temBAD_SIGNATURE;
|
||||
}
|
||||
|
||||
if (ctx.tx.getFieldU32(sfSequence) != 0 ||
|
||||
ctx.tx.isFieldPresent(sfPreviousTxnID))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Change: Bad sequence";
|
||||
return temBAD_SEQUENCE;
|
||||
}
|
||||
JLOG(ctx.j.warn()) << "Change: Bad source id";
|
||||
return temBAD_SRC_ACCOUNT;
|
||||
}
|
||||
//@@start uvtx-preflight
|
||||
else
|
||||
|
||||
// No point in going any further if the transaction fee is malformed.
|
||||
auto const fee = ctx.tx.getFieldAmount(sfFee);
|
||||
if (!fee.native() || fee != beast::zero)
|
||||
{
|
||||
// UVTx preflight checks
|
||||
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
if (!ctx.rules.enabled(featureExport))
|
||||
return temDISABLED;
|
||||
|
||||
if (auto const ret = preflight2(ctx); !isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
return tesSUCCESS;
|
||||
JLOG(ctx.j.warn()) << "Change: invalid fee";
|
||||
return temBAD_FEE;
|
||||
}
|
||||
|
||||
if (!ctx.tx.getSigningPubKey().empty() || !ctx.tx.getSignature().empty() ||
|
||||
ctx.tx.isFieldPresent(sfSigners))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Change: Bad signature";
|
||||
return temBAD_SIGNATURE;
|
||||
}
|
||||
|
||||
if (ctx.tx.getFieldU32(sfSequence) != 0 ||
|
||||
ctx.tx.isFieldPresent(sfPreviousTxnID))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Change: Bad sequence";
|
||||
return temBAD_SEQUENCE;
|
||||
}
|
||||
//@@end uvtx-preflight
|
||||
|
||||
if (ctx.tx.getTxnType() == ttUNL_MODIFY &&
|
||||
!ctx.rules.enabled(featureNegativeUNL))
|
||||
@@ -122,13 +96,13 @@ Change::preflight(PreflightContext const& ctx)
|
||||
}
|
||||
}
|
||||
|
||||
if ((ctx.tx.getTxnType() == ttEXPORT_SIGN ||
|
||||
ctx.tx.getTxnType() == ttEXPORT) &&
|
||||
!ctx.rules.enabled(featureExport))
|
||||
//@@start export-preflight-check
|
||||
if (ctx.tx.getTxnType() == ttEXPORT && !ctx.rules.enabled(featureExport))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Change: Export not enabled";
|
||||
return temDISABLED;
|
||||
}
|
||||
//@@end export-preflight-check
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
@@ -138,38 +112,12 @@ Change::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
// If tapOPEN_LEDGER is resurrected into ApplyFlags,
|
||||
// this block can be moved to preflight.
|
||||
// UVTxns like ttEXPORT_SIGN can be applied in the open ledger.
|
||||
bool const isUVTx = ctx.tx.getTxnType() == ttEXPORT_SIGN;
|
||||
if (ctx.view.open() && !isUVTx)
|
||||
if (ctx.view.open())
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Change transaction against open ledger";
|
||||
return temINVALID;
|
||||
}
|
||||
|
||||
//@@start uvtx-preclaim
|
||||
// UVTx (ttEXPORT_SIGN) validation: signer must be in UNLReport
|
||||
if (isUVTx)
|
||||
{
|
||||
if (!ctx.view.rules().enabled(featureExport))
|
||||
return temDISABLED;
|
||||
|
||||
auto const& pkSignerField = ctx.tx.getSigningPubKey();
|
||||
if (!publicKeyType(makeSlice(pkSignerField)))
|
||||
return tefBAD_AUTH;
|
||||
|
||||
PublicKey pkSigner{makeSlice(pkSignerField)};
|
||||
|
||||
if (!inUNLReport(ctx.view, ctx.app, pkSigner, ctx.j))
|
||||
{
|
||||
JLOG(ctx.j.warn())
|
||||
<< "ExportSign: Txn Account isn't in the UNLReport.";
|
||||
return tefFAILURE;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
//@@end uvtx-preclaim
|
||||
|
||||
switch (ctx.tx.getTxnType())
|
||||
{
|
||||
case ttFEE:
|
||||
@@ -214,9 +162,10 @@ Change::preclaim(PreclaimContext const& ctx)
|
||||
case ttAMENDMENT:
|
||||
case ttUNL_MODIFY:
|
||||
case ttEMIT_FAILURE:
|
||||
//@@start export-preclaim-case
|
||||
case ttEXPORT:
|
||||
case ttEXPORT_SIGN:
|
||||
return tesSUCCESS;
|
||||
//@@end export-preclaim-case
|
||||
case ttUNL_REPORT: {
|
||||
if (!ctx.tx.isFieldPresent(sfImportVLKey) ||
|
||||
ctx.app.config().IMPORT_VL_KEYS.empty())
|
||||
@@ -271,12 +220,10 @@ Change::doApply()
|
||||
return applyEmitFailure();
|
||||
case ttUNL_REPORT:
|
||||
return applyUNLReport();
|
||||
//@@start export-doapply-case
|
||||
case ttEXPORT:
|
||||
return applyExport();
|
||||
//@@start export-sign-route
|
||||
case ttEXPORT_SIGN:
|
||||
return applyExportSign();
|
||||
//@@end export-sign-route
|
||||
//@@end export-doapply-case
|
||||
|
||||
default:
|
||||
assert(0);
|
||||
@@ -670,6 +617,7 @@ Change::activateXahauGenesis()
|
||||
for (auto const& [hookOn, wasmBytes, params] : genesis_hooks)
|
||||
{
|
||||
std::ostringstream loggerStream;
|
||||
//@@start export-validate-guards
|
||||
auto result = validateGuards(
|
||||
wasmBytes, // wasm to verify
|
||||
loggerStream,
|
||||
@@ -677,6 +625,7 @@ Change::activateXahauGenesis()
|
||||
(ctx_.view().rules().enabled(featureHooksUpdate1) ? 1 : 0) +
|
||||
(ctx_.view().rules().enabled(fix20250131) ? 2 : 0) +
|
||||
(ctx_.view().rules().enabled(featureExport) ? 4 : 0));
|
||||
//@@end export-validate-guards
|
||||
|
||||
if (!result)
|
||||
{
|
||||
@@ -1142,6 +1091,7 @@ Change::applyEmitFailure()
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
//@@start apply-export
|
||||
TER
|
||||
Change::applyExport()
|
||||
{
|
||||
@@ -1180,45 +1130,7 @@ Change::applyExport()
|
||||
} while (0);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
Change::applyExportSign()
|
||||
{
|
||||
uint256 txnID(ctx_.tx.getFieldH256(sfTransactionHash));
|
||||
do
|
||||
{
|
||||
JLOG(j_.info()) << "HookExport[" << txnID
|
||||
<< "]: ttExportSign adding signature to transaction";
|
||||
|
||||
auto key = keylet::exportedTxn(txnID);
|
||||
|
||||
auto const& sle = view().peek(key);
|
||||
|
||||
if (!sle)
|
||||
{
|
||||
// most likely explanation is that this was somehow a double-up, so
|
||||
// just ignore
|
||||
JLOG(j_.warn())
|
||||
<< "HookError[" << txnID
|
||||
<< "]: ttExportSign could not find exported txn in ledger";
|
||||
break;
|
||||
}
|
||||
|
||||
// grab the signer object off the txn
|
||||
STObject signerObj = const_cast<ripple::STTx&>(ctx_.tx)
|
||||
.getField(sfSigner)
|
||||
.downcast<STObject>();
|
||||
|
||||
// append it to the signers field in the ledger object
|
||||
STArray signers = sle->getFieldArray(sfSigners);
|
||||
signers.push_back(signerObj);
|
||||
sle->setFieldArray(sfSigners, signers);
|
||||
|
||||
// done
|
||||
view().update(sle);
|
||||
} while (0);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
//@@end apply-export
|
||||
|
||||
TER
|
||||
Change::applyUNLModify()
|
||||
@@ -1347,143 +1259,4 @@ Change::applyUNLModify()
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<STTx const>>
|
||||
makeExportSignTxns(OpenView& view, Application& app, beast::Journal const& j)
|
||||
{
|
||||
std::vector<std::shared_ptr<STTx const>> result;
|
||||
|
||||
if (!view.rules().enabled(featureExport))
|
||||
return result;
|
||||
|
||||
JLOG(j.debug()) << "EXPORT_SIGN processing: started";
|
||||
|
||||
auto const seq = view.info().seq;
|
||||
|
||||
// if we're not a validator we do nothing here
|
||||
if (app.getValidationPublicKey().empty())
|
||||
return result;
|
||||
|
||||
auto const& keys = app.getValidatorKeys();
|
||||
|
||||
if (keys.configInvalid())
|
||||
return result;
|
||||
|
||||
PublicKey pkSigning = app.getValidationPublicKey();
|
||||
auto const pk = app.validatorManifests().getMasterKey(pkSigning);
|
||||
|
||||
// Only continue if we're on the UNLReport
|
||||
if (!inUNLReport(view, app, pk, j))
|
||||
return result;
|
||||
|
||||
AccountID signingAcc = calcAccountID(pkSigning);
|
||||
|
||||
Keylet const exportedDirKeylet{keylet::exportedDir()};
|
||||
if (dirIsEmpty(view, exportedDirKeylet))
|
||||
return result;
|
||||
|
||||
std::shared_ptr<SLE const> sleDirNode{};
|
||||
unsigned int uDirEntry{0};
|
||||
uint256 dirEntry{beast::zero};
|
||||
|
||||
if (!cdirFirst(
|
||||
view, exportedDirKeylet.key, sleDirNode, uDirEntry, dirEntry))
|
||||
return result;
|
||||
|
||||
do
|
||||
{
|
||||
Keylet const itemKeylet{ltCHILD, dirEntry};
|
||||
auto sleItem = view.read(itemKeylet);
|
||||
if (!sleItem)
|
||||
{
|
||||
JLOG(j.warn())
|
||||
<< "ExportedTxn processing: directory node in ledger " << seq
|
||||
<< " has index to object that is missing: "
|
||||
<< to_string(dirEntry);
|
||||
continue;
|
||||
}
|
||||
|
||||
LedgerEntryType const nodeType{
|
||||
safe_cast<LedgerEntryType>((*sleItem)[sfLedgerEntryType])};
|
||||
|
||||
if (nodeType != ltEXPORTED_TXN)
|
||||
{
|
||||
JLOG(j.warn()) << "ExportedTxn processing: exported directory "
|
||||
"contained non ltEXPORTED_TXN type";
|
||||
continue;
|
||||
}
|
||||
|
||||
auto const& exported = const_cast<ripple::STLedgerEntry&>(*sleItem)
|
||||
.getField(sfExportedTxn)
|
||||
.downcast<STObject>();
|
||||
|
||||
auto exportedLgrSeq = exported.getFieldU32(sfLedgerSequence);
|
||||
|
||||
// Only sign transactions that were added in the previous ledger
|
||||
if (exportedLgrSeq != seq - 1)
|
||||
continue;
|
||||
|
||||
auto s = std::make_shared<ripple::Serializer>();
|
||||
exported.add(*s);
|
||||
SerialIter sitTrans(s->slice());
|
||||
try
|
||||
{
|
||||
auto const& stpTrans =
|
||||
std::make_shared<STTx const>(std::ref(sitTrans));
|
||||
|
||||
if (!stpTrans->isFieldPresent(sfAccount) ||
|
||||
stpTrans->getAccountID(sfAccount) == beast::zero)
|
||||
{
|
||||
JLOG(j.warn())
|
||||
<< "Hook: Export failure: sfAccount missing or zero.";
|
||||
continue;
|
||||
}
|
||||
|
||||
auto txnHash = stpTrans->getTransactionID();
|
||||
|
||||
// Build the multisig for the inner exported transaction
|
||||
Serializer sigData = buildMultiSigningData(*stpTrans, signingAcc);
|
||||
auto multisig =
|
||||
ripple::sign(keys.publicKey, keys.secretKey, sigData.slice());
|
||||
|
||||
// Create the ttEXPORT_SIGN transaction
|
||||
auto exportSignTx =
|
||||
std::make_shared<STTx>(ttEXPORT_SIGN, [&](auto& obj) {
|
||||
obj.set(([&]() {
|
||||
auto inner = std::make_unique<STObject>(sfSigner);
|
||||
inner->setFieldVL(sfSigningPubKey, keys.publicKey);
|
||||
inner->setAccountID(sfAccount, signingAcc);
|
||||
inner->setFieldVL(sfTxnSignature, multisig);
|
||||
return inner;
|
||||
})());
|
||||
obj.setFieldU32(sfLedgerSequence, seq);
|
||||
obj.setFieldH256(sfTransactionHash, txnHash);
|
||||
obj.setAccountID(sfAccount, calcAccountID(pk));
|
||||
obj.setFieldU32(sfSequence, 0);
|
||||
obj.setFieldVL(sfSigningPubKey, pkSigning.slice());
|
||||
obj.setFieldU32(sfFlags, tfFullyCanonicalSig);
|
||||
|
||||
if (app.config().NETWORK_ID > 1024)
|
||||
obj.setFieldU32(sfNetworkID, app.config().NETWORK_ID);
|
||||
});
|
||||
|
||||
// Sign the outer transaction using our ephemeral key
|
||||
exportSignTx->sign(pkSigning, app.getValidationSecretKey());
|
||||
|
||||
JLOG(j.debug())
|
||||
<< "EXPORT_SIGN txn: " << exportSignTx->getFullText();
|
||||
|
||||
result.push_back(exportSignTx);
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
JLOG(j.warn()) << "ExportedTxn Processing: Failure: " << e.what()
|
||||
<< "\n";
|
||||
}
|
||||
|
||||
} while (
|
||||
cdirNext(view, exportedDirKeylet.key, sleDirNode, uDirEntry, dirEntry));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#include <ripple/app/misc/NetworkOPs.h>
|
||||
#include <ripple/app/tx/impl/Transactor.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/ledger/OpenView.h>
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
|
||||
namespace ripple {
|
||||
@@ -75,23 +74,15 @@ private:
|
||||
TER
|
||||
applyEmitFailure();
|
||||
|
||||
//@@start apply-export-decl
|
||||
TER
|
||||
applyExport();
|
||||
|
||||
TER
|
||||
applyExportSign();
|
||||
//@@end apply-export-decl
|
||||
|
||||
TER
|
||||
applyUNLReport();
|
||||
};
|
||||
|
||||
/**
|
||||
* If this validator is on the UNLReport, generate signed ttEXPORT_SIGN
|
||||
* transactions for any exported transactions that need signing.
|
||||
*/
|
||||
std::vector<std::shared_ptr<STTx const>>
|
||||
makeExportSignTxns(OpenView& view, Application& app, beast::Journal const& j);
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
|
||||
255
src/ripple/app/tx/impl/ExportSign.cpp
Normal file
255
src/ripple/app/tx/impl/ExportSign.cpp
Normal file
@@ -0,0 +1,255 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <ripple/app/main/Application.h>
|
||||
#include <ripple/app/misc/ValidatorKeys.h>
|
||||
#include <ripple/app/tx/impl/ExportSign.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/ledger/OpenView.h>
|
||||
#include <ripple/ledger/View.h>
|
||||
#include <ripple/protocol/Feature.h>
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
#include <ripple/protocol/Sign.h>
|
||||
#include <ripple/protocol/TxFlags.h>
|
||||
#include <ripple/protocol/st.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
//@@start exportsign-transactor
|
||||
TxConsequences
|
||||
ExportSign::makeTxConsequences(PreflightContext const& ctx)
|
||||
{
|
||||
return TxConsequences{ctx.tx, TxConsequences::normal};
|
||||
}
|
||||
|
||||
NotTEC
|
||||
ExportSign::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
if (!ctx.rules.enabled(featureExport))
|
||||
return temDISABLED;
|
||||
|
||||
return preflight2(ctx);
|
||||
}
|
||||
|
||||
TER
|
||||
ExportSign::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
if (!ctx.view.rules().enabled(featureExport))
|
||||
return temDISABLED;
|
||||
|
||||
// Signer must be in the UNLReport
|
||||
auto const& pkSignerField = ctx.tx.getSigningPubKey();
|
||||
if (!publicKeyType(makeSlice(pkSignerField)))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "ExportSign: Invalid signing public key type.";
|
||||
return tefBAD_AUTH;
|
||||
}
|
||||
|
||||
PublicKey pkSigner{makeSlice(pkSignerField)};
|
||||
|
||||
if (!inUNLReport(ctx.view, ctx.app, pkSigner, ctx.j))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "ExportSign: Signer isn't in the UNLReport.";
|
||||
return tefFAILURE;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
ExportSign::doApply()
|
||||
{
|
||||
uint256 txnID(ctx_.tx.getFieldH256(sfTransactionHash));
|
||||
|
||||
JLOG(j_.info()) << "HookExport[" << txnID
|
||||
<< "]: ttExportSign adding signature to transaction";
|
||||
|
||||
auto key = keylet::exportedTxn(txnID);
|
||||
auto const& sle = view().peek(key);
|
||||
|
||||
if (!sle)
|
||||
{
|
||||
// Most likely this was a double-up, so just ignore
|
||||
JLOG(j_.warn()) << "HookError[" << txnID
|
||||
<< "]: ttExportSign could not find exported txn in "
|
||||
"ledger";
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
// Grab the signer object off the txn
|
||||
STObject signerObj = const_cast<ripple::STTx&>(ctx_.tx)
|
||||
.getField(sfSigner)
|
||||
.downcast<STObject>();
|
||||
|
||||
// Append it to the signers field in the ledger object
|
||||
STArray signers = sle->getFieldArray(sfSigners);
|
||||
signers.push_back(signerObj);
|
||||
sle->setFieldArray(sfSigners, signers);
|
||||
|
||||
view().update(sle);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
//@@end exportsign-transactor
|
||||
|
||||
//@@start make-export-sign-txns
|
||||
std::vector<std::shared_ptr<STTx const>>
|
||||
makeExportSignTxns(OpenView& view, Application& app, beast::Journal const& j)
|
||||
{
|
||||
std::vector<std::shared_ptr<STTx const>> result;
|
||||
|
||||
if (!view.rules().enabled(featureExport))
|
||||
return result;
|
||||
|
||||
JLOG(j.debug()) << "EXPORT_SIGN processing: started";
|
||||
|
||||
auto const seq = view.info().seq;
|
||||
|
||||
// If we're not a validator we do nothing here
|
||||
if (app.getValidationPublicKey().empty())
|
||||
return result;
|
||||
|
||||
auto const& keys = app.getValidatorKeys();
|
||||
|
||||
if (keys.configInvalid())
|
||||
return result;
|
||||
|
||||
PublicKey pkSigning = app.getValidationPublicKey();
|
||||
auto const pk = app.validatorManifests().getMasterKey(pkSigning);
|
||||
|
||||
// Only continue if we're on the UNLReport
|
||||
if (!inUNLReport(view, app, pk, j))
|
||||
return result;
|
||||
|
||||
AccountID signingAcc = calcAccountID(pkSigning);
|
||||
|
||||
Keylet const exportedDirKeylet{keylet::exportedDir()};
|
||||
if (dirIsEmpty(view, exportedDirKeylet))
|
||||
return result;
|
||||
|
||||
std::shared_ptr<SLE const> sleDirNode{};
|
||||
unsigned int uDirEntry{0};
|
||||
uint256 dirEntry{beast::zero};
|
||||
|
||||
if (!cdirFirst(
|
||||
view, exportedDirKeylet.key, sleDirNode, uDirEntry, dirEntry))
|
||||
return result;
|
||||
|
||||
do
|
||||
{
|
||||
Keylet const itemKeylet{ltCHILD, dirEntry};
|
||||
auto sleItem = view.read(itemKeylet);
|
||||
if (!sleItem)
|
||||
{
|
||||
JLOG(j.warn())
|
||||
<< "ExportedTxn processing: directory node in ledger " << seq
|
||||
<< " has index to object that is missing: "
|
||||
<< to_string(dirEntry);
|
||||
continue;
|
||||
}
|
||||
|
||||
LedgerEntryType const nodeType{
|
||||
safe_cast<LedgerEntryType>((*sleItem)[sfLedgerEntryType])};
|
||||
|
||||
if (nodeType != ltEXPORTED_TXN)
|
||||
{
|
||||
JLOG(j.warn()) << "ExportedTxn processing: exported directory "
|
||||
"contained non ltEXPORTED_TXN type";
|
||||
continue;
|
||||
}
|
||||
|
||||
auto const& exported = const_cast<ripple::STLedgerEntry&>(*sleItem)
|
||||
.getField(sfExportedTxn)
|
||||
.downcast<STObject>();
|
||||
|
||||
auto exportedLgrSeq = exported.getFieldU32(sfLedgerSequence);
|
||||
|
||||
// Only sign transactions that were added in the previous ledger
|
||||
if (exportedLgrSeq != seq - 1)
|
||||
continue;
|
||||
|
||||
auto s = std::make_shared<ripple::Serializer>();
|
||||
exported.add(*s);
|
||||
SerialIter sitTrans(s->slice());
|
||||
try
|
||||
{
|
||||
auto const& stpTrans =
|
||||
std::make_shared<STTx const>(std::ref(sitTrans));
|
||||
|
||||
if (!stpTrans->isFieldPresent(sfAccount) ||
|
||||
stpTrans->getAccountID(sfAccount) == beast::zero)
|
||||
{
|
||||
JLOG(j.warn())
|
||||
<< "Hook: Export failure: sfAccount missing or zero.";
|
||||
continue;
|
||||
}
|
||||
|
||||
auto txnHash = stpTrans->getTransactionID();
|
||||
|
||||
// Build the multisig for the inner exported transaction
|
||||
Serializer sigData = buildMultiSigningData(*stpTrans, signingAcc);
|
||||
auto multisig =
|
||||
ripple::sign(keys.publicKey, keys.secretKey, sigData.slice());
|
||||
|
||||
// Create the ttEXPORT_SIGN transaction
|
||||
auto exportSignTx =
|
||||
std::make_shared<STTx>(ttEXPORT_SIGN, [&](auto& obj) {
|
||||
obj.set(([&]() {
|
||||
auto inner = std::make_unique<STObject>(sfSigner);
|
||||
inner->setFieldVL(sfSigningPubKey, keys.publicKey);
|
||||
inner->setAccountID(sfAccount, signingAcc);
|
||||
inner->setFieldVL(sfTxnSignature, multisig);
|
||||
return inner;
|
||||
})());
|
||||
obj.setFieldU32(sfLedgerSequence, seq);
|
||||
obj.setFieldH256(sfTransactionHash, txnHash);
|
||||
obj.setAccountID(sfAccount, calcAccountID(pk));
|
||||
obj.setFieldU32(sfSequence, 0);
|
||||
obj.setFieldVL(sfSigningPubKey, pkSigning.slice());
|
||||
obj.setFieldU32(sfFlags, tfFullyCanonicalSig);
|
||||
|
||||
if (app.config().NETWORK_ID > 1024)
|
||||
obj.setFieldU32(sfNetworkID, app.config().NETWORK_ID);
|
||||
});
|
||||
|
||||
// Sign the outer transaction using our ephemeral key
|
||||
exportSignTx->sign(pkSigning, app.getValidationSecretKey());
|
||||
|
||||
JLOG(j.debug())
|
||||
<< "EXPORT_SIGN txn: " << exportSignTx->getFullText();
|
||||
|
||||
result.push_back(exportSignTx);
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
JLOG(j.warn()) << "ExportedTxn Processing: Failure: " << e.what()
|
||||
<< "\n";
|
||||
}
|
||||
|
||||
} while (
|
||||
cdirNext(view, exportedDirKeylet.key, sleDirNode, uDirEntry, dirEntry));
|
||||
|
||||
return result;
|
||||
}
|
||||
//@@end make-export-sign-txns
|
||||
|
||||
} // namespace ripple
|
||||
78
src/ripple/app/tx/impl/ExportSign.h
Normal file
78
src/ripple/app/tx/impl/ExportSign.h
Normal file
@@ -0,0 +1,78 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_TX_EXPORTSIGN_H_INCLUDED
|
||||
#define RIPPLE_TX_EXPORTSIGN_H_INCLUDED
|
||||
|
||||
#include <ripple/app/misc/Manifest.h>
|
||||
#include <ripple/app/tx/impl/Transactor.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/core/Config.h>
|
||||
#include <ripple/ledger/View.h>
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class OpenView;
|
||||
|
||||
//@@start exportsign-class
|
||||
/**
|
||||
* ExportSign is a UVTxn (UNL Validator Transaction) that allows validators
|
||||
* on the UNLReport to submit their signatures for exported cross-chain
|
||||
* transactions without requiring a funded account.
|
||||
*/
|
||||
class ExportSign : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Custom};
|
||||
|
||||
explicit ExportSign(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static XRPAmount
|
||||
calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
return XRPAmount{0};
|
||||
}
|
||||
|
||||
static TxConsequences
|
||||
makeTxConsequences(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
/**
|
||||
* If this validator is on the UNLReport, generate signed ttEXPORT_SIGN
|
||||
* transactions for any exported transactions that need signing.
|
||||
*/
|
||||
std::vector<std::shared_ptr<STTx const>>
|
||||
makeExportSignTxns(OpenView& view, Application& app, beast::Journal const& j);
|
||||
//@@end exportsign-class
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -41,8 +41,10 @@
|
||||
|
||||
namespace ripple {
|
||||
|
||||
//@@start shadow-ticket-namespace
|
||||
static const uint256 shadowTicketNamespace =
|
||||
uint256::fromVoid("RESERVED NAMESPACE SHADOW TICKET");
|
||||
//@@end shadow-ticket-namespace
|
||||
|
||||
TxConsequences
|
||||
Import::makeTxConsequences(PreflightContext const& ctx)
|
||||
@@ -201,12 +203,14 @@ Import::preflight(PreflightContext const& ctx)
|
||||
if (!stpTrans || !meta)
|
||||
return temMALFORMED;
|
||||
|
||||
//@@start import-ticket-preflight
|
||||
if (stpTrans->isFieldPresent(sfTicketSequence) &&
|
||||
!ctx.rules.enabled(featureExport))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Import: cannot use TicketSequence XPOP.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
//@@end import-ticket-preflight
|
||||
|
||||
// check if txn is emitted or a psuedo
|
||||
if (isPseudoTx(*stpTrans) || stpTrans->isFieldPresent(sfEmitDetails))
|
||||
@@ -893,6 +897,7 @@ Import::preclaim(PreclaimContext const& ctx)
|
||||
return tefINTERNAL;
|
||||
}
|
||||
|
||||
//@@start import-shadow-ticket-preclaim
|
||||
bool const hasTicket = stpTrans->isFieldPresent(sfTicketSequence);
|
||||
|
||||
if (hasTicket)
|
||||
@@ -916,6 +921,7 @@ Import::preclaim(PreclaimContext const& ctx)
|
||||
// SF_BAD
|
||||
}
|
||||
}
|
||||
//@@end import-shadow-ticket-preclaim
|
||||
|
||||
auto const& sle = ctx.view.read(keylet::account(ctx.tx[sfAccount]));
|
||||
|
||||
@@ -957,6 +963,7 @@ Import::preclaim(PreclaimContext const& ctx)
|
||||
} while (0);
|
||||
}
|
||||
|
||||
//@@start import-ticket-replay-check
|
||||
if (!hasTicket)
|
||||
{
|
||||
if (sle && sle->isFieldPresent(sfImportSequence))
|
||||
@@ -968,6 +975,7 @@ Import::preclaim(PreclaimContext const& ctx)
|
||||
return tefPAST_IMPORT_SEQ;
|
||||
}
|
||||
}
|
||||
//@@end import-ticket-replay-check
|
||||
|
||||
// when importing for the first time the fee must be zero
|
||||
if (!sle && ctx.tx.getFieldAmount(sfFee) != beast::zero)
|
||||
@@ -1274,6 +1282,7 @@ Import::doApply()
|
||||
auto const id = ctx_.tx[sfAccount];
|
||||
auto sle = view().peek(keylet::account(id));
|
||||
|
||||
//@@start import-ticket-apply-check
|
||||
std::optional<uint256> ticket;
|
||||
if (stpTrans->isFieldPresent(sfTicketSequence))
|
||||
ticket = uint256(stpTrans->getFieldU32(sfTicketSequence));
|
||||
@@ -1281,6 +1290,7 @@ Import::doApply()
|
||||
if (sle && !ticket.has_value() &&
|
||||
sle->getFieldU32(sfImportSequence) >= importSequence)
|
||||
{
|
||||
//@@end import-ticket-apply-check
|
||||
// make double sure import seq hasn't passed
|
||||
JLOG(ctx_.journal.warn()) << "Import: ImportSequence passed";
|
||||
return tefINTERNAL;
|
||||
@@ -1372,6 +1382,7 @@ Import::doApply()
|
||||
}
|
||||
}
|
||||
|
||||
//@@start import-ticket-apply-finalize
|
||||
if (!ticket.has_value())
|
||||
sle->setFieldU32(sfImportSequence, importSequence);
|
||||
|
||||
@@ -1391,6 +1402,7 @@ Import::doApply()
|
||||
|
||||
// RHUPTO: ticketseq billing?
|
||||
}
|
||||
//@@end import-ticket-apply-finalize
|
||||
|
||||
if (create)
|
||||
{
|
||||
|
||||
@@ -486,6 +486,7 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
|
||||
hsacc = ss.str();
|
||||
}
|
||||
|
||||
//@@start sethook-rules-version
|
||||
auto result = validateGuards(
|
||||
hook, // wasm to verify
|
||||
logger,
|
||||
@@ -493,6 +494,7 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
|
||||
(ctx.rules.enabled(featureHooksUpdate1) ? 1 : 0) +
|
||||
(ctx.rules.enabled(fix20250131) ? 2 : 0) +
|
||||
(ctx.rules.enabled(featureExport) ? 4 : 0));
|
||||
//@@end sethook-rules-version
|
||||
|
||||
if (ctx.j.trace())
|
||||
{
|
||||
|
||||
@@ -450,6 +450,7 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee)
|
||||
{
|
||||
auto feeDue = minimumFee(ctx.app, baseFee, ctx.view.fees(), ctx.flags);
|
||||
|
||||
//@@start uvtx-fee-waiver
|
||||
// UVTxns from validators in UNLReport don't have to pay a fee
|
||||
if (ctx.view.rules().enabled(featureExport) && isUVTx(ctx.tx))
|
||||
{
|
||||
@@ -461,6 +462,7 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee)
|
||||
feeDue = beast::zero;
|
||||
}
|
||||
}
|
||||
//@@end uvtx-fee-waiver
|
||||
|
||||
if (feePaid < feeDue)
|
||||
{
|
||||
@@ -478,9 +480,11 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee)
|
||||
auto const sle = ctx.view.read(keylet::account(id));
|
||||
if (!sle)
|
||||
{
|
||||
//@@start uvtx-account-check
|
||||
// UVTxns don't need an underlying account
|
||||
if (isUVTx(ctx.tx))
|
||||
return tesSUCCESS;
|
||||
//@@end uvtx-account-check
|
||||
|
||||
if (ctx.tx.getTxnType() == ttIMPORT)
|
||||
{
|
||||
@@ -656,8 +660,10 @@ Transactor::checkPriorTxAndLastLedger(PreclaimContext const& ctx)
|
||||
ctx.view.rules().enabled(featureImport) &&
|
||||
ctx.tx.getTxnType() == ttIMPORT && !ctx.tx.isFieldPresent(sfIssuer);
|
||||
|
||||
//@@start uvtx-preclaim-account
|
||||
// UVTxns don't require an underlying account
|
||||
bool const accRequired = !(isFirstImport || isUVTx(ctx.tx));
|
||||
//@@end uvtx-preclaim-account
|
||||
|
||||
if (!sle && accRequired)
|
||||
{
|
||||
@@ -814,6 +820,7 @@ Transactor::apply()
|
||||
// list one, preflight will have already a flagged a failure.
|
||||
auto const sle = view().peek(keylet::account(account_));
|
||||
|
||||
//@@start uvtx-apply-assert
|
||||
// sle must exist except for transactions
|
||||
// that allow zero account. (ttIMPORT and UVTxns)
|
||||
assert(
|
||||
@@ -822,6 +829,7 @@ Transactor::apply()
|
||||
ctx_.tx.getTxnType() == ttIMPORT &&
|
||||
!ctx_.tx.isFieldPresent(sfIssuer) ||
|
||||
isUVTx(ctx_.tx));
|
||||
//@@end uvtx-apply-assert
|
||||
|
||||
if (sle)
|
||||
{
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include <ripple/app/tx/impl/DeleteAccount.h>
|
||||
#include <ripple/app/tx/impl/DepositPreauth.h>
|
||||
#include <ripple/app/tx/impl/Escrow.h>
|
||||
#include <ripple/app/tx/impl/ExportSign.h>
|
||||
#include <ripple/app/tx/impl/GenesisMint.h>
|
||||
#include <ripple/app/tx/impl/Import.h>
|
||||
#include <ripple/app/tx/impl/Invoke.h>
|
||||
@@ -147,16 +148,17 @@ invoke_preflight(PreflightContext const& ctx)
|
||||
return invoke_preflight_helper<CreateTicket>(ctx);
|
||||
case ttTRUST_SET:
|
||||
return invoke_preflight_helper<SetTrust>(ctx);
|
||||
//@@start export-sign-preflight
|
||||
case ttAMENDMENT:
|
||||
case ttFEE:
|
||||
case ttUNL_MODIFY:
|
||||
case ttUNL_REPORT:
|
||||
case ttEMIT_FAILURE:
|
||||
//@@start export-preflight
|
||||
case ttEXPORT:
|
||||
case ttEXPORT_SIGN:
|
||||
return invoke_preflight_helper<Change>(ctx);
|
||||
//@@end export-sign-preflight
|
||||
case ttEXPORT_SIGN:
|
||||
return invoke_preflight_helper<ExportSign>(ctx);
|
||||
//@@end export-preflight
|
||||
case ttHOOK_SET:
|
||||
return invoke_preflight_helper<SetHook>(ctx);
|
||||
case ttNFTOKEN_MINT:
|
||||
@@ -282,16 +284,17 @@ invoke_preclaim(PreclaimContext const& ctx)
|
||||
return invoke_preclaim<SetTrust>(ctx);
|
||||
case ttHOOK_SET:
|
||||
return invoke_preclaim<SetHook>(ctx);
|
||||
//@@start export-sign-preclaim
|
||||
case ttAMENDMENT:
|
||||
case ttFEE:
|
||||
case ttUNL_MODIFY:
|
||||
case ttUNL_REPORT:
|
||||
case ttEMIT_FAILURE:
|
||||
//@@start export-preclaim
|
||||
case ttEXPORT:
|
||||
case ttEXPORT_SIGN:
|
||||
return invoke_preclaim<Change>(ctx);
|
||||
//@@end export-sign-preclaim
|
||||
case ttEXPORT_SIGN:
|
||||
return invoke_preclaim<ExportSign>(ctx);
|
||||
//@@end export-preclaim
|
||||
case ttNFTOKEN_MINT:
|
||||
return invoke_preclaim<NFTokenMint>(ctx);
|
||||
case ttNFTOKEN_BURN:
|
||||
@@ -377,16 +380,17 @@ invoke_calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
return SetTrust::calculateBaseFee(view, tx);
|
||||
case ttHOOK_SET:
|
||||
return SetHook::calculateBaseFee(view, tx);
|
||||
//@@start export-sign-fee
|
||||
case ttAMENDMENT:
|
||||
case ttFEE:
|
||||
case ttUNL_MODIFY:
|
||||
case ttUNL_REPORT:
|
||||
case ttEMIT_FAILURE:
|
||||
case ttEXPORT_SIGN:
|
||||
//@@start export-basefee
|
||||
case ttEXPORT:
|
||||
return Change::calculateBaseFee(view, tx);
|
||||
//@@end export-sign-fee
|
||||
case ttEXPORT_SIGN:
|
||||
return ExportSign::calculateBaseFee(view, tx);
|
||||
//@@end export-basefee
|
||||
case ttNFTOKEN_MINT:
|
||||
return NFTokenMint::calculateBaseFee(view, tx);
|
||||
case ttNFTOKEN_BURN:
|
||||
@@ -552,18 +556,21 @@ invoke_apply(ApplyContext& ctx)
|
||||
SetHook p(ctx);
|
||||
return p();
|
||||
}
|
||||
//@@start export-sign-apply
|
||||
case ttAMENDMENT:
|
||||
case ttFEE:
|
||||
case ttUNL_MODIFY:
|
||||
case ttUNL_REPORT:
|
||||
//@@start export-apply
|
||||
case ttEXPORT:
|
||||
case ttEXPORT_SIGN:
|
||||
case ttEMIT_FAILURE: {
|
||||
Change p(ctx);
|
||||
return p();
|
||||
}
|
||||
//@@end export-sign-apply
|
||||
case ttEXPORT_SIGN: {
|
||||
ExportSign p(ctx);
|
||||
return p();
|
||||
}
|
||||
//@@end export-apply
|
||||
case ttNFTOKEN_MINT: {
|
||||
NFTokenMint p(ctx);
|
||||
return p();
|
||||
|
||||
@@ -1097,6 +1097,7 @@ trustTransferLockedBalance(
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
//@@start inUNLReport-account
|
||||
/**
|
||||
* Check if an account (derived from a validator's master public key) is
|
||||
* in the UNLReport's ActiveValidators list.
|
||||
@@ -1145,7 +1146,9 @@ inUNLReport(V const& view, AccountID const& id, beast::Journal const& j)
|
||||
|
||||
return cache[id] = false;
|
||||
}
|
||||
//@@end inUNLReport-account
|
||||
|
||||
//@@start inUNLReport-pubkey
|
||||
/**
|
||||
* Check if a public key (or its master key via manifest lookup) is
|
||||
* in the UNLReport's ActiveValidators list.
|
||||
@@ -1163,6 +1166,7 @@ inUNLReport(
|
||||
return inUNLReport(view, calcAccountID(pk), j) ||
|
||||
(uvPk != pk && inUNLReport(view, calcAccountID(uvPk), j));
|
||||
}
|
||||
//@@end inUNLReport-pubkey
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
|
||||
@@ -378,7 +378,9 @@ extern uint256 const fixInvalidTxFlags;
|
||||
extern uint256 const featureExtendedHookState;
|
||||
extern uint256 const fixCronStacking;
|
||||
extern uint256 const fixHookAPI20251128;
|
||||
//@@start feature-export
|
||||
extern uint256 const featureExport;
|
||||
//@@end feature-export
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
|
||||
@@ -56,14 +56,18 @@ namespace keylet {
|
||||
Keylet const&
|
||||
emittedDir() noexcept;
|
||||
|
||||
//@@start keylet-exported-dir-decl
|
||||
Keylet const&
|
||||
exportedDir() noexcept;
|
||||
//@@end keylet-exported-dir-decl
|
||||
|
||||
Keylet
|
||||
emittedTxn(uint256 const& id) noexcept;
|
||||
|
||||
//@@start keylet-exported-txn-decl
|
||||
Keylet
|
||||
exportedTxn(uint256 const& id) noexcept;
|
||||
//@@end keylet-exported-txn-decl
|
||||
|
||||
Keylet
|
||||
hookDefinition(uint256 const& hash) noexcept;
|
||||
|
||||
@@ -261,7 +261,9 @@ enum LedgerEntryType : std::uint16_t
|
||||
*/
|
||||
ltEMITTED_TXN = 'E',
|
||||
|
||||
//@@start lt-exported-txn
|
||||
ltEXPORTED_TXN = 0x4578, // Ex (exported transaction)
|
||||
//@@end lt-exported-txn
|
||||
};
|
||||
// clang-format off
|
||||
|
||||
|
||||
@@ -355,7 +355,9 @@ extern SF_UINT16 const sfHookEmitCount;
|
||||
extern SF_UINT16 const sfHookExecutionIndex;
|
||||
extern SF_UINT16 const sfHookApiVersion;
|
||||
extern SF_UINT16 const sfHookStateScale;
|
||||
//@@start sf-hook-export-count
|
||||
extern SF_UINT16 const sfHookExportCount;
|
||||
//@@end sf-hook-export-count
|
||||
|
||||
// 32-bit integers (common)
|
||||
extern SF_UINT32 const sfNetworkID;
|
||||
@@ -596,7 +598,9 @@ extern SField const sfSigner;
|
||||
extern SField const sfMajority;
|
||||
extern SField const sfDisabledValidator;
|
||||
extern SField const sfEmittedTxn;
|
||||
//@@start sf-exported-txn
|
||||
extern SField const sfExportedTxn;
|
||||
//@@end sf-exported-txn
|
||||
extern SField const sfHookExecution;
|
||||
extern SField const sfHookDefinition;
|
||||
extern SField const sfHookParameter;
|
||||
|
||||
@@ -171,9 +171,12 @@ sterilize(STTx const& stx);
|
||||
bool
|
||||
isPseudoTx(STObject const& tx);
|
||||
|
||||
/** Check whether a transaction is a UNL Validator Transaction (UVTx) */
|
||||
//@@start is-uvtx-decl
|
||||
/** Check whether a transaction is a UNL Validator Transaction (UVTx) */
|
||||
bool
|
||||
isUVTx(STObject const& tx);
|
||||
//@@end is-uvtx-decl
|
||||
|
||||
inline STTx::STTx(SerialIter&& sit) : STTx(sit)
|
||||
{
|
||||
|
||||
@@ -67,7 +67,9 @@ enum TELcodes : TERUnderlyingType {
|
||||
telNON_LOCAL_EMITTED_TXN,
|
||||
telIMPORT_VL_KEY_NOT_RECOGNISED,
|
||||
telCAN_NOT_QUEUE_IMPORT,
|
||||
//@@start tel-shadow-ticket
|
||||
telSHADOW_TICKET_REQUIRED,
|
||||
//@@end tel-shadow-ticket
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -149,11 +149,13 @@ enum TxType : std::uint16_t
|
||||
ttURITOKEN_CREATE_SELL_OFFER = 48,
|
||||
ttURITOKEN_CANCEL_SELL_OFFER = 49,
|
||||
|
||||
//@@start tt-export
|
||||
/* A pseudo-txn containing an exported transaction plus signatures from the validators */
|
||||
ttEXPORT = 90,
|
||||
|
||||
/* A pseudo-txn containing a validator's signature for an export transaction */
|
||||
ttEXPORT_SIGN = 91,
|
||||
//@@end tt-export
|
||||
|
||||
/* A pseudo-txn alarm signal for invoking a hook, emitted by validators after alarm set conditions are met */
|
||||
ttCRON = 92,
|
||||
|
||||
@@ -484,7 +484,9 @@ REGISTER_FIX (fixInvalidTxFlags, Supported::yes, VoteBehavior::De
|
||||
REGISTER_FEATURE(ExtendedHookState, Supported::yes, VoteBehavior::DefaultNo);
|
||||
REGISTER_FIX (fixCronStacking, Supported::yes, VoteBehavior::DefaultYes);
|
||||
REGISTER_FIX (fixHookAPI20251128, Supported::yes, VoteBehavior::DefaultYes);
|
||||
//@@start register-export
|
||||
REGISTER_FEATURE(Export, Supported::yes, VoteBehavior::DefaultNo);
|
||||
//@@end register-export
|
||||
|
||||
// The following amendments are obsolete, but must remain supported
|
||||
// because they could potentially get enabled.
|
||||
|
||||
@@ -66,8 +66,10 @@ enum class LedgerNameSpace : std::uint16_t {
|
||||
HOOK_DEFINITION = 'D',
|
||||
EMITTED_TXN = 'E',
|
||||
EMITTED_DIR = 'F',
|
||||
//@@start ledger-namespace-export
|
||||
EXPORTED_TXN = 0x4578, // Ex
|
||||
EXPORTED_DIR = 0x4564, // Ed
|
||||
//@@end ledger-namespace-export
|
||||
NFTOKEN_OFFER = 'q',
|
||||
NFTOKEN_BUY_OFFERS = 'h',
|
||||
NFTOKEN_SELL_OFFERS = 'i',
|
||||
|
||||
@@ -73,6 +73,7 @@ InnerObjectFormats::InnerObjectFormats()
|
||||
{sfHookExecutionIndex, soeREQUIRED},
|
||||
{sfHookStateChangeCount, soeREQUIRED},
|
||||
{sfHookEmitCount, soeREQUIRED},
|
||||
{sfHookExportCount, soeREQUIRED},
|
||||
{sfFlags, soeOPTIONAL}});
|
||||
|
||||
add(sfHookEmission.jsonName.c_str(),
|
||||
|
||||
@@ -381,6 +381,7 @@ LedgerFormats::LedgerFormats()
|
||||
},
|
||||
commonFields);
|
||||
|
||||
//@@start exported-txn-format
|
||||
add(jss::ExportedTxn,
|
||||
ltEXPORTED_TXN,
|
||||
{
|
||||
@@ -389,6 +390,7 @@ LedgerFormats::LedgerFormats()
|
||||
{sfLedgerSequence, soeREQUIRED},
|
||||
},
|
||||
commonFields);
|
||||
//@@end exported-txn-format
|
||||
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
@@ -103,7 +103,9 @@ CONSTRUCT_TYPED_SFIELD(sfHookEmitCount, "HookEmitCount", UINT16,
|
||||
CONSTRUCT_TYPED_SFIELD(sfHookExecutionIndex, "HookExecutionIndex", UINT16, 19);
|
||||
CONSTRUCT_TYPED_SFIELD(sfHookApiVersion, "HookApiVersion", UINT16, 20);
|
||||
CONSTRUCT_TYPED_SFIELD(sfHookStateScale, "HookStateScale", UINT16, 21);
|
||||
//@@start construct-sf-hook-export-count
|
||||
CONSTRUCT_TYPED_SFIELD(sfHookExportCount, "HookExportCount", UINT16, 22);
|
||||
//@@end construct-sf-hook-export-count
|
||||
|
||||
// 32-bit integers (common)
|
||||
CONSTRUCT_TYPED_SFIELD(sfNetworkID, "NetworkID", UINT32, 1);
|
||||
@@ -362,7 +364,9 @@ CONSTRUCT_UNTYPED_SFIELD(sfImportVLKey, "ImportVLKey", OBJECT,
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfHookEmission, "HookEmission", OBJECT, 93);
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfMintURIToken, "MintURIToken", OBJECT, 92);
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfAmountEntry, "AmountEntry", OBJECT, 91);
|
||||
//@@start construct-sf-exported-txn
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfExportedTxn, "ExportedTxn", OBJECT, 90);
|
||||
//@@end construct-sf-exported-txn
|
||||
|
||||
// array of objects
|
||||
// ARRAY/1 is reserved for end of array
|
||||
|
||||
@@ -141,7 +141,9 @@ transResults()
|
||||
MAKE_ERROR(telNON_LOCAL_EMITTED_TXN, "Emitted transaction cannot be applied because it was not generated locally."),
|
||||
MAKE_ERROR(telIMPORT_VL_KEY_NOT_RECOGNISED, "Import vl key was not recognized."),
|
||||
MAKE_ERROR(telCAN_NOT_QUEUE_IMPORT, "Import transaction was not able to be directly applied and cannot be queued."),
|
||||
//@@start ter-shadow-ticket
|
||||
MAKE_ERROR(telSHADOW_TICKET_REQUIRED, "The imported transaction uses a TicketSequence but no shadow ticket exists."),
|
||||
//@@end ter-shadow-ticket
|
||||
MAKE_ERROR(temMALFORMED, "Malformed transaction."),
|
||||
MAKE_ERROR(temBAD_AMOUNT, "Can only send positive amounts."),
|
||||
MAKE_ERROR(temBAD_CURRENCY, "Malformed: Bad currency."),
|
||||
|
||||
@@ -491,6 +491,7 @@ TxFormats::TxFormats()
|
||||
},
|
||||
commonFields);
|
||||
|
||||
//@@start export-formats
|
||||
add(jss::ExportSign,
|
||||
ttEXPORT_SIGN,
|
||||
{
|
||||
@@ -505,10 +506,13 @@ TxFormats::TxFormats()
|
||||
{
|
||||
{sfTransactionHash, soeREQUIRED},
|
||||
{sfExportedTxn, soeREQUIRED},
|
||||
{sfSigners, soeREQUIRED},
|
||||
// We can't actually do this because it's already optional so it
|
||||
// causes parse failures Enforce this in the transactor. {sfSigners,
|
||||
// soeREQUIRED},
|
||||
{sfLedgerSequence, soeREQUIRED},
|
||||
},
|
||||
commonFields);
|
||||
//@@end export-formats
|
||||
}
|
||||
|
||||
TxFormats const&
|
||||
|
||||
@@ -140,9 +140,11 @@ JSS(HookState); // ledger type.
|
||||
JSS(HookStateData); // field.
|
||||
JSS(HookStateKey); // field.
|
||||
JSS(EmittedTxn); // ledger type.
|
||||
//@@start jss-export
|
||||
JSS(ExportedTxn);
|
||||
JSS(Export);
|
||||
JSS(ExportSign);
|
||||
//@@end jss-export
|
||||
JSS(SignerList); // ledger type.
|
||||
JSS(SignerListSet); // transaction type.
|
||||
JSS(SigningPubKey); // field.
|
||||
|
||||
101
src/test/app/Export_test.cpp
Normal file
101
src/test/app/Export_test.cpp
Normal file
@@ -0,0 +1,101 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <ripple/protocol/Feature.h>
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
#include <ripple/protocol/jss.h>
|
||||
#include <test/app/Export_test_hooks.h>
|
||||
#include <test/jtx.h>
|
||||
#include <test/jtx/hook.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
using TestHook = std::vector<uint8_t> const&;
|
||||
|
||||
// Large fee for hook operations
|
||||
#define HSFEE fee(100'000'000)
|
||||
|
||||
struct Export_test : public beast::unit_test::suite
|
||||
{
|
||||
// Bare bones hook that just accepts
|
||||
TestHook accept_wasm = export_test_wasm[R"[test.hook](
|
||||
#include <stdint.h>
|
||||
extern int32_t _g(uint32_t id, uint32_t maxiter);
|
||||
extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
|
||||
int64_t hook(uint32_t reserved) {
|
||||
_g(1, 1);
|
||||
accept(0, 0, 0);
|
||||
return 0;
|
||||
}
|
||||
)[test.hook]"];
|
||||
|
||||
void
|
||||
testBasicSetup(FeatureBitset features)
|
||||
{
|
||||
testcase("Basic Setup");
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
auto severity = beast::severities::kNone;
|
||||
// Minimal setup: validator keys + amendments
|
||||
Env env{*this, envconfig(), features, nullptr, severity};
|
||||
|
||||
Account const alice{"alice"}; // Hook owner
|
||||
Account const bob{"bob"}; // Sender
|
||||
|
||||
// Fund accounts
|
||||
env.fund(XRP(10000), alice, bob);
|
||||
env.close();
|
||||
|
||||
// Verify setup
|
||||
BEAST_EXPECT(env.current()->seq() < 256); // Grace period
|
||||
// Note: Validator key check removed - not needed for basic hook testing
|
||||
|
||||
// Deploy hook on alice
|
||||
env(ripple::test::jtx::hook(alice, {{hso(accept_wasm)}}, 0),
|
||||
HSFEE,
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
// Bob sends payment to alice (triggers hook)
|
||||
auto const alicePreBal = env.balance(alice);
|
||||
auto const bobPreBal = env.balance(bob);
|
||||
|
||||
env(pay(bob, alice, XRP(100)), fee(XRP(1)), ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
// Verify payment went through
|
||||
BEAST_EXPECT(env.balance(alice) == alicePreBal + XRP(100));
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
using namespace test::jtx;
|
||||
FeatureBitset const all{supported_amendments()};
|
||||
testBasicSetup(all);
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(Export, app, ripple);
|
||||
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
45
src/test/app/Export_test_hooks.h
Normal file
45
src/test/app/Export_test_hooks.h
Normal file
@@ -0,0 +1,45 @@
|
||||
|
||||
// This file is generated by build_test_hooks.py
|
||||
#ifndef EXPORT_TEST_WASM_INCLUDED
|
||||
#define EXPORT_TEST_WASM_INCLUDED
|
||||
#include <map>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
std::map<std::string, std::vector<uint8_t>> export_test_wasm = {
|
||||
/* ==== WASM: 0 ==== */
|
||||
{R"[test.hook](
|
||||
#include <stdint.h>
|
||||
extern int32_t _g(uint32_t id, uint32_t maxiter);
|
||||
extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
|
||||
int64_t hook(uint32_t reserved) {
|
||||
_g(1, 1);
|
||||
accept(0, 0, 0);
|
||||
return 0;
|
||||
}
|
||||
)[test.hook]",
|
||||
{
|
||||
0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U, 0x13U,
|
||||
0x03U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U, 0x03U, 0x7FU,
|
||||
0x7FU, 0x7EU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU, 0x02U,
|
||||
0x17U, 0x02U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU, 0x67U, 0x00U,
|
||||
0x00U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x06U, 0x61U, 0x63U, 0x63U, 0x65U,
|
||||
0x70U, 0x74U, 0x00U, 0x01U, 0x03U, 0x02U, 0x01U, 0x02U, 0x05U, 0x03U,
|
||||
0x01U, 0x00U, 0x02U, 0x06U, 0x21U, 0x05U, 0x7FU, 0x01U, 0x41U, 0x80U,
|
||||
0x88U, 0x04U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x08U, 0x0BU, 0x7FU,
|
||||
0x00U, 0x41U, 0x80U, 0x08U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x88U,
|
||||
0x04U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x08U, 0x0BU, 0x07U, 0x08U,
|
||||
0x01U, 0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x00U, 0x02U, 0x0AU, 0xA0U,
|
||||
0x80U, 0x00U, 0x01U, 0x9CU, 0x80U, 0x00U, 0x00U, 0x41U, 0x01U, 0x41U,
|
||||
0x01U, 0x10U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x41U, 0x00U,
|
||||
0x41U, 0x00U, 0x42U, 0x00U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U,
|
||||
0x1AU, 0x42U, 0x00U, 0x0BU,
|
||||
}},
|
||||
|
||||
};
|
||||
}
|
||||
} // namespace ripple
|
||||
#endif
|
||||
Reference in New Issue
Block a user