Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -583,10 +583,10 @@ FUZZ_LINKER_FLAGS = -Wl,--whole-archive,libfuzz_common.a,--no-whole-archive $(if
# - Create regression test target

# List of fuzz targets (lowercase names)
FUZZ_TARGETS = tx overlay soroban_expr soroban_wasmi
FUZZ_TARGETS = tx overlay scp parallel_tx bucketlist soroban_expr soroban_wasmi

# Fuzz binaries are noinst because they're development tools
noinst_PROGRAMS = fuzz_tx fuzz_overlay fuzz_soroban_expr fuzz_soroban_wasmi
noinst_PROGRAMS = fuzz_tx fuzz_overlay fuzz_scp fuzz_parallel_tx fuzz_bucketlist fuzz_soroban_expr fuzz_soroban_wasmi

# Each fuzz target only compiles FuzzMain.cpp (with its specific -DFUZZ_TARGET_NAME)
# and links against the shared libfuzz_common.a library.
Expand All @@ -600,6 +600,21 @@ fuzz_overlay_CXXFLAGS = $(FUZZ_CXXFLAGS) -DFUZZ_TARGET_NAME=\"overlay\"
fuzz_overlay_LDADD = $(FUZZ_LIBS)
fuzz_overlay_LDFLAGS = $(FUZZ_LINKER_FLAGS)

fuzz_scp_SOURCES = $(FUZZ_MAIN)
fuzz_scp_CXXFLAGS = $(FUZZ_CXXFLAGS) -DFUZZ_TARGET_NAME=\"scp\"
fuzz_scp_LDADD = $(FUZZ_LIBS)
fuzz_scp_LDFLAGS = $(FUZZ_LINKER_FLAGS)

fuzz_parallel_tx_SOURCES = $(FUZZ_MAIN)
fuzz_parallel_tx_CXXFLAGS = $(FUZZ_CXXFLAGS) -DFUZZ_TARGET_NAME=\"parallel_tx\"
fuzz_parallel_tx_LDADD = $(FUZZ_LIBS)
fuzz_parallel_tx_LDFLAGS = $(FUZZ_LINKER_FLAGS)

fuzz_bucketlist_SOURCES = $(FUZZ_MAIN)
fuzz_bucketlist_CXXFLAGS = $(FUZZ_CXXFLAGS) -DFUZZ_TARGET_NAME=\"bucketlist\"
fuzz_bucketlist_LDADD = $(FUZZ_LIBS)
fuzz_bucketlist_LDFLAGS = $(FUZZ_LINKER_FLAGS)

# Soroban fuzz targets (require --features fuzz in Rust build)
fuzz_soroban_expr_SOURCES = $(FUZZ_MAIN)
fuzz_soroban_expr_CXXFLAGS = $(FUZZ_CXXFLAGS) -DFUZZ_TARGET_NAME=\"soroban_expr\"
Expand Down
2 changes: 2 additions & 0 deletions src/main/CommandLine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
#include "simulation/ApplyLoad.h"
#include "test/TestUtils.h"
#include "test/fuzz/FuzzTargetRegistry.h"
#include "test/fuzz/ScopedCatchResultCapture.h"
#include "test/test.h"
#endif

Expand Down Expand Up @@ -1799,6 +1800,7 @@ runFuzz(CommandLineArgs const& args)
if (actual > 0)
{
data.resize(actual);
ScopedCatchResultCapture catchCapture;
target->run(data.data(), data.size());
}

Expand Down
1 change: 1 addition & 0 deletions src/rust/src/soroban_proto_all.rs
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@ pub(crate) mod p28 {
}
}

#[cfg(not(feature = "fastdev"))]
#[path = "."]
pub(crate) mod p27 {
pub(crate) extern crate soroban_env_host_p27;
Expand Down
26 changes: 21 additions & 5 deletions src/test/TxTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,8 @@ closeLedgerOn(Application& app, int day, int month, int year,

TransactionResultSet
closeLedger(Application& app, std::vector<TransactionFrameBasePtr> const& txs,
ParallelSorobanOrder const& parallelSorobanOrder)
ParallelSorobanOrder const& parallelSorobanOrder,
ApplyOrderTxCallback const& applyOrderTxCallback)
{
auto lastCloseTime = app.getLedgerManager()
.getLastClosedLedgerHeader()
Expand All @@ -561,12 +562,14 @@ closeLedger(Application& app, std::vector<TransactionFrameBasePtr> const& txs,
auto nextLedgerSeq = app.getLedgerManager().getLastClosedLedgerNum() + 1;

return closeLedgerOn(app, nextLedgerSeq, lastCloseTime, txs, true,
emptyUpgradeSteps, parallelSorobanOrder);
emptyUpgradeSteps, parallelSorobanOrder,
applyOrderTxCallback);
}

TransactionResultSet
closeLedger(Application& app, std::vector<TransactionFrameBasePtr> const& txs,
bool strictOrder, xdr::xvector<UpgradeType, 6> const& upgrades)
bool strictOrder, xdr::xvector<UpgradeType, 6> const& upgrades,
ApplyOrderTxCallback const& applyOrderTxCallback)
{
auto lastCloseTime = app.getLedgerManager()
.getLastClosedLedgerHeader()
Expand All @@ -575,14 +578,15 @@ closeLedger(Application& app, std::vector<TransactionFrameBasePtr> const& txs,
auto nextLedgerSeq = app.getLedgerManager().getLastClosedLedgerNum() + 1;

return closeLedgerOn(app, nextLedgerSeq, lastCloseTime, txs, strictOrder,
upgrades);
upgrades, {}, applyOrderTxCallback);
}

TransactionResultSet
closeLedgerOn(Application& app, uint32 ledgerSeq, TimePoint closeTime,
std::vector<TransactionFrameBasePtr> const& txs, bool strictOrder,
xdr::xvector<UpgradeType, 6> const& upgrades,
ParallelSorobanOrder const& parallelSorobanOrder)
ParallelSorobanOrder const& parallelSorobanOrder,
ApplyOrderTxCallback const& applyOrderTxCallback)
{
// Ensure that parallelSorobanOrder is only used with strictOrder
releaseAssert((parallelSorobanOrder.empty() || strictOrder));
Expand Down Expand Up @@ -636,6 +640,18 @@ closeLedgerOn(Application& app, uint32 ledgerSeq, TimePoint closeTime,
// themselves maybe intentionally invalid for testing purpose.
releaseAssert(txSet.second->checkValid(app, 0, 0));
}
if (applyOrderTxCallback)
{
size_t txIndex = 0;
for (auto const& phase : txSet.second->getPhasesInApplyOrder())
{
for (auto const& tx : phase)
{
applyOrderTxCallback(txIndex, tx);
++txIndex;
}
}
}
app.getHerder().externalizeValue(txSet.first, ledgerSeq, closeTime,
upgrades);
while (app.getLedgerManager().getLastClosedLedgerNum() < ledgerSeq)
Expand Down
14 changes: 11 additions & 3 deletions src/test/TxTests.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
#include "herder/LedgerCloseData.h"
#include "herder/Upgrades.h"
#include "overlay/StellarXDR.h"
#include "transactions/TransactionFrameBase.h"
#include "transactions/test/TransactionTestFrame.h"
#include <functional>
#include <optional>

namespace stellar
Expand All @@ -23,6 +25,9 @@ class TestAccount;
namespace txtest
{

using ApplyOrderTxCallback =
std::function<void(size_t, stellar::TransactionFrameBaseConstPtr const&)>;

struct ExpectedOpResult
{
OperationResult mOperationResult;
Expand Down Expand Up @@ -88,11 +93,13 @@ TransactionResultSet
closeLedger(Application& app,
std::vector<TransactionFrameBasePtr> const& txs = {},
bool strictOrder = false,
xdr::xvector<UpgradeType, 6> const& upgrades = emptyUpgradeSteps);
xdr::xvector<UpgradeType, 6> const& upgrades = emptyUpgradeSteps,
ApplyOrderTxCallback const& applyOrderTxCallback = {});

TransactionResultSet
closeLedger(Application& app, std::vector<TransactionFrameBasePtr> const& txs,
ParallelSorobanOrder const& parallelSorobanOrder);
ParallelSorobanOrder const& parallelSorobanOrder,
ApplyOrderTxCallback const& applyOrderTxCallback = {});

TransactionResultSet
closeLedgerOn(Application& app, int day, int month, int year,
Expand All @@ -104,7 +111,8 @@ closeLedgerOn(Application& app, uint32 ledgerSeq, TimePoint closeTime,
std::vector<TransactionFrameBasePtr> const& txs = {},
bool strictOrder = false,
xdr::xvector<UpgradeType, 6> const& upgrades = emptyUpgradeSteps,
ParallelSorobanOrder const& parallelSorobanOrder = {});
ParallelSorobanOrder const& parallelSorobanOrder = {},
ApplyOrderTxCallback const& applyOrderTxCallback = {});

TransactionResultSet closeLedger(Application& app, TxSetXDRFrameConstPtr txSet);

Expand Down
2 changes: 2 additions & 0 deletions src/test/fuzz/FuzzMain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#endif

#include "test/fuzz/FuzzTargetRegistry.h"
#include "test/fuzz/ScopedCatchResultCapture.h"
#include "util/Logging.h"
#include "util/Math.h"

Expand Down Expand Up @@ -95,6 +96,7 @@ LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
}

// Run the target once
stellar::ScopedCatchResultCapture catchCapture;
switch (gFuzzTarget->run(data, size))
{
case stellar::FuzzResultCode::FUZZ_SUCCESS:
Expand Down
210 changes: 210 additions & 0 deletions src/test/fuzz/ScopedCatchResultCapture.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
// Copyright 2026 Stellar Development Foundation and contributors. Licensed
// under the Apache License, Version 2.0. See the COPYING file at the root
// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0

#pragma once

#include "test/Catch2.h"

#include <stdexcept>
#include <string>

namespace stellar
{

// Standalone fuzz entry points do not run inside Catch's test runner, but many
// test helpers used by fuzz targets contain REQUIRE/CHECK assertions. Without a
// result capture installed, those assertions fail with Catch's internal "No
// result capture instance" error instead of reporting the real assertion.
//
// Install this at non-Catch fuzz entry points, such as FuzzMain or fuzz-one,
// around target->run(). It is intentionally a no-op when a real Catch capture
// is already present, so smoke/corpus unit tests keep their normal reporting.
class ScopedCatchResultCapture : public Catch::IResultCapture
{
public:
ScopedCatchResultCapture()
: mPreviousCapture(Catch::getCurrentContext().getResultCapture())
{
if (mPreviousCapture == nullptr)
{
Catch::getCurrentMutableContext().setResultCapture(this);
mInstalled = true;
}
}

~ScopedCatchResultCapture() override
{
if (mInstalled)
{
Catch::getCurrentMutableContext().setResultCapture(
mPreviousCapture);
}
}

bool
sectionStarted(Catch::SectionInfo const&, Catch::Counts&) override
{
return true;
}

void
sectionEnded(Catch::SectionEndInfo const&) override
{
}

void
sectionEndedEarly(Catch::SectionEndInfo const&) override
{
}

Catch::IGeneratorTracker&
acquireGeneratorTracker(Catch::StringRef,
Catch::SourceLineInfo const&) override
{
throw std::runtime_error("generators are unsupported in fuzz target");
}

#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)
void
benchmarkPreparing(std::string const&) override
{
}

void
benchmarkStarting(Catch::BenchmarkInfo const&) override
{
}

void
benchmarkEnded(Catch::BenchmarkStats<> const&) override
{
}

void
benchmarkFailed(std::string const&) override
{
throw std::runtime_error("benchmark failed in fuzz target");
}
#endif

void
pushScopedMessage(Catch::MessageInfo const&) override
{
}

void
popScopedMessage(Catch::MessageInfo const&) override
{
}

void
emplaceUnscopedMessage(Catch::MessageBuilder const&) override
{
}

void
handleFatalErrorCondition(Catch::StringRef message) override
{
throw std::runtime_error(static_cast<std::string>(message));
}

void
handleExpr(Catch::AssertionInfo const& info,
Catch::ITransientExpression const& expr,
Catch::AssertionReaction& reaction) override
{
mLastAssertionPassed =
expr.getResult() != Catch::isFalseTest(info.resultDisposition);
if (!mLastAssertionPassed)
{
reaction.shouldThrow = true;
}
}

void
handleMessage(Catch::AssertionInfo const&, Catch::ResultWas::OfType result,
Catch::StringRef const& message,
Catch::AssertionReaction& reaction) override
{
mLastAssertionPassed = Catch::isOk(result);
if (!mLastAssertionPassed)
{
reaction.shouldThrow = true;
mLastMessage = static_cast<std::string>(message);
}
}

void
handleUnexpectedExceptionNotThrown(
Catch::AssertionInfo const&,
Catch::AssertionReaction& reaction) override
{
mLastAssertionPassed = false;
reaction.shouldThrow = true;
}

void
handleUnexpectedInflightException(
Catch::AssertionInfo const&, std::string const& message,
Catch::AssertionReaction& reaction) override
{
mLastAssertionPassed = false;
reaction.shouldThrow = true;
mLastMessage = message;
}

void
handleIncomplete(Catch::AssertionInfo const&) override
{
mLastAssertionPassed = false;
}

void
handleNonExpr(Catch::AssertionInfo const&, Catch::ResultWas::OfType result,
Catch::AssertionReaction& reaction) override
{
mLastAssertionPassed = Catch::isOk(result);
if (!mLastAssertionPassed)
{
reaction.shouldThrow = true;
}
}

bool
lastAssertionPassed() override
{
return mLastAssertionPassed;
}

void
assertionPassed() override
{
mLastAssertionPassed = true;
}

std::string
getCurrentTestName() const override
{
return "fuzz target";
}

Catch::AssertionResult const*
getLastResult() const override
{
return nullptr;
}

void
exceptionEarlyReported() override
{
}

private:
Catch::IResultCapture* mPreviousCapture;
bool mInstalled{false};
bool mLastAssertionPassed{true};
std::string mLastMessage;
};

} // namespace stellar
Loading
Loading