Skip to content

Commit

Permalink
Legacy tx sets: avoid using read-only LCL meant for main thread only (#…
Browse files Browse the repository at this point in the history
…4659)

Looks like parallel ledger close is tripping `threadIsMain` assert in
`getLastClosedLedgerHeader` in `prepareForApply`. This only impacts new
networks in protocol 0 (which use legacy tx sets). Fix is easy: callers
of prepareForApply typically hold a thread-safe LCL already, so use that
instead. We didn't catch this in tests because tests use a custom
upgrade logic, such that we upgrade to generalized transactions sets on
genesis.
  • Loading branch information
marta-lokhova authored Mar 4, 2025
2 parents 69cb5cb + 80068b7 commit adb6f3f
Show file tree
Hide file tree
Showing 7 changed files with 46 additions and 18 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
continue-on-error: ${{ matrix.checks == 'advisories' }}
steps:
- uses: actions/checkout@v4
- uses: EmbarkStudios/cargo-deny-action@b01e7a8cfb1f496c52d77361e84c1840d8246393
- uses: EmbarkStudios/cargo-deny-action@8d73959fce1cdc8989f23fdf03bec6ae6a6576ef
with:
command: check ${{ matrix.checks }}
# leave arguments empty so we don't test --all-features
Expand Down
4 changes: 2 additions & 2 deletions src/herder/HerderSCPDriver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -709,7 +709,7 @@ HerderSCPDriver::combineCandidates(uint64_t slotIndex,
auto cTxSet = mPendingEnvelopes.getTxSet(sv.txSetHash);
releaseAssert(cTxSet);
// Only valid applicable tx sets should be combined.
auto cApplicableTxSet = cTxSet->prepareForApply(mApp);
auto cApplicableTxSet = cTxSet->prepareForApply(mApp, lcl.header);
releaseAssert(cApplicableTxSet);
if (cTxSet->previousLedgerHash() == lcl.hash)
{
Expand Down Expand Up @@ -1246,7 +1246,7 @@ HerderSCPDriver::checkAndCacheTxSetValid(TxSetXDRFrame const& txSet,
ApplicableTxSetFrameConstPtr applicableTxSet;
if (txSet.previousLedgerHash() == lcl.hash)
{
applicableTxSet = txSet.prepareForApply(mApp);
applicableTxSet = txSet.prepareForApply(mApp, lcl.header);
}

bool res = true;
Expand Down
8 changes: 4 additions & 4 deletions src/herder/TxSetFrame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -783,7 +783,7 @@ makeTxSetFromTransactions(PerPhaseTransactionList const& txPhases,
#endif

ApplicableTxSetFrameConstPtr outputApplicableTxSet =
outputTxSet->prepareForApply(app);
outputTxSet->prepareForApply(app, lclHeader.header);

if (!outputApplicableTxSet)
{
Expand Down Expand Up @@ -934,7 +934,8 @@ TxSetXDRFrame::toStellarMessage() const
#endif

ApplicableTxSetFrameConstPtr
TxSetXDRFrame::prepareForApply(Application& app) const
TxSetXDRFrame::prepareForApply(Application& app,
LedgerHeader const& lclHeader) const
{
#ifdef BUILD_TESTS
if (mApplicableTxSetOverride)
Expand Down Expand Up @@ -972,8 +973,7 @@ TxSetXDRFrame::prepareForApply(Application& app) const
{
auto const& xdrTxSet = std::get<TransactionSet>(mXDRTxSet);
auto maybePhase = TxSetPhaseFrame::makeFromWireLegacy(
app.getLedgerManager().getLastClosedLedgerHeader().header,
app.getNetworkID(), xdrTxSet.txs);
lclHeader, app.getNetworkID(), xdrTxSet.txs);
if (!maybePhase)
{
return nullptr;
Expand Down
3 changes: 2 additions & 1 deletion src/herder/TxSetFrame.h
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,8 @@ class TxSetXDRFrame : public NonMovableOrCopyable
// This may *only* be called when LCL hash matches the `previousLedgerHash`
// of this `TxSetFrame` - tx sets with a wrong ledger hash shouldn't even
// be attempted to be interpreted.
ApplicableTxSetFrameConstPtr prepareForApply(Application& app) const;
ApplicableTxSetFrameConstPtr
prepareForApply(Application& app, LedgerHeader const& lclHeader) const;

bool isGeneralizedTxSet() const;

Expand Down
13 changes: 10 additions & 3 deletions src/herder/test/TestTxSetUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@ makeNonValidatedTxSet(std::vector<TransactionFrameBasePtr> const& txs,
{
auto xdrTxSet = makeTxSetXDR(txs, previousLedgerHash);
auto txSet = TxSetXDRFrame::makeFromWire(xdrTxSet);
auto applicableTxSet = txSet->prepareForApply(app);
auto applicableTxSet = txSet->prepareForApply(
app, app.getLedgerManager().getLastClosedLedgerHeader().header);
return std::make_pair(txSet, std::move(applicableTxSet));
}
} // namespace
Expand All @@ -135,7 +136,10 @@ makeNonValidatedGeneralizedTxSet(
auto xdrTxSet = makeGeneralizedTxSetXDR(txsPerBaseFee, previousLedgerHash,
*useParallelSorobanPhase);
auto txSet = TxSetXDRFrame::makeFromWire(xdrTxSet);
return std::make_pair(txSet, txSet->prepareForApply(app));
return std::make_pair(
txSet,
txSet->prepareForApply(
app, app.getLedgerManager().getLastClosedLedgerHeader().header));
}

std::pair<TxSetXDRFrameConstPtr, ApplicableTxSetFrameConstPtr>
Expand Down Expand Up @@ -214,7 +218,10 @@ makeNonValidatedGeneralizedTxSet(PhaseComponents const& classicTxsPerBaseFee,
}
normalizeParallelPhaseXDR(phase);
auto txSet = TxSetXDRFrame::makeFromWire(xdrTxSet);
return std::make_pair(txSet, txSet->prepareForApply(app));
return std::make_pair(
txSet,
txSet->prepareForApply(
app, app.getLedgerManager().getLastClosedLedgerHeader().header));
}
#endif

Expand Down
32 changes: 26 additions & 6 deletions src/herder/test/TxSetTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,21 +41,33 @@ TEST_CASE("generalized tx set XDR validation", "[txset]")
SECTION("no phases")
{
auto txSet = TxSetXDRFrame::makeFromWire(xdrTxSet);
REQUIRE(txSet->prepareForApply(*app) == nullptr);
REQUIRE(
txSet->prepareForApply(
*app,
app->getLedgerManager().getLastClosedLedgerHeader().header) ==
nullptr);
}
SECTION("one phase")
{
auto txSet = TxSetXDRFrame::makeFromWire(xdrTxSet);
xdrTxSet.v1TxSet().phases.emplace_back();
REQUIRE(txSet->prepareForApply(*app) == nullptr);
REQUIRE(
txSet->prepareForApply(
*app,
app->getLedgerManager().getLastClosedLedgerHeader().header) ==
nullptr);
}
SECTION("too many phases")
{
xdrTxSet.v1TxSet().phases.emplace_back();
xdrTxSet.v1TxSet().phases.emplace_back();
xdrTxSet.v1TxSet().phases.emplace_back();
auto txSet = TxSetXDRFrame::makeFromWire(xdrTxSet);
REQUIRE(txSet->prepareForApply(*app) == nullptr);
REQUIRE(
txSet->prepareForApply(
*app,
app->getLedgerManager().getLastClosedLedgerHeader().header) ==
nullptr);
}

SECTION("two phase scenarios")
Expand Down Expand Up @@ -442,11 +454,17 @@ TEST_CASE("generalized tx set XDR validation", "[txset]")
bool valid = classicIsValid && sorobanIsValid;
if (valid)
{
REQUIRE(txSet->prepareForApply(*app) != nullptr);
REQUIRE(txSet->prepareForApply(
*app, app->getLedgerManager()
.getLastClosedLedgerHeader()
.header) != nullptr);
}
else
{
REQUIRE(txSet->prepareForApply(*app) == nullptr);
REQUIRE(txSet->prepareForApply(
*app, app->getLedgerManager()
.getLastClosedLedgerHeader()
.header) == nullptr);
}
}
}
Expand Down Expand Up @@ -502,7 +520,9 @@ testGeneralizedTxSetXDRConversion(ProtocolVersion protocolVersion)
auto checkXdrRoundtrip = [&](GeneralizedTransactionSet const& txSetXdr) {
auto txSetFrame = TxSetXDRFrame::makeFromWire(txSetXdr);
ApplicableTxSetFrameConstPtr applicableFrame =
txSetFrame->prepareForApply(*app);
txSetFrame->prepareForApply(
*app,
app->getLedgerManager().getLastClosedLedgerHeader().header);
REQUIRE(applicableFrame->checkValid(*app, 0, 0));
GeneralizedTransactionSet newXdr;
applicableFrame->toWireTxSetFrame()->toXDR(newXdr);
Expand Down
2 changes: 1 addition & 1 deletion src/ledger/LedgerManagerImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -877,7 +877,7 @@ LedgerManagerImpl::applyLedger(LedgerCloseData const& ledgerData,
header.current().scpValue = sv;

maybeResetLedgerCloseMetaDebugStream(header.current().ledgerSeq);
auto applicableTxSet = txSet->prepareForApply(mApp);
auto applicableTxSet = txSet->prepareForApply(mApp, prevHeader);

if (applicableTxSet == nullptr)
{
Expand Down

0 comments on commit adb6f3f

Please sign in to comment.