ethrex_blockchain/
fork_choice.rs1use ethrex_common::{
2 H256,
3 types::{BlockHash, BlockHeader, BlockNumber},
4};
5use ethrex_metrics::metrics;
6use ethrex_storage::{Store, error::StoreError};
7use tracing::{error, warn};
8
9use crate::{
10 error::{self, InvalidForkChoice},
11 is_canonical,
12};
13
14pub const REORG_DEPTH_LIMIT: u64 = 128;
29
30pub async fn apply_fork_choice(
40 store: &Store,
41 head_hash: H256,
42 safe_hash: H256,
43 finalized_hash: H256,
44) -> Result<BlockHeader, InvalidForkChoice> {
45 if head_hash.is_zero() {
46 return Err(InvalidForkChoice::InvalidHeadHash);
47 }
48
49 let finalized_res = if !finalized_hash.is_zero() {
50 store.get_block_header_by_hash(finalized_hash)?
51 } else {
52 None
53 };
54
55 let safe_res = if !safe_hash.is_zero() {
56 store.get_block_header_by_hash(safe_hash)?
57 } else {
58 None
59 };
60
61 let head_res = store.get_block_header_by_hash(head_hash)?;
62
63 if !safe_hash.is_zero() {
64 check_order(&safe_res, &head_res)?;
65 }
66
67 if !finalized_hash.is_zero() && !safe_hash.is_zero() {
68 check_order(&finalized_res, &safe_res)?;
69 }
70
71 let Some(head) = head_res else {
72 return Err(InvalidForkChoice::Syncing);
73 };
74
75 let latest = store.get_latest_block_number().await?;
76 let head_is_canonical = is_canonical(store, head.number, head_hash).await?;
77
78 if let Some(stored_finalized) = store.get_finalized_block_number().await?
86 && head.number < latest
87 && head.number <= stored_finalized
88 && head_is_canonical
89 {
90 return Err(InvalidForkChoice::NewHeadAlreadyCanonical);
91 }
92
93 let Some(new_canonical_blocks) = find_link_with_canonical_chain(store, &head).await? else {
95 return Err(InvalidForkChoice::UnlinkedHead);
96 };
97
98 let (link_block_number, link_block_hash) = match new_canonical_blocks.last() {
99 Some((number, hash)) => (*number, *hash),
100 None => (head.number, head_hash),
101 };
102
103 if let Some(ref finalized) = finalized_res
105 && !((is_canonical(store, finalized.number, finalized_hash).await?
106 && finalized.number <= link_block_number)
107 || (finalized.number == head.number && finalized_hash == head_hash)
108 || new_canonical_blocks.contains(&(finalized.number, finalized_hash)))
109 {
110 return Err(InvalidForkChoice::Disconnected(
111 error::ForkChoiceElement::Head,
112 error::ForkChoiceElement::Finalized,
113 ));
114 }
115
116 if let Some(ref safe) = safe_res
117 && !((is_canonical(store, safe.number, safe_hash).await?
118 && safe.number <= link_block_number)
119 || (safe.number == head.number && safe_hash == head_hash)
120 || new_canonical_blocks.contains(&(safe.number, safe_hash)))
121 {
122 return Err(InvalidForkChoice::Disconnected(
123 error::ForkChoiceElement::Head,
124 error::ForkChoiceElement::Safe,
125 ));
126 }
127
128 let canonical_link_height = if head_is_canonical {
141 head.number
142 } else {
143 new_canonical_blocks
144 .last()
145 .map(|(n, _)| *n)
146 .unwrap_or(head.number)
147 .saturating_sub(1)
148 };
149 let reorg_depth = latest.saturating_sub(canonical_link_height);
150 if reorg_depth > REORG_DEPTH_LIMIT {
151 return Err(InvalidForkChoice::TooDeepReorg {
152 reorg_depth,
153 limit: REORG_DEPTH_LIMIT,
154 });
155 }
156
157 let Some(link_header) = store.get_block_header_by_hash(link_block_hash)? else {
158 error!("Link block not found although it was just retrieved from the DB");
160 return Err(InvalidForkChoice::UnlinkedHead);
161 };
162
163 if !store.has_state_root(link_header.state_root)? {
167 warn!(
168 link_block=%link_block_hash,
169 link_number=%link_header.number,
170 head_number=%head.number,
171 "FCU head state not reachable from DB state. Starting sync toward head. This is expected if the consensus client is currently syncing."
172 );
173 return Err(InvalidForkChoice::StateNotReachable);
174 }
175
176 store
179 .forkchoice_update(
180 new_canonical_blocks,
181 head.number,
182 head_hash,
183 safe_res.map(|h| h.number),
184 finalized_res.map(|h| h.number),
185 )
186 .await?;
187
188 metrics!(
189 use ethrex_metrics::blocks::METRICS_BLOCKS;
190
191 METRICS_BLOCKS.set_head_height(head.number);
192 );
193
194 Ok(head)
195}
196
197fn check_order(
199 block_1: &Option<BlockHeader>,
200 block_2: &Option<BlockHeader>,
201) -> Result<(), InvalidForkChoice> {
202 match (block_1, block_2) {
204 (None, Some(_)) => Err(InvalidForkChoice::ElementNotFound(
205 error::ForkChoiceElement::Finalized,
206 )),
207 (Some(b1), Some(b2)) => {
208 if b1.number > b2.number {
209 Err(InvalidForkChoice::Unordered)
210 } else {
211 Ok(())
212 }
213 }
214 _ => Err(InvalidForkChoice::Syncing),
215 }
216}
217
218async fn find_link_with_canonical_chain(
229 store: &Store,
230 block_header: &BlockHeader,
231) -> Result<Option<Vec<(BlockNumber, BlockHash)>>, StoreError> {
232 let mut block_number = block_header.number;
233 let block_hash = block_header.hash();
234 let mut branch = Vec::new();
235
236 if is_canonical(store, block_number, block_hash).await? {
237 return Ok(Some(branch));
238 }
239
240 let genesis_number = store.get_earliest_block_number().await?;
241 let mut header = block_header.clone();
242
243 while block_number > genesis_number {
244 block_number -= 1;
245 let parent_hash = header.parent_hash;
246
247 let parent_header = match store.get_block_header_by_hash(parent_hash) {
249 Ok(Some(header)) => header,
250 Ok(None) => return Ok(None),
251 Err(error) => return Err(error),
252 };
253
254 if is_canonical(store, block_number, parent_hash).await? {
255 return Ok(Some(branch));
256 } else {
257 branch.push((block_number, parent_hash));
258 }
259
260 header = parent_header;
261 }
262
263 Ok(None)
264}