node/snark_pool/
snark_pool_actions.rs

1use std::sync::Arc;
2
3use ledger::scan_state::scan_state::{transaction_snark::OneOrTwo, AvailableJobMessage};
4use openmina_core::{
5    snark::{Snark, SnarkJobCommitment, SnarkJobId},
6    ActionEvent,
7};
8use serde::{Deserialize, Serialize};
9
10use crate::p2p::PeerId;
11
12use super::{candidate::SnarkPoolCandidateAction, SnarkWork};
13
14pub type SnarkPoolActionWithMeta = redux::ActionWithMeta<SnarkPoolAction>;
15pub type SnarkPoolActionWithMetaRef<'a> = redux::ActionWithMeta<&'a SnarkPoolAction>;
16
17#[derive(Serialize, Deserialize, Debug, Clone, ActionEvent)]
18pub enum SnarkPoolAction {
19    Candidate(SnarkPoolCandidateAction),
20
21    JobsUpdate {
22        jobs: Arc<Vec<OneOrTwo<AvailableJobMessage>>>,
23        orphaned_snarks: Vec<SnarkWork>,
24    },
25    AutoCreateCommitment,
26    CommitmentCreateMany {
27        job_ids: Vec<SnarkJobId>,
28    },
29    CommitmentCreate {
30        job_id: SnarkJobId,
31    },
32    CommitmentAdd {
33        commitment: SnarkJobCommitment,
34        sender: PeerId,
35    },
36    #[action_event(level = info, fields(is_sender_local))]
37    WorkAdd {
38        snark: Snark,
39        sender: PeerId,
40        is_sender_local: bool,
41    },
42    #[action_event(level = trace)]
43    P2pSendAll,
44    #[action_event(level = trace)]
45    P2pSend {
46        peer_id: PeerId,
47    },
48    CheckTimeouts,
49    JobCommitmentTimeout {
50        job_id: SnarkJobId,
51    },
52}
53
54impl redux::EnablingCondition<crate::State> for SnarkPoolAction {
55    fn is_enabled(&self, state: &crate::State, time: redux::Timestamp) -> bool {
56        match self {
57            SnarkPoolAction::Candidate(action) => action.is_enabled(state, time),
58            SnarkPoolAction::AutoCreateCommitment => {
59                state.config.snarker.as_ref().is_some_and(|v| v.auto_commit)
60            }
61            SnarkPoolAction::CommitmentCreateMany { .. } => state.config.snarker.is_some(),
62            SnarkPoolAction::CommitmentCreate { job_id } => {
63                state.config.snarker.is_some() && state.snark_pool.should_create_commitment(job_id)
64            }
65            SnarkPoolAction::CommitmentAdd { commitment, .. } => state
66                .snark_pool
67                .get(&commitment.job_id)
68                .is_some_and(|s| match s.commitment.as_ref() {
69                    Some(cur) => commitment > &cur.commitment,
70                    None => true,
71                }),
72            SnarkPoolAction::WorkAdd { snark, .. } => state
73                .snark_pool
74                .get(&snark.job_id())
75                .is_some_and(|s| match s.snark.as_ref() {
76                    Some(cur) => snark > &cur.work,
77                    None => true,
78                }),
79            SnarkPoolAction::P2pSend { peer_id } => state
80                .p2p
81                .get_ready_peer(peer_id)
82                // can't propagate empty snarkpool
83                .filter(|_| !state.snark_pool.is_empty())
84                // Only send commitments/snarks if peer has the same best tip,
85                // or its best tip is extension of our best tip. In such case
86                // no commitment/snark will be dropped by peer, because it
87                // doesn't yet have those jobs.
88                //
89                // By sending commitments/snarks to the peer, which has next
90                // best tip, we might send outdated commitments/snarks, but
91                // we might send useful ones as well.
92                .and_then(|p| {
93                    let peer_best_tip = p.best_tip.as_ref()?;
94                    let our_best_tip = state.transition_frontier.best_tip()?.hash();
95                    Some(p).filter(|_| {
96                        peer_best_tip.hash() == our_best_tip
97                            || peer_best_tip.pred_hash() == our_best_tip
98                    })
99                })
100                .is_some_and(|p| {
101                    let check =
102                        |(next_index, limit), last_index| limit > 0 && next_index <= last_index;
103                    let last_index = state.snark_pool.last_index();
104
105                    check(
106                        p.channels.snark_job_commitment.next_send_index_and_limit(),
107                        last_index,
108                    ) || check(p.channels.snark.next_send_index_and_limit(), last_index)
109                }),
110            SnarkPoolAction::CheckTimeouts => time
111                .checked_sub(state.snark_pool.last_check_timeouts)
112                .is_some_and(|dur| dur.as_secs() >= 5),
113            SnarkPoolAction::JobCommitmentTimeout { job_id } => {
114                state.snark_pool.is_commitment_timed_out(job_id, time)
115            }
116            SnarkPoolAction::JobsUpdate { .. } => true,
117            SnarkPoolAction::P2pSendAll => true,
118        }
119    }
120}
121
122// Effectful actions
123
124#[derive(Serialize, Deserialize, Debug, Clone)]
125pub enum SnarkPoolEffectfulAction {
126    SnarkPoolJobsRandomChoose {
127        choices: Vec<SnarkJobId>,
128        count: usize,
129        on_result: redux::Callback<Vec<SnarkJobId>>,
130    },
131}
132
133pub type SnarkPoolEffectfulActionWithMeta = redux::ActionWithMeta<SnarkPoolEffectfulAction>;
134
135impl redux::EnablingCondition<crate::State> for SnarkPoolEffectfulAction {
136    fn is_enabled(&self, _state: &crate::State, _time: redux::Timestamp) -> bool {
137        true
138    }
139}