node/transition_frontier/candidate/
transition_frontier_candidate_actions.rs

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