Compare commits

..

7 Commits

Author SHA1 Message Date
Ed Hennis
80579dca62 Merge branch 'develop' into ximinez/fix-batchinnersigs 2025-11-28 15:46:27 -05:00
Ed Hennis
37c3133a91 Merge branch 'develop' into ximinez/fix-batchinnersigs 2025-11-27 01:48:40 -05:00
Ed Hennis
5c31b55357 Merge branch 'develop' into ximinez/fix-batchinnersigs 2025-11-26 00:24:58 -05:00
Ed Hennis
cc26829d32 Merge branch 'develop' into ximinez/fix-batchinnersigs 2025-11-25 14:54:48 -05:00
Ed Hennis
ebb05195cf Merge branch 'develop' into ximinez/fix-batchinnersigs 2025-11-24 21:48:54 -05:00
Ed Hennis
bd7a9051db Merge branch 'develop' into ximinez/fix-batchinnersigs 2025-11-24 21:30:04 -05:00
Ed Hennis
f4d55c8b77 fix: Inner batch transactions never have a valid signatures
- Introduces amendment `fixBatchInnerSigs`
2025-11-21 16:11:05 -05:00
9 changed files with 57 additions and 31 deletions

View File

@@ -14,6 +14,7 @@
// Add new amendments to the top of this list. // Add new amendments to the top of this list.
// Keep it sorted in reverse chronological order. // Keep it sorted in reverse chronological order.
XRPL_FIX (BatchInnerSigs, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(PermissionDelegationV1_1, Supported::no, VoteBehavior::DefaultNo) XRPL_FEATURE(PermissionDelegationV1_1, Supported::no, VoteBehavior::DefaultNo)
XRPL_FIX (DirectoryLimit, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (DirectoryLimit, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (IncludeKeyletFields, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (IncludeKeyletFields, Supported::yes, VoteBehavior::DefaultNo)

View File

@@ -2295,9 +2295,12 @@ class Batch_test : public beast::unit_test::suite
Serializer s; Serializer s;
parsed.object->add(s); parsed.object->add(s);
auto const jrr = env.rpc("submit", strHex(s.slice()))[jss::result]; auto const jrr = env.rpc("submit", strHex(s.slice()))[jss::result];
BEAST_EXPECT( BEAST_EXPECTS(
jrr[jss::status] == "success" && jrr[jss::status] == "error" &&
jrr[jss::engine_result] == "temINVALID_FLAG"); jrr[jss::error] == "invalidTransaction" &&
jrr[jss::error_exception] ==
"fails local checks: Empty SigningPubKey.",
to_string(jrr));
env.close(); env.close();
} }
@@ -4151,6 +4154,7 @@ public:
using namespace test::jtx; using namespace test::jtx;
auto const sa = testable_amendments(); auto const sa = testable_amendments();
testWithFeats(sa); testWithFeats(sa);
testWithFeats(sa - fixBatchInnerSigs);
} }
}; };

View File

@@ -49,8 +49,7 @@ public:
inner( inner(
Json::Value const& txn, Json::Value const& txn,
std::uint32_t const& sequence, std::uint32_t const& sequence,
std::optional<std::uint32_t> const& ticket = std::nullopt, std::optional<std::uint32_t> const& ticket = std::nullopt)
std::optional<std::uint32_t> const& fee = std::nullopt)
: txn_(txn), seq_(sequence), ticket_(ticket) : txn_(txn), seq_(sequence), ticket_(ticket)
{ {
txn_[jss::SigningPubKey] = ""; txn_[jss::SigningPubKey] = "";

View File

@@ -106,7 +106,10 @@ OpenLedger::accept(
// skip batch txns // skip batch txns
// LCOV_EXCL_START // LCOV_EXCL_START
if (tx->isFlag(tfInnerBatchTxn) && rules.enabled(featureBatch)) // The flag should only be settable if Batch feature is enabled. If
// Batch is not enabled, the flag is always invalid, so don't relay it
// regardless.
if (tx->isFlag(tfInnerBatchTxn))
{ {
XRPL_ASSERT( XRPL_ASSERT(
txpair.second && txpair.second->isFieldPresent(sfParentBatchID), txpair.second && txpair.second->isFieldPresent(sfParentBatchID),

View File

@@ -1677,10 +1677,11 @@ NetworkOPsImp::apply(std::unique_lock<std::mutex>& batchLock)
app_.getHashRouter().shouldRelay(e.transaction->getID()); app_.getHashRouter().shouldRelay(e.transaction->getID());
if (auto const sttx = *(e.transaction->getSTransaction()); if (auto const sttx = *(e.transaction->getSTransaction());
toSkip && toSkip &&
// Skip relaying if it's an inner batch txn and batch // Skip relaying if it's an inner batch txn. The flag should
// feature is enabled // only be set if the Batch feature is enabled. If Batch is
!(sttx.isFlag(tfInnerBatchTxn) && // not enabled, the flag is always invalid, so don't relay
newOL->rules().enabled(featureBatch))) // it regardless.
!(sttx.isFlag(tfInnerBatchTxn)))
{ {
protocol::TMTransaction tx; protocol::TMTransaction tx;
Serializer s; Serializer s;
@@ -3040,9 +3041,11 @@ NetworkOPsImp::pubProposedTransaction(
std::shared_ptr<STTx const> const& transaction, std::shared_ptr<STTx const> const& transaction,
TER result) TER result)
{ {
// never publish an inner txn inside a batch txn // never publish an inner txn inside a batch txn. The flag should
if (transaction->isFlag(tfInnerBatchTxn) && // only be set if the Batch feature is enabled. If Batch is not
ledger->rules().enabled(featureBatch)) // enabled, the flag is always invalid, so don't publish it
// regardless.
if (transaction->isFlag(tfInnerBatchTxn))
return; return;
MultiApiJson jvObj = MultiApiJson jvObj =

View File

@@ -204,12 +204,17 @@ Transactor::preflight2(PreflightContext const& ctx)
// regardless of success or failure // regardless of success or failure
return *ret; return *ret;
auto const sigValid = checkValidity( // Skip signature check on batch inner transactions
ctx.app.getHashRouter(), ctx.tx, ctx.rules, ctx.app.config()); if (!ctx.tx.isFlag(tfInnerBatchTxn) || !ctx.rules.enabled(featureBatch))
if (sigValid.first == Validity::SigBad)
{ {
JLOG(ctx.j.debug()) << "preflight2: bad signature. " << sigValid.second; auto const sigValid = checkValidity(
return temINVALID; // LCOV_EXCL_LINE ctx.app.getHashRouter(), ctx.tx, ctx.rules, ctx.app.config());
if (sigValid.first == Validity::SigBad)
{
JLOG(ctx.j.debug())
<< "preflight2: bad signature. " << sigValid.second;
return temINVALID; // LCOV_EXCL_LINE
}
} }
return tesSUCCESS; return tesSUCCESS;
} }
@@ -642,13 +647,17 @@ NotTEC
Transactor::checkSign( Transactor::checkSign(
ReadView const& view, ReadView const& view,
ApplyFlags flags, ApplyFlags flags,
std::optional<uint256 const> const& parentBatchId,
AccountID const& idAccount, AccountID const& idAccount,
STObject const& sigObject, STObject const& sigObject,
beast::Journal const j) beast::Journal const j)
{ {
auto const pkSigner = sigObject.getFieldVL(sfSigningPubKey); auto const pkSigner = sigObject.getFieldVL(sfSigningPubKey);
// Ignore signature check on batch inner transactions // Ignore signature check on batch inner transactions
if (sigObject.isFlag(tfInnerBatchTxn) && view.rules().enabled(featureBatch)) bool const useCtx = view.rules().enabled(fixBatchInnerSigs);
if ((useCtx ? parentBatchId.has_value()
: sigObject.isFlag(tfInnerBatchTxn)) &&
view.rules().enabled(featureBatch))
{ {
// Defensive Check: These values are also checked in Batch::preflight // Defensive Check: These values are also checked in Batch::preflight
if (sigObject.isFieldPresent(sfTxnSignature) || !pkSigner.empty() || if (sigObject.isFieldPresent(sfTxnSignature) || !pkSigner.empty() ||
@@ -701,7 +710,8 @@ Transactor::checkSign(PreclaimContext const& ctx)
auto const idAccount = ctx.tx.isFieldPresent(sfDelegate) auto const idAccount = ctx.tx.isFieldPresent(sfDelegate)
? ctx.tx.getAccountID(sfDelegate) ? ctx.tx.getAccountID(sfDelegate)
: ctx.tx.getAccountID(sfAccount); : ctx.tx.getAccountID(sfAccount);
return checkSign(ctx.view, ctx.flags, idAccount, ctx.tx, ctx.j); return checkSign(
ctx.view, ctx.flags, ctx.parentBatchId, idAccount, ctx.tx, ctx.j);
} }
NotTEC NotTEC

View File

@@ -266,6 +266,7 @@ protected:
checkSign( checkSign(
ReadView const& view, ReadView const& view,
ApplyFlags flags, ApplyFlags flags,
std::optional<uint256 const> const& parentBatchId,
AccountID const& idAccount, AccountID const& idAccount,
STObject const& sigObject, STObject const& sigObject,
beast::Journal const j); beast::Journal const j);

View File

@@ -41,15 +41,22 @@ checkValidity(
Validity::SigBad, Validity::SigBad,
"Malformed: Invalid inner batch transaction."}; "Malformed: Invalid inner batch transaction."};
std::string reason; // This block should probably have never been included in the
if (!passesLocalChecks(tx, reason)) // original `Batch` implementation. An inner transaction never
// has a valid signature.
bool const neverValid = rules.enabled(fixBatchInnerSigs);
if (!neverValid)
{ {
router.setFlags(id, SF_LOCALBAD); std::string reason;
return {Validity::SigGoodOnly, reason}; if (!passesLocalChecks(tx, reason))
} {
router.setFlags(id, SF_LOCALBAD);
return {Validity::SigGoodOnly, reason};
}
router.setFlags(id, SF_SIGGOOD); router.setFlags(id, SF_SIGGOOD);
return {Validity::Valid, ""}; return {Validity::Valid, ""};
}
} }
if (any(flags & SF_SIGBAD)) if (any(flags & SF_SIGBAD))

View File

@@ -1365,8 +1365,7 @@ PeerImp::handleTransaction(
// Charge strongly for attempting to relay a txn with tfInnerBatchTxn // Charge strongly for attempting to relay a txn with tfInnerBatchTxn
// LCOV_EXCL_START // LCOV_EXCL_START
if (stx->isFlag(tfInnerBatchTxn) && if (stx->isFlag(tfInnerBatchTxn))
getCurrentTransactionRules()->enabled(featureBatch))
{ {
JLOG(p_journal_.warn()) << "Ignoring Network relayed Tx containing " JLOG(p_journal_.warn()) << "Ignoring Network relayed Tx containing "
"tfInnerBatchTxn (handleTransaction)."; "tfInnerBatchTxn (handleTransaction).";
@@ -2924,8 +2923,7 @@ PeerImp::checkTransaction(
{ {
// charge strongly for relaying batch txns // charge strongly for relaying batch txns
// LCOV_EXCL_START // LCOV_EXCL_START
if (stx->isFlag(tfInnerBatchTxn) && if (stx->isFlag(tfInnerBatchTxn))
getCurrentTransactionRules()->enabled(featureBatch))
{ {
JLOG(p_journal_.warn()) << "Ignoring Network relayed Tx containing " JLOG(p_journal_.warn()) << "Ignoring Network relayed Tx containing "
"tfInnerBatchTxn (checkSignature)."; "tfInnerBatchTxn (checkSignature).";