mina_node/
action.rs

1use crate::p2p::P2pEffectfulAction;
2use mina_core::bug_condition;
3use serde::{Deserialize, Serialize};
4
5pub type ActionWithMeta = redux::ActionWithMeta<Action>;
6pub type ActionWithMetaRef<'a> = redux::ActionWithMeta<&'a Action>;
7
8pub use crate::{
9    block_producer::BlockProducerAction,
10    block_producer_effectful::BlockProducerEffectfulAction,
11    event_source::EventSourceAction,
12    external_snark_worker::ExternalSnarkWorkerAction,
13    ledger::LedgerAction,
14    p2p::P2pAction,
15    rpc::RpcAction,
16    snark::SnarkAction,
17    snark_pool::{SnarkPoolAction, SnarkPoolEffectfulAction},
18    transaction_pool::TransactionPoolAction,
19    transition_frontier::TransitionFrontierAction,
20    watched_accounts::WatchedAccountsAction,
21};
22use crate::{
23    external_snark_worker_effectful::ExternalSnarkWorkerEffectfulAction,
24    ledger_effectful::LedgerEffectfulAction, p2p::callbacks::P2pCallbacksAction,
25    rpc_effectful::RpcEffectfulAction, transaction_pool::TransactionPoolEffectfulAction,
26};
27
28pub trait ActionKindGet {
29    fn kind(&self) -> crate::ActionKind;
30}
31
32// Static limit for size of [`Action`] set to 512 bytes, if [`Action`] size is bigger code won't compile
33// compile error: "attempt to compute `0_usize - 1_usize`, which would overflow"
34static_assertions::const_assert!(std::mem::size_of::<Action>() <= 512);
35
36#[derive(derive_more::From, Serialize, Deserialize, Debug, Clone)]
37pub enum Action {
38    CheckTimeouts(CheckTimeoutsAction),
39    CheckInvalidPeersAction(CheckInvalidPeersAction),
40    EventSource(EventSourceAction),
41
42    P2p(P2pAction),
43    P2pEffectful(P2pEffectfulAction),
44    P2pCallbacks(P2pCallbacksAction),
45
46    Ledger(LedgerAction),
47    LedgerEffects(LedgerEffectfulAction),
48    Snark(SnarkAction),
49    TransitionFrontier(TransitionFrontierAction),
50    SnarkPool(SnarkPoolAction),
51    SnarkPoolEffect(SnarkPoolEffectfulAction),
52    TransactionPool(TransactionPoolAction),
53    TransactionPoolEffect(TransactionPoolEffectfulAction),
54    ExternalSnarkWorker(ExternalSnarkWorkerAction),
55    ExternalSnarkWorkerEffects(ExternalSnarkWorkerEffectfulAction),
56    BlockProducer(BlockProducerAction),
57    BlockProducerEffectful(BlockProducerEffectfulAction),
58    Rpc(RpcAction),
59    RpcEffectful(RpcEffectfulAction),
60
61    WatchedAccounts(WatchedAccountsAction),
62}
63
64impl Action {
65    pub fn kind(&self) -> crate::ActionKind {
66        ActionKindGet::kind(self)
67    }
68}
69
70/// Checks for timeouts and dispatches other time dependant actions.
71///
72/// Gets called repeatedly, so it's effects should be as light as possible.
73#[derive(Serialize, Deserialize, Debug, Clone)]
74pub struct CheckTimeoutsAction {}
75
76impl redux::EnablingCondition<crate::State> for CheckTimeoutsAction {}
77
78/// Checks if node has been started with invalid peers, and depending on feature flags exits/logs
79#[derive(Serialize, Deserialize, Debug, Clone)]
80pub struct CheckInvalidPeersAction {}
81
82impl redux::EnablingCondition<crate::State> for CheckInvalidPeersAction {
83    fn is_enabled(&self, state: &crate::State, time: redux::Timestamp) -> bool {
84        /// This is starting block height, loaded from state, if node doesn't connect to any other nodes this will be best height
85        const STARTING_BLOCK_HEIGHT: u32 = 296_372;
86        const GRACE_PERIOD: u64 = 60 * 1_000 * 1_000 * 1000;
87
88        let Some(grace_period) = state.start_time().checked_add(GRACE_PERIOD) else {
89            bug_condition!("Failed to add duration");
90            return false;
91        };
92        if grace_period > time {
93            return false;
94        }
95
96        let Some(p2p_state) = state.p2p.ready() else {
97            return false;
98        };
99
100        let Some(kad_state) = p2p_state.network.scheduler.discovery_state() else {
101            return false;
102        };
103
104        let has_bootstrapped = kad_state.has_bootstrapped && p2p_state.ready_peers().is_empty();
105        if !has_bootstrapped {
106            return false;
107        }
108
109        let Some(tip) = &state.transition_frontier.best_tip_breadcrumb() else {
110            return false;
111        };
112
113        tip.height() == STARTING_BLOCK_HEIGHT
114    }
115}
116
117impl redux::EnablingCondition<crate::State> for Action {
118    fn is_enabled(&self, state: &crate::State, time: redux::Timestamp) -> bool {
119        match self {
120            Action::CheckTimeouts(a) => a.is_enabled(state, time),
121            Action::CheckInvalidPeersAction(a) => a.is_enabled(state, time),
122            Action::EventSource(a) => a.is_enabled(state, time),
123            Action::P2p(a) => match a {
124                P2pAction::Initialization(a) => a.is_enabled(state, time),
125                other => state
126                    .p2p
127                    .ready()
128                    .is_some_and(|p2p| other.is_enabled(p2p, time)),
129            },
130            Action::P2pEffectful(a) => state
131                .p2p
132                .ready()
133                .is_some_and(|state| a.is_enabled(state, time)),
134            Action::Ledger(a) => a.is_enabled(state, time),
135            Action::LedgerEffects(a) => a.is_enabled(state, time),
136            Action::Snark(a) => a.is_enabled(&state.snark, time),
137            Action::TransitionFrontier(a) => a.is_enabled(state, time),
138            Action::SnarkPool(a) => a.is_enabled(state, time),
139            Action::SnarkPoolEffect(a) => a.is_enabled(state, time),
140            Action::ExternalSnarkWorker(a) => a.is_enabled(state, time),
141            Action::ExternalSnarkWorkerEffects(a) => a.is_enabled(state, time),
142            Action::BlockProducer(a) => a.is_enabled(state, time),
143            Action::BlockProducerEffectful(a) => a.is_enabled(state, time),
144            Action::Rpc(a) => a.is_enabled(state, time),
145            Action::WatchedAccounts(a) => a.is_enabled(state, time),
146            Action::TransactionPool(a) => a.is_enabled(state, time),
147            Action::TransactionPoolEffect(a) => a.is_enabled(state, time),
148            Action::P2pCallbacks(a) => a.is_enabled(state, time),
149            Action::RpcEffectful(a) => a.is_enabled(state, time),
150        }
151    }
152}
153
154impl From<redux::AnyAction> for Action {
155    fn from(action: redux::AnyAction) -> Self {
156        match action.0.downcast() {
157            Ok(action) => *action,
158            Err(action) => Self::P2p(*action.downcast().expect("Downcast failed")),
159        }
160    }
161}