node/transition_frontier/
transition_frontier_state.rs1use std::collections::BTreeMap;
2
3use ledger::transaction_pool::diff::BestTipDiff;
4use mina_p2p_messages::v2::{
5 MinaStateProtocolStateBodyValueStableV2, MinaStateProtocolStateValueStableV2, StateHash,
6 TransactionHash,
7};
8use openmina_core::{
9 block::{AppliedBlock, ArcBlockWithHash},
10 bug_condition,
11};
12use serde::{Deserialize, Serialize};
13
14use super::{
15 candidate::TransitionFrontierCandidatesState, genesis::TransitionFrontierGenesisState,
16 sync::TransitionFrontierSyncState, TransitionFrontierConfig,
17};
18
19#[derive(Serialize, Deserialize, Debug, Clone)]
20pub struct TransitionFrontierState {
21 pub config: TransitionFrontierConfig,
22 pub genesis: TransitionFrontierGenesisState,
24 pub best_chain: Vec<AppliedBlock>,
26 pub needed_protocol_states: BTreeMap<StateHash, MinaStateProtocolStateValueStableV2>,
29 pub candidates: TransitionFrontierCandidatesState,
30 pub sync: TransitionFrontierSyncState,
32
33 pub blacklist: BTreeMap<StateHash, u32>,
36 pub chain_diff: Option<BestTipDiff>,
38 pub archive_enabled: bool,
40}
41
42impl TransitionFrontierState {
43 pub fn new(config: TransitionFrontierConfig, archive_enabled: bool) -> Self {
44 Self {
45 config,
46 genesis: TransitionFrontierGenesisState::Idle,
47 candidates: TransitionFrontierCandidatesState::new(),
48 best_chain: Vec::with_capacity(290),
49 needed_protocol_states: Default::default(),
50 sync: TransitionFrontierSyncState::Idle,
51 blacklist: Default::default(),
52 chain_diff: None,
53 archive_enabled,
54 }
55 }
56
57 pub fn best_tip(&self) -> Option<&ArcBlockWithHash> {
58 self.best_chain.last().map(|b| &b.block)
59 }
60
61 pub fn root(&self) -> Option<&ArcBlockWithHash> {
62 self.best_chain.first().map(|b| &b.block)
63 }
64
65 pub fn best_tip_breadcrumb(&self) -> Option<&AppliedBlock> {
66 self.best_chain.last()
67 }
68
69 pub fn root_breadcrumb(&self) -> Option<&AppliedBlock> {
70 self.best_chain.first()
71 }
72
73 pub fn contains_transaction(&self, hash: &TransactionHash) -> bool {
76 self.best_chain.iter().any(|block| {
77 block
78 .body()
79 .transactions()
80 .any(|transaction| transaction.hash().as_ref().ok() == Some(hash))
81 })
82 }
83
84 pub fn get_state_body(
86 &self,
87 hash: &StateHash,
88 ) -> Option<&MinaStateProtocolStateBodyValueStableV2> {
89 self.best_chain
90 .iter()
91 .find_map(|block| {
92 if block.hash() == hash {
93 Some(&block.header().protocol_state.body)
94 } else {
95 None
96 }
97 })
98 .or_else(|| {
99 self.needed_protocol_states
100 .iter()
101 .find_map(|(block_hash, state)| {
102 if block_hash == hash {
103 Some(&state.body)
104 } else {
105 None
106 }
107 })
108 })
109 }
110
111 pub fn maybe_make_chain_diff(&self, new_chain: &[AppliedBlock]) -> Option<BestTipDiff> {
114 let old_chain = self.best_chain.as_slice();
115 let new_root = new_chain.first();
116
117 if old_chain.last() == new_chain.last() {
118 return None;
120 }
121
122 let new_chain_start_at = match new_root {
124 None => None,
125 Some(new_root) => old_chain
126 .iter()
127 .enumerate()
128 .rev()
129 .find(|(_index, block)| *block == new_root),
130 };
131
132 let (diff_old_chain, diff_new_chain) = match new_chain_start_at {
133 None => {
134 (old_chain, new_chain)
137 }
138 Some((new_chain_start_at, _)) => {
139 let Some(old_chain_advanced) = &old_chain.get(new_chain_start_at..) else {
141 bug_condition!("old_chain[{}] out of bounds", new_chain_start_at);
142 return None;
143 };
144
145 let len = old_chain_advanced.len().min(new_chain.len());
147
148 let diff_start_at = old_chain_advanced
150 .get(..len)
151 .unwrap() .iter()
153 .rev()
154 .zip(
155 new_chain
156 .get(..len)
157 .unwrap() .iter()
159 .rev(),
160 )
161 .position(|(old_block, new_block)| old_block == new_block)
162 .map(|index| len.saturating_sub(index)) .unwrap(); let Some(diff_old_chain) = old_chain_advanced.get(diff_start_at..) else {
166 bug_condition!("old_chain[{}] out of bounds", diff_start_at);
167 return None;
168 };
169 let Some(diff_new_chain) = new_chain.get(diff_start_at..) else {
170 bug_condition!("new_chain[{}] out of bounds", diff_start_at);
171 return None;
172 };
173
174 (diff_old_chain, diff_new_chain)
175 }
176 };
177
178 let collect = |chain: &[AppliedBlock]| {
180 chain
181 .iter()
182 .flat_map(|breadcrumb| breadcrumb.commands_iter())
183 .filter_map(|cmd| {
184 use ledger::scan_state::transaction_logic::{UserCommand, WithStatus};
185 Some(
186 WithStatus::<UserCommand>::try_from(cmd)
187 .ok()?
188 .into_map(UserCommand::to_valid_unsafe),
189 )
190 })
191 .collect::<Vec<_>>()
192 };
193
194 let removed_commands = collect(diff_old_chain);
195 let new_commands = collect(diff_new_chain);
196
197 if removed_commands.is_empty() && new_commands.is_empty() {
198 return None;
199 }
200
201 Some(BestTipDiff {
202 new_commands,
203 removed_commands,
204 reorg_best_tip: false, })
206 }
207
208 pub fn resources_usage(&self) -> serde_json::Value {
209 serde_json::json!({
210 "best_chain_size": self.best_chain.len(),
211 "needed_protocol_states_size": self
212 .needed_protocol_states
213 .len(),
214 "blacklist_size": self.blacklist.len(),
215 "diff_tx_size": self
216 .chain_diff
217 .as_ref()
218 .map(|d| d.new_commands.len().saturating_add(d.removed_commands.len()))
221 .unwrap_or_default()
222 })
223 }
224}