How the Akash Simulation Framework Tests Module State Transitions

The Akash simulation framework validates module state transitions by spawning a full in-memory blockchain, generating weighted random transactions for every module, executing them through mock deliveries against the BaseApp, and verifying determinism via export-import cycles.

The akash-network/node repository employs the Cosmos SDK simulation framework to fuzz-test state-transition logic across all Akash modules. This approach constructs a temporary LevelDB-backed application instance, feeds it randomized transaction sequences, and asserts that keeper mutations, event emissions, and store updates remain consistent and panic-free under chaotic workloads.

Architecture of the Akash Simulation Framework

Simulation Environment Setup

Every simulation test begins with SetupSimulation in testutil/sims/simulation_helpers.go. This helper initializes a disposable environment consisting of a temporary directory, a LevelDB instance, a structured logger, and a simtypes.Config object that carries the random seed and chain ID.

config := sim.NewConfigFromFlags()
config.ChainID = "akash-sim"

dir, _ := os.MkdirTemp("", dirPrefix)
db, _   := dbm.NewDB(dbName, dbm.BackendType(config.DBBackend), dir)
logger  := log.NewNopLogger()

If the FlagEnabledValue disables simulation, the function returns early and the test skips. Otherwise, the returned config, db, dir, and logger are passed to the application constructor.

Application Instantiation

The framework instantiates the Akash application exactly as a production node would, with two critical modifications for speed. In app/sim_test.go, the TestFullAppSimulation function calls akash.NewApp with fauxMerkleModeOpt, which forces BaseApp to use an in-memory “faux-Merkle” adapter, and sets the chain ID to "akash-sim".

appOpts := viper.New()
appOpts.Set("home", akash.DefaultHome)

app := akash.NewApp(
    logger, db, nil, true,
    sim.FlagPeriodValue, map[int64]bool{},
    encodingConfig, appOpts,
    fauxMerkleModeOpt, baseapp.SetChainID("akash-sim"),
)

This creates a fully functional app with all keepers and stores, ready to process simulated blocks without the I/O overhead of real Merkle proofs.

How Modules Define State Transition Tests

WeightedOperations Implementation

Each Akash module implements a WeightedOperations function that tells the simulator which messages to generate and how often. For example, in x/provider/simulation/operations.go, the function returns weighted operations for MsgCreateProvider and MsgUpdateProvider.

func WeightedOperations(
    appParams simtypes.AppParams,
    _ codec.JSONCodec,
    ak govtypes.AccountKeeper,
    bk bankkeeper.Keeper,
    k keeper.IKeeper,
) simulation.WeightedOperations {
    var weightMsgCreate, weightMsgUpdate int

    appParams.GetOrGenerate(OpWeightMsgCreate, &weightMsgCreate, nil,
        func(_ *rand.Rand) { weightMsgCreate = appparams.DefaultWeightMsgCreateProvider })
    appParams.GetOrGenerate(OpWeightMsgUpdate, &weightMsgUpdate, nil,
        func(_ *rand.Rand) { weightMsgUpdate = appparams.DefaultWeightMsgUpdateProvider })

    return simulation.WeightedOperations{
        simulation.NewWeightedOperation(weightMsgCreate, SimulateMsgCreate(ak, bk, k)),
        simulation.NewWeightedOperation(weightMsgUpdate, SimulateMsgUpdate(ak, bk, k)),
    }
}

The SimulateMsgCreate and SimulateMsgUpdate generators construct random valid messages, select random accounts, and prepare fees. Similar patterns exist in x/market/simulation/operations.go, x/deployment/simulation/operations.go, and other modules under x/*/simulation.

Mock Transaction Delivery

Once a message is generated, the operation calls deliverMockTx (defined in the same operations.go files). This helper builds a signed transaction using simtestutil.GenSignedMockTx, injects random fees, and executes it via app.SimDeliver.

tx, err := simtestutil.GenSignedMockTx(
    r, txGen, []sdk.Msg{msg}, fees,
    simtestutil.DefaultGenTxGas, chainID,
    []uint64{acc.GetAccountNumber()}, []uint64{acc.GetSequence()}, privKey)
_, _, err = app.SimDeliver(txGen.TxEncoder(), tx)

If SimDeliver succeeds, the state transition— including all keeper writes, module hooks, and event emissions—is applied to the in-memory KV-stores. Any panic or error aborts the simulation, immediately surfacing bugs in the state-transition logic.

Executing the Full Simulation Cycle

Running SimulateFromSeed

The entry point TestFullAppSimulation in app/sim_test.go invokes simulation.SimulateFromSeed from the Cosmos SDK. This function orchestrates the entire fuzz run.

_, simParams, simErr := simulation.SimulateFromSeed(
    t,
    os.Stdout,
    app.BaseApp,
    simtestutil.AppStateFn(...),
    sdksim.RandomAccounts,
    simtestutil.BuildSimulationOperations(app, app.AppCodec(), config, app.TxConfig()),
    app.ModuleAccountAddrs(),
    config,
    app.AppCodec(),
)

BuildSimulationOperations aggregates the WeightedOperations from every module into a single slice. During execution, the simulator repeatedly selects a weighted operation, delivers its mock transaction, commits the block, and advances the random seed. If config.Commit is true, sim.PrintStats(db) dumps LevelDB statistics for debugging.

Export and Import Validation

After the run completes, CheckExportSimulation (in testutil/sims/simulation_helpers.go) optionally writes the final app state and simulation parameters to JSON files. The TestAppImportExport test then performs a determinism check:

  1. Calls app.ExportAppStateAndValidators to produce a genesis JSON.
  2. Starts a fresh simulation environment with SetupSimulation.
  3. Bootstraps a new app instance using the exported genesis.

If the new app initializes without errors, the simulation confirms that state transitions are deterministic and that exported state can be replayed faithfully.

Code Example: End-to-End Simulation Test

The following snippet demonstrates the complete flow used in app/sim_test.go to validate the Akash simulation framework:

// 1️⃣  Setup the simulation environment
config, db, dir, logger, skip, err := sim.SetupSimulation(
    "leveldb-app-sim", "Simulation")
if skip { t.Skip("simulation disabled") }
require.NoError(t, err)

// 2️⃣  Build the Akash app
encoding := sdkutil.MakeEncodingConfig()
app := akash.NewApp(
    logger, db, nil, true,
    sim.FlagPeriodValue, map[int64]bool{},
    encoding, viper.New(),
    fauxMerkleModeOpt, baseapp.SetChainID("akash-sim"),
)

// 3️⃣  Run the randomised simulation
_, simParams, simErr := simulation.SimulateFromSeed(
    t, os.Stdout,
    app.BaseApp,
    simtestutil.AppStateFn(app.AppCodec(),
        app.SimulationManager(),
        akash.NewDefaultGenesisState(app.AppCodec())),
    sdksim.RandomAccounts,
    simtestutil.BuildSimulationOperations(
        app, app.AppCodec(), config, app.TxConfig()),
    app.ModuleAccountAddrs(),
    config,
    app.AppCodec(),
)
require.NoError(t, simErr)

// 4️⃣  Export for inspection (optional)
err = simtestutil.CheckExportSimulation(app, config, simParams)
require.NoError(t, err)

// 5️⃣  Clean-up
defer func() {
    _ = db.Close()
    require.NoError(t, os.RemoveAll(dir))
}()

Summary

The Akash simulation framework rigorously tests module state transitions by:

  • Creating a temporary simulation environment with LevelDB and a fast in-memory Merkle adapter via SetupSimulation in testutil/sims/simulation_helpers.go.
  • Collecting weighted operations from every module’s WeightedOperations function (e.g., x/provider/simulation/operations.go).
  • Delivering mock transactions through deliverMockTx and app.SimDeliver to exercise keeper logic and store mutations.
  • Running randomized block production via simulation.SimulateFromSeed to ensure stability under unpredictable workloads.
  • Validating determinism through export-import cycles in TestAppImportExport, confirming that state can be faithfully replayed from genesis.

Frequently Asked Questions

What is the purpose of WeightedOperations in Akash module simulations?

WeightedOperations tells the simulator which messages a module can process and how frequently to generate them. Defined in files like x/provider/simulation/operations.go, this function returns a slice of weighted operation generators (e.g., SimulateMsgCreate) that the Cosmos SDK simulator samples according to their assigned probabilities, ensuring diverse state-transition coverage.

How does the framework ensure deterministic state transitions?

After a simulation run, the TestAppImportExport test in app/sim_test.go exports the final state via app.ExportAppStateAndValidators, then instantiates a fresh application using that exported genesis. If the new app boots without errors, the test proves that the sequence of state transitions produced a deterministic, reproducible state that can be re-imported reliably.

Which database backend does the Akash simulation framework use?

The framework uses LevelDB as the temporary storage backend. The SetupSimulation helper in testutil/sims/simulation_helpers.go creates a LevelDB instance via dbm.NewDB inside a temporary directory, providing fast, persistent storage for the in-memory app’s KV-stores without affecting the host’s default data directory.

Where are the simulation tests located in the Akash repository?

Simulation utilities and helpers reside in testutil/sims/simulation_helpers.go. Full-application simulation tests live in app/sim_test.go. Per-module simulation logic— including WeightedOperations and mock delivery functions—is located in x/*/simulation/ directories (e.g., x/provider/simulation/operations.go, x/market/simulation/operations.go).

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →