mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-04 01:06:48 +00:00
Merge branch 'develop' into ximinez/lending-XLS-66
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user