How the Akash Network Market Module Handles Bid Settlement and Lease Closure Events
The Akash Network market module processes bid settlement and lease closure through escrow-triggered hooks that cascade from group-level account closures to individual bid and lease state updates.
The akash-network/node repository implements a sophisticated market mechanism where the escrow module drives settlement logic by invoking specific hooks in the market module. When escrow accounts or payments close due to fund exhaustion or completion, the market module executes a deterministic sequence of state transitions to finalize bids and leases. This architecture ensures that financial finality in the escrow layer automatically propagates to the market layer’s order book and lease registry.
Settlement Architecture Overview
The bid settlement and lease closure flow operates through three coordinated steps triggered by escrow events:
- Escrow Account Closure (
OnEscrowAccountClosed) – Handles group-level shutdowns when a deployment runs out of funds - Escrow Payment Closure (
OnEscrowPaymentClosed) – Handles individual bid settlement when a specific lease payment closes - Keeper State Updates – Updates internal state machines for bids, orders, and leases while emitting corresponding events
This hook-based design decouples financial settlement from market state management while ensuring atomic consistency between the escrow and market modules.
Group-Level Closure via OnEscrowAccountClosed
When an escrow account closes—typically because a deployment has exhausted its funds—the escrow module invokes hooks.OnEscrowAccountClosed in x/market/hooks/hooks.go (lines 30–66).
The hook resolves the deployment ID, closes the active deployment, and iterates through all associated groups. For each group that can be closed, it executes:
func (h *hooks) OnEscrowAccountClosed(ctx sdk.Context, obj etypes.Account) error {
// Resolve deployment ID from escrow account
// Close deployment if still active
// Iterate groups and call dkeeper.OnCloseGroup
// Call mkeeper.OnGroupClosed for each group
}
The OnGroupClosed method in x/market/keeper/keeper.go (lines 78–109) then iterates through every order in the group. For each order, it runs processClose, which cascades to OnBidClosed and OnLeaseClosed for every bid attached to that order. This ensures that when a deployment’s escrow account drains, all associated market entities transition to closed states automatically.
Bid-Level Settlement via OnEscrowPaymentClosed
Individual bid settlement occurs when an escrow payment closes, signaling the end of a specific lease. The escrow module triggers hooks.OnEscrowPaymentClosed in x/market/hooks/hooks.go (lines 70–115), which implements the core settlement logic:
func (h *hooks) OnEscrowPaymentClosed(ctx sdk.Context, obj etypes.Payment) error {
id, _ := mv1.LeaseIDFromPaymentID(obj.ID)
bid := h.mkeeper.GetBid(ctx, id.BidID())
order := h.mkeeper.GetOrder(ctx, id.OrderID())
lease := h.mkeeper.GetLease(ctx, id)
// Close order
if err := h.mkeeper.OnOrderClosed(ctx, order); err != nil {
return err
}
// Close bid
if err := h.mkeeper.OnBidClosed(ctx, bid); err != nil {
return err
}
// Close lease with appropriate reason
if obj.State.State == etypes.StateOverdrawn {
err = h.mkeeper.OnLeaseClosed(ctx, lease,
mv1.LeaseInsufficientFunds,
mv1.LeaseClosedReasonInsufficientFunds)
} else {
err = h.mkeeper.OnLeaseClosed(ctx, lease,
mv1.LeaseClosed,
mv1.LeaseClosedReasonUnspecified)
}
return err
}
This method resolves the Lease ID from the payment ID, retrieves the associated bid, order, and lease objects, and orchestrates their closure. The closure reason depends on the escrow payment’s final state: StateOverdrawn triggers an insufficient-funds closure, while normal completion triggers a standard lease closure.
State Machine Updates in the Market Keeper
The market keeper in x/market/keeper/keeper.go implements three primary methods to handle the actual state transitions:
OnBidClosed
The OnBidClosed method (lines 100–123) finalizes bid settlement by:
- Setting the bid state to
BidClosed(unless already closed or lost) - Invoking the escrow keeper to close the bid’s escrow account
- Emitting an
EventBidClosedevent for external consumers
OnOrderClosed
The OnOrderClosed method (lines 124–146) handles order finalization:
- Updates the order state to
OrderClosedif not already closed - Emits
EventOrderClosedto notify subscribers of the order book change
OnLeaseClosed
The OnLeaseClosed method (lines 147–176) implements the final lease settlement logic:
- Validates the lease is not already in a terminal state (closed or insufficient funds)
- Updates the lease’s
State,ClosedOntimestamp, andReasonfields - Persists the lease to update all internal indexes
- Emits
EventLeaseClosedwith the appropriate closure reason
These keeper methods ensure that the market module’s internal state remains consistent with the escrow module’s financial finality, providing a complete audit trail through emitted events.
Practical Implementation Examples
Simulating Payment Closure in Tests
To verify that bid settlement triggers correctly when escrow payments close, you can simulate the hook invocation in test scenarios:
func TestPaymentCloseTriggersLeaseClosure(t *testing.T) {
ctx := sdk.NewContext(...)
// Create a lease in active state
lease := createTestLease(ctx, keeper, bidID)
// Simulate escrow payment close with overdrawn state
payment := etypes.Payment{
ID: lease.ID.ToEscrowPaymentID(),
State: etypes.State{State: etypes.StateOverdrawn},
}
// Invoke the market hook as the escrow module would
err := marketHooks.OnEscrowPaymentClosed(ctx, payment)
require.NoError(t, err)
// Verify lease reflects insufficient-funds closure
l, ok := keeper.GetLease(ctx, lease.ID)
require.True(t, ok)
assert.Equal(t, mv1.LeaseInsufficientFunds, l.State)
assert.Equal(t, mv1.LeaseClosedReasonInsufficientFunds, l.Reason)
}
Direct Bid Closure Pattern
When implementing manual bid management or cleanup operations, use the keeper’s closure method directly:
func closeBid(ctx sdk.Context, k keeper.IKeeper, bidID mv1.BidID) error {
bid, ok := k.GetBid(ctx, bidID)
if !ok {
return fmt.Errorf("bid %s not found", bidID)
}
// Updates state, closes escrow account, emits EventBidClosed
return k.OnBidClosed(ctx, bid)
}
Summary
- The escrow module drives market settlement through the
OnEscrowAccountClosedandOnEscrowPaymentClosedhooks inx/market/hooks/hooks.go - Group-level closures cascade from deployment escrow accounts through orders to individual bids and leases via
OnGroupClosed - Bid settlement resolves the lease from the payment ID and orchestrates closure of the order, bid, and lease with appropriate state reasons
- The market keeper’s
OnBidClosed,OnOrderClosed, andOnLeaseClosedmethods enforce state consistency and emitEventBidClosed,EventOrderClosed, andEventLeaseClosedevents - Closure reasons distinguish between normal completion (
LeaseClosed) and fund exhaustion (LeaseInsufficientFunds) based on the escrow payment’sStateOverdrawnstatus
Frequently Asked Questions
What triggers bid settlement in the Akash market module?
Bid settlement triggers when the escrow module closes a payment associated with an active lease. The escrow module invokes OnEscrowPaymentClosed, which resolves the bid-to-lease relationship and calls the market keeper’s OnBidClosed method to finalize the state transition and emit closure events.
How does the market module distinguish between normal lease closure and insufficient funds closure?
The market module checks the escrow payment’s state within OnEscrowPaymentClosed. If obj.State.State == etypes.StateOverdrawn, the module calls OnLeaseClosed with LeaseInsufficientFunds and LeaseClosedReasonInsufficientFunds. Otherwise, it uses LeaseClosed with LeaseClosedReasonUnspecified, allowing external consumers to differentiate between natural completion and premature termination due to depleted funds.
Where is the group-level closure logic implemented when a deployment runs out of funds?
Group-level closure logic resides in x/market/hooks/hooks.go within the OnEscrowAccountClosed function (lines 30–66). This hook iterates through all groups associated with the closed escrow account and invokes OnGroupClosed in the market keeper, which subsequently processes every order and bid within those groups to ensure complete market cleanup.
What events are emitted during the bid settlement and lease closure process?
The market module emits three distinct events during settlement: EventBidClosed when OnBidClosed finalizes the bid state, EventOrderClosed when OnOrderClosed completes the order lifecycle, and EventLeaseClosed when OnLeaseClosed records the final lease disposition. These events provide a complete audit trail for block explorers and external monitoring systems tracking deployment lifecycles.
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 →