mirror of
https://github.com/Xahau/xahaud.git
synced 2025-11-25 13:05:49 +00:00
Hook API Unit Testing
This commit is contained in:
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
|
||||
Reference in New Issue
Block a user