mirror of
https://github.com/XRPLF/rippled.git
synced 2026-03-04 11:52:25 +00:00
Compare commits
9 Commits
develop
...
dangell7/b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
200f93a287 | ||
|
|
90d2eb839a | ||
|
|
359a94b766 | ||
|
|
5e39c1d80c | ||
|
|
25abc8ffae | ||
|
|
a669dcec87 | ||
|
|
4f5a3241de | ||
|
|
c729a26dd3 | ||
|
|
69084a6ff5 |
@@ -15,10 +15,9 @@
|
|||||||
|
|
||||||
// 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_FEATURE(BatchV1_1, Supported::no, VoteBehavior::DefaultNo)
|
||||||
XRPL_FIX (PermissionedDomainInvariant, Supported::yes, VoteBehavior::DefaultNo)
|
XRPL_FIX (PermissionedDomainInvariant, Supported::yes, VoteBehavior::DefaultNo)
|
||||||
XRPL_FIX (ExpiredNFTokenOfferRemoval, Supported::yes, VoteBehavior::DefaultNo)
|
XRPL_FIX (ExpiredNFTokenOfferRemoval, Supported::yes, VoteBehavior::DefaultNo)
|
||||||
XRPL_FIX (BatchInnerSigs, Supported::no, VoteBehavior::DefaultNo)
|
|
||||||
XRPL_FEATURE(LendingProtocol, Supported::yes, VoteBehavior::DefaultNo)
|
XRPL_FEATURE(LendingProtocol, 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)
|
||||||
@@ -32,7 +31,6 @@ XRPL_FEATURE(TokenEscrow, Supported::yes, VoteBehavior::DefaultNo
|
|||||||
XRPL_FIX (EnforceNFTokenTrustlineV2, Supported::yes, VoteBehavior::DefaultNo)
|
XRPL_FIX (EnforceNFTokenTrustlineV2, Supported::yes, VoteBehavior::DefaultNo)
|
||||||
XRPL_FIX (AMMv1_3, Supported::yes, VoteBehavior::DefaultNo)
|
XRPL_FIX (AMMv1_3, Supported::yes, VoteBehavior::DefaultNo)
|
||||||
XRPL_FEATURE(PermissionedDEX, Supported::yes, VoteBehavior::DefaultNo)
|
XRPL_FEATURE(PermissionedDEX, Supported::yes, VoteBehavior::DefaultNo)
|
||||||
XRPL_FEATURE(Batch, Supported::no, VoteBehavior::DefaultNo)
|
|
||||||
XRPL_FEATURE(SingleAssetVault, Supported::yes, VoteBehavior::DefaultNo)
|
XRPL_FEATURE(SingleAssetVault, Supported::yes, VoteBehavior::DefaultNo)
|
||||||
XRPL_FIX (PayChanCancelAfter, Supported::yes, VoteBehavior::DefaultNo)
|
XRPL_FIX (PayChanCancelAfter, Supported::yes, VoteBehavior::DefaultNo)
|
||||||
// Check flags in Credential transactions
|
// Check flags in Credential transactions
|
||||||
|
|||||||
@@ -918,7 +918,7 @@ TRANSACTION(ttVAULT_CLAWBACK, 70, VaultClawback,
|
|||||||
#endif
|
#endif
|
||||||
TRANSACTION(ttBATCH, 71, Batch,
|
TRANSACTION(ttBATCH, 71, Batch,
|
||||||
Delegation::notDelegable,
|
Delegation::notDelegable,
|
||||||
featureBatch,
|
featureBatchV1_1,
|
||||||
noPriv,
|
noPriv,
|
||||||
({
|
({
|
||||||
{sfRawTransactions, soeREQUIRED},
|
{sfRawTransactions, soeREQUIRED},
|
||||||
|
|||||||
@@ -162,9 +162,6 @@ public:
|
|||||||
static NotTEC
|
static NotTEC
|
||||||
checkSign(PreclaimContext const& ctx);
|
checkSign(PreclaimContext const& ctx);
|
||||||
|
|
||||||
static NotTEC
|
|
||||||
checkBatchSign(PreclaimContext const& ctx);
|
|
||||||
|
|
||||||
// Returns the fee in fee units, not scaled for load.
|
// Returns the fee in fee units, not scaled for load.
|
||||||
static XRPAmount
|
static XRPAmount
|
||||||
calculateBaseFee(ReadView const& view, STTx const& tx);
|
calculateBaseFee(ReadView const& view, STTx const& tx);
|
||||||
@@ -293,14 +290,7 @@ protected:
|
|||||||
std::optional<T> value,
|
std::optional<T> value,
|
||||||
unit::ValueUnit<Unit, T> min = unit::ValueUnit<Unit, T>{});
|
unit::ValueUnit<Unit, T> min = unit::ValueUnit<Unit, T>{});
|
||||||
|
|
||||||
private:
|
protected:
|
||||||
std::pair<TER, XRPAmount>
|
|
||||||
reset(XRPAmount fee);
|
|
||||||
|
|
||||||
TER
|
|
||||||
consumeSeqProxy(SLE::pointer const& sleAccount);
|
|
||||||
TER
|
|
||||||
payFee();
|
|
||||||
static NotTEC
|
static NotTEC
|
||||||
checkSingleSign(
|
checkSingleSign(
|
||||||
ReadView const& view,
|
ReadView const& view,
|
||||||
@@ -316,6 +306,15 @@ private:
|
|||||||
STObject const& sigObject,
|
STObject const& sigObject,
|
||||||
beast::Journal const j);
|
beast::Journal const j);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::pair<TER, XRPAmount>
|
||||||
|
reset(XRPAmount fee);
|
||||||
|
|
||||||
|
TER
|
||||||
|
consumeSeqProxy(SLE::pointer const& sleAccount);
|
||||||
|
TER
|
||||||
|
payFee();
|
||||||
|
|
||||||
void trapTransaction(uint256) const;
|
void trapTransaction(uint256) const;
|
||||||
|
|
||||||
/** Performs early sanity checks on the account and fee fields.
|
/** Performs early sanity checks on the account and fee fields.
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ public:
|
|||||||
static NotTEC
|
static NotTEC
|
||||||
preflightSigValidated(PreflightContext const& ctx);
|
preflightSigValidated(PreflightContext const& ctx);
|
||||||
|
|
||||||
|
static NotTEC
|
||||||
|
checkBatchSign(PreclaimContext const& ctx);
|
||||||
|
|
||||||
static NotTEC
|
static NotTEC
|
||||||
checkSign(PreclaimContext const& ctx);
|
checkSign(PreclaimContext const& ctx);
|
||||||
|
|
||||||
|
|||||||
@@ -278,6 +278,8 @@ STTx::checkBatchSign(Rules const& rules) const
|
|||||||
JLOG(debugLog().fatal()) << "not a batch transaction";
|
JLOG(debugLog().fatal()) << "not a batch transaction";
|
||||||
return Unexpected("Not a batch transaction.");
|
return Unexpected("Not a batch transaction.");
|
||||||
}
|
}
|
||||||
|
if (!isFieldPresent(sfBatchSigners))
|
||||||
|
return Unexpected("Missing BatchSigners field.");
|
||||||
STArray const& signers{getFieldArray(sfBatchSigners)};
|
STArray const& signers{getFieldArray(sfBatchSigners)};
|
||||||
for (auto const& signer : signers)
|
for (auto const& signer : signers)
|
||||||
{
|
{
|
||||||
@@ -292,9 +294,8 @@ STTx::checkBatchSign(Rules const& rules) const
|
|||||||
}
|
}
|
||||||
catch (std::exception const& e)
|
catch (std::exception const& e)
|
||||||
{
|
{
|
||||||
JLOG(debugLog().error()) << "Batch signature check failed: " << e.what();
|
return Unexpected(std::string("Internal batch signature check failure: ") + e.what());
|
||||||
}
|
}
|
||||||
return Unexpected("Internal batch signature check failure.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Json::Value
|
Json::Value
|
||||||
@@ -416,6 +417,7 @@ STTx::checkBatchSingleSign(STObject const& batchSigner) const
|
|||||||
{
|
{
|
||||||
Serializer msg;
|
Serializer msg;
|
||||||
serializeBatch(msg, getFlags(), getBatchTransactionIDs());
|
serializeBatch(msg, getFlags(), getBatchTransactionIDs());
|
||||||
|
finishMultiSigningData(batchSigner.getAccountID(sfAccount), msg);
|
||||||
return singleSignHelper(batchSigner, msg.slice());
|
return singleSignHelper(batchSigner, msg.slice());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -488,7 +490,7 @@ multiSignHelper(
|
|||||||
if (!validSig)
|
if (!validSig)
|
||||||
return Unexpected(
|
return Unexpected(
|
||||||
std::string("Invalid signature on account ") + toBase58(accountID) +
|
std::string("Invalid signature on account ") + toBase58(accountID) +
|
||||||
errorWhat.value_or("") + ".");
|
(errorWhat ? ": " + *errorWhat : "") + ".");
|
||||||
}
|
}
|
||||||
// All signatures verified.
|
// All signatures verified.
|
||||||
return {};
|
return {};
|
||||||
|
|||||||
@@ -175,12 +175,12 @@ Transactor::preflight1(PreflightContext const& ctx, std::uint32_t flagMask)
|
|||||||
if (ctx.tx.getSeqProxy().isTicket() && ctx.tx.isFieldPresent(sfAccountTxnID))
|
if (ctx.tx.getSeqProxy().isTicket() && ctx.tx.isFieldPresent(sfAccountTxnID))
|
||||||
return temINVALID;
|
return temINVALID;
|
||||||
|
|
||||||
if (ctx.tx.isFlag(tfInnerBatchTxn) && !ctx.rules.enabled(featureBatch))
|
if (ctx.tx.isFlag(tfInnerBatchTxn) && !ctx.rules.enabled(featureBatchV1_1))
|
||||||
return temINVALID_FLAG;
|
return temINVALID_FLAG;
|
||||||
|
|
||||||
XRPL_ASSERT(
|
XRPL_ASSERT(
|
||||||
ctx.tx.isFlag(tfInnerBatchTxn) == ctx.parentBatchId.has_value() ||
|
ctx.tx.isFlag(tfInnerBatchTxn) == ctx.parentBatchId.has_value() ||
|
||||||
!ctx.rules.enabled(featureBatch),
|
!ctx.rules.enabled(featureBatchV1_1),
|
||||||
"Inner batch transaction must have a parent batch ID.");
|
"Inner batch transaction must have a parent batch ID.");
|
||||||
|
|
||||||
return tesSUCCESS;
|
return tesSUCCESS;
|
||||||
@@ -196,13 +196,13 @@ Transactor::preflight2(PreflightContext const& ctx)
|
|||||||
return *ret;
|
return *ret;
|
||||||
|
|
||||||
// It should be impossible for the InnerBatchTxn flag to be set without
|
// It should be impossible for the InnerBatchTxn flag to be set without
|
||||||
// featureBatch being enabled
|
// featureBatchV1_1 being enabled
|
||||||
XRPL_ASSERT_PARTS(
|
XRPL_ASSERT_PARTS(
|
||||||
!ctx.tx.isFlag(tfInnerBatchTxn) || ctx.rules.enabled(featureBatch),
|
!ctx.tx.isFlag(tfInnerBatchTxn) || ctx.rules.enabled(featureBatchV1_1),
|
||||||
"xrpl::Transactor::preflight2",
|
"xrpl::Transactor::preflight2",
|
||||||
"InnerBatch flag only set if feature enabled");
|
"InnerBatch flag only set if feature enabled");
|
||||||
// Skip signature check on batch inner transactions
|
// Skip signature check on batch inner transactions
|
||||||
if (ctx.tx.isFlag(tfInnerBatchTxn) && ctx.rules.enabled(featureBatch))
|
if (ctx.tx.isFlag(tfInnerBatchTxn) && ctx.rules.enabled(featureBatchV1_1))
|
||||||
return tesSUCCESS;
|
return tesSUCCESS;
|
||||||
// Do not add any checks after this point that are relevant for
|
// Do not add any checks after this point that are relevant for
|
||||||
// batch inner transactions. They will be skipped.
|
// batch inner transactions. They will be skipped.
|
||||||
@@ -647,7 +647,7 @@ Transactor::checkSign(
|
|||||||
|
|
||||||
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 (parentBatchId && view.rules().enabled(featureBatch))
|
if (parentBatchId && view.rules().enabled(featureBatchV1_1))
|
||||||
{
|
{
|
||||||
// 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() ||
|
||||||
@@ -699,50 +699,6 @@ Transactor::checkSign(PreclaimContext const& ctx)
|
|||||||
return checkSign(ctx.view, ctx.flags, ctx.parentBatchId, idAccount, ctx.tx, ctx.j);
|
return checkSign(ctx.view, ctx.flags, ctx.parentBatchId, idAccount, ctx.tx, ctx.j);
|
||||||
}
|
}
|
||||||
|
|
||||||
NotTEC
|
|
||||||
Transactor::checkBatchSign(PreclaimContext const& ctx)
|
|
||||||
{
|
|
||||||
NotTEC ret = tesSUCCESS;
|
|
||||||
STArray const& signers{ctx.tx.getFieldArray(sfBatchSigners)};
|
|
||||||
for (auto const& signer : signers)
|
|
||||||
{
|
|
||||||
auto const idAccount = signer.getAccountID(sfAccount);
|
|
||||||
|
|
||||||
Blob const& pkSigner = signer.getFieldVL(sfSigningPubKey);
|
|
||||||
if (pkSigner.empty())
|
|
||||||
{
|
|
||||||
if (ret = checkMultiSign(ctx.view, ctx.flags, idAccount, signer, ctx.j);
|
|
||||||
!isTesSuccess(ret))
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// LCOV_EXCL_START
|
|
||||||
if (!publicKeyType(makeSlice(pkSigner)))
|
|
||||||
return tefBAD_AUTH;
|
|
||||||
// LCOV_EXCL_STOP
|
|
||||||
|
|
||||||
auto const idSigner = calcAccountID(PublicKey(makeSlice(pkSigner)));
|
|
||||||
auto const sleAccount = ctx.view.read(keylet::account(idAccount));
|
|
||||||
|
|
||||||
// A batch can include transactions from an un-created account ONLY
|
|
||||||
// when the account master key is the signer
|
|
||||||
if (!sleAccount)
|
|
||||||
{
|
|
||||||
if (idAccount != idSigner)
|
|
||||||
return tefBAD_AUTH;
|
|
||||||
|
|
||||||
return tesSUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ret = checkSingleSign(ctx.view, idSigner, idAccount, sleAccount, ctx.j);
|
|
||||||
!isTesSuccess(ret))
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
NotTEC
|
NotTEC
|
||||||
Transactor::checkSingleSign(
|
Transactor::checkSingleSign(
|
||||||
ReadView const& view,
|
ReadView const& view,
|
||||||
|
|||||||
@@ -24,29 +24,12 @@ checkValidity(HashRouter& router, STTx const& tx, Rules const& rules)
|
|||||||
auto const flags = router.getFlags(id);
|
auto const flags = router.getFlags(id);
|
||||||
|
|
||||||
// Ignore signature check on batch inner transactions
|
// Ignore signature check on batch inner transactions
|
||||||
if (tx.isFlag(tfInnerBatchTxn) && rules.enabled(featureBatch))
|
if (tx.isFlag(tfInnerBatchTxn) && rules.enabled(featureBatchV1_1))
|
||||||
{
|
{
|
||||||
// Defensive Check: These values are also checked in Batch::preflight
|
// Defensive Check: These values are also checked in Batch::preflight
|
||||||
if (tx.isFieldPresent(sfTxnSignature) || !tx.getSigningPubKey().empty() ||
|
if (tx.isFieldPresent(sfTxnSignature) || !tx.getSigningPubKey().empty() ||
|
||||||
tx.isFieldPresent(sfSigners))
|
tx.isFieldPresent(sfSigners))
|
||||||
return {Validity::SigBad, "Malformed: Invalid inner batch transaction."};
|
return {Validity::SigBad, "Malformed: Invalid inner batch transaction."};
|
||||||
|
|
||||||
// This block should probably have never been included in the
|
|
||||||
// original `Batch` implementation. An inner transaction never
|
|
||||||
// has a valid signature.
|
|
||||||
bool const neverValid = rules.enabled(fixBatchInnerSigs);
|
|
||||||
if (!neverValid)
|
|
||||||
{
|
|
||||||
std::string reason;
|
|
||||||
if (!passesLocalChecks(tx, reason))
|
|
||||||
{
|
|
||||||
router.setFlags(id, SF_LOCALBAD);
|
|
||||||
return {Validity::SigGoodOnly, reason};
|
|
||||||
}
|
|
||||||
|
|
||||||
router.setFlags(id, SF_SIGGOOD);
|
|
||||||
return {Validity::Valid, ""};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (any(flags & SF_SIGBAD))
|
if (any(flags & SF_SIGBAD))
|
||||||
|
|||||||
@@ -107,7 +107,18 @@ Batch::calculateBaseFee(ReadView const& view, STTx const& tx)
|
|||||||
if (signer.isFieldPresent(sfTxnSignature))
|
if (signer.isFieldPresent(sfTxnSignature))
|
||||||
signerCount += 1;
|
signerCount += 1;
|
||||||
else if (signer.isFieldPresent(sfSigners))
|
else if (signer.isFieldPresent(sfSigners))
|
||||||
signerCount += signer.getFieldArray(sfSigners).size();
|
{
|
||||||
|
auto const& nestedSigners = signer.getFieldArray(sfSigners);
|
||||||
|
// LCOV_EXCL_START
|
||||||
|
if (nestedSigners.size() > STTx::maxMultiSigners)
|
||||||
|
{
|
||||||
|
JLOG(debugLog().error())
|
||||||
|
<< "BatchTrace: Nested Signers array exceeds max entries.";
|
||||||
|
return XRPAmount{INITIAL_XRP};
|
||||||
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
signerCount += nestedSigners.size();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,6 +216,14 @@ Batch::preflight(PreflightContext const& ctx)
|
|||||||
return temARRAY_TOO_LARGE;
|
return temARRAY_TOO_LARGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ctx.tx.isFieldPresent(sfBatchSigners) &&
|
||||||
|
ctx.tx.getFieldArray(sfBatchSigners).size() > maxBatchTxCount)
|
||||||
|
{
|
||||||
|
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]:"
|
||||||
|
<< "signers array exceeds 8 entries.";
|
||||||
|
return temARRAY_TOO_LARGE;
|
||||||
|
}
|
||||||
|
|
||||||
// Validation Inner Batch Txns
|
// Validation Inner Batch Txns
|
||||||
std::unordered_set<uint256> uniqueHashes;
|
std::unordered_set<uint256> uniqueHashes;
|
||||||
std::unordered_map<AccountID, std::unordered_set<std::uint32_t>> accountSeqTicket;
|
std::unordered_map<AccountID, std::unordered_set<std::uint32_t>> accountSeqTicket;
|
||||||
@@ -426,7 +445,7 @@ Batch::preflightSigValidated(PreflightContext const& ctx)
|
|||||||
if (requiredSigners.erase(signerAccount) == 0)
|
if (requiredSigners.erase(signerAccount) == 0)
|
||||||
{
|
{
|
||||||
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
|
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
|
||||||
<< "no account signature for inner txn.";
|
<< "extra signer provided: " << signerAccount;
|
||||||
return temBAD_SIGNER;
|
return temBAD_SIGNER;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -451,6 +470,54 @@ Batch::preflightSigValidated(PreflightContext const& ctx)
|
|||||||
return tesSUCCESS;
|
return tesSUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NotTEC
|
||||||
|
Batch::checkBatchSign(PreclaimContext const& ctx)
|
||||||
|
{
|
||||||
|
NotTEC ret = tesSUCCESS;
|
||||||
|
STArray const& signers{ctx.tx.getFieldArray(sfBatchSigners)};
|
||||||
|
for (auto const& signer : signers)
|
||||||
|
{
|
||||||
|
auto const idAccount = signer.getAccountID(sfAccount);
|
||||||
|
|
||||||
|
Blob const& pkSigner = signer.getFieldVL(sfSigningPubKey);
|
||||||
|
if (pkSigner.empty())
|
||||||
|
{
|
||||||
|
if (ret = checkMultiSign(ctx.view, ctx.flags, idAccount, signer, ctx.j);
|
||||||
|
!isTesSuccess(ret))
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// LCOV_EXCL_START
|
||||||
|
if (!publicKeyType(makeSlice(pkSigner)))
|
||||||
|
return tefBAD_AUTH;
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
|
auto const idSigner = calcAccountID(PublicKey(makeSlice(pkSigner)));
|
||||||
|
auto const sleAccount = ctx.view.read(keylet::account(idAccount));
|
||||||
|
|
||||||
|
if (sleAccount)
|
||||||
|
{
|
||||||
|
if (isPseudoAccount(sleAccount))
|
||||||
|
return tefBAD_AUTH;
|
||||||
|
|
||||||
|
if (ret = checkSingleSign(ctx.view, idSigner, idAccount, sleAccount, ctx.j);
|
||||||
|
!isTesSuccess(ret))
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (idAccount != idSigner)
|
||||||
|
return tefBAD_AUTH;
|
||||||
|
|
||||||
|
// A batch can include transactions from an un-created account ONLY
|
||||||
|
// when the account master key is the signer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Checks the validity of signatures for a batch transaction.
|
* @brief Checks the validity of signatures for a batch transaction.
|
||||||
*
|
*
|
||||||
@@ -459,7 +526,7 @@ Batch::preflightSigValidated(PreflightContext const& ctx)
|
|||||||
* corresponding error code.
|
* corresponding error code.
|
||||||
*
|
*
|
||||||
* Next, it verifies the batch-specific signature requirements by calling
|
* Next, it verifies the batch-specific signature requirements by calling
|
||||||
* Transactor::checkBatchSign. If this check fails, it also returns the
|
* Batch::checkBatchSign. If this check fails, it also returns the
|
||||||
* corresponding error code.
|
* corresponding error code.
|
||||||
*
|
*
|
||||||
* If both checks succeed, the function returns tesSUCCESS.
|
* If both checks succeed, the function returns tesSUCCESS.
|
||||||
@@ -474,8 +541,11 @@ Batch::checkSign(PreclaimContext const& ctx)
|
|||||||
if (auto ret = Transactor::checkSign(ctx); !isTesSuccess(ret))
|
if (auto ret = Transactor::checkSign(ctx); !isTesSuccess(ret))
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
if (auto ret = Transactor::checkBatchSign(ctx); !isTesSuccess(ret))
|
if (ctx.tx.isFieldPresent(sfBatchSigners))
|
||||||
return ret;
|
{
|
||||||
|
if (auto ret = checkBatchSign(ctx); !isTesSuccess(ret))
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
return tesSUCCESS;
|
return tesSUCCESS;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ LoanSet::preflight(PreflightContext const& ctx)
|
|||||||
auto const& tx = ctx.tx;
|
auto const& tx = ctx.tx;
|
||||||
|
|
||||||
// Special case for Batch inner transactions
|
// Special case for Batch inner transactions
|
||||||
if (tx.isFlag(tfInnerBatchTxn) && ctx.rules.enabled(featureBatch) &&
|
if (tx.isFlag(tfInnerBatchTxn) && ctx.rules.enabled(featureBatchV1_1) &&
|
||||||
!tx.isFieldPresent(sfCounterparty))
|
!tx.isFieldPresent(sfCounterparty))
|
||||||
{
|
{
|
||||||
auto const parentBatchId = ctx.parentBatchId.value_or(uint256{0});
|
auto const parentBatchId = ctx.parentBatchId.value_or(uint256{0});
|
||||||
|
|||||||
@@ -141,14 +141,11 @@ class Batch_test : public beast::unit_test::suite
|
|||||||
using namespace test::jtx;
|
using namespace test::jtx;
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
|
|
||||||
bool const withInnerSigFix = features[fixBatchInnerSigs];
|
|
||||||
|
|
||||||
for (bool const withBatch : {true, false})
|
for (bool const withBatch : {true, false})
|
||||||
{
|
{
|
||||||
testcase << "enabled: Batch " << (withBatch ? "enabled" : "disabled")
|
testcase << "enabled: Batch " << (withBatch ? "enabled" : "disabled");
|
||||||
<< ", Inner Sig Fix: " << (withInnerSigFix ? "enabled" : "disabled");
|
|
||||||
|
|
||||||
auto const amend = withBatch ? features : features - featureBatch;
|
auto const amend = withBatch ? features : features - featureBatchV1_1;
|
||||||
|
|
||||||
test::jtx::Env env{*this, amend};
|
test::jtx::Env env{*this, amend};
|
||||||
|
|
||||||
@@ -553,6 +550,7 @@ class Batch_test : public beast::unit_test::suite
|
|||||||
|
|
||||||
Serializer msg;
|
Serializer msg;
|
||||||
serializeBatch(msg, tfAllOrNothing, jt.stx->getBatchTransactionIDs());
|
serializeBatch(msg, tfAllOrNothing, jt.stx->getBatchTransactionIDs());
|
||||||
|
finishMultiSigningData(bob.id(), msg);
|
||||||
auto const sig = xrpl::sign(bob.pk(), bob.sk(), msg.slice());
|
auto const sig = xrpl::sign(bob.pk(), bob.sk(), msg.slice());
|
||||||
jt.jv[sfBatchSigners.jsonName][0u][sfBatchSigner.jsonName][sfAccount.jsonName] =
|
jt.jv[sfBatchSigners.jsonName][0u][sfBatchSigner.jsonName][sfAccount.jsonName] =
|
||||||
bob.human();
|
bob.human();
|
||||||
@@ -1405,7 +1403,7 @@ class Batch_test : public beast::unit_test::suite
|
|||||||
env.close();
|
env.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
// temARRAY_TOO_LARGE: Batch: signers array exceeds 8 entries.
|
// temARRAY_TOO_LARGE: Batch preflight: signers array exceeds 8 entries.
|
||||||
{
|
{
|
||||||
test::jtx::Env env{*this, features};
|
test::jtx::Env env{*this, features};
|
||||||
|
|
||||||
@@ -2191,22 +2189,16 @@ class Batch_test : public beast::unit_test::suite
|
|||||||
void
|
void
|
||||||
doTestInnerSubmitRPC(FeatureBitset features, bool withBatch)
|
doTestInnerSubmitRPC(FeatureBitset features, bool withBatch)
|
||||||
{
|
{
|
||||||
bool const withInnerSigFix = features[fixBatchInnerSigs];
|
std::string const testName =
|
||||||
|
std::string("inner submit rpc: batch ") + (withBatch ? "enabled" : "disabled") + ": ";
|
||||||
|
|
||||||
std::string const testName = [&]() {
|
auto const amend = withBatch ? features : features - featureBatchV1_1;
|
||||||
std::stringstream ss;
|
|
||||||
ss << "inner submit rpc: batch " << (withBatch ? "enabled" : "disabled")
|
|
||||||
<< ", inner sig fix: " << (withInnerSigFix ? "enabled" : "disabled") << ": ";
|
|
||||||
return ss.str();
|
|
||||||
}();
|
|
||||||
|
|
||||||
auto const amend = withBatch ? features : features - featureBatch;
|
|
||||||
|
|
||||||
using namespace test::jtx;
|
using namespace test::jtx;
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
|
|
||||||
test::jtx::Env env{*this, amend};
|
test::jtx::Env env{*this, amend};
|
||||||
if (!BEAST_EXPECT(amend[featureBatch] == withBatch))
|
if (!BEAST_EXPECT(amend[featureBatchV1_1] == withBatch))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto const alice = Account("alice");
|
auto const alice = Account("alice");
|
||||||
@@ -2328,8 +2320,7 @@ class Batch_test : public beast::unit_test::suite
|
|||||||
s.slice(),
|
s.slice(),
|
||||||
__LINE__,
|
__LINE__,
|
||||||
"fails local checks: Empty SigningPubKey.",
|
"fails local checks: Empty SigningPubKey.",
|
||||||
"fails local checks: Empty SigningPubKey.",
|
"fails local checks: Empty SigningPubKey.");
|
||||||
withBatch && !withInnerSigFix);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invalid RPC Submission: tfInnerBatchTxn pseudo-transaction
|
// Invalid RPC Submission: tfInnerBatchTxn pseudo-transaction
|
||||||
@@ -2340,7 +2331,7 @@ class Batch_test : public beast::unit_test::suite
|
|||||||
{
|
{
|
||||||
STTx amendTx(ttAMENDMENT, [seq = env.closed()->header().seq + 1](auto& obj) {
|
STTx amendTx(ttAMENDMENT, [seq = env.closed()->header().seq + 1](auto& obj) {
|
||||||
obj.setAccountID(sfAccount, AccountID());
|
obj.setAccountID(sfAccount, AccountID());
|
||||||
obj.setFieldH256(sfAmendment, fixBatchInnerSigs);
|
obj.setFieldH256(sfAmendment, featureBatchV1_1);
|
||||||
obj.setFieldU32(sfLedgerSequence, seq);
|
obj.setFieldU32(sfLedgerSequence, seq);
|
||||||
obj.setFieldU32(sfFlags, tfInnerBatchTxn);
|
obj.setFieldU32(sfFlags, tfInnerBatchTxn);
|
||||||
});
|
});
|
||||||
@@ -2352,8 +2343,7 @@ class Batch_test : public beast::unit_test::suite
|
|||||||
"Pseudo-transaction",
|
"Pseudo-transaction",
|
||||||
s.slice(),
|
s.slice(),
|
||||||
__LINE__,
|
__LINE__,
|
||||||
withInnerSigFix ? "fails local checks: Empty SigningPubKey."
|
"fails local checks: Empty SigningPubKey.",
|
||||||
: "fails local checks: Cannot submit pseudo transactions.",
|
|
||||||
"fails local checks: Empty SigningPubKey.");
|
"fails local checks: Empty SigningPubKey.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2414,6 +2404,53 @@ class Batch_test : public beast::unit_test::suite
|
|||||||
BEAST_EXPECT(env.balance(bob) == XRP(1000));
|
BEAST_EXPECT(env.balance(bob) == XRP(1000));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
testCheckAllSignatures(FeatureBitset features)
|
||||||
|
{
|
||||||
|
testcase("check all signatures");
|
||||||
|
|
||||||
|
using namespace test::jtx;
|
||||||
|
using namespace std::literals;
|
||||||
|
|
||||||
|
// Verifies that checkBatchSign validates all signers even when an
|
||||||
|
// unfunded account (signed with its master key) appears first in the
|
||||||
|
// sorted signer list. A funded account with an invalid signature must
|
||||||
|
// still be rejected with tefBAD_AUTH.
|
||||||
|
|
||||||
|
test::jtx::Env env{*this, features};
|
||||||
|
|
||||||
|
auto const alice = Account("alice");
|
||||||
|
// "aaa" sorts before other accounts alphabetically, ensuring the
|
||||||
|
// unfunded account is checked first in the sorted signer list
|
||||||
|
auto const unfunded = Account("aaa");
|
||||||
|
auto const carol = Account("carol");
|
||||||
|
env.fund(XRP(10000), alice, carol);
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// Verify sort order: unfunded.id() < carol.id()
|
||||||
|
BEAST_EXPECT(unfunded.id() < carol.id());
|
||||||
|
|
||||||
|
auto const seq = env.seq(alice);
|
||||||
|
auto const ledSeq = env.current()->seq();
|
||||||
|
auto const batchFee = batch::calcBatchFee(env, 2, 3);
|
||||||
|
|
||||||
|
// The batch includes:
|
||||||
|
// 1. alice pays unfunded (to create unfunded's account)
|
||||||
|
// 2. unfunded does a noop (signed by unfunded's master key - valid)
|
||||||
|
// 3. carol pays alice (signed by alice's key - INVALID since alice is
|
||||||
|
// not carol's regular key)
|
||||||
|
//
|
||||||
|
// checkBatchSign must validate all signers regardless of order.
|
||||||
|
// This must fail with tefBAD_AUTH.
|
||||||
|
env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
|
||||||
|
batch::inner(pay(alice, unfunded, XRP(100)), seq + 1),
|
||||||
|
batch::inner(noop(unfunded), ledSeq),
|
||||||
|
batch::inner(pay(carol, alice, XRP(1000)), env.seq(carol)),
|
||||||
|
batch::sig(unfunded, Reg{carol, alice}),
|
||||||
|
ter(tefBAD_AUTH));
|
||||||
|
env.close();
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
testAccountSet(FeatureBitset features)
|
testAccountSet(FeatureBitset features)
|
||||||
{
|
{
|
||||||
@@ -4331,6 +4368,7 @@ class Batch_test : public beast::unit_test::suite
|
|||||||
testIndependent(features);
|
testIndependent(features);
|
||||||
testInnerSubmitRPC(features);
|
testInnerSubmitRPC(features);
|
||||||
testAccountActivation(features);
|
testAccountActivation(features);
|
||||||
|
testCheckAllSignatures(features);
|
||||||
testAccountSet(features);
|
testAccountSet(features);
|
||||||
testAccountDelete(features);
|
testAccountDelete(features);
|
||||||
testLoan(features);
|
testLoan(features);
|
||||||
@@ -4356,7 +4394,6 @@ public:
|
|||||||
{
|
{
|
||||||
using namespace test::jtx;
|
using namespace test::jtx;
|
||||||
auto const sa = testable_amendments();
|
auto const sa = testable_amendments();
|
||||||
testWithFeats(sa - fixBatchInnerSigs);
|
|
||||||
testWithFeats(sa);
|
testWithFeats(sa);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ sig::operator()(Env& env, JTx& jt) const
|
|||||||
|
|
||||||
Serializer msg;
|
Serializer msg;
|
||||||
serializeBatch(msg, stx.getFlags(), stx.getBatchTransactionIDs());
|
serializeBatch(msg, stx.getFlags(), stx.getBatchTransactionIDs());
|
||||||
|
finishMultiSigningData(e.acct.id(), msg);
|
||||||
auto const sig = xrpl::sign(*publicKeyType(e.sig.pk().slice()), e.sig.sk(), msg.slice());
|
auto const sig = xrpl::sign(*publicKeyType(e.sig.pk().slice()), e.sig.sk(), msg.slice());
|
||||||
jo[sfTxnSignature.getJsonName()] = strHex(Slice{sig.data(), sig.size()});
|
jo[sfTxnSignature.getJsonName()] = strHex(Slice{sig.data(), sig.size()});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ class Feature_test : public beast::unit_test::suite
|
|||||||
// or removed, swap out for any other feature.
|
// or removed, swap out for any other feature.
|
||||||
BEAST_EXPECT(
|
BEAST_EXPECT(
|
||||||
featureToName(fixRemoveNFTokenAutoTrustLine) == "fixRemoveNFTokenAutoTrustLine");
|
featureToName(fixRemoveNFTokenAutoTrustLine) == "fixRemoveNFTokenAutoTrustLine");
|
||||||
BEAST_EXPECT(featureToName(featureBatch) == "Batch");
|
BEAST_EXPECT(featureToName(featureBatchV1_1) == "BatchV1_1");
|
||||||
BEAST_EXPECT(featureToName(featureDID) == "DID");
|
BEAST_EXPECT(featureToName(featureDID) == "DID");
|
||||||
BEAST_EXPECT(featureToName(fixIncludeKeyletFields) == "fixIncludeKeyletFields");
|
BEAST_EXPECT(featureToName(fixIncludeKeyletFields) == "fixIncludeKeyletFields");
|
||||||
BEAST_EXPECT(featureToName(featureTokenEscrow) == "TokenEscrow");
|
BEAST_EXPECT(featureToName(featureTokenEscrow) == "TokenEscrow");
|
||||||
|
|||||||
@@ -1119,7 +1119,8 @@ NetworkOPsImp::submitTransaction(std::shared_ptr<STTx const> const& iTrans)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Enforce Network bar for batch txn
|
// Enforce Network bar for batch txn
|
||||||
if (iTrans->isFlag(tfInnerBatchTxn) && m_ledgerMaster.getValidatedRules().enabled(featureBatch))
|
if (iTrans->isFlag(tfInnerBatchTxn) &&
|
||||||
|
m_ledgerMaster.getValidatedRules().enabled(featureBatchV1_1))
|
||||||
{
|
{
|
||||||
JLOG(m_journal.error()) << "Submitted transaction invalid: tfInnerBatchTxn flag present.";
|
JLOG(m_journal.error()) << "Submitted transaction invalid: tfInnerBatchTxn flag present.";
|
||||||
return;
|
return;
|
||||||
@@ -1185,7 +1186,7 @@ NetworkOPsImp::preProcessTransaction(std::shared_ptr<Transaction>& transaction)
|
|||||||
// under no circumstances will we ever accept an inner txn within a batch
|
// under no circumstances will we ever accept an inner txn within a batch
|
||||||
// txn from the network.
|
// txn from the network.
|
||||||
auto const sttx = *transaction->getSTransaction();
|
auto const sttx = *transaction->getSTransaction();
|
||||||
if (sttx.isFlag(tfInnerBatchTxn) && view->rules().enabled(featureBatch))
|
if (sttx.isFlag(tfInnerBatchTxn) && view->rules().enabled(featureBatchV1_1))
|
||||||
{
|
{
|
||||||
transaction->setStatus(INVALID);
|
transaction->setStatus(INVALID);
|
||||||
transaction->setResult(temINVALID_FLAG);
|
transaction->setResult(temINVALID_FLAG);
|
||||||
|
|||||||
@@ -1291,7 +1291,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
|
||||||
/*
|
/*
|
||||||
There is no need to check whether the featureBatch amendment is
|
There is no need to check whether the featureBatchV1_1 amendment is
|
||||||
enabled.
|
enabled.
|
||||||
|
|
||||||
* If the `tfInnerBatchTxn` flag is set, and the amendment is
|
* If the `tfInnerBatchTxn` flag is set, and the amendment is
|
||||||
@@ -2740,7 +2740,7 @@ PeerImp::checkTransaction(
|
|||||||
// charge strongly for relaying batch txns
|
// charge strongly for relaying batch txns
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
/*
|
/*
|
||||||
There is no need to check whether the featureBatch amendment is
|
There is no need to check whether the featureBatchV1_1 amendment is
|
||||||
enabled.
|
enabled.
|
||||||
|
|
||||||
* If the `tfInnerBatchTxn` flag is set, and the amendment is
|
* If the `tfInnerBatchTxn` flag is set, and the amendment is
|
||||||
|
|||||||
Reference in New Issue
Block a user