cnidarium_component

Trait ActionHandler

Source
pub trait ActionHandler {
    type CheckStatelessContext: Clone + Send + Sync + 'static;

    // Required methods
    fn check_stateless<'life0, 'async_trait>(
        &'life0 self,
        context: Self::CheckStatelessContext,
    ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
       where Self: 'async_trait,
             'life0: 'async_trait;
    fn check_and_execute<'life0, 'async_trait, S>(
        &'life0 self,
        state: S,
    ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
       where S: 'async_trait + StateWrite,
             Self: 'async_trait,
             'life0: 'async_trait;

    // Provided method
    fn check_historical<'life0, 'async_trait, S>(
        &'life0 self,
        _state: Arc<S>,
    ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
       where S: 'async_trait + StateRead + 'static,
             Self: Sync + 'async_trait,
             'life0: 'async_trait { ... }
}
Expand description

Defines the interface for handling transaction actions.

Block-wide execution is performed using the Component trait. Per-transaction execution is performed using the ActionHandler trait.

The ActionHandler trait has a top-level implementation on [Transaction], which performs any transaction-wide checks and then calls the ActionHandler implementation for each Action.

The validation logic in the ActionHandler trait is split into three phases:

  • ActionHandler::check_stateless, which has no access to chain state, only to the [CheckStatelessContext];
  • [ActionHandler::check_stateful], which has read access to a snapshot of state prior to transaction execution;
  • [ActionHandler::execute], which has write access to the state and read access to its own writes.

All of these methods are asynchronous and fallible; an error at any level fails the transaction and aborts any in-progress execution.

These methods are described in more detail below, but in general, as much work as possible should be pushed up the stack, where greater parallelism is available, with checks performed in execute only as a last resort.

Required Associated Types§

Source

type CheckStatelessContext: Clone + Send + Sync + 'static

Context for stateless validity checks, like the transaction containing the action.

Required Methods§

Source

fn check_stateless<'life0, 'async_trait>( &'life0 self, context: Self::CheckStatelessContext, ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Performs all of this action’s stateless validity checks in the context of some [Transaction].

This method is async to make it easy to perform stateless validity checks in parallel, by allowing ActionHandler implementations to easily spawn tasks internally.

Supplying the context means that stateless checks can use transaction-wide data like the SCT anchor.

As much work as possible should be done in check_stateless, as it can be run in parallel across all transactions in a block.

Source

fn check_and_execute<'life0, 'async_trait, S>( &'life0 self, state: S, ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
where S: 'async_trait + StateWrite, Self: 'async_trait, 'life0: 'async_trait,

Attempts to execute this action against the provided state.

This method provides read and write access to the state. It is fallible, so it’s possible to perform checks within the check_and_execute implementation and abort execution on error; the [StateTransaction] mechanism ensures that all writes are correctly discarded.

Because execute must run sequentially, whenever possible, checks should be performed in ActionHandler::check_stateless, or (more carefully) in ActionHandler::check_historical. One example of where this is not possible (in fact, the motivating example) is for IBC, where a transaction may (1) submit a client update and then (2) relay messages valid relative to the newly updated state. In this case, the checks for (2) must be performed during execution, as they depend on the state changes written while processing the client update.

However, this data flow pattern should be avoided whenever possible.

§Invariants

This method should only be called after an invocation of ActionHandler::check_historical on the same transaction. This method can be called before Component::begin_block.

Provided Methods§

Source

fn check_historical<'life0, 'async_trait, S>( &'life0 self, _state: Arc<S>, ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
where S: 'async_trait + StateRead + 'static, Self: Sync + 'async_trait, 'life0: 'async_trait,

Performs those stateful validity checks that can be performed against a historical state snapshot.

This method provides read access to a snapshot of the State prior to transaction execution. It is intended to be run in parallel across all actions within a transaction.

§Warning

Misuse of this method creates TOCTOU vulnerabilities. Checks performed in this method must be valid if they are performed against a prior state, as another action in the same transaction may execute first and change the state.

Checks performed in this phase should have a justification for why they are safe to run in parallel with other actions in the same transaction, and the default behavior should be to perform checks in ActionHandler::check_and_execute.

§Invariants

This method should only be called on data that has been checked with ActionHandler::check_stateless. This method can be called before Component::begin_block.

Dyn Compatibility§

This trait is not dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety", so this trait is not object safe.

Implementors§