mirror of
https://github.com/Xahau/xahaud.git
synced 2026-07-30 18:40:10 +00:00
feat(export): validate canonical release memo capacity
This commit is contained in:
@@ -946,6 +946,49 @@ struct Export_test : public beast::unit_test::suite
|
||||
innerTx, maxNetworkIDWithoutTxField + 1, j) == tesSUCCESS);
|
||||
}
|
||||
|
||||
void
|
||||
testExportRejectsInvalidOriginMemoProjection()
|
||||
{
|
||||
testcase("Export validates future origin Memo projection");
|
||||
|
||||
jtx::Account const alice{"alice"};
|
||||
jtx::Account const carol{"carol"};
|
||||
auto const j = beast::Journal{beast::Journal::getNullSink()};
|
||||
auto const sourceNetworkID = maxNetworkIDWithoutTxField + 1;
|
||||
|
||||
auto innerObj = buildExportedPayment(alice.id(), carol.id(), 2, 6);
|
||||
BEAST_EXPECT(
|
||||
ExportLedgerOps::validateOriginMemoProjection(
|
||||
makeSTTx(innerObj), sourceNetworkID, j) == tesSUCCESS);
|
||||
|
||||
STArray memos{sfMemos};
|
||||
STObject reservedMemo{sfMemo};
|
||||
reservedMemo.setFieldVL(
|
||||
sfMemoType,
|
||||
Blob(
|
||||
ExportOriginMemo::memoType.begin(),
|
||||
ExportOriginMemo::memoType.end()));
|
||||
reservedMemo.setFieldVL(
|
||||
sfMemoData, Blob(ExportOriginMemo::identityBytes));
|
||||
memos.emplace_back(std::move(reservedMemo));
|
||||
innerObj.setFieldArray(sfMemos, memos);
|
||||
BEAST_EXPECT(
|
||||
ExportLedgerOps::validateOriginMemoProjection(
|
||||
makeSTTx(innerObj), sourceNetworkID, j) == temMALFORMED);
|
||||
|
||||
memos.clear();
|
||||
STObject fullMemo{sfMemo};
|
||||
fullMemo.setFieldVL(sfMemoType, Blob{'u', 's', 'e', 'r'});
|
||||
fullMemo.setFieldVL(sfMemoData, Blob(930, 0x5A));
|
||||
memos.emplace_back(std::move(fullMemo));
|
||||
innerObj.setFieldArray(sfMemos, memos);
|
||||
std::string reason;
|
||||
BEAST_EXPECT(passesLocalChecks(makeSTTx(innerObj), reason));
|
||||
BEAST_EXPECT(
|
||||
ExportLedgerOps::validateOriginMemoProjection(
|
||||
makeSTTx(innerObj), sourceNetworkID, j) == temMALFORMED);
|
||||
}
|
||||
|
||||
void
|
||||
testXportEmissionLimit(FeatureBitset features)
|
||||
{
|
||||
@@ -2359,6 +2402,7 @@ struct Export_test : public beast::unit_test::suite
|
||||
testXportRejectsLocalNetworkID(allWithExport);
|
||||
testXportRejectsUnconfiguredNetworkID(allWithExport);
|
||||
testExportRejectsAmbiguousAbsentNetworkID();
|
||||
testExportRejectsInvalidOriginMemoProjection();
|
||||
testXportEmissionLimit(allWithExport);
|
||||
|
||||
// ttEXPORT transactor tests
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <xrpl/basics/Expected.h>
|
||||
#include <xrpl/beast/unit_test.h>
|
||||
#include <xrpl/protocol/ExportLimits.h>
|
||||
#include <xrpl/protocol/ExportOriginMemo.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STArray.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
@@ -85,6 +86,19 @@ makeExportedPayment(
|
||||
return makeSTTx(obj);
|
||||
}
|
||||
|
||||
STTx
|
||||
withMemo(STTx tx, Blob type, Blob data)
|
||||
{
|
||||
STArray memos = tx.isFieldPresent(sfMemos) ? tx.getFieldArray(sfMemos)
|
||||
: STArray{sfMemos};
|
||||
STObject memo{sfMemo};
|
||||
memo.setFieldVL(sfMemoType, std::move(type));
|
||||
memo.setFieldVL(sfMemoData, std::move(data));
|
||||
memos.emplace_back(std::move(memo));
|
||||
tx.setFieldArray(sfMemos, memos);
|
||||
return tx;
|
||||
}
|
||||
|
||||
beast::Journal
|
||||
nullJournal()
|
||||
{
|
||||
@@ -208,6 +222,37 @@ public:
|
||||
BEAST_EXPECT(!emitDetails.isFieldPresent(sfEmitCallback));
|
||||
}
|
||||
|
||||
void
|
||||
testPreservesUserMemos()
|
||||
{
|
||||
testcase("preserves user memos");
|
||||
|
||||
auto const exporter = randomKeyPair(KeyType::secp256k1);
|
||||
auto const dst = randomKeyPair(KeyType::secp256k1);
|
||||
auto const innerTx = withMemo(
|
||||
makeExportedPayment(
|
||||
calcAccountID(exporter.first), calcAccountID(dst.first)),
|
||||
Blob{'u', 's', 'e', 'r'},
|
||||
Blob{1, 2, 3, 4});
|
||||
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& exported =
|
||||
result->wrapperTx.peekAtField(sfExportedTxn).downcast<STObject>();
|
||||
Serializer exportedSer;
|
||||
exported.add(exportedSer);
|
||||
STTx parsedInner{SerialIter{exportedSer.slice()}};
|
||||
BEAST_EXPECT(serialize(parsedInner) == serialized);
|
||||
BEAST_EXPECT(!ExportOriginMemo::hasReservedMemo(parsedInner));
|
||||
}
|
||||
|
||||
void
|
||||
testRejectsInvalidInputs()
|
||||
{
|
||||
@@ -382,6 +427,23 @@ public:
|
||||
result.error() == ::hook_api::hook_return_code::EXPORT_FAILURE);
|
||||
BEAST_EXPECT(!nonceCalled);
|
||||
}
|
||||
|
||||
{
|
||||
auto const reservedType = Blob(
|
||||
ExportOriginMemo::memoType.begin(),
|
||||
ExportOriginMemo::memoType.end());
|
||||
auto const reservedMemoTx = withMemo(
|
||||
innerTx, reservedType, Blob(ExportOriginMemo::identityBytes));
|
||||
expectSigningEnvelopeRejected(reservedMemoTx);
|
||||
}
|
||||
|
||||
{
|
||||
auto const fullMemoTx =
|
||||
withMemo(innerTx, Blob{'u', 's', 'e', 'r'}, Blob(930, 0x5A));
|
||||
std::string reason;
|
||||
BEAST_EXPECT(passesLocalChecks(fullMemoTx, reason));
|
||||
expectSigningEnvelopeRejected(fullMemoTx);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -479,6 +541,7 @@ public:
|
||||
{
|
||||
testBuildsWrapper();
|
||||
testBuildsWrapperWithoutCallback();
|
||||
testPreservesUserMemos();
|
||||
testRejectsInvalidInputs();
|
||||
testRejectsMissingCallbacks();
|
||||
testMapsNonceFailureToInternalError();
|
||||
|
||||
@@ -45,6 +45,11 @@ build(Input const& input)
|
||||
!isTesSuccess(ter))
|
||||
return Unexpected(::hook_api::hook_return_code::EXPORT_FAILURE);
|
||||
|
||||
if (auto ter = ExportLedgerOps::validateOriginMemoProjection(
|
||||
*innerTx, input.networkID, input.j);
|
||||
!isTesSuccess(ter))
|
||||
return Unexpected(::hook_api::hook_return_code::EXPORT_FAILURE);
|
||||
|
||||
if (auto ter = ExportLedgerOps::validateTicketSequence(*innerTx, input.j);
|
||||
!isTesSuccess(ter))
|
||||
return Unexpected(::hook_api::hook_return_code::EXPORT_FAILURE);
|
||||
|
||||
@@ -71,6 +71,11 @@ Export::preclaim(PreclaimContext const& ctx)
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
if (auto ter = ExportLedgerOps::validateOriginMemoProjection(
|
||||
*innerTx, ctx.app.config().NETWORK_ID, ctx.j);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
if (auto ter = ExportLedgerOps::validateTicketSequence(*innerTx, ctx.j);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <xrpld/ledger/View.h>
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/protocol/ExportLimits.h>
|
||||
#include <xrpl/protocol/ExportOriginMemo.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
@@ -183,6 +184,32 @@ validateNetworkID(
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
/// Validate the embedded target transaction and its future canonical release
|
||||
/// projection before accepting an Export intent. The all-zero origin/anchor
|
||||
/// values are size-equivalent placeholders; live signing later substitutes
|
||||
/// the real source transaction and validated-ledger identity.
|
||||
inline TER
|
||||
validateOriginMemoProjection(
|
||||
STTx const& stx,
|
||||
std::uint32_t sourceNetworkID,
|
||||
beast::Journal j)
|
||||
{
|
||||
auto const targetNetworkID = stx.isFieldPresent(sfNetworkID)
|
||||
? stx.getFieldU32(sfNetworkID)
|
||||
: std::uint32_t{0};
|
||||
auto const projected = ExportOriginMemo::releaseForm(
|
||||
stx,
|
||||
ExportOriginMemo::Origin{sourceNetworkID, targetNetworkID, uint256{}},
|
||||
ExportOriginMemo::Anchor{0, uint256{}});
|
||||
if (projected)
|
||||
return tesSUCCESS;
|
||||
|
||||
JLOG(j.warn())
|
||||
<< "ExportLedgerOps: exported tx cannot form canonical origin Memo"
|
||||
<< " error=" << static_cast<unsigned>(projected.error());
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
/// Validate that the exported transaction's Account matches the
|
||||
/// expected exporting account.
|
||||
inline TER
|
||||
|
||||
Reference in New Issue
Block a user