cnidarium_component/
action_handler.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
use std::sync::Arc;

use anyhow::Result;
use async_trait::async_trait;
use cnidarium::{StateRead, StateWrite};

#[async_trait]
/// Defines the interface for handling transaction actions.
///
/// Block-wide execution is performed using the [`Component`](crate::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`](penumbra_transaction::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.
pub trait ActionHandler {
    /// Context for stateless validity checks, like the transaction containing the action.
    type CheckStatelessContext: Clone + Send + Sync + 'static;
    /// 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.
    async fn check_stateless(&self, context: Self::CheckStatelessContext) -> Result<()>;

    /// 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`](crate::Component::begin_block).
    async fn check_historical<S: StateRead + 'static>(&self, _state: Arc<S>) -> Result<()> {
        // Default behavior: no-op
        Ok(())
    }

    /// 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`](crate::Component::begin_block).
    async fn check_and_execute<S: StateWrite>(&self, state: S) -> Result<()>;
}