node/transition_frontier/candidate/
transition_frontier_candidate_reducer.rs1use openmina_core::{
2 block::{ArcBlockWithHash, BlockHash},
3 bug_condition,
4};
5use snark::block_verify::{SnarkBlockVerifyAction, SnarkBlockVerifyError, SnarkBlockVerifyId};
6
7use crate::{
8 transition_frontier::sync::{
9 ledger::{
10 snarked::TransitionFrontierSyncLedgerSnarkedAction,
11 staged::TransitionFrontierSyncLedgerStagedAction,
12 },
13 TransitionFrontierSyncAction,
14 },
15 WatchedAccountsAction,
16};
17
18use super::{
19 TransitionFrontierCandidateAction, TransitionFrontierCandidateActionWithMetaRef,
20 TransitionFrontierCandidateStatus, TransitionFrontierCandidatesState,
21};
22
23impl TransitionFrontierCandidatesState {
24 pub fn reducer(
25 mut state_context: crate::Substate<Self>,
26 action: TransitionFrontierCandidateActionWithMetaRef<'_>,
27 ) {
28 let Ok(state) = state_context.get_substate_mut() else {
29 return;
31 };
32 let (action, meta) = action.split();
33
34 match action {
35 TransitionFrontierCandidateAction::P2pBestTipUpdate { best_tip } => {
36 let dispatcher = state_context.into_dispatcher();
37 dispatcher.push(TransitionFrontierCandidateAction::BlockReceived {
38 block: best_tip.clone(),
39 chain_proof: None,
40 });
41
42 dispatcher.push(TransitionFrontierSyncLedgerSnarkedAction::PeersQuery);
43 dispatcher.push(TransitionFrontierSyncLedgerStagedAction::PartsPeerFetchInit);
44 dispatcher.push(TransitionFrontierSyncAction::BlocksPeersQuery);
45 }
46 TransitionFrontierCandidateAction::BlockReceived { block, chain_proof } => {
47 state.add(meta.time(), block.clone(), chain_proof.clone());
48
49 let (dispatcher, state) = state_context.into_dispatcher_and_state();
51
52 let allow_block_too_late = allow_block_too_late(state, block);
53
54 match state.prevalidate_block(block, allow_block_too_late) {
55 Ok(()) => {
56 dispatcher.push(
57 TransitionFrontierCandidateAction::BlockPrevalidateSuccess {
58 hash: block.hash().clone(),
59 },
60 );
61 }
62 Err(error) => {
63 dispatcher.push(TransitionFrontierCandidateAction::BlockPrevalidateError {
64 hash: block.hash().clone(),
65 error,
66 });
67 }
68 }
69 }
70 TransitionFrontierCandidateAction::BlockPrevalidateError { hash, error } => {
71 state.invalidate(hash, error.is_forever_invalid());
72 }
73 TransitionFrontierCandidateAction::BlockPrevalidateSuccess { hash } => {
74 state.update_status(hash, |_| TransitionFrontierCandidateStatus::Prevalidated);
75 let Some(block) = state.get(hash).map(|s| s.block.clone()) else {
76 bug_condition!("TransitionFrontierCandidateAction::BlockPrevalidateSuccess block not found but action enabled");
77 return;
78 };
79
80 let dispatcher = state_context.into_dispatcher();
82 dispatcher.push(SnarkBlockVerifyAction::Init {
83 block: block.into(),
84 on_init: redux::callback!(
85 on_received_block_snark_verify_init((hash: BlockHash, req_id: SnarkBlockVerifyId)) -> crate::Action {
86 TransitionFrontierCandidateAction::BlockSnarkVerifyPending { hash, req_id }
87 }),
88 on_success: redux::callback!(
89 on_received_block_snark_verify_success(hash: BlockHash) -> crate::Action {
90 TransitionFrontierCandidateAction::BlockSnarkVerifySuccess { hash }
91 }),
92 on_error: redux::callback!(
93 on_received_block_snark_verify_error((hash: BlockHash, error: SnarkBlockVerifyError)) -> crate::Action {
94 TransitionFrontierCandidateAction::BlockSnarkVerifyError { hash, error }
95 }),
96 });
97 }
98 TransitionFrontierCandidateAction::BlockChainProofUpdate { hash, chain_proof } => {
99 state.set_chain_proof(hash, chain_proof.clone());
100
101 let dispatcher = state_context.into_dispatcher();
102 dispatcher
103 .push(TransitionFrontierCandidateAction::TransitionFrontierSyncTargetUpdate);
104 }
105 TransitionFrontierCandidateAction::BlockSnarkVerifyPending { req_id, hash } => {
106 state.update_status(hash, |_| {
107 TransitionFrontierCandidateStatus::SnarkVerifyPending {
108 time: meta.time(),
109 req_id: *req_id,
110 }
111 });
112 }
113 TransitionFrontierCandidateAction::BlockSnarkVerifyError { hash, .. } => {
114 state.invalidate(hash, true);
115 }
116 TransitionFrontierCandidateAction::BlockSnarkVerifySuccess { hash } => {
117 state.update_status(hash, |_| {
118 TransitionFrontierCandidateStatus::SnarkVerifySuccess { time: meta.time() }
119 });
120
121 let (dispatcher, global_state) = state_context.into_dispatcher_and_state();
123 let Some(block) = global_state
124 .transition_frontier
125 .candidates
126 .best_verified_block()
127 else {
128 return;
129 };
130 for pub_key in global_state.watched_accounts.accounts() {
131 dispatcher.push(WatchedAccountsAction::LedgerInitialStateGetInit {
132 pub_key: pub_key.clone(),
133 });
134 dispatcher.push(WatchedAccountsAction::TransactionsIncludedInBlock {
135 pub_key,
136 block: block.clone(),
137 });
138 }
139
140 dispatcher
141 .push(TransitionFrontierCandidateAction::TransitionFrontierSyncTargetUpdate);
142 }
143 TransitionFrontierCandidateAction::TransitionFrontierSyncTargetUpdate => {
144 let (dispatcher, state) = state_context.into_dispatcher_and_state();
145 let Some(best_tip) = state.transition_frontier.candidates.best_verified_block()
146 else {
147 bug_condition!(
148 "ConsensusAction::TransitionFrontierSyncTargetUpdate | no chosen best tip"
149 );
150 return;
151 };
152
153 let Some((blocks_inbetween, root_block)) = state
154 .transition_frontier
155 .candidates
156 .best_verified_block_chain_proof(&state.transition_frontier)
157 else {
158 bug_condition!("ConsensusAction::TransitionFrontierSyncTargetUpdate | no best tip chain proof");
159 return;
160 };
161
162 let previous_root_snarked_ledger_hash = state
163 .transition_frontier
164 .root()
165 .map(|b| b.snarked_ledger_hash().clone());
166
167 dispatcher.push(TransitionFrontierSyncAction::BestTipUpdate {
168 previous_root_snarked_ledger_hash,
169 best_tip: best_tip.clone(),
170 root_block,
171 blocks_inbetween,
172 on_success: None,
173 });
174 }
175 TransitionFrontierCandidateAction::Prune => {
176 state.prune();
177 }
178 }
179 }
180}
181
182pub fn allow_block_too_late(state: &crate::State, block: &ArcBlockWithHash) -> bool {
191 let (has_greater_blobal_slot, diff_with_best_tip) = state
192 .transition_frontier
193 .best_tip()
194 .map(|b| {
195 (
196 block.global_slot() > b.global_slot(),
197 b.global_slot().abs_diff(block.global_slot()),
198 )
199 })
200 .unwrap_or((false, 0));
201
202 has_greater_blobal_slot && diff_with_best_tip > 2
203}