# How the Akash Deployment Module Manages State Transitions: A Deep Dive into the Cosmos SDK Keeper

> Explore how the Akash deployment module manages state transitions using a KV-store key rotation system and a two-level state machine for deployments and groups.

- Repository: [Akash Network/node](https://github.com/akash-network/node)
- Tags: deep-dive
- Published: 2026-02-24

---

**The Akash deployment module manages state transitions through a prefix-based KV-store key rotation system that enforces a two-level state machine: deployments transition only between Active and Closed, while groups handle Open, Paused, InsufficientFunds, and Closed states via atomic delete-and-set operations in the keeper layer.**

The Akash Network's deployment module implements a deterministic state machine for container orchestration contracts using the Cosmos SDK. According to the `akash-network/node` source code, the module separates lifecycle concerns between **deployments** (top-level contracts) and **groups** (resource sets), encoding state directly into KV-store prefixes to guarantee valid transitions and ensure every mutation emits a typed event for external observability.

## Understanding the Two-Level State Machine

The module maintains distinct state machines for deployments and their constituent groups. This separation allows granular control over resource allocation while preserving top-level contract integrity.

### Deployment-Level States: Active and Closed

Deployments support only two terminal states encoded as byte prefixes:

- **Active** (`0x01`): The initial state set during creation in [`handler/server.go`](https://github.com/akash-network/node/blob/main/handler/server.go) (lines 49–52) via `Keeper.Create`. The deployment is stored with `DeploymentStateActivePrefix`.
- **Closed** (`0x02`): The terminal state reached exclusively through `Keeper.CloseDeployment`, which deletes the Active key and rewrites the deployment with `DeploymentStateClosedPrefix`.

There is no *paused* state at the deployment level—pausing is strictly a group-level operation. The module explicitly forbids operations on closed deployments; calls to `UpdateDeployment`, `CloseDeployment`, or group hooks on a non-Active deployment return `v1.ErrDeploymentClosed` (see [`handler/server.go`](https://github.com/akash-network/node/blob/main/handler/server.go) lines 103–106).

### Group-Level States: Open, Paused, InsufficientFunds, and Closed

Groups represent resource allocations within a deployment and support a richer state machine:

- **Open** (`0x01`): Active resource allocation state.
- **Paused** (`0x02`): Temporarily suspended state reached via `OnPauseGroup`.
- **InsufficientFunds** (`0x03`): Entered automatically when escrowed funds deplete (set by market/escrow hooks).
- **Closed** (`0x04`): Terminal state reached via `OnCloseGroup` from any non-Closed state.

## How State Transitions Work in the Keeper Layer

All state mutations occur through the keeper's public methods in [`x/deployment/keeper/keeper.go`](https://github.com/akash-network/node/blob/main/x/deployment/keeper/keeper.go), which implement an atomic key rotation pattern to ensure consistency.

### The Prefix-Based Key Rotation Pattern

Every state transition follows a deterministic four-step process:

1. **Lookup**: `findGroup` or `findDeployment` locates the current key by iterating possible prefixes until finding a match.
2. **Deletion**: `store.Delete(key)` removes the old state entry.
3. **Mutation**: The struct's `State` field updates to the target enum value.
4. **Insertion**: A new key is encoded using `GroupStateToPrefix` or `DeploymentStateToPrefix`, and the marshaled value is stored via `store.Set`.

This pattern ensures that a group's state exists in exactly one prefix namespace at any time, making prefix scans efficient for queries.

### Deployment Transitions: Active to Closed

When closing a deployment, the keeper performs an atomic swap between prefixes:

```go
// In x/deployment/keeper/keeper.go → CloseDeployment
store.Delete(key)                         // Remove Active (0x01) key
deployment.State = v1.DeploymentClosed    // Flip state enum
key = MustDeploymentKey(
    DeploymentStateToPrefix(deployment.State), // Convert to Closed prefix (0x02)
    deployment.ID
)
store.Set(key, k.cdc.MustMarshal(&deployment))
ctx.EventManager().EmitTypedEvent(&v1.EventDeploymentClosed{ID: deployment.ID})

```

The message server in [`handler/server.go`](https://github.com/akash-network/node/blob/main/handler/server.go) validates the transition before invocation, checking that `deployment.State == v1.DeploymentActive` and returning `v1.ErrDeploymentClosed` if the deployment is already terminal.

### Group Transitions: Open, Paused, and Closed

Group state changes follow the same key rotation logic with specific entry points:

**Open → Paused** (`OnPauseGroup`):

```go
store.Delete(key)               // Delete Open key (0x01)
group.State = types.GroupPaused // Set to Paused
key, err = GroupKey(GroupStateToPrefix(group.State), group.ID) // New key with 0x02 prefix
store.Set(key, k.cdc.MustMarshal(&group))
ctx.EventManager().EmitTypedEvent(&v1.EventGroupPaused{ID: group.ID})

```

**Paused → Open** (`OnStartGroup`):

```go
store.Delete(key)               // Delete Paused key (0x02)
group.State = types.GroupOpen   // Revert to Open
key, err = GroupKey(GroupStateToPrefix(group.State), group.ID) // Back to 0x01 prefix
store.Set(key, k.cdc.MustMarshal(&group))
ctx.EventManager().EmitTypedEvent(&v1.EventGroupStarted{ID: group.ID})

```

**Any → Closed** (`OnCloseGroup`):
This method accepts groups in Open, Paused, or InsufficientFunds states, deletes the current key regardless of prefix, and stores the group under `GroupStateClosedPrefix` (`0x04`), emitting `EventGroupClosed`.

## Validation and Guardrails in the Message Server

The gRPC message server in [`x/deployment/handler/server.go`](https://github.com/akash-network/node/blob/main/x/deployment/handler/server.go) provides the first line of defense against illegal transitions:

- **CreateDeployment**: Initializes with `State: v1.DeploymentActive` and validates groups.
- **UpdateDeployment**: Aborts if `deployment.State != v1.DeploymentActive` (lines 103–106).
- **CloseDeployment**: Returns early if already closed (lines 30–32).
- **PauseGroup/StartGroup/CloseGroup**: Each calls validation methods (`ValidatePausable`, `ValidateStartable`, `ValidateClosable`) on the group object before invoking the keeper.

These checks prevent unnecessary gas consumption and provide clear error messages, while the keeper's internal logic serves as the final safety net.

## Event Emissions for State Changes

Every state transition emits typed events through `ctx.EventManager().EmitTypedEvent`, creating an immutable audit trail:

| Transition | Event Type | Emitted In |
|------------|------------|------------|
| Deployment creation | `EventDeploymentCreated` | `Keeper.Create` |
| Deployment closure | `EventDeploymentClosed` | `Keeper.CloseDeployment` |
| Group pause | `EventGroupPaused` | `Keeper.OnPauseGroup` |
| Group start | `EventGroupStarted` | `Keeper.OnStartGroup` |
| Group closure | `EventGroupClosed` | `Keeper.OnCloseGroup` |

These events enable block explorers to index state changes and allow downstream modules (market, escrow) to react to lifecycle changes asynchronously.

## Summary

- **Two-level architecture**: Deployments track Active/Closed states while groups manage Open, Paused, InsufficientFunds, and Closed states independently.
- **Prefix-based storage**: State is encoded in KV-store keys (`0x01` for Active/Open, `0x02` for Paused/Closed, `0x03` for InsufficientFunds, `0x04` for Closed groups), enabling efficient prefix scans.
- **Atomic transitions**: The keeper implements delete-and-set key rotation in [`x/deployment/keeper/keeper.go`](https://github.com/akash-network/node/blob/main/x/deployment/keeper/keeper.go) to ensure state consistency.
- **Double validation**: The message server in [`handler/server.go`](https://github.com/akash-network/node/blob/main/handler/server.go) validates business logic before the keeper enforces storage constraints.
- **Observability**: Every transition emits typed Cosmos SDK events for external indexing and cross-module communication.

## Frequently Asked Questions

### Can a deployment be paused in the Akash deployment module?

No, pausing is not supported at the deployment level. The `v1.Deployment` proto only defines `DeploymentActive` and `DeploymentClosed` states. Pausing is implemented exclusively at the group level through `OnPauseGroup`, which transitions groups between `GroupOpen` and `GroupPaused` states while the parent deployment remains Active.

### What happens if I try to update a closed deployment?

The message server rejects the transaction immediately. In [`handler/server.go`](https://github.com/akash-network/node/blob/main/handler/server.go) lines 103–106, `UpdateDeployment` checks `if deployment.State != v1.DeploymentActive` and returns `v1.ErrDeploymentClosed`. This guardrail prevents any modifications to terminal deployments, ensuring that only the escrow module can trigger final closure through `CloseDeployment`.

### How does the module handle groups that run out of funds?

When escrowed funds deplete, the market or escrow module hooks trigger a transition to `GroupStateInsufficientFunds` (prefix `0x03`). This is not a direct external transaction but an internal state change initiated by economic mechanisms. The group remains in this state until either funding is restored (transitioning back to Open) or the group is Closed.

### Where are the state transition prefixes defined?

The byte prefixes are defined in [`x/deployment/keeper/key.go`](https://github.com/akash-network/node/blob/main/x/deployment/keeper/key.go) through helper functions `DeploymentStateToPrefix` and `GroupStateToPrefix`. Deployments use `0x01` for Active and `0x02` for Closed, while groups use `0x01` (Open), `0x02` (Paused), `0x03` (InsufficientFunds), and `0x04` (Closed). These prefixes are prepended to owner addresses and sequence numbers to create unique KV-store keys for each state instance.