mirror of
https://github.com/Xahau/xahaud.git
synced 2026-09-27 23:48:05 +00:00
fix(import): enforce account authorization for Export callbacks
This commit is contained in:
@@ -2542,6 +2542,92 @@ struct Export_test : public beast::unit_test::suite
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testExportCallbackAccountAuthorization(FeatureBitset features)
|
||||
{
|
||||
using namespace jtx;
|
||||
for (std::string const mode : {"master", "regular", "multisign"})
|
||||
{
|
||||
testcase("Export callback source-account authorization: " + mode);
|
||||
auto const xpopCtx = xpop::TestXPOPContext::create(3);
|
||||
Account const alice{"alice"};
|
||||
Account const carol{"carol"};
|
||||
Account const dave{"dave"};
|
||||
Env env{*this, xpopCtx.makeEnvConfig(21337), features};
|
||||
env.fund(XRP(10000), alice, carol, dave);
|
||||
env.close();
|
||||
auto const callback =
|
||||
buildExportCallbackXPOP(env, xpopCtx, alice, carol, 31337, 2);
|
||||
auto const latchKey =
|
||||
keylet::exportLatch(alice.id(), callback.originTxn);
|
||||
BEAST_EXPECT(env.current()->exists(latchKey));
|
||||
|
||||
if (mode == "regular")
|
||||
{
|
||||
env(regkey(alice, carol));
|
||||
env(fset(alice, asfDisableMaster));
|
||||
}
|
||||
else if (mode == "multisign")
|
||||
{
|
||||
env(signers(alice, 2, {{carol, 1}, {dave, 1}}));
|
||||
env(fset(alice, asfDisableMaster));
|
||||
}
|
||||
env.close();
|
||||
|
||||
auto const before =
|
||||
env.current()->read(keylet::account(alice.id()));
|
||||
auto const balance = before->getFieldAmount(sfBalance).xrp();
|
||||
auto const sequence = before->getFieldU32(sfSequence);
|
||||
auto const feeDrops = env.current()->fees().base * 10;
|
||||
auto const reject = [&](auto const& signing, TER const result) {
|
||||
env(import::import(alice, callback.xpopJson),
|
||||
signing,
|
||||
fee(feeDrops),
|
||||
ter(result));
|
||||
auto const after =
|
||||
env.current()->read(keylet::account(alice.id()));
|
||||
BEAST_EXPECT(after->getFieldAmount(sfBalance).xrp() == balance);
|
||||
BEAST_EXPECT(after->getFieldU32(sfSequence) == sequence);
|
||||
BEAST_EXPECT(env.current()->exists(latchKey));
|
||||
if (callback.vlInfo)
|
||||
BEAST_EXPECT(
|
||||
importVLSequence(env, callback.vlInfo->second) == 0);
|
||||
};
|
||||
|
||||
reject(sig(dave), tefBAD_AUTH);
|
||||
if (mode == "master")
|
||||
{
|
||||
reject(msig(carol), tefNOT_MULTI_SIGNING);
|
||||
env(import::import(alice, callback.xpopJson),
|
||||
sig(alice),
|
||||
fee(feeDrops),
|
||||
ter(tesSUCCESS));
|
||||
}
|
||||
else if (mode == "regular")
|
||||
{
|
||||
reject(sig(alice), tefMASTER_DISABLED);
|
||||
env(import::import(alice, callback.xpopJson),
|
||||
sig(carol),
|
||||
fee(feeDrops),
|
||||
ter(tesSUCCESS));
|
||||
}
|
||||
else
|
||||
{
|
||||
reject(sig(alice), tefMASTER_DISABLED);
|
||||
reject(msig(carol), tefBAD_QUORUM);
|
||||
env(import::import(alice, callback.xpopJson),
|
||||
msig(carol, dave),
|
||||
fee(feeDrops),
|
||||
ter(tesSUCCESS));
|
||||
}
|
||||
auto const after = env.current()->read(keylet::account(alice.id()));
|
||||
BEAST_EXPECT(
|
||||
after->getFieldAmount(sfBalance).xrp() == balance - feeDrops);
|
||||
BEAST_EXPECT(after->getFieldU32(sfSequence) == sequence + 1);
|
||||
BEAST_EXPECT(!env.current()->exists(latchKey));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testCanceledExportAcceptsMatchingImport(FeatureBitset features)
|
||||
{
|
||||
@@ -2691,6 +2777,7 @@ struct Export_test : public beast::unit_test::suite
|
||||
|
||||
// Round-trip test
|
||||
testExportImportRoundTrip(allWithExport);
|
||||
testExportCallbackAccountAuthorization(allWithExport);
|
||||
testCanceledExportAcceptsMatchingImport(allWithExport);
|
||||
testExportImportRejectsStaleImportVL(allWithExport);
|
||||
}
|
||||
|
||||
@@ -283,6 +283,12 @@ transaction's `sfAccount`, which Export admission already bound to the exporter
|
||||
and latch owner. Possession of an XPOP alone never authorizes another account's
|
||||
callback.
|
||||
|
||||
The source account's enabled master key, current regular key, or configured
|
||||
multisigning quorum authorizes the outer Import and its fee/sequence effects.
|
||||
An unrelated signing key must fail before balance, sequence, latch, or Import
|
||||
validator-list state changes. The target committee's signatures and possession
|
||||
of its valid XPOP do not substitute for source-account authorization.
|
||||
|
||||
The callback may omit the burn-to-mint `sfOperationLimit` and outer/inner
|
||||
signing-key-equality checks because the validator-multisigned target and Export
|
||||
latch replace those bindings. It still requires a fully canonical target
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <xrpld/app/misc/HashRouter.h>
|
||||
#include <xrpld/app/misc/LoadFeeTrack.h>
|
||||
#include <xrpld/app/tx/apply.h>
|
||||
#include <xrpld/app/tx/detail/Import.h>
|
||||
#include <xrpld/app/tx/detail/NFTokenUtils.h>
|
||||
#include <xrpld/app/tx/detail/SetHook.h>
|
||||
#include <xrpld/app/tx/detail/SignerEntries.h>
|
||||
@@ -908,11 +909,20 @@ Transactor::checkSign(PreclaimContext const& ctx)
|
||||
ctx.tx.getFieldU32(sfNetworkID) == 65535)
|
||||
return tesSUCCESS;
|
||||
|
||||
// pass ttIMPORTs, their signatures are checked at the preflight against the
|
||||
// internal xpop txn
|
||||
// Burn-to-mint Imports retain their XPOP-based signing authorization.
|
||||
// Export callbacks instead authorize the outer source-account transaction
|
||||
// normally: target committee signatures do not authorize its fee/sequence.
|
||||
if (ctx.view.rules().enabled(featureImport) &&
|
||||
ctx.tx.getTxnType() == ttIMPORT)
|
||||
return tesSUCCESS;
|
||||
{
|
||||
if (!ctx.view.rules().enabled(featureExport))
|
||||
return tesSUCCESS;
|
||||
auto const [inner, meta] = Import::getInnerTxn(ctx.tx, ctx.j);
|
||||
if (!inner || !meta)
|
||||
return temMALFORMED;
|
||||
if (!inner->isFieldPresent(sfTicketSequence))
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
// pass ttMANIFEST_SETs, their signatures are checked in preflight against
|
||||
// the manifest's internal key logic
|
||||
|
||||
Reference in New Issue
Block a user