How the Akash Deployment Module Manages State Transitions: A Deep Dive into the Cosmos SDK Keeper
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 inhandler/server.go(lines 49–52) viaKeeper.Create. The deployment is stored withDeploymentStateActivePrefix. - Closed (
0x02): The terminal state reached exclusively throughKeeper.CloseDeployment, which deletes the Active key and rewrites the deployment withDeploymentStateClosedPrefix.
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 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 viaOnPauseGroup. - InsufficientFunds (
0x03): Entered automatically when escrowed funds deplete (set by market/escrow hooks). - Closed (
0x04): Terminal state reached viaOnCloseGroupfrom 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, 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:
- Lookup:
findGrouporfindDeploymentlocates the current key by iterating possible prefixes until finding a match. - Deletion:
store.Delete(key)removes the old state entry. - Mutation: The struct's
Statefield updates to the target enum value. - Insertion: A new key is encoded using
GroupStateToPrefixorDeploymentStateToPrefix, and the marshaled value is stored viastore.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:
// 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 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):
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):
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 provides the first line of defense against illegal transitions:
- CreateDeployment: Initializes with
State: v1.DeploymentActiveand 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 (
0x01for Active/Open,0x02for Paused/Closed,0x03for InsufficientFunds,0x04for Closed groups), enabling efficient prefix scans. - Atomic transitions: The keeper implements delete-and-set key rotation in
x/deployment/keeper/keeper.goto ensure state consistency. - Double validation: The message server in
handler/server.govalidates 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 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 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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →