hopr_chain_actions/
lib.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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
//! Contains high-level traits that translate to on-chain transaction interactions
//!
//! ## Actions
//! The main concept is an "action", which a node can perform and results into an on-chain
//! operation. These actions are all on the external interface of this crate which is represented
//! by the [ChainActions] type.
//! There are 3 classes of actions implemented in submodules of this crate:
//! - [channel actions](channels)
//! - [ticket redeem actions](redeem)
//! - [node actions](node)
//!
//! Each action is represented by a method (or methods) that are imported into the [ChainActions] type
//! through a trait from the respective module (e.g. [ChannelActions](channels::ChannelActions) trait for channel actions).
//! Each action will eventually translate to an on-chain transaction.
//! An action will always return a [PendingAction](action_queue::PendingAction) future. This
//! future can be awaited or not, depending if the caller wishes to obtain the [ActionConfirmation](action_queue::ActionConfirmation)
//! of the submitted action.
//! If the action's caller  wishes to await the confirmation, this process can end in one of 3 possible states:
//!
//! 1. the action gets confirmed, meaning it has been successfully executed.
//! 2. awaiting the confirmation returns an error, which typically means a failure during action prerequisite checks,
//!    an invalid state to execute the action or invalid arguments given to the action
//! 3. awaiting the confirmation times out, meaning the execution failed on-chain and the "action expectations"
//!    did not yield (see below for details on how "action expectations work").
//!
//! Not awaiting the returned [PendingAction](action_queue::PendingAction) future does not give the caller any guarantees
//! on how the action has executed, although this might be perfectly fine for certain cases (fire & forget).
//!
//! ## How are actions executed and make it on-chain ?
//! Call to any [ChainAction's](ChainActions<Db>) method is eventually represented as an [Action](chain_types::actions::Action)
//! enum with parameters that already closely resemble the required on-chain transaction input for that action to be
//! The [Action](chain_types::actions::Action) enum instance is then passed via
//! an [ActionSender] into the [ActionQueue](action_queue::ActionQueue).
//! The [ActionQueue](action_queue::ActionQueue) takes care of ensuring the FIFO order of the
//! actions which is driven by a standalone [action loop](`action_queue::ActionQueue::start()`) and must be instantiated
//! before [ChainActions], so that it can provide it with an [ActionSender].
//!
//! ### Queueing of actions
//! The [ActionQueue](action_queue::ActionQueue) operates a MPSC queue, which picks up the [Actions](chain_types::actions::Action) submitted
//! to it one by one. With each such action it will:
//! 1. transform [Action](chain_types::actions::Action) into a [TypedTransaction](chain_types::TypedTransaction)
//!    via a [PayloadGenerator](payload::PayloadGenerator<T>)
//! 2. submit the [TypedTransaction](chain_types::TypedTransaction) on-chain via a [TransactionExecutor](action_queue::TransactionExecutor)
//! 3. generate an [IndexerExpectation](action_state::IndexerExpectation) from the submitted action
//! 4. submit the [IndexerExpectation](action_state::IndexerExpectation) in an [ActionState](action_state::ActionState) implementation
//! 5. wait for expectation to be resolved (or timeout, see [ActionQueueConfig](action_queue::ActionQueueConfig)
//!    and resolve the action submitter's [PendingAction](action_queue::PendingAction).
//!
//! In other words, the [ActionQueue](action_queue::ActionQueue) takes care of two important mappings:
//! 1. [Action](chain_types::actions::Action) to [TypedTransaction](chain_types::TypedTransaction)
//! 2. [Action](chain_types::actions::Action) to [IndexerExpectation](action_state::IndexerExpectation)
//!
//! The first one makes it possible for an action to make it on-chain, the second one allows to
//! be informed of the action's result and effect.
//!
//! See the [action_queue] module for details.
//!
//! ### On-chain expectations after an action is submitted
//! The [action_state] module defines the [ActionState](action_state::ActionState) trait which is responsible
//! for registering [IndexerExpectation](action_state::IndexerExpectation) done by the [ActionQueue](action_queue::ActionQueue).
//! The implementor of the [ActionState](action_state::ActionState) should be monitoring the state of the block chain (reading
//! event logs contained inside newly mined blocks) and with each on-chain event that matches a registered expectation,
//! mark it as resolved to allow the bound action to be confirmed in the [ActionQueue](action_queue::ActionQueue).
//! Therefore, the two components which interact with an [ActionState](action_state::ActionState) implementation
//! are the [ActionQueue](action_queue::ActionQueue) and the on-chain Indexer (see `chain-indexer` crate for details).
//!
//! There's an exception on construction of an expectation for the `withdraw` [NodeAction](node::NodeActions):
//! Since the current implementation of the Indexer does not track native token transfers, but only smart contract
//! events, it is impossible to detect the native token's transfer confirmation.
//! Since the `withdraw` action is not very common, its confirmation is tracked via direct polling of the RPC
//! provider until the transaction is confirmed. The confirmation horizon is set in the [TransactionExecutor](action_queue::TransactionExecutor).
//!
//! See the [action_state] module for details.
//!
//! ## Payload generators
//! As described above, the [ActionQueue](action_queue::ActionQueue) needs a [PayloadGenerator](payload::PayloadGenerator<T>)
//! implementation to be able to translate the [Action](chain_types::actions::Action) into the [TypedTransaction](chain_types::TypedTransaction).
//! There are currently two possible ways of constructing the action's transaction:
//! - via plain EIP1559 payload
//! - via EIP1559 payload containing a SAFE transaction that embeds the actual payload
//!
//! The former one is implemented via [BasicPayloadGenerator](payload::BasicPayloadGenerator), the latter
//! is implemented in [SafePayloadGenerator](payload::SafePayloadGenerator).
//!
//! In most situations, the transaction payload an action translates to, typically constitutes a smart contract call
//! of one of the HOPR smart contracts deployed on-chain.
//!
//! See the [payload] module for details.
use hopr_crypto_types::prelude::*;
use hopr_primitive_types::prelude::*;

use crate::action_queue::ActionSender;

pub mod action_queue;
pub mod action_state;
pub mod channels;
/// Contains all errors used in this crate.
pub mod errors;
pub mod node;
pub mod payload;
pub mod redeem;

/// Contains all actions that a node can execute on-chain.
#[derive(Debug, Clone)]
pub struct ChainActions<Db>
where
    Db: Clone + std::fmt::Debug,
{
    me: Address,
    chain_key: ChainKeypair,
    db: Db,
    tx_sender: ActionSender,
}

impl<Db> ChainActions<Db>
where
    Db: Clone + std::fmt::Debug,
{
    /// Creates new instance.
    pub fn new(me: &ChainKeypair, db: Db, tx_sender: ActionSender) -> Self {
        Self {
            me: me.public().to_address(),
            chain_key: me.clone(),
            db,
            tx_sender,
        }
    }

    /// On-chain address of this node
    pub fn self_address(&self) -> Address {
        self.me
    }
}