mirror of
https://github.com/Xahau/xahaud.git
synced 2025-11-26 13:35:50 +00:00
Hook API Unit Testing
This commit is contained in:
@@ -473,6 +473,7 @@ target_sources (rippled PRIVATE
|
|||||||
src/ripple/app/tx/impl/apply.cpp
|
src/ripple/app/tx/impl/apply.cpp
|
||||||
src/ripple/app/tx/impl/applySteps.cpp
|
src/ripple/app/tx/impl/applySteps.cpp
|
||||||
src/ripple/app/hook/impl/applyHook.cpp
|
src/ripple/app/hook/impl/applyHook.cpp
|
||||||
|
src/ripple/app/hook/impl/HookAPI.cpp
|
||||||
src/ripple/app/tx/impl/details/NFTokenUtils.cpp
|
src/ripple/app/tx/impl/details/NFTokenUtils.cpp
|
||||||
#[===============================[
|
#[===============================[
|
||||||
main sources:
|
main sources:
|
||||||
|
|||||||
50
src/ripple/app/hook/HookAPI.h
Normal file
50
src/ripple/app/hook/HookAPI.h
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
// A decoupled Hook host API helper for programmatic use and testing.
|
||||||
|
// Provides selected Hook APIs (emit-related and dependencies) without any
|
||||||
|
// dependency on WASM memory. It operates directly on ripple types.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ripple/app/tx/impl/ApplyContext.h>
|
||||||
|
#include <ripple/basics/Blob.h>
|
||||||
|
#include <ripple/basics/Expected.h>
|
||||||
|
#include <ripple/basics/Slice.h>
|
||||||
|
#include <ripple/protocol/STTx.h>
|
||||||
|
|
||||||
|
#include <ripple/app/misc/Transaction.h>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace hook {
|
||||||
|
|
||||||
|
struct HookContext; // defined in applyHook.h
|
||||||
|
|
||||||
|
class HookAPI
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit HookAPI(HookContext& ctx) : hookCtx(ctx)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit a transaction from the running hook. On success, returns 32-byte
|
||||||
|
// transaction ID bytes (same content written by the wasm host function).
|
||||||
|
ripple::Expected<std::shared_ptr<ripple::Transaction>, std::int64_t>
|
||||||
|
emit(ripple::Slice txBlob);
|
||||||
|
|
||||||
|
// Dependencies (public so callers can compose):
|
||||||
|
// etxn_generation == otxn_generation() + 1
|
||||||
|
std::int64_t
|
||||||
|
etxn_generation() const;
|
||||||
|
std::int64_t
|
||||||
|
etxn_burden() const;
|
||||||
|
std::int64_t
|
||||||
|
etxn_fee_base(ripple::Slice txBlob) const;
|
||||||
|
|
||||||
|
std::int64_t
|
||||||
|
otxn_generation() const;
|
||||||
|
std::int64_t
|
||||||
|
otxn_burden() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
HookContext& hookCtx;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace hook
|
||||||
@@ -481,7 +481,6 @@ struct HookResult
|
|||||||
ripple::uint256 const hookHash;
|
ripple::uint256 const hookHash;
|
||||||
ripple::uint256 const hookCanEmit;
|
ripple::uint256 const hookCanEmit;
|
||||||
ripple::Keylet const accountKeylet;
|
ripple::Keylet const accountKeylet;
|
||||||
ripple::Keylet const ownerDirKeylet;
|
|
||||||
ripple::Keylet const hookKeylet;
|
ripple::Keylet const hookKeylet;
|
||||||
ripple::AccountID const account;
|
ripple::AccountID const account;
|
||||||
ripple::AccountID const otxnAccount;
|
ripple::AccountID const otxnAccount;
|
||||||
|
|||||||
440
src/ripple/app/hook/impl/HookAPI.cpp
Normal file
440
src/ripple/app/hook/impl/HookAPI.cpp
Normal file
@@ -0,0 +1,440 @@
|
|||||||
|
// Implementation of decoupled Hook APIs for emit and related helpers.
|
||||||
|
|
||||||
|
#include <ripple/app/hook/HookAPI.h>
|
||||||
|
#include <ripple/app/hook/applyHook.h>
|
||||||
|
#include <ripple/app/ledger/OpenLedger.h>
|
||||||
|
#include <ripple/app/misc/Transaction.h>
|
||||||
|
#include <ripple/app/tx/apply.h>
|
||||||
|
#include <ripple/app/tx/impl/ApplyContext.h>
|
||||||
|
#include <ripple/app/tx/impl/Transactor.h>
|
||||||
|
#include <ripple/basics/Log.h>
|
||||||
|
#include <ripple/protocol/STObject.h>
|
||||||
|
#include <ripple/protocol/TxFlags.h>
|
||||||
|
#include <ripple/protocol/tokens.h>
|
||||||
|
|
||||||
|
namespace hook {
|
||||||
|
|
||||||
|
using namespace ripple;
|
||||||
|
|
||||||
|
std::int64_t
|
||||||
|
HookAPI::otxn_burden() const
|
||||||
|
{
|
||||||
|
auto& applyCtx = hookCtx.applyCtx;
|
||||||
|
auto j = applyCtx.app.journal("View");
|
||||||
|
|
||||||
|
if (hookCtx.burden)
|
||||||
|
return hookCtx.burden;
|
||||||
|
|
||||||
|
auto const& tx = applyCtx.tx;
|
||||||
|
if (!tx.isFieldPresent(sfEmitDetails))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
auto const& pd = const_cast<ripple::STTx&>(tx)
|
||||||
|
.getField(sfEmitDetails)
|
||||||
|
.downcast<STObject>();
|
||||||
|
|
||||||
|
if (!pd.isFieldPresent(sfEmitBurden))
|
||||||
|
{
|
||||||
|
JLOG(j.warn())
|
||||||
|
<< "HookError[" << HC_ACC()
|
||||||
|
<< "]: found sfEmitDetails but sfEmitBurden was not present";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint64_t burden = pd.getFieldU64(sfEmitBurden);
|
||||||
|
burden &= ((1ULL << 63) - 1);
|
||||||
|
hookCtx.burden = burden;
|
||||||
|
return static_cast<std::int64_t>(burden);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::int64_t
|
||||||
|
HookAPI::otxn_generation() const
|
||||||
|
{
|
||||||
|
auto& applyCtx = hookCtx.applyCtx;
|
||||||
|
auto j = applyCtx.app.journal("View");
|
||||||
|
|
||||||
|
if (hookCtx.generation)
|
||||||
|
return hookCtx.generation;
|
||||||
|
|
||||||
|
auto const& tx = applyCtx.tx;
|
||||||
|
if (!tx.isFieldPresent(sfEmitDetails))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
auto const& pd = const_cast<ripple::STTx&>(tx)
|
||||||
|
.getField(sfEmitDetails)
|
||||||
|
.downcast<STObject>();
|
||||||
|
|
||||||
|
if (!pd.isFieldPresent(sfEmitGeneration))
|
||||||
|
{
|
||||||
|
JLOG(j.warn())
|
||||||
|
<< "HookError[" << HC_ACC()
|
||||||
|
<< "]: found sfEmitDetails but sfEmitGeneration was not present";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
hookCtx.generation = pd.getFieldU32(sfEmitGeneration);
|
||||||
|
return hookCtx.generation;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::int64_t
|
||||||
|
HookAPI::etxn_generation() const
|
||||||
|
{
|
||||||
|
return otxn_generation() + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::int64_t
|
||||||
|
HookAPI::etxn_burden() const
|
||||||
|
{
|
||||||
|
if (hookCtx.expected_etxn_count <= -1)
|
||||||
|
return hook_api::PREREQUISITE_NOT_MET;
|
||||||
|
|
||||||
|
std::uint64_t last_burden = static_cast<std::uint64_t>(otxn_burden());
|
||||||
|
std::uint64_t burden =
|
||||||
|
last_burden * static_cast<std::uint64_t>(hookCtx.expected_etxn_count);
|
||||||
|
if (burden < last_burden)
|
||||||
|
return hook_api::FEE_TOO_LARGE;
|
||||||
|
return static_cast<std::int64_t>(burden);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::int64_t
|
||||||
|
HookAPI::etxn_fee_base(ripple::Slice txBlob) const
|
||||||
|
{
|
||||||
|
auto& applyCtx = hookCtx.applyCtx;
|
||||||
|
auto j = applyCtx.app.journal("View");
|
||||||
|
|
||||||
|
if (hookCtx.expected_etxn_count <= -1)
|
||||||
|
return hook_api::PREREQUISITE_NOT_MET;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SerialIter sitTrans(txBlob);
|
||||||
|
std::unique_ptr<STTx const> stpTrans =
|
||||||
|
std::make_unique<STTx const>(std::ref(sitTrans));
|
||||||
|
return Transactor::calculateBaseFee(
|
||||||
|
*(applyCtx.app.openLedger().current()), *stpTrans)
|
||||||
|
.drops();
|
||||||
|
}
|
||||||
|
catch (std::exception const& e)
|
||||||
|
{
|
||||||
|
JLOG(j.trace()) << "HookInfo[" << HC_ACC()
|
||||||
|
<< "]: etxn_fee_base exception: " << e.what();
|
||||||
|
return hook_api::INVALID_TXN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ripple::Expected<std::shared_ptr<Transaction>, std::int64_t>
|
||||||
|
HookAPI::emit(ripple::Slice txBlob)
|
||||||
|
{
|
||||||
|
auto& applyCtx = hookCtx.applyCtx;
|
||||||
|
auto j = applyCtx.app.journal("View");
|
||||||
|
auto& view = applyCtx.view();
|
||||||
|
|
||||||
|
if (hookCtx.expected_etxn_count < 0)
|
||||||
|
return ripple::Unexpected<std::int64_t>(hook_api::PREREQUISITE_NOT_MET);
|
||||||
|
|
||||||
|
if (hookCtx.result.emittedTxn.size() >= hookCtx.expected_etxn_count)
|
||||||
|
return ripple::Unexpected<std::int64_t>(hook_api::TOO_MANY_EMITTED_TXN);
|
||||||
|
|
||||||
|
std::shared_ptr<STTx const> stpTrans;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SerialIter sit(txBlob);
|
||||||
|
stpTrans = std::make_shared<STTx const>(sit);
|
||||||
|
}
|
||||||
|
catch (std::exception const& e)
|
||||||
|
{
|
||||||
|
JLOG(j.trace()) << "HookEmit[" << HC_ACC() << "]: Failed " << e.what();
|
||||||
|
return ripple::Unexpected<std::int64_t>(hook_api::EMISSION_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPseudoTx(*stpTrans))
|
||||||
|
{
|
||||||
|
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||||
|
<< "]: Attempted to emit pseudo txn.";
|
||||||
|
return ripple::Unexpected<std::int64_t>(hook_api::EMISSION_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
ripple::TxType txType = stpTrans->getTxnType();
|
||||||
|
|
||||||
|
ripple::uint256 const& hookCanEmit = hookCtx.result.hookCanEmit;
|
||||||
|
if (!hook::canEmit(txType, hookCanEmit))
|
||||||
|
{
|
||||||
|
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||||
|
<< "]: Hook cannot emit this txn.";
|
||||||
|
return ripple::Unexpected<std::uint64_t>(hook_api::EMISSION_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the emitted txn is valid
|
||||||
|
/* Emitted TXN rules
|
||||||
|
* 0. Account must match the hook account
|
||||||
|
* 1. Sequence: 0
|
||||||
|
* 2. PubSigningKey: 000000000000000
|
||||||
|
* 3. sfEmitDetails present and valid
|
||||||
|
* 4. No sfTxnSignature
|
||||||
|
* 5. LastLedgerSeq > current ledger, > firstledgerseq & LastLedgerSeq < seq
|
||||||
|
* + 5
|
||||||
|
* 6. FirstLedgerSeq > current ledger
|
||||||
|
* 7. Fee must be correctly high
|
||||||
|
* 8. The generation cannot be higher than 10
|
||||||
|
*/
|
||||||
|
|
||||||
|
// rule 0: account must match the hook account
|
||||||
|
if (!stpTrans->isFieldPresent(sfAccount) ||
|
||||||
|
stpTrans->getAccountID(sfAccount) != hookCtx.result.account)
|
||||||
|
{
|
||||||
|
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||||
|
<< "]: sfAccount does not match hook account";
|
||||||
|
return ripple::Unexpected<std::int64_t>(hook_api::EMISSION_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// rule 1: sfSequence must be present and 0
|
||||||
|
if (!stpTrans->isFieldPresent(sfSequence) ||
|
||||||
|
stpTrans->getFieldU32(sfSequence) != 0)
|
||||||
|
{
|
||||||
|
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||||
|
<< "]: sfSequence missing or non-zero";
|
||||||
|
return ripple::Unexpected<std::int64_t>(hook_api::EMISSION_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// rule 2: sfSigningPubKey must be present and 00...00
|
||||||
|
if (!stpTrans->isFieldPresent(sfSigningPubKey))
|
||||||
|
{
|
||||||
|
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||||
|
<< "]: sfSigningPubKey missing";
|
||||||
|
return ripple::Unexpected<std::int64_t>(hook_api::EMISSION_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto const pk = stpTrans->getSigningPubKey();
|
||||||
|
if (pk.size() != 33 && pk.size() != 0)
|
||||||
|
{
|
||||||
|
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||||
|
<< "]: sfSigningPubKey present but wrong size";
|
||||||
|
return ripple::Unexpected<std::int64_t>(hook_api::EMISSION_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < pk.size(); ++i)
|
||||||
|
if (pk[i] != 0)
|
||||||
|
{
|
||||||
|
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||||
|
<< "]: sfSigningPubKey present but non-zero.";
|
||||||
|
return ripple::Unexpected<std::int64_t>(hook_api::EMISSION_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// rule 2.a: no signers
|
||||||
|
if (stpTrans->isFieldPresent(sfSigners))
|
||||||
|
{
|
||||||
|
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||||
|
<< "]: sfSigners not allowed in emitted txns.";
|
||||||
|
return ripple::Unexpected<std::int64_t>(hook_api::EMISSION_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// rule 2.b: ticketseq cannot be used
|
||||||
|
if (stpTrans->isFieldPresent(sfTicketSequence))
|
||||||
|
{
|
||||||
|
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||||
|
<< "]: sfTicketSequence not allowed in emitted txns.";
|
||||||
|
return ripple::Unexpected<std::int64_t>(hook_api::EMISSION_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// rule 2.c sfAccountTxnID not allowed
|
||||||
|
if (stpTrans->isFieldPresent(sfAccountTxnID))
|
||||||
|
{
|
||||||
|
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||||
|
<< "]: sfAccountTxnID not allowed in emitted txns.";
|
||||||
|
return ripple::Unexpected<std::int64_t>(hook_api::EMISSION_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// rule 3: sfEmitDetails must be present and valid
|
||||||
|
if (!stpTrans->isFieldPresent(sfEmitDetails))
|
||||||
|
{
|
||||||
|
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||||
|
<< "]: sfEmitDetails missing.";
|
||||||
|
return ripple::Unexpected<std::int64_t>(hook_api::EMISSION_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto const& emitDetails = const_cast<ripple::STTx&>(*stpTrans)
|
||||||
|
.getField(sfEmitDetails)
|
||||||
|
.downcast<STObject>();
|
||||||
|
|
||||||
|
if (!emitDetails.isFieldPresent(sfEmitGeneration) ||
|
||||||
|
!emitDetails.isFieldPresent(sfEmitBurden) ||
|
||||||
|
!emitDetails.isFieldPresent(sfEmitParentTxnID) ||
|
||||||
|
!emitDetails.isFieldPresent(sfEmitNonce) ||
|
||||||
|
!emitDetails.isFieldPresent(sfEmitHookHash))
|
||||||
|
{
|
||||||
|
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||||
|
<< "]: sfEmitDetails malformed.";
|
||||||
|
return ripple::Unexpected<std::int64_t>(hook_api::EMISSION_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// rule 8: emit generation cannot exceed 10
|
||||||
|
if (emitDetails.getFieldU32(sfEmitGeneration) >= 10)
|
||||||
|
{
|
||||||
|
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||||
|
<< "]: sfEmitGeneration was 10 or more.";
|
||||||
|
return ripple::Unexpected<std::int64_t>(hook_api::EMISSION_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto const gen = emitDetails.getFieldU32(sfEmitGeneration);
|
||||||
|
auto const bur = emitDetails.getFieldU64(sfEmitBurden);
|
||||||
|
auto const pTxnID = emitDetails.getFieldH256(sfEmitParentTxnID);
|
||||||
|
auto const nonce = emitDetails.getFieldH256(sfEmitNonce);
|
||||||
|
|
||||||
|
std::optional<ripple::AccountID> callback;
|
||||||
|
if (emitDetails.isFieldPresent(sfEmitCallback))
|
||||||
|
callback = emitDetails.getAccountID(sfEmitCallback);
|
||||||
|
|
||||||
|
auto const& hash = emitDetails.getFieldH256(sfEmitHookHash);
|
||||||
|
|
||||||
|
std::uint32_t gen_proper = static_cast<std::uint32_t>(etxn_generation());
|
||||||
|
|
||||||
|
if (gen != gen_proper)
|
||||||
|
{
|
||||||
|
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||||
|
<< "]: sfEmitGeneration provided in EmitDetails "
|
||||||
|
<< "not correct (" << gen << ") "
|
||||||
|
<< "should be " << gen_proper;
|
||||||
|
return ripple::Unexpected<std::int64_t>(hook_api::EMISSION_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint64_t bur_proper = static_cast<std::uint64_t>(etxn_burden());
|
||||||
|
if (bur != bur_proper)
|
||||||
|
{
|
||||||
|
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||||
|
<< "]: sfEmitBurden provided in EmitDetails "
|
||||||
|
<< "was not correct (" << bur << ") "
|
||||||
|
<< "should be " << bur_proper;
|
||||||
|
return ripple::Unexpected<std::int64_t>(hook_api::EMISSION_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pTxnID != applyCtx.tx.getTransactionID())
|
||||||
|
{
|
||||||
|
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||||
|
<< "]: sfEmitParentTxnID provided in EmitDetails"
|
||||||
|
<< "was not correct";
|
||||||
|
return ripple::Unexpected<std::int64_t>(hook_api::EMISSION_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hookCtx.nonce_used.find(nonce) == hookCtx.nonce_used.end())
|
||||||
|
{
|
||||||
|
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||||
|
<< "]: sfEmitNonce provided in EmitDetails was not "
|
||||||
|
"generated by nonce api";
|
||||||
|
return ripple::Unexpected<std::int64_t>(hook_api::EMISSION_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callback && *callback != hookCtx.result.account)
|
||||||
|
{
|
||||||
|
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||||
|
<< "]: sfEmitCallback account must be the account of "
|
||||||
|
"the emitting hook";
|
||||||
|
return ripple::Unexpected<std::int64_t>(hook_api::EMISSION_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hash != hookCtx.result.hookHash)
|
||||||
|
{
|
||||||
|
JLOG(j.trace())
|
||||||
|
<< "HookEmit[" << HC_ACC()
|
||||||
|
<< "]: sfEmitHookHash must be the hash of the emitting hook";
|
||||||
|
return ripple::Unexpected<std::int64_t>(hook_api::EMISSION_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// rule 4: sfTxnSignature must be absent
|
||||||
|
if (stpTrans->isFieldPresent(sfTxnSignature))
|
||||||
|
{
|
||||||
|
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||||
|
<< "]: sfTxnSignature is present but should not be";
|
||||||
|
return ripple::Unexpected<std::int64_t>(hook_api::EMISSION_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// rule 5: LastLedgerSeq must be present and after current ledger
|
||||||
|
if (!stpTrans->isFieldPresent(sfLastLedgerSequence))
|
||||||
|
{
|
||||||
|
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||||
|
<< "]: sfLastLedgerSequence missing";
|
||||||
|
return ripple::Unexpected<std::int64_t>(hook_api::EMISSION_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint32_t tx_lls = stpTrans->getFieldU32(sfLastLedgerSequence);
|
||||||
|
std::uint32_t ledgerSeq = view.info().seq;
|
||||||
|
if (tx_lls < ledgerSeq + 1)
|
||||||
|
{
|
||||||
|
JLOG(j.trace())
|
||||||
|
<< "HookEmit[" << HC_ACC()
|
||||||
|
<< "]: sfLastLedgerSequence invalid (less than next ledger)";
|
||||||
|
return ripple::Unexpected<std::int64_t>(hook_api::EMISSION_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tx_lls > ledgerSeq + 5)
|
||||||
|
{
|
||||||
|
JLOG(j.trace())
|
||||||
|
<< "HookEmit[" << HC_ACC()
|
||||||
|
<< "]: sfLastLedgerSequence cannot be greater than current seq + 5";
|
||||||
|
return ripple::Unexpected<std::int64_t>(hook_api::EMISSION_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// rule 6
|
||||||
|
if (!stpTrans->isFieldPresent(sfFirstLedgerSequence) ||
|
||||||
|
stpTrans->getFieldU32(sfFirstLedgerSequence) > tx_lls)
|
||||||
|
{
|
||||||
|
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||||
|
<< "]: sfFirstLedgerSequence must be present and <= "
|
||||||
|
"LastLedgerSequence";
|
||||||
|
return ripple::Unexpected<std::int64_t>(hook_api::EMISSION_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// rule 7 check the emitted txn pays the appropriate fee
|
||||||
|
std::int64_t minfee = etxn_fee_base(txBlob);
|
||||||
|
|
||||||
|
if (minfee < 0)
|
||||||
|
{
|
||||||
|
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||||
|
<< "]: Fee could not be calculated";
|
||||||
|
return ripple::Unexpected<std::int64_t>(hook_api::EMISSION_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stpTrans->isFieldPresent(sfFee))
|
||||||
|
{
|
||||||
|
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||||
|
<< "]: Fee missing from emitted tx";
|
||||||
|
return ripple::Unexpected<std::int64_t>(hook_api::EMISSION_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::int64_t fee = stpTrans->getFieldAmount(sfFee).xrp().drops();
|
||||||
|
if (fee < minfee)
|
||||||
|
{
|
||||||
|
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||||
|
<< "]: Fee less than minimum required";
|
||||||
|
return ripple::Unexpected<std::int64_t>(hook_api::EMISSION_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string reason;
|
||||||
|
auto tpTrans =
|
||||||
|
std::make_shared<Transaction>(stpTrans, reason, applyCtx.app);
|
||||||
|
if (tpTrans->getStatus() != NEW)
|
||||||
|
{
|
||||||
|
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||||
|
<< "]: tpTrans->getStatus() != NEW";
|
||||||
|
return ripple::Unexpected<std::int64_t>(hook_api::EMISSION_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// preflight the transaction
|
||||||
|
auto preflightResult = ripple::preflight(
|
||||||
|
applyCtx.app,
|
||||||
|
view.rules(),
|
||||||
|
*stpTrans,
|
||||||
|
ripple::ApplyFlags::tapPREFLIGHT_EMIT,
|
||||||
|
j);
|
||||||
|
|
||||||
|
if (!isTesSuccess(preflightResult.ter))
|
||||||
|
{
|
||||||
|
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||||
|
<< "]: Transaction preflight failure: "
|
||||||
|
<< preflightResult.ter;
|
||||||
|
return ripple::Unexpected<std::int64_t>(hook_api::EMISSION_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tpTrans;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace hook
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#include <ripple/app/hook/HookAPI.h>
|
||||||
#include <ripple/app/hook/applyHook.h>
|
#include <ripple/app/hook/applyHook.h>
|
||||||
#include <ripple/app/ledger/OpenLedger.h>
|
#include <ripple/app/ledger/OpenLedger.h>
|
||||||
#include <ripple/app/ledger/TransactionMaster.h>
|
#include <ripple/app/ledger/TransactionMaster.h>
|
||||||
@@ -1237,7 +1238,6 @@ hook::apply(
|
|||||||
.hookHash = hookHash,
|
.hookHash = hookHash,
|
||||||
.hookCanEmit = hookCanEmit,
|
.hookCanEmit = hookCanEmit,
|
||||||
.accountKeylet = keylet::account(account),
|
.accountKeylet = keylet::account(account),
|
||||||
.ownerDirKeylet = keylet::ownerDir(account),
|
|
||||||
.hookKeylet = keylet::hook(account),
|
.hookKeylet = keylet::hook(account),
|
||||||
.account = account,
|
.account = account,
|
||||||
.otxnAccount = applyCtx.tx.getAccountID(sfAccount),
|
.otxnAccount = applyCtx.tx.getAccountID(sfAccount),
|
||||||
@@ -2290,35 +2290,9 @@ DEFINE_HOOK_FUNCTION(int64_t, otxn_slot, uint32_t slot_into)
|
|||||||
// hook invocation
|
// hook invocation
|
||||||
DEFINE_HOOK_FUNCNARG(int64_t, otxn_burden)
|
DEFINE_HOOK_FUNCNARG(int64_t, otxn_burden)
|
||||||
{
|
{
|
||||||
HOOK_SETUP(); // populates memory_ctx, memory, memory_length, applyCtx,
|
HOOK_SETUP();
|
||||||
// hookCtx on current stack
|
hook::HookAPI api(hookCtx);
|
||||||
|
return api.otxn_burden();
|
||||||
if (hookCtx.burden)
|
|
||||||
return hookCtx.burden;
|
|
||||||
|
|
||||||
auto const& tx = applyCtx.tx;
|
|
||||||
if (!tx.isFieldPresent(sfEmitDetails))
|
|
||||||
return 1; // burden is always 1 if the tx wasn't a emit
|
|
||||||
|
|
||||||
auto const& pd = const_cast<ripple::STTx&>(tx)
|
|
||||||
.getField(sfEmitDetails)
|
|
||||||
.downcast<STObject>();
|
|
||||||
|
|
||||||
if (!pd.isFieldPresent(sfEmitBurden))
|
|
||||||
{
|
|
||||||
JLOG(j.warn())
|
|
||||||
<< "HookError[" << HC_ACC()
|
|
||||||
<< "]: found sfEmitDetails but sfEmitBurden was not present";
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t burden = pd.getFieldU64(sfEmitBurden);
|
|
||||||
burden &=
|
|
||||||
((1ULL << 63) -
|
|
||||||
1); // wipe out the two high bits just in case somehow they are set
|
|
||||||
hookCtx.burden = burden;
|
|
||||||
return (int64_t)(burden);
|
|
||||||
|
|
||||||
HOOK_TEARDOWN();
|
HOOK_TEARDOWN();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2327,32 +2301,9 @@ DEFINE_HOOK_FUNCNARG(int64_t, otxn_burden)
|
|||||||
// hook invocation
|
// hook invocation
|
||||||
DEFINE_HOOK_FUNCNARG(int64_t, otxn_generation)
|
DEFINE_HOOK_FUNCNARG(int64_t, otxn_generation)
|
||||||
{
|
{
|
||||||
HOOK_SETUP(); // populates memory_ctx, memory, memory_length, applyCtx,
|
HOOK_SETUP();
|
||||||
// hookCtx on current stack
|
hook::HookAPI api(hookCtx);
|
||||||
|
return api.otxn_generation();
|
||||||
// cache the result as it will not change for this hook execution
|
|
||||||
if (hookCtx.generation)
|
|
||||||
return hookCtx.generation;
|
|
||||||
|
|
||||||
auto const& tx = applyCtx.tx;
|
|
||||||
if (!tx.isFieldPresent(sfEmitDetails))
|
|
||||||
return 0; // generation is always 0 if the tx wasn't a emit
|
|
||||||
|
|
||||||
auto const& pd = const_cast<ripple::STTx&>(tx)
|
|
||||||
.getField(sfEmitDetails)
|
|
||||||
.downcast<STObject>();
|
|
||||||
|
|
||||||
if (!pd.isFieldPresent(sfEmitGeneration))
|
|
||||||
{
|
|
||||||
JLOG(j.warn())
|
|
||||||
<< "HookError[" << HC_ACC()
|
|
||||||
<< "]: found sfEmitDetails but sfEmitGeneration was not present";
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
hookCtx.generation = pd.getFieldU32(sfEmitGeneration);
|
|
||||||
return hookCtx.generation;
|
|
||||||
|
|
||||||
HOOK_TEARDOWN();
|
HOOK_TEARDOWN();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2360,7 +2311,8 @@ DEFINE_HOOK_FUNCNARG(int64_t, otxn_generation)
|
|||||||
DEFINE_HOOK_FUNCNARG(int64_t, etxn_generation)
|
DEFINE_HOOK_FUNCNARG(int64_t, etxn_generation)
|
||||||
{
|
{
|
||||||
// proxy only, no setup or teardown
|
// proxy only, no setup or teardown
|
||||||
return otxn_generation(hookCtx, frameCtx) + 1;
|
hook::HookAPI api(hookCtx);
|
||||||
|
return api.etxn_generation();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the current ledger sequence number
|
// Return the current ledger sequence number
|
||||||
@@ -3270,341 +3222,43 @@ DEFINE_HOOK_FUNCTION(
|
|||||||
if (write_len < 32)
|
if (write_len < 32)
|
||||||
return TOO_SMALL;
|
return TOO_SMALL;
|
||||||
|
|
||||||
auto& app = hookCtx.applyCtx.app;
|
// Delegate to decoupled HookAPI for emit logic
|
||||||
|
hook::HookAPI api(hookCtx);
|
||||||
if (hookCtx.expected_etxn_count < 0)
|
ripple::Slice txBlob{
|
||||||
return PREREQUISITE_NOT_MET;
|
reinterpret_cast<const void*>(memory + read_ptr), read_len};
|
||||||
|
if (auto res = api.emit(txBlob))
|
||||||
if (hookCtx.result.emittedTxn.size() >= hookCtx.expected_etxn_count)
|
|
||||||
return TOO_MANY_EMITTED_TXN;
|
|
||||||
|
|
||||||
ripple::Blob blob{memory + read_ptr, memory + read_ptr + read_len};
|
|
||||||
|
|
||||||
std::shared_ptr<STTx const> stpTrans;
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
stpTrans = std::make_shared<STTx const>(
|
auto const& tpTrans = *res; // 32 bytes
|
||||||
SerialIter{memory + read_ptr, read_len});
|
auto const& txID = tpTrans->getID();
|
||||||
|
|
||||||
|
if (txID.size() > write_len)
|
||||||
|
return TOO_SMALL;
|
||||||
|
|
||||||
|
if (NOT_IN_BOUNDS(write_ptr, txID.size(), memory_length))
|
||||||
|
return OUT_OF_BOUNDS;
|
||||||
|
|
||||||
|
auto const write_txid = [&]() -> int64_t {
|
||||||
|
WRITE_WASM_MEMORY_AND_RETURN(
|
||||||
|
write_ptr,
|
||||||
|
txID.size(),
|
||||||
|
txID.data(),
|
||||||
|
txID.size(),
|
||||||
|
memory,
|
||||||
|
memory_length);
|
||||||
|
};
|
||||||
|
|
||||||
|
int64_t result = write_txid();
|
||||||
|
|
||||||
|
if (result == 32)
|
||||||
|
hookCtx.result.emittedTxn.push(tpTrans);
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
catch (std::exception& e)
|
else
|
||||||
{
|
{
|
||||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC() << "]: Failed " << e.what()
|
return res.error();
|
||||||
<< "\n";
|
|
||||||
return EMISSION_FAILURE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPseudoTx(*stpTrans))
|
|
||||||
{
|
|
||||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
|
||||||
<< "]: Attempted to emit pseudo txn.";
|
|
||||||
return EMISSION_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
ripple::TxType txType = stpTrans->getTxnType();
|
|
||||||
|
|
||||||
ripple::uint256 const& hookCanEmit = hookCtx.result.hookCanEmit;
|
|
||||||
if (!hook::canEmit(txType, hookCanEmit))
|
|
||||||
{
|
|
||||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
|
||||||
<< "]: Hook cannot emit this txn.";
|
|
||||||
return EMISSION_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check the emitted txn is valid
|
|
||||||
/* Emitted TXN rules
|
|
||||||
* 0. Account must match the hook account
|
|
||||||
* 1. Sequence: 0
|
|
||||||
* 2. PubSigningKey: 000000000000000
|
|
||||||
* 3. sfEmitDetails present and valid
|
|
||||||
* 4. No sfTxnSignature
|
|
||||||
* 5. LastLedgerSeq > current ledger, > firstledgerseq & LastLedgerSeq < seq
|
|
||||||
* + 5
|
|
||||||
* 6. FirstLedgerSeq > current ledger
|
|
||||||
* 7. Fee must be correctly high
|
|
||||||
* 8. The generation cannot be higher than 10
|
|
||||||
*/
|
|
||||||
|
|
||||||
// rule 0: account must match the hook account
|
|
||||||
if (!stpTrans->isFieldPresent(sfAccount) ||
|
|
||||||
stpTrans->getAccountID(sfAccount) != hookCtx.result.account)
|
|
||||||
{
|
|
||||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
|
||||||
<< "]: sfAccount does not match hook account";
|
|
||||||
return EMISSION_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// rule 1: sfSequence must be present and 0
|
|
||||||
if (!stpTrans->isFieldPresent(sfSequence) ||
|
|
||||||
stpTrans->getFieldU32(sfSequence) != 0)
|
|
||||||
{
|
|
||||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
|
||||||
<< "]: sfSequence missing or non-zero";
|
|
||||||
return EMISSION_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// rule 2: sfSigningPubKey must be present and 00...00
|
|
||||||
if (!stpTrans->isFieldPresent(sfSigningPubKey))
|
|
||||||
{
|
|
||||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
|
||||||
<< "]: sfSigningPubKey missing";
|
|
||||||
return EMISSION_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto const pk = stpTrans->getSigningPubKey();
|
|
||||||
if (pk.size() != 33 && pk.size() != 0)
|
|
||||||
{
|
|
||||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
|
||||||
<< "]: sfSigningPubKey present but wrong size"
|
|
||||||
<< " expecting 33 bytes";
|
|
||||||
return EMISSION_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < pk.size(); ++i)
|
|
||||||
if (pk[i] != 0)
|
|
||||||
{
|
|
||||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
|
||||||
<< "]: sfSigningPubKey present but non-zero.";
|
|
||||||
return EMISSION_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// rule 2.a: no signers
|
|
||||||
if (stpTrans->isFieldPresent(sfSigners))
|
|
||||||
{
|
|
||||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
|
||||||
<< "]: sfSigners not allowed in emitted txns.";
|
|
||||||
return EMISSION_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// rule 2.b: ticketseq cannot be used
|
|
||||||
if (stpTrans->isFieldPresent(sfTicketSequence))
|
|
||||||
{
|
|
||||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
|
||||||
<< "]: sfTicketSequence not allowed in emitted txns.";
|
|
||||||
return EMISSION_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// rule 2.c sfAccountTxnID not allowed
|
|
||||||
if (stpTrans->isFieldPresent(sfAccountTxnID))
|
|
||||||
{
|
|
||||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
|
||||||
<< "]: sfAccountTxnID not allowed in emitted txns.";
|
|
||||||
return EMISSION_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// rule 3: sfEmitDetails must be present and valid
|
|
||||||
if (!stpTrans->isFieldPresent(sfEmitDetails))
|
|
||||||
{
|
|
||||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
|
||||||
<< "]: sfEmitDetails missing.";
|
|
||||||
return EMISSION_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto const& emitDetails = const_cast<ripple::STTx&>(*stpTrans)
|
|
||||||
.getField(sfEmitDetails)
|
|
||||||
.downcast<STObject>();
|
|
||||||
|
|
||||||
if (!emitDetails.isFieldPresent(sfEmitGeneration) ||
|
|
||||||
!emitDetails.isFieldPresent(sfEmitBurden) ||
|
|
||||||
!emitDetails.isFieldPresent(sfEmitParentTxnID) ||
|
|
||||||
!emitDetails.isFieldPresent(sfEmitNonce) ||
|
|
||||||
!emitDetails.isFieldPresent(sfEmitHookHash))
|
|
||||||
{
|
|
||||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
|
||||||
<< "]: sfEmitDetails malformed.";
|
|
||||||
return EMISSION_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// rule 8: emit generation cannot exceed 10
|
|
||||||
if (emitDetails.getFieldU32(sfEmitGeneration) >= 10)
|
|
||||||
{
|
|
||||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
|
||||||
<< "]: sfEmitGeneration was 10 or more.";
|
|
||||||
return EMISSION_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t gen = emitDetails.getFieldU32(sfEmitGeneration);
|
|
||||||
uint64_t bur = emitDetails.getFieldU64(sfEmitBurden);
|
|
||||||
ripple::uint256 const& pTxnID = emitDetails.getFieldH256(sfEmitParentTxnID);
|
|
||||||
ripple::uint256 const& nonce = emitDetails.getFieldH256(sfEmitNonce);
|
|
||||||
|
|
||||||
std::optional<ripple::AccountID> callback;
|
|
||||||
if (emitDetails.isFieldPresent(sfEmitCallback))
|
|
||||||
callback = emitDetails.getAccountID(sfEmitCallback);
|
|
||||||
|
|
||||||
auto const& hash = emitDetails.getFieldH256(sfEmitHookHash);
|
|
||||||
|
|
||||||
uint32_t gen_proper = etxn_generation(hookCtx, frameCtx);
|
|
||||||
|
|
||||||
if (gen != gen_proper)
|
|
||||||
{
|
|
||||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
|
||||||
<< "]: sfEmitGeneration provided in EmitDetails "
|
|
||||||
<< "not correct (" << gen << ") "
|
|
||||||
<< "should be " << gen_proper;
|
|
||||||
return EMISSION_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t bur_proper = etxn_burden(hookCtx, frameCtx);
|
|
||||||
if (bur != bur_proper)
|
|
||||||
{
|
|
||||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
|
||||||
<< "]: sfEmitBurden provided in EmitDetails "
|
|
||||||
<< "was not correct (" << bur << ") "
|
|
||||||
<< "should be " << bur_proper;
|
|
||||||
return EMISSION_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pTxnID != applyCtx.tx.getTransactionID())
|
|
||||||
{
|
|
||||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
|
||||||
<< "]: sfEmitParentTxnID provided in EmitDetails "
|
|
||||||
<< "was not correct";
|
|
||||||
return EMISSION_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hookCtx.nonce_used.find(nonce) == hookCtx.nonce_used.end())
|
|
||||||
{
|
|
||||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
|
||||||
<< "]: sfEmitNonce provided in EmitDetails "
|
|
||||||
<< "was not generated by nonce api";
|
|
||||||
return EMISSION_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (callback && *callback != hookCtx.result.account)
|
|
||||||
{
|
|
||||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
|
||||||
<< "]: sfEmitCallback account must be the account "
|
|
||||||
<< "of the emitting hook";
|
|
||||||
return EMISSION_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hash != hookCtx.result.hookHash)
|
|
||||||
{
|
|
||||||
JLOG(j.trace())
|
|
||||||
<< "HookEmit[" << HC_ACC()
|
|
||||||
<< "]: sfEmitHookHash must be the hash of the emitting hook";
|
|
||||||
return EMISSION_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// rule 4: sfTxnSignature must be absent
|
|
||||||
if (stpTrans->isFieldPresent(sfTxnSignature))
|
|
||||||
{
|
|
||||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
|
||||||
<< "]: sfTxnSignature is present but should not be";
|
|
||||||
return EMISSION_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// rule 5: LastLedgerSeq must be present and after current ledger
|
|
||||||
if (!stpTrans->isFieldPresent(sfLastLedgerSequence))
|
|
||||||
{
|
|
||||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
|
||||||
<< "]: sfLastLedgerSequence missing";
|
|
||||||
return EMISSION_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t tx_lls = stpTrans->getFieldU32(sfLastLedgerSequence);
|
|
||||||
uint32_t ledgerSeq = view.info().seq;
|
|
||||||
if (tx_lls < ledgerSeq + 1)
|
|
||||||
{
|
|
||||||
JLOG(j.trace())
|
|
||||||
<< "HookEmit[" << HC_ACC()
|
|
||||||
<< "]: sfLastLedgerSequence invalid (less than next ledger)";
|
|
||||||
return EMISSION_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tx_lls > ledgerSeq + 5)
|
|
||||||
{
|
|
||||||
JLOG(j.trace())
|
|
||||||
<< "HookEmit[" << HC_ACC()
|
|
||||||
<< "]: sfLastLedgerSequence cannot be greater than current seq + 5";
|
|
||||||
return EMISSION_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// rule 6
|
|
||||||
if (!stpTrans->isFieldPresent(sfFirstLedgerSequence) ||
|
|
||||||
stpTrans->getFieldU32(sfFirstLedgerSequence) > tx_lls)
|
|
||||||
{
|
|
||||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
|
||||||
<< "]: sfFirstLedgerSequence must be present and "
|
|
||||||
<< "<= LastLedgerSequence";
|
|
||||||
return EMISSION_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// rule 7 check the emitted txn pays the appropriate fee
|
|
||||||
int64_t minfee = etxn_fee_base(hookCtx, frameCtx, read_ptr, read_len);
|
|
||||||
|
|
||||||
if (minfee < 0)
|
|
||||||
{
|
|
||||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
|
||||||
<< "]: Fee could not be calculated";
|
|
||||||
return EMISSION_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!stpTrans->isFieldPresent(sfFee))
|
|
||||||
{
|
|
||||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
|
||||||
<< "]: Fee missing from emitted tx";
|
|
||||||
return EMISSION_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
int64_t fee = stpTrans->getFieldAmount(sfFee).xrp().drops();
|
|
||||||
if (fee < minfee)
|
|
||||||
{
|
|
||||||
JLOG(j.trace())
|
|
||||||
<< "HookEmit[" << HC_ACC()
|
|
||||||
<< "]: Fee on emitted txn is less than the minimum required fee";
|
|
||||||
return EMISSION_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string reason;
|
|
||||||
auto tpTrans = std::make_shared<Transaction>(stpTrans, reason, app);
|
|
||||||
if (tpTrans->getStatus() != NEW)
|
|
||||||
{
|
|
||||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
|
||||||
<< "]: tpTrans->getStatus() != NEW";
|
|
||||||
return EMISSION_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// preflight the transaction
|
|
||||||
auto preflightResult = ripple::preflight(
|
|
||||||
applyCtx.app,
|
|
||||||
applyCtx.view().rules(),
|
|
||||||
*stpTrans,
|
|
||||||
ripple::ApplyFlags::tapPREFLIGHT_EMIT,
|
|
||||||
j);
|
|
||||||
|
|
||||||
if (!isTesSuccess(preflightResult.ter))
|
|
||||||
{
|
|
||||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
|
||||||
<< "]: Transaction preflight failure: "
|
|
||||||
<< preflightResult.ter;
|
|
||||||
return EMISSION_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto const& txID = tpTrans->getID();
|
|
||||||
|
|
||||||
if (txID.size() > write_len)
|
|
||||||
return TOO_SMALL;
|
|
||||||
|
|
||||||
if (NOT_IN_BOUNDS(write_ptr, txID.size(), memory_length))
|
|
||||||
return OUT_OF_BOUNDS;
|
|
||||||
|
|
||||||
auto const write_txid = [&]() -> int64_t {
|
|
||||||
WRITE_WASM_MEMORY_AND_RETURN(
|
|
||||||
write_ptr,
|
|
||||||
txID.size(),
|
|
||||||
txID.data(),
|
|
||||||
txID.size(),
|
|
||||||
memory,
|
|
||||||
memory_length);
|
|
||||||
};
|
|
||||||
|
|
||||||
int64_t result = write_txid();
|
|
||||||
|
|
||||||
if (result == 32)
|
|
||||||
hookCtx.result.emittedTxn.push(tpTrans);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
HOOK_TEARDOWN();
|
HOOK_TEARDOWN();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3834,22 +3488,9 @@ DEFINE_HOOK_FUNCTION(int64_t, etxn_reserve, uint32_t count)
|
|||||||
// Compute the burden of an emitted transaction based on a number of factors
|
// Compute the burden of an emitted transaction based on a number of factors
|
||||||
DEFINE_HOOK_FUNCNARG(int64_t, etxn_burden)
|
DEFINE_HOOK_FUNCNARG(int64_t, etxn_burden)
|
||||||
{
|
{
|
||||||
HOOK_SETUP(); // populates memory_ctx, memory, memory_length, applyCtx,
|
HOOK_SETUP();
|
||||||
// hookCtx on current stack
|
hook::HookAPI api(hookCtx);
|
||||||
|
return api.etxn_burden();
|
||||||
if (hookCtx.expected_etxn_count <= -1)
|
|
||||||
return PREREQUISITE_NOT_MET;
|
|
||||||
|
|
||||||
uint64_t last_burden = (uint64_t)otxn_burden(
|
|
||||||
hookCtx, frameCtx); // always non-negative so cast is safe
|
|
||||||
|
|
||||||
uint64_t burden = last_burden * hookCtx.expected_etxn_count;
|
|
||||||
if (burden <
|
|
||||||
last_burden) // this overflow will never happen but handle it anyway
|
|
||||||
return FEE_TOO_LARGE;
|
|
||||||
|
|
||||||
return burden;
|
|
||||||
|
|
||||||
HOOK_TEARDOWN();
|
HOOK_TEARDOWN();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4633,36 +4274,13 @@ DEFINE_HOOK_FUNCTION(
|
|||||||
uint32_t read_ptr,
|
uint32_t read_ptr,
|
||||||
uint32_t read_len)
|
uint32_t read_len)
|
||||||
{
|
{
|
||||||
HOOK_SETUP(); // populates memory_ctx, memory, memory_length, applyCtx,
|
HOOK_SETUP();
|
||||||
// hookCtx on current stack
|
|
||||||
|
|
||||||
if (NOT_IN_BOUNDS(read_ptr, read_len, memory_length))
|
if (NOT_IN_BOUNDS(read_ptr, read_len, memory_length))
|
||||||
return OUT_OF_BOUNDS;
|
return OUT_OF_BOUNDS;
|
||||||
|
hook::HookAPI api(hookCtx);
|
||||||
if (hookCtx.expected_etxn_count <= -1)
|
ripple::Slice tx{
|
||||||
return PREREQUISITE_NOT_MET;
|
reinterpret_cast<const void*>(read_ptr + memory), read_len};
|
||||||
|
return api.etxn_fee_base(tx);
|
||||||
try
|
|
||||||
{
|
|
||||||
ripple::Slice tx{
|
|
||||||
reinterpret_cast<const void*>(read_ptr + memory), read_len};
|
|
||||||
|
|
||||||
SerialIter sitTrans(tx);
|
|
||||||
|
|
||||||
std::unique_ptr<STTx const> stpTrans;
|
|
||||||
stpTrans = std::make_unique<STTx const>(std::ref(sitTrans));
|
|
||||||
|
|
||||||
return Transactor::calculateBaseFee(
|
|
||||||
*(applyCtx.app.openLedger().current()), *stpTrans)
|
|
||||||
.drops();
|
|
||||||
}
|
|
||||||
catch (std::exception& e)
|
|
||||||
{
|
|
||||||
JLOG(j.trace()) << "HookInfo[" << HC_ACC()
|
|
||||||
<< "]: etxn_fee_base exception: " << e.what();
|
|
||||||
return INVALID_TXN;
|
|
||||||
}
|
|
||||||
|
|
||||||
HOOK_TEARDOWN();
|
HOOK_TEARDOWN();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,11 +17,19 @@
|
|||||||
*/
|
*/
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
#include <ripple/app/hook/Enum.h>
|
#include <ripple/app/hook/Enum.h>
|
||||||
|
#include <ripple/app/hook/HookAPI.h>
|
||||||
|
#include <ripple/app/hook/applyHook.h>
|
||||||
#include <ripple/app/ledger/LedgerMaster.h>
|
#include <ripple/app/ledger/LedgerMaster.h>
|
||||||
|
#include <ripple/app/tx/impl/ApplyContext.h>
|
||||||
#include <ripple/app/tx/impl/SetHook.h>
|
#include <ripple/app/tx/impl/SetHook.h>
|
||||||
|
#include <ripple/basics/base_uint.h>
|
||||||
#include <ripple/json/json_reader.h>
|
#include <ripple/json/json_reader.h>
|
||||||
#include <ripple/json/json_writer.h>
|
#include <ripple/json/json_writer.h>
|
||||||
|
#include <ripple/ledger/OpenView.h>
|
||||||
|
#include <ripple/protocol/SField.h>
|
||||||
|
#include <ripple/protocol/STAccount.h>
|
||||||
#include <ripple/protocol/TxFlags.h>
|
#include <ripple/protocol/TxFlags.h>
|
||||||
|
#include <ripple/protocol/TxFormats.h>
|
||||||
#include <ripple/protocol/jss.h>
|
#include <ripple/protocol/jss.h>
|
||||||
#include <test/app/Import_json.h>
|
#include <test/app/Import_json.h>
|
||||||
#include <test/app/SetHook_wasm.h>
|
#include <test/app/SetHook_wasm.h>
|
||||||
@@ -2431,15 +2439,105 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ApplyContext
|
||||||
|
createApplyContext(jtx::Env& env, OpenView ov, STTx const& tx)
|
||||||
|
{
|
||||||
|
ApplyContext applyCtx{
|
||||||
|
env.app(),
|
||||||
|
ov,
|
||||||
|
tx,
|
||||||
|
tesSUCCESS,
|
||||||
|
env.current()->fees().base,
|
||||||
|
tapNONE,
|
||||||
|
env.journal};
|
||||||
|
return applyCtx;
|
||||||
|
}
|
||||||
|
|
||||||
|
hook::HookContext
|
||||||
|
createHookContext(
|
||||||
|
AccountID const& hookAccount,
|
||||||
|
AccountID const& otxnAccount,
|
||||||
|
hook::HookContext ctx)
|
||||||
|
{
|
||||||
|
hook::HookContext hookCtx{
|
||||||
|
.applyCtx = ctx.applyCtx,
|
||||||
|
.result =
|
||||||
|
{
|
||||||
|
.hookSetTxnID = uint256(),
|
||||||
|
.hookHash = uint256(),
|
||||||
|
.hookCanEmit = uint256(),
|
||||||
|
.accountKeylet = keylet::account(otxnAccount),
|
||||||
|
.hookKeylet = keylet::hook(hookAccount),
|
||||||
|
.account = otxnAccount,
|
||||||
|
.otxnAccount = otxnAccount,
|
||||||
|
.hookNamespace = uint256(),
|
||||||
|
.stateMap = ctx.result.stateMap,
|
||||||
|
.hookParamOverrides = {},
|
||||||
|
.hookParams = {{}},
|
||||||
|
.hookSkips = {uint256{}},
|
||||||
|
},
|
||||||
|
.module = nullptr};
|
||||||
|
|
||||||
|
return hookCtx;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
test_emit(FeatureBitset features)
|
test_emit(FeatureBitset features)
|
||||||
{
|
{
|
||||||
testcase("Test float_emit");
|
testcase("Test float_emit");
|
||||||
using namespace jtx;
|
using namespace jtx;
|
||||||
Env env{*this, features};
|
|
||||||
|
|
||||||
auto const alice = Account{"alice"};
|
auto const alice = Account{"alice"};
|
||||||
auto const bob = Account{"bob"};
|
auto const bob = Account{"bob"};
|
||||||
|
|
||||||
|
{
|
||||||
|
Env env{*this, features};
|
||||||
|
|
||||||
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
||||||
|
OpenView ov{*env.current()};
|
||||||
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
||||||
|
|
||||||
|
hook::HookStateMap stateMap;
|
||||||
|
auto hookCtx = hook::HookContext{
|
||||||
|
.applyCtx = applyCtx,
|
||||||
|
.expected_etxn_count = 1,
|
||||||
|
.nonce_used = {{uint256(0), true}},
|
||||||
|
.result =
|
||||||
|
{
|
||||||
|
.account = alice.id(),
|
||||||
|
.accountKeylet = keylet::account(alice),
|
||||||
|
.hookKeylet = keylet::hook(alice),
|
||||||
|
.stateMap = stateMap,
|
||||||
|
.hookParams = {{}},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
hook::HookAPI api(hookCtx);
|
||||||
|
|
||||||
|
STTx emitTx = STTx(ttINVOKE, [&](STObject& obj) {
|
||||||
|
obj[sfAccount] = alice.id();
|
||||||
|
obj[sfSequence] = 0;
|
||||||
|
obj[sfSigningPubKey] = PublicKey();
|
||||||
|
obj[sfFirstLedgerSequence] = env.closed()->seq() + 1;
|
||||||
|
obj[sfLastLedgerSequence] = env.closed()->seq() + 5;
|
||||||
|
obj[sfFee] = env.closed()->fees().base;
|
||||||
|
|
||||||
|
auto& emitDetails = obj.peekFieldObject(sfEmitDetails);
|
||||||
|
emitDetails[sfEmitGeneration] = 1;
|
||||||
|
emitDetails[sfEmitBurden] = 1;
|
||||||
|
emitDetails[sfEmitParentTxnID] = invokeTx.getTransactionID();
|
||||||
|
emitDetails[sfEmitNonce] = uint256();
|
||||||
|
emitDetails[sfEmitHookHash] = uint256();
|
||||||
|
});
|
||||||
|
|
||||||
|
Serializer s;
|
||||||
|
emitTx.add(s);
|
||||||
|
auto result = api.emit(s.slice());
|
||||||
|
BEAST_EXPECT(result.value()->getID() == emitTx.getTransactionID());
|
||||||
|
}
|
||||||
|
|
||||||
|
Env env{*this, features};
|
||||||
|
|
||||||
env.fund(XRP(10000), alice);
|
env.fund(XRP(10000), alice);
|
||||||
env.fund(XRP(10000), bob);
|
env.fund(XRP(10000), bob);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user