node/transition_frontier/candidate/
transition_frontier_candidate_actions.rs

1use mina_p2p_messages::v2::StateHash;
2use openmina_core::{
3    action_event,
4    block::{prevalidate::BlockPrevalidationError, ArcBlockWithHash},
5    consensus::consensus_take,
6    ActionEvent,
7};
8use serde::{Deserialize, Serialize};
9use snark::block_verify::SnarkBlockVerifyError;
10
11use crate::snark::block_verify::SnarkBlockVerifyId;
12
13pub type TransitionFrontierCandidateActionWithMeta =
14    redux::ActionWithMeta<TransitionFrontierCandidateAction>;
15pub type TransitionFrontierCandidateActionWithMetaRef<'a> =
16    redux::ActionWithMeta<&'a TransitionFrontierCandidateAction>;
17
18// NOTE: `debug(hash)` must be used instead of `display(hash)` because
19// for some reason the later breaks CI. `hash = display(&hash)` works too.
20#[derive(Serialize, Deserialize, Debug, Clone, ActionEvent)]
21#[action_event(level = debug, fields(debug(hash), debug(error)))]
22pub enum TransitionFrontierCandidateAction {
23    P2pBestTipUpdate {
24        best_tip: ArcBlockWithHash,
25    },
26    BlockReceived {
27        block: ArcBlockWithHash,
28        chain_proof: Option<(Vec<StateHash>, ArcBlockWithHash)>,
29    },
30    BlockPrevalidateSuccess {
31        hash: StateHash,
32    },
33    BlockPrevalidateError {
34        hash: StateHash,
35        error: BlockPrevalidationError,
36    },
37    BlockChainProofUpdate {
38        hash: StateHash,
39        chain_proof: (Vec<StateHash>, ArcBlockWithHash),
40    },
41    BlockSnarkVerifyPending {
42        req_id: SnarkBlockVerifyId,
43        hash: StateHash,
44    },
45    #[action_event(level = info)]
46    BlockSnarkVerifySuccess {
47        hash: StateHash,
48    },
49    #[action_event(level = warn)]
50    BlockSnarkVerifyError {
51        hash: StateHash,
52        error: SnarkBlockVerifyError,
53    },
54    TransitionFrontierSyncTargetUpdate,
55    Prune,
56}
57
58impl redux::EnablingCondition<crate::State> for TransitionFrontierCandidateAction {
59    fn is_enabled(&self, state: &crate::State, _time: redux::Timestamp) -> bool {
60        match self {
61            TransitionFrontierCandidateAction::P2pBestTipUpdate { .. } => true,
62            TransitionFrontierCandidateAction::BlockReceived { block, .. } => {
63                !block.is_genesis() && !state.transition_frontier.candidates.contains(block.hash())
64            }
65            TransitionFrontierCandidateAction::BlockPrevalidateSuccess { hash }
66            | TransitionFrontierCandidateAction::BlockPrevalidateError { hash, .. } => state
67                .transition_frontier
68                .candidates
69                .get(hash)
70                .is_some_and(|block| block.status.is_received()),
71            TransitionFrontierCandidateAction::BlockChainProofUpdate { hash, .. } => state
72                .transition_frontier
73                .candidates
74                .is_chain_proof_needed(hash),
75            TransitionFrontierCandidateAction::BlockSnarkVerifyPending { req_id, hash } => {
76                state
77                    .transition_frontier
78                    .candidates
79                    .get(hash)
80                    .is_some_and(|block| block.status.is_prevalidated())
81                    && state.snark.block_verify.jobs.contains(*req_id)
82            }
83            TransitionFrontierCandidateAction::BlockSnarkVerifySuccess { hash } => state
84                .transition_frontier
85                .candidates
86                .get(hash)
87                .is_some_and(|block| block.status.is_snark_verify_pending()),
88            TransitionFrontierCandidateAction::BlockSnarkVerifyError { hash, .. } => state
89                .transition_frontier
90                .candidates
91                .get(hash)
92                .is_some_and(|block| block.status.is_snark_verify_pending()),
93            TransitionFrontierCandidateAction::TransitionFrontierSyncTargetUpdate => {
94                let Some(best_candidate) =
95                    state.transition_frontier.candidates.best_verified_block()
96                else {
97                    return false;
98                };
99                // do not need to update transition frontier sync target.
100                if IntoIterator::into_iter([
101                    state.transition_frontier.best_tip(),
102                    state.transition_frontier.sync.best_tip(),
103                ])
104                .flatten()
105                .any(|b| {
106                    b.hash() == best_candidate.hash()
107                        || !consensus_take(
108                            b.consensus_state(),
109                            best_candidate.consensus_state(),
110                            b.hash(),
111                            best_candidate.hash(),
112                        )
113                }) {
114                    return false;
115                }
116
117                // has enough data
118                state
119                    .transition_frontier
120                    .candidates
121                    .best_verified_block_chain_proof(&state.transition_frontier)
122                    .is_some()
123            }
124            TransitionFrontierCandidateAction::Prune => {
125                state.transition_frontier.candidates.best().is_some()
126            }
127        }
128    }
129}
130
131impl From<TransitionFrontierCandidateAction> for crate::Action {
132    fn from(value: TransitionFrontierCandidateAction) -> Self {
133        Self::TransitionFrontier(crate::TransitionFrontierAction::Candidate(value))
134    }
135}