Skip to content

Commit

Permalink
Refactor TxSet validation logic.
Browse files Browse the repository at this point in the history
Also added the validation logic for the parallel Soroban phase, which has motivated the refactoring in the first place. The general idea is to move the phase-specific validation into phase frames.

Also improved the test coverage for the TxSet validation:

- Fixed the test for XDR structure validation - the whole 'valid' section has never been executed.
- Added a test for Soroban resource validation
- Added more coverage for parallel tx set phase validation
  • Loading branch information
dmkozh committed Jan 18, 2025
1 parent c888dfb commit ba003bb
Show file tree
Hide file tree
Showing 10 changed files with 1,518 additions and 707 deletions.
712 changes: 446 additions & 266 deletions src/herder/TxSetFrame.cpp

Large diffs are not rendered by default.

44 changes: 28 additions & 16 deletions src/herder/TxSetFrame.h
Original file line number Diff line number Diff line change
Expand Up @@ -196,18 +196,18 @@ class TxSetXDRFrame : public NonMovableOrCopyable
// - The whole phase (`TxStageFrameList`) consists of several sequential
// 'stages' (`TxStageFrame`). A stage has to be executed after every
// transaction in the previous stage has been applied.
// - A 'stage' (`TxStageFrame`) consists of several parallel 'threads'
// (`TxThreadFrame`). Transactions in different 'threads' are independent of
// - A 'stage' (`TxStageFrame`) consists of several independent 'clusters'
// (`TxClusterFrame`). Transactions in different 'clusters' are independent of
// each other and can be applied in parallel.
// - A 'thread' (`TxThreadFrame`) consists of transactions that should
// - A 'cluster' (`TxClusterFrame`) consists of transactions that should
// generally be applied sequentially. However, not all the transactions in
// the thread are necessarily conflicting with each other; it is possible
// that some, or even all transactions in the thread structure can be applied
// the cluster are necessarily conflicting with each other; it is possible
// that some, or even all transactions in the cluster structure can be applied
// in parallel with each other (depending on their footprints).
//
// This structure mimics the XDR structure of the `ParallelTxsComponent`.
using TxThreadFrame = TxFrameList;
using TxStageFrame = std::vector<TxThreadFrame>;
using TxClusterFrame = TxFrameList;
using TxStageFrame = std::vector<TxClusterFrame>;
using TxStageFrameList = std::vector<TxStageFrame>;

// Alias for the map from transaction to its inclusion fee as defined by the
Expand Down Expand Up @@ -276,19 +276,23 @@ class TxSetPhaseFrame
Iterator(TxStageFrameList const& txs, size_t stageIndex);
TxStageFrameList const& mStages;
size_t mStageIndex = 0;
size_t mThreadIndex = 0;
size_t mClusterIndex = 0;
size_t mTxIndex = 0;
};
Iterator begin() const;
Iterator end() const;
size_t size() const;
size_t sizeTx() const;
size_t sizeOp() const;
size_t size(LedgerHeader const& lclHeader) const;
bool empty() const;

// Get _inclusion_ fee map for this phase. The map contains lowest base
// fee for each transaction (lowest base fee is identical for all
// transactions in the same lane)
InclusionFeeMap const& getInclusionFeeMap() const;

std::optional<Resource> getTotalResources() const;

private:
friend class TxSetXDRFrame;
friend class ApplicableTxSetFrame;
Expand All @@ -312,16 +316,16 @@ class TxSetPhaseFrame
TxFrameList& invalidTxs,
bool enforceTxsApplyOrder);
#endif

TxSetPhaseFrame(TxFrameList const& txs,
TxSetPhaseFrame(TxSetPhase phase, TxFrameList const& txs,
std::shared_ptr<InclusionFeeMap> inclusionFeeMap);
TxSetPhaseFrame(TxStageFrameList&& txs,
TxSetPhaseFrame(TxSetPhase phase, TxStageFrameList&& txs,
std::shared_ptr<InclusionFeeMap> inclusionFeeMap);

// Creates a new phase from `TransactionPhase` XDR coming from a
// `GeneralizedTransactionSet`.
static std::optional<TxSetPhaseFrame>
makeFromWire(Hash const& networkID, TransactionPhase const& xdrPhase);
makeFromWire(TxSetPhase phase, Hash const& networkID,
TransactionPhase const& xdrPhase);

// Creates a new phase from all the transactions in the legacy
// `TransactionSet` XDR.
Expand All @@ -330,10 +334,20 @@ class TxSetPhaseFrame
xdr::xvector<TransactionEnvelope> const& xdrTxs);

// Creates a valid empty phase with given `isParallel` flag.
static TxSetPhaseFrame makeEmpty(bool isParallel);
static TxSetPhaseFrame makeEmpty(TxSetPhase phase, bool isParallel);

// Returns a copy of this phase with transactions sorted for apply.
TxSetPhaseFrame sortedForApply(Hash const& txSetHash) const;
bool checkValid(Application& app, uint64_t lowerBoundCloseTimeOffset,
uint64_t upperBoundCloseTimeOffset) const;
bool checkValidClassic(LedgerHeader const& lclHeader) const;
bool checkValidSoroban(LedgerHeader const& lclHeader,
SorobanNetworkConfig const& sorobanConfig) const;

bool txsAreValid(Application& app, uint64_t lowerBoundCloseTimeOffset,
uint64_t upperBoundCloseTimeOffset) const;

TxSetPhase mPhase;

TxStageFrameList mStages;
std::shared_ptr<InclusionFeeMap> mInclusionFeeMap;
Expand Down Expand Up @@ -471,8 +485,6 @@ class ApplicableTxSetFrame
ApplicableTxSetFrame(ApplicableTxSetFrame const&) = default;
ApplicableTxSetFrame(ApplicableTxSetFrame&&) = default;

std::optional<Resource> getTxSetSorobanResource() const;

void toXDR(TransactionSet& set) const;
void toXDR(GeneralizedTransactionSet& generalizedTxSet) const;

Expand Down
69 changes: 65 additions & 4 deletions src/herder/test/TestTxSetUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ makeTxSetXDR(std::vector<TransactionFrameBasePtr> const& txs,
}

GeneralizedTransactionSet
makeGeneralizedTxSetXDR(std::vector<ComponentPhases> const& phases,
makeGeneralizedTxSetXDR(std::vector<PhaseComponents> const& phases,
Hash const& previousLedgerHash,
bool useParallelSorobanPhase)
{
Expand Down Expand Up @@ -76,11 +76,11 @@ makeGeneralizedTxSetXDR(std::vector<ComponentPhases> const& phases,
}
if (!txs.empty())
{
auto& thread =
auto& cluster =
component.executionStages.emplace_back().emplace_back();
for (auto const& tx : txs)
{
thread.emplace_back(tx->getEnvelope());
cluster.emplace_back(tx->getEnvelope());
}
}
#else
Expand Down Expand Up @@ -120,7 +120,7 @@ makeNonValidatedTxSet(std::vector<TransactionFrameBasePtr> const& txs,

std::pair<TxSetXDRFrameConstPtr, ApplicableTxSetFrameConstPtr>
makeNonValidatedGeneralizedTxSet(
std::vector<ComponentPhases> const& txsPerBaseFee, Application& app,
std::vector<PhaseComponents> const& txsPerBaseFee, Application& app,
Hash const& previousLedgerHash, std::optional<bool> useParallelSorobanPhase)
{
if (!useParallelSorobanPhase.has_value())
Expand Down Expand Up @@ -157,5 +157,66 @@ makeNonValidatedTxSetBasedOnLedgerVersion(
}
}

#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION
void
normalizeParallelPhaseXDR(TransactionPhase& phase)
{
auto compareTxHash = [](TransactionEnvelope const& tx1,
TransactionEnvelope const& tx2) -> bool {
return xdrSha256(tx1) < xdrSha256(tx2);
};
for (auto& stage : phase.parallelTxsComponent().executionStages)
{
for (auto& cluster : stage)
{
std::sort(cluster.begin(), cluster.end(), compareTxHash);
}
std::sort(stage.begin(), stage.end(),
[&](auto const& c1, auto const& c2) {
return compareTxHash(c1.front(), c2.front());
});
}
std::sort(phase.parallelTxsComponent().executionStages.begin(),
phase.parallelTxsComponent().executionStages.end(),
[&](auto const& s1, auto const& s2) {
return compareTxHash(s1.front().front(), s2.front().front());
});
}

std::pair<TxSetXDRFrameConstPtr, ApplicableTxSetFrameConstPtr>
makeNonValidatedGeneralizedTxSet(PhaseComponents const& classicTxsPerBaseFee,
std::optional<int64_t> sorobanBaseFee,
TxStageFrameList const& sorobanTxsPerStage,
Application& app,
Hash const& previousLedgerHash)
{
auto xdrTxSet = makeGeneralizedTxSetXDR({classicTxsPerBaseFee},
previousLedgerHash, false);
xdrTxSet.v1TxSet().phases.emplace_back(1);
auto& phase = xdrTxSet.v1TxSet().phases.back();
if (sorobanBaseFee)
{
phase.parallelTxsComponent().baseFee.activate() = *sorobanBaseFee;
}

auto& stages = phase.parallelTxsComponent().executionStages;
for (auto const& stage : sorobanTxsPerStage)
{
auto& xdrStage = stages.emplace_back();
for (auto const& cluster : stage)
{
auto& xdrCluster = xdrStage.emplace_back();
for (auto const& tx : cluster)
{
xdrCluster.emplace_back(tx->getEnvelope());
}
}
}
normalizeParallelPhaseXDR(phase);
auto txSet = TxSetXDRFrame::makeFromWire(xdrTxSet);
return std::make_pair(txSet, txSet->prepareForApply(app));
}
#endif

} // namespace testtxset
} // namespace stellar
14 changes: 12 additions & 2 deletions src/herder/test/TestTxSetUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,27 @@ namespace stellar
namespace testtxset
{

using ComponentPhases = std::vector<
using PhaseComponents = std::vector<
std::pair<std::optional<int64_t>, std::vector<TransactionFrameBasePtr>>>;
std::pair<TxSetXDRFrameConstPtr, ApplicableTxSetFrameConstPtr>
makeNonValidatedGeneralizedTxSet(
std::vector<ComponentPhases> const& txsPerBaseFee, Application& app,
std::vector<PhaseComponents> const& txsPerBaseFee, Application& app,
Hash const& previousLedgerHash,
std::optional<bool> useParallelSorobanPhase = std::nullopt);

std::pair<TxSetXDRFrameConstPtr, ApplicableTxSetFrameConstPtr>
makeNonValidatedTxSetBasedOnLedgerVersion(
std::vector<TransactionFrameBasePtr> const& txs, Application& app,
Hash const& previousLedgerHash);
#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION
void normalizeParallelPhaseXDR(TransactionPhase& phase);

std::pair<TxSetXDRFrameConstPtr, ApplicableTxSetFrameConstPtr>
makeNonValidatedGeneralizedTxSet(PhaseComponents const& classicTxsPerBaseFee,
std::optional<int64_t> sorobanBaseFee,
TxStageFrameList const& sorobanTxsPerStage,
Application& app,
Hash const& previousLedgerHash);
#endif
} // namespace testtxset
} // namespace stellar
Loading

0 comments on commit ba003bb

Please sign in to comment.