Merge branch 'develop' into ximinez/lending-XLS-66

This commit is contained in:
Bronek Kozicki
2025-06-03 14:36:20 +01:00
161 changed files with 11530 additions and 1234 deletions

View File

@@ -19,6 +19,7 @@
#include <xrpl/basics/Blob.h>
#include <xrpl/basics/Expected.h>
#include <xrpl/basics/Log.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/StringUtilities.h>
#include <xrpl/basics/base_uint.h>
@@ -29,6 +30,7 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/json/json_value.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Batch.h>
#include <xrpl/protocol/HashPrefix.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/Protocol.h>
@@ -42,6 +44,7 @@
#include <xrpl/protocol/STBase.h>
#include <xrpl/protocol/STObject.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/STVector256.h>
#include <xrpl/protocol/SecretKey.h>
#include <xrpl/protocol/SeqProxy.h>
#include <xrpl/protocol/Serializer.h>
@@ -283,6 +286,42 @@ STTx::checkSign(
return {};
}
Expected<void, std::string>
STTx::checkBatchSign(
RequireFullyCanonicalSig requireCanonicalSig,
Rules const& rules) const
{
try
{
XRPL_ASSERT(
getTxnType() == ttBATCH,
"STTx::checkBatchSign : not a batch transaction");
if (getTxnType() != ttBATCH)
{
JLOG(debugLog().fatal()) << "not a batch transaction";
return Unexpected("Not a batch transaction.");
}
STArray const& signers{getFieldArray(sfBatchSigners)};
for (auto const& signer : signers)
{
Blob const& signingPubKey = signer.getFieldVL(sfSigningPubKey);
auto const result = signingPubKey.empty()
? checkBatchMultiSign(signer, requireCanonicalSig, rules)
: checkBatchSingleSign(signer, requireCanonicalSig);
if (!result)
return result;
}
return {};
}
catch (std::exception const& e)
{
JLOG(debugLog().error())
<< "Batch signature check failed: " << e.what();
}
return Unexpected("Internal batch signature check failure.");
}
Json::Value
STTx::getJson(JsonOptions options) const
{
@@ -362,13 +401,12 @@ STTx::getMetaSQL(
getFieldU32(sfSequence) % inLedger % status % rTxn % escapedMetaData);
}
Expected<void, std::string>
STTx::checkSingleSign(
RequireFullyCanonicalSig requireCanonicalSig,
STObject const* pSig) const
static Expected<void, std::string>
singleSignHelper(
STObject const& sigObject,
Slice const& data,
bool const fullyCanonical)
{
STObject const& sigObject{pSig ? *pSig : *this};
// We don't allow both a non-empty sfSigningPubKey and an sfSigners.
// That would allow the transaction to be signed two ways. So if both
// fields are present the signature is invalid.
@@ -378,42 +416,60 @@ STTx::checkSingleSign(
bool validSig = false;
try
{
bool const fullyCanonical = (getFlags() & tfFullyCanonicalSig) ||
(requireCanonicalSig == RequireFullyCanonicalSig::yes);
auto const spk = sigObject.getFieldVL(sfSigningPubKey);
if (publicKeyType(makeSlice(spk)))
{
Blob const signature = sigObject.getFieldVL(sfTxnSignature);
Blob const data = getSigningData(*this);
validSig = verify(
PublicKey(makeSlice(spk)),
makeSlice(data),
data,
makeSlice(signature),
fullyCanonical);
}
}
catch (std::exception const&)
{
// Assume it was a signature failure.
validSig = false;
}
if (validSig == false)
if (!validSig)
return Unexpected("Invalid signature.");
// Signature was verified.
return {};
}
Expected<void, std::string>
STTx::checkMultiSign(
STTx::checkSingleSign(
RequireFullyCanonicalSig requireCanonicalSig,
Rules const& rules,
STObject const* pSig) const
{
STObject const& sigObject{pSig ? *pSig : *this};
auto const data = getSigningData(*this);
bool const fullyCanonical = (getFlags() & tfFullyCanonicalSig) ||
(requireCanonicalSig == STTx::RequireFullyCanonicalSig::yes);
return singleSignHelper(sigObject, makeSlice(data), fullyCanonical);
}
Expected<void, std::string>
STTx::checkBatchSingleSign(
STObject const& batchSigner,
RequireFullyCanonicalSig requireCanonicalSig) const
{
Serializer msg;
serializeBatch(msg, getFlags(), getBatchTransactionIDs());
bool const fullyCanonical = (getFlags() & tfFullyCanonicalSig) ||
(requireCanonicalSig == STTx::RequireFullyCanonicalSig::yes);
return singleSignHelper(batchSigner, msg.slice(), fullyCanonical);
}
Expected<void, std::string>
multiSignHelper(
STObject const& sigObject,
std::optional<AccountID> txnAccountID,
bool const fullyCanonical,
std::function<Serializer(AccountID const&)> makeMsg,
Rules const& rules)
{
// Make sure the MultiSigners are present. Otherwise they are not
// attempting multi-signing and we just have a bad SigningPubKey.
if (!sigObject.isFieldPresent(sfSigners))
@@ -427,22 +483,10 @@ STTx::checkMultiSign(
STArray const& signers{sigObject.getFieldArray(sfSigners)};
// There are well known bounds that the number of signers must be within.
if (signers.size() < minMultiSigners ||
signers.size() > maxMultiSigners(&rules))
if (signers.size() < STTx::minMultiSigners ||
signers.size() > STTx::maxMultiSigners(&rules))
return Unexpected("Invalid Signers array size.");
// We can ease the computational load inside the loop a bit by
// pre-constructing part of the data that we hash. Fill a Serializer
// with the stuff that stays constant from signature to signature.
Serializer const dataStart{startMultiSigningData(*this)};
// We also use the sfAccount field inside the loop. Get it once.
auto const txnAccountID = getAccountID(sfAccount);
// Determine whether signatures must be full canonical.
bool const fullyCanonical = (getFlags() & tfFullyCanonicalSig) ||
(requireCanonicalSig == RequireFullyCanonicalSig::yes);
// Signers must be in sorted order by AccountID.
AccountID lastAccountID(beast::zero);
@@ -451,7 +495,7 @@ STTx::checkMultiSign(
auto const accountID = signer.getAccountID(sfAccount);
// The account owner may not usually multisign for themselves.
if (!pSig && accountID == txnAccountID)
if (txnAccountID == accountID)
return Unexpected("Invalid multisigner.");
// No duplicate signers allowed.
@@ -469,18 +513,13 @@ STTx::checkMultiSign(
bool validSig = false;
try
{
Serializer s = dataStart;
finishMultiSigningData(accountID, s);
auto spk = signer.getFieldVL(sfSigningPubKey);
if (publicKeyType(makeSlice(spk)))
{
Blob const signature = signer.getFieldVL(sfTxnSignature);
validSig = verify(
PublicKey(makeSlice(spk)),
s.slice(),
makeMsg(accountID).slice(),
makeSlice(signature),
fullyCanonical);
}
@@ -499,6 +538,100 @@ STTx::checkMultiSign(
return {};
}
Expected<void, std::string>
STTx::checkBatchMultiSign(
STObject const& batchSigner,
RequireFullyCanonicalSig requireCanonicalSig,
Rules const& rules) const
{
bool const fullyCanonical = (getFlags() & tfFullyCanonicalSig) ||
(requireCanonicalSig == RequireFullyCanonicalSig::yes);
// We can ease the computational load inside the loop a bit by
// pre-constructing part of the data that we hash. Fill a Serializer
// with the stuff that stays constant from signature to signature.
Serializer dataStart;
serializeBatch(dataStart, getFlags(), getBatchTransactionIDs());
return multiSignHelper(
batchSigner,
std::nullopt,
fullyCanonical,
[&dataStart](AccountID const& accountID) mutable -> Serializer {
Serializer s = dataStart;
finishMultiSigningData(accountID, s);
return s;
},
rules);
}
Expected<void, std::string>
STTx::checkMultiSign(
RequireFullyCanonicalSig requireCanonicalSig,
Rules const& rules,
STObject const* pSig) const
{
STObject const& sigObject{pSig ? *pSig : *this};
bool const fullyCanonical = (getFlags() & tfFullyCanonicalSig) ||
(requireCanonicalSig == RequireFullyCanonicalSig::yes);
// Used inside the loop in multiSignHelper to enforce that
// the account owner may not multisign for themselves.
auto const txnAccountID =
pSig ? std::nullopt : std::optional<AccountID>(getAccountID(sfAccount));
// We can ease the computational load inside the loop a bit by
// pre-constructing part of the data that we hash. Fill a Serializer
// with the stuff that stays constant from signature to signature.
Serializer dataStart = startMultiSigningData(*this);
return multiSignHelper(
sigObject,
txnAccountID,
fullyCanonical,
[&dataStart](AccountID const& accountID) mutable -> Serializer {
Serializer s = dataStart;
finishMultiSigningData(accountID, s);
return s;
},
rules);
}
/**
* @brief Retrieves a batch of transaction IDs from the STTx.
*
* This function returns a vector of transaction IDs by extracting them from
* the field array `sfRawTransactions` within the STTx. If the batch
* transaction IDs have already been computed and cached in `batch_txn_ids_`,
* it returns the cached vector. Otherwise, it computes the transaction IDs,
* caches them, and then returns the vector.
*
* @return A vector of `uint256` containing the batch transaction IDs.
*
* @note The function asserts that the `sfRawTransactions` field array is not
* empty and that the size of the computed batch transaction IDs matches the
* size of the `sfRawTransactions` field array.
*/
std::vector<uint256>
STTx::getBatchTransactionIDs() const
{
XRPL_ASSERT(
getTxnType() == ttBATCH,
"STTx::getBatchTransactionIDs : not a batch transaction");
XRPL_ASSERT(
getFieldArray(sfRawTransactions).size() != 0,
"STTx::getBatchTransactionIDs : empty raw transactions");
if (batch_txn_ids_.size() != 0)
return batch_txn_ids_;
for (STObject const& rb : getFieldArray(sfRawTransactions))
batch_txn_ids_.push_back(rb.getHash(HashPrefix::transactionID));
XRPL_ASSERT(
batch_txn_ids_.size() == getFieldArray(sfRawTransactions).size(),
"STTx::getBatchTransactionIDs : batch transaction IDs size mismatch");
return batch_txn_ids_;
}
//------------------------------------------------------------------------------
static bool
@@ -634,6 +767,42 @@ invalidMPTAmountInTx(STObject const& tx)
return false;
}
static bool
isRawTransactionOkay(STObject const& st, std::string& reason)
{
if (!st.isFieldPresent(sfRawTransactions))
return true;
if (st.isFieldPresent(sfBatchSigners) &&
st.getFieldArray(sfBatchSigners).size() > maxBatchTxCount)
{
reason = "Batch Signers array exceeds max entries.";
return false;
}
auto const& rawTxns = st.getFieldArray(sfRawTransactions);
if (rawTxns.size() > maxBatchTxCount)
{
reason = "Raw Transactions array exceeds max entries.";
return false;
}
for (STObject raw : rawTxns)
{
try
{
TxType const tt =
safe_cast<TxType>(raw.getFieldU16(sfTransactionType));
raw.applyTemplate(getTxFormat(tt)->getSOTemplate());
}
catch (std::exception const& e)
{
reason = e.what();
return false;
}
}
return true;
}
bool
passesLocalChecks(STObject const& st, std::string& reason)
{
@@ -658,6 +827,9 @@ passesLocalChecks(STObject const& st, std::string& reason)
return false;
}
if (!isRawTransactionOkay(st, reason))
return false;
return true;
}
@@ -673,10 +845,13 @@ sterilize(STTx const& stx)
bool
isPseudoTx(STObject const& tx)
{
auto t = tx[~sfTransactionType];
auto const t = tx[~sfTransactionType];
if (!t)
return false;
auto tt = safe_cast<TxType>(*t);
auto const tt = safe_cast<TxType>(*t);
return tt == ttAMENDMENT || tt == ttFEE || tt == ttUNL_MODIFY;
}