state.h

Architecture

Asynchronous Execution Model

This module implements an asynchronous state machine for proof generation and verification. The state object (c4_state_t) is typically part of a context struct (prover_ctx_t or verify_ctx_t) and manages external data requests that cannot be fulfilled synchronously.

Execution Flow

The main execution function (e.g., c4_prover_execute) is called repeatedly in a loop until it returns either C4_SUCCESS or C4_ERROR. When external data is needed, the function:

  1. Creates a data_request_t and adds it to the state

  2. Returns C4_PENDING to signal that data is required

  3. The host system fetches the required data

  4. The host system calls the execution function again

  5. The function resumes and processes the now-available data

This design allows the host system to use native async technologies (promises, async/await, futures, etc.) in the language bindings while keeping the C core synchronous and portable.

Request Lifecycle

Host System Implementation

The host system is responsible for executing data requests. When C4_PENDING is returned, the host system should iterate through all pending requests using c4_state_get_pending_request() and execute them according to the following rules:

Request Execution

For each data_request_t, the host system must:

  1. Select Node: The host system maintains a list of nodes (max 16) for each data_request_type_t

    • Beacon API nodes for C4_DATA_TYPE_BEACON_API

    • Ethereum RPC nodes for C4_DATA_TYPE_ETH_RPC

    • Checkpointz servers for C4_DATA_TYPE_CHECKPOINTZ

    • etc.

  2. Apply Filters:

    • Skip nodes where node_exclude_mask & (1 << node_index) is set

    • Prefer nodes matching preferred_client_type (if set, 0 = any client)

  3. Build Request:

    • Use method field for HTTP method (C4_DATA_METHOD_GET, POST, etc.)

    • Use url field for the endpoint path (if set)

    • Use payload for POST body (if set, typically JSON-RPC for C4_DATA_TYPE_ETH_RPC)

    • Set Content-Type and Accept headers based on encoding:

      • C4_DATA_ENCODING_JSON: application/json

      • C4_DATA_ENCODING_SSZ: application/octet-stream

  4. Execute Request:

    • Try the selected node

    • On failure: try next available node (respecting exclude mask)

    • On success: set response and response_node_index

    • If all nodes fail: set error

  5. Caching:

    • If ttl is set (> 0), the response may be cached for the specified duration

    • Cache key should include request type, URL, and payload

  6. Set Result:

    • Success: Allocate memory for response.data, copy data, set response.len and response_node_index

    • Failure: Allocate memory for error string describing the failure

    • Important: Both response.data and error will be freed by c4_state_free() - use malloc/strdup

Example Implementation

See bindings/emscripten/src/index.ts function handle_request() for a complete TypeScript implementation.

Best Practices

To minimize the number of execution cycles, developers should:

  • Collect all required data requests as early as possible in the execution

  • Use TRY_ADD_ASYNC to queue multiple requests before returning C4_PENDING

  • The host system should fetch all pending requests in parallel when possible

  • Cache responses according to ttl to avoid redundant network requests

  • Implement smart node selection (e.g., prefer faster nodes, track reliability)

Example Usage

Macros for Async Control Flow

The TRY_ASYNC family of macros simplifies error handling and control flow:

  • TRY_ASYNC(fn): Execute function, return immediately if not C4_SUCCESS

  • TRY_ADD_ASYNC(status, fn): Queue multiple requests, only fail on C4_ERROR

  • TRY_2_ASYNC(fn1, fn2): Execute two functions in parallel

  • TRY_ASYNC_FINAL(fn, cleanup): Always run cleanup regardless of result

  • TRY_ASYNC_CATCH(fn, cleanup): Run cleanup only on failure

These macros handle the repetitive pattern of checking status codes and allow the developer to focus on the business logic.

data_request_type_t

util/state.h

Defines the type of data source for a request.

data_request_encoding_t

util/state.h

Defines the encoding format for request/response data.

data_request_method_t

util/state.h

HTTP methods for data requests.

c4_status_t

util/state.h

Status codes for asynchronous operations.

data_request_t

util/state.h

Represents a single asynchronous data request in the state machine.

This structure encapsulates all information needed to make, track, and retry external data requests. Requests can be to various data sources (Beacon API, Eth RPC, etc.) and support retry logic with node exclusion.

c4_state_t

util/state.h

Global state container for asynchronous operations.

Contains all pending data requests and accumulated error messages. Used by the async state machine to track operations that require external data.

c4_state_free

util/state.h

Frees all resources associated with a state object.

Releases all memory allocated for data requests, URLs, payloads, responses, and error messages. Safe to call with partially initialized state objects.

Parameters

  • state : Pointer to the state object to free

c4_state_get_data_request_by_id

util/state.h

Finds a data request by its unique identifier.

Performs a linear search through the request list to find a request with the matching 32-byte ID.

Parameters

  • state : Pointer to the state object

  • id : 32-byte identifier to search for

Returns

Pointer to the matching request, or NULL if not found

c4_state_get_data_request_by_url

util/state.h

Finds a data request by its URL.

Performs a linear search through the request list to find a request with the matching URL string.

Parameters

  • state : Pointer to the state object

  • url : URL string to search for

Returns

Pointer to the matching request, or NULL if not found

c4_state_is_pending

util/state.h

Checks if a request is still pending.

A request is considered pending if it has no error and no response data yet.

Parameters

  • req : Pointer to the request to check

Returns

true if pending, false if completed or failed

c4_state_add_request

util/state.h

Adds a new data request to the state.

Automatically generates a unique ID for the request if not already set (hash of payload or URL). Adds the request to the front of the linked list.

Parameters

  • state : Pointer to the state object

  • data_request : Pointer to the request to add (ownership transfers to state)

c4_state_get_pending_request

util/state.h

Gets the first pending request from the state.

Searches through the request list and returns the first request that is still pending (has no error and no response).

Parameters

  • state : Pointer to the state object

Returns

Pointer to the first pending request, or NULL if none pending

c4_state_add_error

util/state.h

Adds an error message to the state.

Appends the error message to any existing error messages, separated by newlines. Handles NULL error parameter gracefully by using a generic message.

Parameters

  • state : Pointer to the state object

  • error : Error message to add (can be NULL)

Returns

Always returns C4_ERROR

c4_req_mockname

util/state.h

Generates a mock filename for a request (test mode only).

Creates a sanitized filename based on the request URL or payload, suitable for storing mock responses. Characters that are invalid in filenames are replaced with underscores.

Note: Only available when compiled with TEST flag.

Parameters

  • req : Pointer to the data request

Returns

Allocated string with the mock filename (caller must free)

Last updated