mirror of
https://github.com/Xahau/xahaud.git
synced 2026-06-09 19:56:35 +00:00
refactor(hook): extract xport wrapper builder
This commit is contained in:
283
src/test/app/XportWrapperBuilder_test.cpp
Normal file
283
src/test/app/XportWrapperBuilder_test.cpp
Normal file
@@ -0,0 +1,283 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
|
||||
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 <xrpld/app/hook/detail/XportWrapperBuilder.h>
|
||||
#include <xrpl/basics/Expected.h>
|
||||
#include <xrpl/beast/unit_test.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace {
|
||||
|
||||
uint256
|
||||
makeHash(char const* label)
|
||||
{
|
||||
return sha512Half(Slice(label, std::strlen(label)));
|
||||
}
|
||||
|
||||
STTx
|
||||
makeSTTx(STObject const& obj)
|
||||
{
|
||||
Serializer s;
|
||||
obj.add(s);
|
||||
SerialIter sit{s.slice()};
|
||||
return STTx{std::ref(sit)};
|
||||
}
|
||||
|
||||
Blob
|
||||
serialize(STTx const& tx)
|
||||
{
|
||||
Serializer s;
|
||||
tx.add(s);
|
||||
return {s.begin(), s.end()};
|
||||
}
|
||||
|
||||
STTx
|
||||
makeExportedPayment(
|
||||
AccountID const& src,
|
||||
AccountID const& dst,
|
||||
std::optional<std::uint32_t> networkID = std::nullopt)
|
||||
{
|
||||
STObject obj(sfExportedTxn);
|
||||
obj.setFieldU16(sfTransactionType, ttPAYMENT);
|
||||
obj.setFieldU32(sfFlags, tfFullyCanonicalSig);
|
||||
obj.setFieldU32(sfSequence, 0);
|
||||
obj.setFieldU32(sfTicketSequence, 1);
|
||||
obj.setFieldU32(sfFirstLedgerSequence, 2);
|
||||
obj.setFieldU32(sfLastLedgerSequence, 6);
|
||||
obj.setFieldAmount(sfAmount, XRPAmount{1000000});
|
||||
obj.setFieldAmount(sfFee, XRPAmount{10});
|
||||
obj.setFieldVL(sfSigningPubKey, Blob{});
|
||||
obj.setAccountID(sfAccount, src);
|
||||
obj.setAccountID(sfDestination, dst);
|
||||
if (networkID)
|
||||
obj.setFieldU32(sfNetworkID, *networkID);
|
||||
return makeSTTx(obj);
|
||||
}
|
||||
|
||||
beast::Journal
|
||||
nullJournal()
|
||||
{
|
||||
return beast::Journal{beast::Journal::getNullSink()};
|
||||
}
|
||||
|
||||
hook::XportWrapperBuilder::Input
|
||||
makeInput(
|
||||
Slice innerTxBlob,
|
||||
AccountID const& exporter,
|
||||
std::uint32_t networkID = 21337,
|
||||
hook::XportWrapperBuilder::NonceGenerator generateNonce =
|
||||
[] {
|
||||
return Expected<uint256, hook_api::hook_return_code>{
|
||||
makeHash("nonce")};
|
||||
},
|
||||
hook::XportWrapperBuilder::FeeCalculator calculateFee =
|
||||
[](Slice const&) {
|
||||
return Expected<std::uint64_t, hook_api::hook_return_code>{12345};
|
||||
})
|
||||
{
|
||||
return hook::XportWrapperBuilder::Input{
|
||||
innerTxBlob,
|
||||
exporter,
|
||||
networkID,
|
||||
10,
|
||||
makeHash("parent-tx"),
|
||||
makeHash("hook-hash"),
|
||||
true,
|
||||
3,
|
||||
7,
|
||||
std::move(generateNonce),
|
||||
std::move(calculateFee),
|
||||
nullJournal()};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class XportWrapperBuilder_test : public beast::unit_test::suite
|
||||
{
|
||||
public:
|
||||
void
|
||||
testBuildsWrapper()
|
||||
{
|
||||
testcase("builds xport wrapper");
|
||||
|
||||
auto const exporter = randomKeyPair(KeyType::secp256k1);
|
||||
auto const dst = randomKeyPair(KeyType::secp256k1);
|
||||
auto const innerTx = makeExportedPayment(
|
||||
calcAccountID(exporter.first), calcAccountID(dst.first));
|
||||
auto const serialized = serialize(innerTx);
|
||||
|
||||
auto const result = hook::XportWrapperBuilder::build(makeInput(
|
||||
Slice(serialized.data(), serialized.size()),
|
||||
calcAccountID(exporter.first)));
|
||||
|
||||
BEAST_EXPECT(result);
|
||||
if (!result)
|
||||
return;
|
||||
|
||||
auto const& wrapper = result->wrapperTx;
|
||||
BEAST_EXPECT(result->innerTxHash == innerTx.getTransactionID());
|
||||
BEAST_EXPECT(wrapper.getTxnType() == ttEXPORT);
|
||||
BEAST_EXPECT(
|
||||
wrapper.getAccountID(sfAccount) == calcAccountID(exporter.first));
|
||||
BEAST_EXPECT(wrapper.getFieldU32(sfSequence) == 0);
|
||||
BEAST_EXPECT(wrapper.getFieldU32(sfFirstLedgerSequence) == 11);
|
||||
BEAST_EXPECT(wrapper.getFieldU32(sfLastLedgerSequence) == 15);
|
||||
BEAST_EXPECT(wrapper.getFieldAmount(sfFee) == STAmount{12345});
|
||||
BEAST_EXPECT(wrapper.getFieldVL(sfSigningPubKey).empty());
|
||||
|
||||
auto const& exported =
|
||||
wrapper.peekAtField(sfExportedTxn).downcast<STObject>();
|
||||
Serializer exportedSer;
|
||||
exported.add(exportedSer);
|
||||
STTx parsedInner{SerialIter{exportedSer.slice()}};
|
||||
BEAST_EXPECT(
|
||||
parsedInner.getTransactionID() == innerTx.getTransactionID());
|
||||
|
||||
auto const& emitDetails =
|
||||
wrapper.peekAtField(sfEmitDetails).downcast<STObject>();
|
||||
BEAST_EXPECT(emitDetails.getFieldU32(sfEmitGeneration) == 3);
|
||||
BEAST_EXPECT(emitDetails.getFieldU64(sfEmitBurden) == 7);
|
||||
BEAST_EXPECT(
|
||||
emitDetails.getFieldH256(sfEmitParentTxnID) ==
|
||||
makeHash("parent-tx"));
|
||||
BEAST_EXPECT(
|
||||
emitDetails.getFieldH256(sfEmitNonce) == makeHash("nonce"));
|
||||
BEAST_EXPECT(
|
||||
emitDetails.getFieldH256(sfEmitHookHash) == makeHash("hook-hash"));
|
||||
BEAST_EXPECT(
|
||||
emitDetails.getAccountID(sfEmitCallback) ==
|
||||
calcAccountID(exporter.first));
|
||||
}
|
||||
|
||||
void
|
||||
testRejectsInvalidInputs()
|
||||
{
|
||||
testcase("rejects invalid inputs");
|
||||
|
||||
auto const exporter = randomKeyPair(KeyType::secp256k1);
|
||||
auto const other = randomKeyPair(KeyType::secp256k1);
|
||||
auto const dst = randomKeyPair(KeyType::secp256k1);
|
||||
auto const innerTx = makeExportedPayment(
|
||||
calcAccountID(exporter.first), calcAccountID(dst.first));
|
||||
auto const serialized = serialize(innerTx);
|
||||
|
||||
{
|
||||
Blob malformed{1, 2, 3};
|
||||
bool nonceCalled = false;
|
||||
auto const result = hook::XportWrapperBuilder::build(makeInput(
|
||||
Slice(malformed.data(), malformed.size()),
|
||||
calcAccountID(exporter.first),
|
||||
21337,
|
||||
[&nonceCalled] {
|
||||
nonceCalled = true;
|
||||
return Expected<uint256, hook_api::hook_return_code>{
|
||||
makeHash("nonce")};
|
||||
}));
|
||||
BEAST_EXPECT(!result);
|
||||
BEAST_EXPECT(result.error() == hook_api::EXPORT_FAILURE);
|
||||
BEAST_EXPECT(!nonceCalled);
|
||||
}
|
||||
|
||||
{
|
||||
bool nonceCalled = false;
|
||||
auto const result = hook::XportWrapperBuilder::build(makeInput(
|
||||
Slice(serialized.data(), serialized.size()),
|
||||
calcAccountID(other.first),
|
||||
21337,
|
||||
[&nonceCalled] {
|
||||
nonceCalled = true;
|
||||
return Expected<uint256, hook_api::hook_return_code>{
|
||||
makeHash("nonce")};
|
||||
}));
|
||||
BEAST_EXPECT(!result);
|
||||
BEAST_EXPECT(result.error() == hook_api::EXPORT_FAILURE);
|
||||
BEAST_EXPECT(!nonceCalled);
|
||||
}
|
||||
|
||||
{
|
||||
auto const networkTx = makeExportedPayment(
|
||||
calcAccountID(exporter.first), calcAccountID(dst.first), 21337);
|
||||
auto const serializedNetwork = serialize(networkTx);
|
||||
bool nonceCalled = false;
|
||||
auto const result = hook::XportWrapperBuilder::build(makeInput(
|
||||
Slice(serializedNetwork.data(), serializedNetwork.size()),
|
||||
calcAccountID(exporter.first),
|
||||
21337,
|
||||
[&nonceCalled] {
|
||||
nonceCalled = true;
|
||||
return Expected<uint256, hook_api::hook_return_code>{
|
||||
makeHash("nonce")};
|
||||
}));
|
||||
BEAST_EXPECT(!result);
|
||||
BEAST_EXPECT(result.error() == hook_api::EXPORT_FAILURE);
|
||||
BEAST_EXPECT(!nonceCalled);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testRejectsFeeFailure()
|
||||
{
|
||||
testcase("rejects fee failure");
|
||||
|
||||
auto const exporter = randomKeyPair(KeyType::secp256k1);
|
||||
auto const dst = randomKeyPair(KeyType::secp256k1);
|
||||
auto const innerTx = makeExportedPayment(
|
||||
calcAccountID(exporter.first), calcAccountID(dst.first));
|
||||
auto const serialized = serialize(innerTx);
|
||||
|
||||
auto const result = hook::XportWrapperBuilder::build(makeInput(
|
||||
Slice(serialized.data(), serialized.size()),
|
||||
calcAccountID(exporter.first),
|
||||
21337,
|
||||
[] {
|
||||
return Expected<uint256, hook_api::hook_return_code>{
|
||||
makeHash("nonce")};
|
||||
},
|
||||
[](Slice const&) {
|
||||
return Expected<std::uint64_t, hook_api::hook_return_code>{
|
||||
Unexpected(hook_api::EXPORT_FAILURE)};
|
||||
}));
|
||||
|
||||
BEAST_EXPECT(!result);
|
||||
BEAST_EXPECT(result.error() == hook_api::EXPORT_FAILURE);
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testBuildsWrapper();
|
||||
testRejectsInvalidInputs();
|
||||
testRejectsFeeFailure();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(XportWrapperBuilder, app, ripple);
|
||||
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
@@ -1,6 +1,7 @@
|
||||
// Implementation of decoupled Hook APIs for emit and related helpers.
|
||||
|
||||
#include <xrpld/app/hook/HookAPI.h>
|
||||
#include <xrpld/app/hook/detail/XportWrapperBuilder.h>
|
||||
#include <xrpld/app/ledger/OpenLedger.h>
|
||||
#include <xrpld/app/ledger/TransactionMaster.h>
|
||||
#include <xrpld/app/tx/detail/ExportLedgerOps.h>
|
||||
@@ -1261,106 +1262,28 @@ HookAPI::xport(Slice const& txBlob) const
|
||||
if (hookCtx.export_count >= hookCtx.expected_export_count)
|
||||
return Unexpected(TOO_MANY_EXPORTED_TXN);
|
||||
|
||||
// Parse and validate the inner (cross-chain) transaction.
|
||||
std::shared_ptr<STTx const> innerTx;
|
||||
try
|
||||
{
|
||||
SerialIter sit(txBlob);
|
||||
innerTx = std::make_shared<STTx const>(sit);
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
JLOG(j.trace()) << "HookExport[" << HC_ACC() << "]: Failed "
|
||||
<< e.what();
|
||||
return Unexpected(EXPORT_FAILURE);
|
||||
}
|
||||
auto const burdenResult = etxn_burden();
|
||||
auto built = XportWrapperBuilder::build(XportWrapperBuilder::Input{
|
||||
txBlob,
|
||||
hookCtx.result.account,
|
||||
app.config().NETWORK_ID,
|
||||
view.info().seq,
|
||||
applyCtx.tx.getTransactionID(),
|
||||
hookCtx.result.hookHash,
|
||||
hookCtx.result.hasCallback,
|
||||
static_cast<uint32_t>(etxn_generation()),
|
||||
burdenResult ? static_cast<uint64_t>(*burdenResult) : 1ULL,
|
||||
[this]() { return etxn_nonce(); },
|
||||
[this](Slice const& serializedWrapper) {
|
||||
return etxn_fee_base(serializedWrapper);
|
||||
},
|
||||
j});
|
||||
if (!built)
|
||||
return Unexpected(built.error());
|
||||
|
||||
if (auto ter = ExportLedgerOps::validateExportAccount(
|
||||
*innerTx, hookCtx.result.account, j);
|
||||
!isTesSuccess(ter))
|
||||
return Unexpected(EXPORT_FAILURE);
|
||||
|
||||
if (auto ter = ExportLedgerOps::validateNetworkID(
|
||||
*innerTx, app.config().NETWORK_ID, j);
|
||||
!isTesSuccess(ter))
|
||||
return Unexpected(EXPORT_FAILURE);
|
||||
|
||||
if (auto ter = ExportLedgerOps::validateTicketSequence(*innerTx, j);
|
||||
!isTesSuccess(ter))
|
||||
return Unexpected(EXPORT_FAILURE);
|
||||
|
||||
// Construct a ttEXPORT wrapping the inner tx, with EmitDetails,
|
||||
// and push onto the emitted txn queue. This flows through the
|
||||
// normal emitted txn path (emitted dir → TxQ injection → open
|
||||
// ledger → retriable Export transactor).
|
||||
uint32_t const ledgerSeq = view.info().seq;
|
||||
|
||||
// Generate a nonce for the emitted ttEXPORT wrapper.
|
||||
auto nonce = etxn_nonce();
|
||||
if (!nonce.has_value())
|
||||
return Unexpected(INTERNAL_ERROR);
|
||||
|
||||
// Serialize inner tx as sfExportedTxn object.
|
||||
Serializer innerSer;
|
||||
innerTx->add(innerSer);
|
||||
|
||||
// Build the ttEXPORT wrapper as an STObject first so we can
|
||||
// compute the fee, set it, then construct the STTx from the
|
||||
// final serialised bytes. This avoids mutating the STTx after
|
||||
// construction (which would leave a stale cached txid — see
|
||||
// the tefNONDIR_EMIT check in Transactor::preclaim).
|
||||
//
|
||||
// The fee field is a fixed 9 bytes regardless of value, so
|
||||
// patching it on the STObject doesn't change the serialised size.
|
||||
STObject exportObj(sfGeneric);
|
||||
{
|
||||
exportObj.setFieldU16(sfTransactionType, ttEXPORT);
|
||||
exportObj[sfAccount] = hookCtx.result.account;
|
||||
exportObj[sfSequence] = 0u;
|
||||
exportObj.setFieldVL(sfSigningPubKey, Blob{});
|
||||
exportObj[sfFirstLedgerSequence] = ledgerSeq + 1;
|
||||
exportObj[sfLastLedgerSequence] = ledgerSeq + 5;
|
||||
exportObj[sfFee] = STAmount{0};
|
||||
|
||||
// sfExportedTxn inner object
|
||||
SerialIter sit(innerSer.slice());
|
||||
exportObj.set(std::make_unique<STObject>(sit, sfExportedTxn));
|
||||
|
||||
// sfEmitDetails
|
||||
STObject emitDetails(sfEmitDetails);
|
||||
emitDetails.setFieldU32(
|
||||
sfEmitGeneration, static_cast<uint32_t>(etxn_generation()));
|
||||
{
|
||||
auto const burdenResult = etxn_burden();
|
||||
emitDetails.setFieldU64(
|
||||
sfEmitBurden,
|
||||
burdenResult ? static_cast<uint64_t>(*burdenResult) : 1ULL);
|
||||
}
|
||||
emitDetails.setFieldH256(
|
||||
sfEmitParentTxnID, applyCtx.tx.getTransactionID());
|
||||
emitDetails.setFieldH256(sfEmitNonce, *nonce);
|
||||
emitDetails.setFieldH256(sfEmitHookHash, hookCtx.result.hookHash);
|
||||
if (hookCtx.result.hasCallback)
|
||||
emitDetails.setAccountID(sfEmitCallback, hookCtx.result.account);
|
||||
exportObj.set(std::move(emitDetails));
|
||||
|
||||
// Compute fee from serialised size and patch it in.
|
||||
Serializer feeSer;
|
||||
exportObj.add(feeSer);
|
||||
auto feeResult = etxn_fee_base(feeSer.slice());
|
||||
if (!feeResult)
|
||||
{
|
||||
JLOG(j.trace()) << "HookExport[" << HC_ACC()
|
||||
<< "]: Fee calculation failed for ttEXPORT wrapper";
|
||||
return Unexpected(EXPORT_FAILURE);
|
||||
}
|
||||
exportObj[sfFee] = STAmount{static_cast<uint64_t>(*feeResult)};
|
||||
}
|
||||
|
||||
// Construct the STTx from the finalised STObject bytes.
|
||||
Serializer exportSer;
|
||||
exportObj.add(exportSer);
|
||||
STTx exportStx(SerialIter{exportSer.slice()});
|
||||
auto builtValue = std::move(built.value());
|
||||
auto innerTxHash = builtValue.innerTxHash;
|
||||
auto exportStx = std::move(builtValue.wrapperTx);
|
||||
|
||||
// Preflight the wrapper.
|
||||
auto preflightResult = ripple::preflight(
|
||||
@@ -1393,7 +1316,7 @@ HookAPI::xport(Slice const& txBlob) const
|
||||
|
||||
// Return the inner tx hash — this is what the hook author cares
|
||||
// about (the cross-chain transaction they built).
|
||||
return innerTx->getTransactionID();
|
||||
return innerTxHash;
|
||||
}
|
||||
|
||||
Expected<uint64_t, HookReturnCode>
|
||||
|
||||
108
src/xrpld/app/hook/detail/XportWrapperBuilder.cpp
Normal file
108
src/xrpld/app/hook/detail/XportWrapperBuilder.cpp
Normal file
@@ -0,0 +1,108 @@
|
||||
#include <xrpld/app/hook/detail/XportWrapperBuilder.h>
|
||||
#include <xrpld/app/tx/detail/ExportLedgerOps.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace hook {
|
||||
namespace XportWrapperBuilder {
|
||||
|
||||
using namespace ripple;
|
||||
|
||||
Expected<Result, HookReturnCode>
|
||||
build(Input const& input)
|
||||
{
|
||||
std::shared_ptr<STTx const> innerTx;
|
||||
try
|
||||
{
|
||||
SerialIter sit(input.innerTxBlob);
|
||||
innerTx = std::make_shared<STTx const>(sit);
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
JLOG(input.j.trace()) << "HookExport: Failed " << e.what();
|
||||
return Unexpected(hook_api::EXPORT_FAILURE);
|
||||
}
|
||||
|
||||
if (auto ter = ExportLedgerOps::validateExportAccount(
|
||||
*innerTx, input.exporter, input.j);
|
||||
!isTesSuccess(ter))
|
||||
return Unexpected(hook_api::EXPORT_FAILURE);
|
||||
|
||||
if (auto ter = ExportLedgerOps::validateNetworkID(
|
||||
*innerTx, input.networkID, input.j);
|
||||
!isTesSuccess(ter))
|
||||
return Unexpected(hook_api::EXPORT_FAILURE);
|
||||
|
||||
if (auto ter = ExportLedgerOps::validateTicketSequence(*innerTx, input.j);
|
||||
!isTesSuccess(ter))
|
||||
return Unexpected(hook_api::EXPORT_FAILURE);
|
||||
|
||||
if (!input.generateNonce)
|
||||
{
|
||||
JLOG(input.j.trace())
|
||||
<< "HookExport: Nonce callback missing for ttEXPORT wrapper";
|
||||
return Unexpected(hook_api::INTERNAL_ERROR);
|
||||
}
|
||||
|
||||
auto nonce = input.generateNonce();
|
||||
if (!nonce)
|
||||
return Unexpected(nonce.error());
|
||||
|
||||
Serializer innerSer;
|
||||
innerTx->add(innerSer);
|
||||
|
||||
STObject exportObj(sfGeneric);
|
||||
exportObj.setFieldU16(sfTransactionType, ttEXPORT);
|
||||
exportObj[sfAccount] = input.exporter;
|
||||
exportObj[sfSequence] = 0u;
|
||||
exportObj.setFieldVL(sfSigningPubKey, Blob{});
|
||||
exportObj[sfFirstLedgerSequence] = input.ledgerSeq + 1;
|
||||
exportObj[sfLastLedgerSequence] = input.ledgerSeq + 5;
|
||||
exportObj[sfFee] = STAmount{0};
|
||||
|
||||
SerialIter sit(innerSer.slice());
|
||||
exportObj.set(std::make_unique<STObject>(sit, sfExportedTxn));
|
||||
|
||||
STObject emitDetails(sfEmitDetails);
|
||||
emitDetails.setFieldU32(sfEmitGeneration, input.emitGeneration);
|
||||
emitDetails.setFieldU64(sfEmitBurden, input.emitBurden);
|
||||
emitDetails.setFieldH256(sfEmitParentTxnID, input.parentTxnID);
|
||||
emitDetails.setFieldH256(sfEmitNonce, *nonce);
|
||||
emitDetails.setFieldH256(sfEmitHookHash, input.hookHash);
|
||||
if (input.hasCallback)
|
||||
emitDetails.setAccountID(sfEmitCallback, input.exporter);
|
||||
exportObj.set(std::move(emitDetails));
|
||||
|
||||
if (!input.calculateFee)
|
||||
{
|
||||
JLOG(input.j.trace()) << "HookExport: Fee calculation callback missing "
|
||||
"for ttEXPORT wrapper";
|
||||
return Unexpected(hook_api::EXPORT_FAILURE);
|
||||
}
|
||||
|
||||
Serializer feeSer;
|
||||
exportObj.add(feeSer);
|
||||
auto feeResult = input.calculateFee(feeSer.slice());
|
||||
if (!feeResult)
|
||||
{
|
||||
JLOG(input.j.trace())
|
||||
<< "HookExport: Fee calculation failed for ttEXPORT wrapper";
|
||||
return Unexpected(hook_api::EXPORT_FAILURE);
|
||||
}
|
||||
exportObj[sfFee] = STAmount{static_cast<std::uint64_t>(*feeResult)};
|
||||
|
||||
Serializer exportSer;
|
||||
exportObj.add(exportSer);
|
||||
STTx wrapperTx(SerialIter{exportSer.slice()});
|
||||
|
||||
return Result{std::move(wrapperTx), innerTx->getTransactionID()};
|
||||
}
|
||||
|
||||
} // namespace XportWrapperBuilder
|
||||
} // namespace hook
|
||||
54
src/xrpld/app/hook/detail/XportWrapperBuilder.h
Normal file
54
src/xrpld/app/hook/detail/XportWrapperBuilder.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#ifndef RIPPLE_HOOK_XPORTWRAPPERBUILDER_H_INCLUDED
|
||||
#define RIPPLE_HOOK_XPORTWRAPPERBUILDER_H_INCLUDED
|
||||
|
||||
#include <xrpl/basics/Expected.h>
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/hook/Enum.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
|
||||
namespace hook {
|
||||
namespace XportWrapperBuilder {
|
||||
|
||||
using HookReturnCode = hook_api::hook_return_code;
|
||||
using FeeCalculator =
|
||||
std::function<ripple::Expected<std::uint64_t, HookReturnCode>(
|
||||
ripple::Slice)>;
|
||||
using NonceGenerator =
|
||||
std::function<ripple::Expected<ripple::uint256, HookReturnCode>()>;
|
||||
|
||||
struct Input
|
||||
{
|
||||
ripple::Slice innerTxBlob;
|
||||
ripple::AccountID exporter;
|
||||
std::uint32_t networkID = 0;
|
||||
ripple::LedgerIndex ledgerSeq = 0;
|
||||
ripple::uint256 parentTxnID;
|
||||
ripple::uint256 hookHash;
|
||||
bool hasCallback = false;
|
||||
std::uint32_t emitGeneration = 0;
|
||||
std::uint64_t emitBurden = 1;
|
||||
NonceGenerator generateNonce;
|
||||
FeeCalculator calculateFee;
|
||||
beast::Journal j;
|
||||
};
|
||||
|
||||
struct Result
|
||||
{
|
||||
ripple::STTx wrapperTx;
|
||||
ripple::uint256 innerTxHash;
|
||||
};
|
||||
|
||||
ripple::Expected<Result, HookReturnCode>
|
||||
build(Input const& input);
|
||||
|
||||
} // namespace XportWrapperBuilder
|
||||
} // namespace hook
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user