Skip to content

ethexe: Refactor DispatchOutcome::InitFailure to support structured reasoning and prevent program "bricking" on OOG #5047

@ukint-vs

Description

@ukint-vs

Problem

Problem Description

Currently, when a program is initialized with zero or insufficient executable balance, the initialization dispatch fails with RanOutOfGas. In the Gear runtime (and specifically in ethexe), any InitFailure is mapped to Program::Terminated.

This results in the program being permanently "bricked." Even if a user later tops up the executable balance, the program remains in the Terminated state and cannot be initialized again. While Terminated is the correct state for fatal initialization errors (e.g., code panics, invalid logic), it is too strict for funding-related errors which should be retryable.

Technical Root Cause

The DispatchOutcome::InitFailure variant currently uses an opaque String to describe the failure reason:

InitFailure {
    program_id: ActorId,
    origin: ActorId,
    reason: String, // Opaque diagnostic string
}

Because the reason is a string, the journal handler cannot reliably distinguish between a transient funding issue (Out of Gas) and a fatal logic error without brittle string matching.

Affected Components

  • core-processor: Definition of outcomes and error mapping.
  • ethexe-runtime-common: Journal handling logic.
  • pallets-gear: Substrate pallet logic and benchmarking.
  • gtest: Testing framework simulation.

Impact

  • UX Improvement: Users who mis-fund their program initialization can top up and retry without losing the program address.
  • Robustness: Eliminates the need for brittle string-matching heuristics in the runtime.

Steps

  1. Create new program
  2. Send init message -> RanOutOfGas
  3. New messages will fail and program will be bricked.

Possible Solution

Refactor the initialization failure logic to use a structured InitFailureReason enum. This allows the runtime journal handler to make semantic decisions about whether to terminate the program or keep it active for a retry.

  1. Introduce InitFailureReason enum in core-processor:
    pub enum InitFailureReason {
        RanOutOfGas,
        UserspacePanic(String),
        BackendError(String),
        Other(String),
    }
  2. Update DispatchOutcome::InitFailure to use this enum instead of String.
  3. Update core-processor mapping logic to populate the correct variant based on the execution result.
  4. Update Journal Handlers (pallets-gear, ethexe, gtest) to handle the structured reason. Specifically, RanOutOfGas should allow the program to remain Active (uninitialized), while other variants should lead to Terminated.

Notes

No response

Relevant Log Output

Click to expand/collapse

<Paste your log here>

Metadata

Metadata

Assignees

Labels

C0-bugSomething isn't working

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions