cnidarium_component/action_handler.rs
1use std::sync::Arc;
2
3use anyhow::Result;
4use async_trait::async_trait;
5use cnidarium::{StateRead, StateWrite};
6
7#[async_trait]
8/// Defines the interface for handling transaction actions.
9///
10/// Block-wide execution is performed using the [`Component`](crate::Component)
11/// trait. Per-transaction execution is performed using the `ActionHandler`
12/// trait.
13///
14/// The `ActionHandler` trait has a top-level implementation on [`Transaction`],
15/// which performs any transaction-wide checks and then calls the
16/// `ActionHandler` implementation for each [`Action`](penumbra_sdk_transaction::Action).
17///
18/// The validation logic in the `ActionHandler` trait is split into three phases:
19///
20/// * [`ActionHandler::check_stateless`], which has no access to chain state, only to the [`CheckStatelessContext`];
21/// * [`ActionHandler::check_stateful`], which has read access to a snapshot of state prior to transaction execution;
22/// * [`ActionHandler::execute`], which has write access to the state and read access to its own writes.
23///
24/// All of these methods are asynchronous and fallible; an error at any level
25/// fails the transaction and aborts any in-progress execution.
26///
27/// These methods are described in more detail below, but in general, as much
28/// work as possible should be pushed up the stack, where greater parallelism is
29/// available, with checks performed in `execute` only as a last resort.
30pub trait ActionHandler {
31 /// Context for stateless validity checks, like the transaction containing the action.
32 type CheckStatelessContext: Clone + Send + Sync + 'static;
33 /// Performs all of this action's stateless validity checks in the
34 /// `context` of some [`Transaction`].
35 ///
36 /// This method is `async` to make it easy to perform stateless validity
37 /// checks in parallel, by allowing `ActionHandler` implementations to
38 /// easily spawn tasks internally.
39 ///
40 /// Supplying the `context` means that stateless checks can use
41 /// transaction-wide data like the SCT anchor.
42 ///
43 /// As much work as possible should be done in `check_stateless`, as it can
44 /// be run in parallel across all transactions in a block.
45 async fn check_stateless(&self, context: Self::CheckStatelessContext) -> Result<()>;
46
47 /// Performs those stateful validity checks that can be performed against a
48 /// historical state snapshot.
49 ///
50 /// This method provides read access to a snapshot of the `State` prior to
51 /// transaction execution. It is intended to be run in parallel across all
52 /// actions within a transaction.
53 ///
54 /// # Warning
55 ///
56 /// Misuse of this method creates TOCTOU vulnerabilities. Checks performed
57 /// in this method must be valid if they are performed against a _prior_
58 /// state, as another action in the same transaction may execute first and
59 /// change the state.
60 ///
61 /// Checks performed in this phase should have a justification for why they
62 /// are safe to run in parallel with other actions in the same transaction,
63 /// and the default behavior should be to perform checks in
64 /// [`ActionHandler::check_and_execute`].
65 ///
66 /// # Invariants
67 ///
68 /// This method should only be called on data that has been checked
69 /// with [`ActionHandler::check_stateless`]. This method can be called
70 /// before [`Component::begin_block`](crate::Component::begin_block).
71 async fn check_historical<S: StateRead + 'static>(&self, _state: Arc<S>) -> Result<()> {
72 // Default behavior: no-op
73 Ok(())
74 }
75
76 /// Attempts to execute this action against the provided `state`.
77 ///
78 /// This method provides read and write access to the `state`. It is
79 /// fallible, so it's possible to perform checks within the `check_and_execute`
80 /// implementation and abort execution on error; the [`StateTransaction`]
81 /// mechanism ensures that all writes are correctly discarded.
82 ///
83 /// Because `execute` must run sequentially, whenever possible, checks
84 /// should be performed in [`ActionHandler::check_stateless`], or (more carefully) in
85 /// [`ActionHandler::check_historical`]. One example of where this is not
86 /// possible (in fact, the motivating example) is for IBC, where a
87 /// transaction may (1) submit a client update and then (2) relay messages
88 /// valid relative to the newly updated state. In this case, the checks for
89 /// (2) must be performed during execution, as they depend on the state
90 /// changes written while processing the client update.
91 ///
92 /// However, this data flow pattern should be avoided whenever possible.
93 ///
94 /// # Invariants
95 ///
96 /// This method should only be called after an invocation of
97 /// [`ActionHandler::check_historical`] on the same transaction. This method
98 /// can be called before [`Component::begin_block`](crate::Component::begin_block).
99 async fn check_and_execute<S: StateWrite>(&self, state: S) -> Result<()>;
100}