1#[cfg(test)]
2mod tests;
3
4use crate::importing_blocks::{ImportingBlockHandle, ImportingBlocks, ParentBlockImportStatus};
5use crate::{BlockImport, BlockImportError};
6use ab_client_api::{
7 BeaconChainInfoWrite, BlockDetails, BlockOrigin, ChainInfo, PersistSuperSegmentHeadersError,
8};
9use ab_client_block_verification::{BlockVerification, BlockVerificationError};
10use ab_client_consensus_common::BlockImportingNotification;
11use ab_client_consensus_common::consensus_parameters::{
12 DeriveConsensusParametersChainInfo, DeriveConsensusParametersConsensusInfo,
13 ShardMembershipEntropySourceChainInfo,
14};
15use ab_client_consensus_common::state::GlobalState;
16use ab_core_primitives::block::header::owned::OwnedBeaconChainHeader;
17use ab_core_primitives::block::owned::OwnedBeaconChainBlock;
18use ab_core_primitives::block::{BlockNumber, BlockRoot};
19use ab_core_primitives::hashes::Blake3Hash;
20use ab_core_primitives::pot::PotOutput;
21use ab_core_primitives::segments::SuperSegment;
22use ab_proof_of_space::Table;
23use async_lock::Mutex as AsyncMutex;
24use futures::channel::mpsc;
25use futures::prelude::*;
26use send_future::SendFuture;
27use std::marker::PhantomData;
28use tracing::{info, warn};
29use triomphe::Arc;
30
31#[derive(Debug, thiserror::Error)]
33pub enum BeaconChainBlockImportError {
34 #[error("Block verification error: {error}")]
36 VerificationError {
37 #[from]
39 error: BlockVerificationError,
40 },
41 #[error("Failed to persist a new super segment header: {error}")]
43 PersistSuperSegmentHeaders {
44 #[from]
46 error: PersistSuperSegmentHeadersError,
47 },
48}
49
50impl From<BeaconChainBlockImportError> for BlockImportError {
51 #[inline(always)]
52 fn from(error: BeaconChainBlockImportError) -> Self {
53 Self::Custom {
54 error: error.into(),
55 }
56 }
57}
58
59#[derive(Debug)]
62struct VerificationChainInfo<'a, CI> {
63 chain_info: &'a CI,
64 importing_blocks: &'a ImportingBlocks<OwnedBeaconChainHeader>,
65}
66
67impl<CI> DeriveConsensusParametersChainInfo for VerificationChainInfo<'_, CI>
68where
69 CI: ChainInfo<OwnedBeaconChainBlock>,
70{
71 fn ancestor_header_consensus_info(
72 &self,
73 ancestor_block_number: BlockNumber,
74 descendant_block_root: &BlockRoot,
75 ) -> Option<DeriveConsensusParametersConsensusInfo> {
76 let header = self.ancestor_header(ancestor_block_number, descendant_block_root)?;
77
78 Some(DeriveConsensusParametersConsensusInfo::from_consensus_info(
79 header.header().consensus_info,
80 ))
81 }
82}
83
84impl<CI> ShardMembershipEntropySourceChainInfo for VerificationChainInfo<'_, CI>
85where
86 CI: ChainInfo<OwnedBeaconChainBlock>,
87{
88 fn ancestor_header_proof_of_time(
89 &self,
90 ancestor_block_number: BlockNumber,
91 descendant_block_root: &BlockRoot,
92 ) -> Option<PotOutput> {
93 let header = self.ancestor_header(ancestor_block_number, descendant_block_root)?;
94
95 Some(header.header().consensus_info.proof_of_time)
96 }
97}
98
99impl<CI> VerificationChainInfo<'_, CI>
100where
101 CI: ChainInfo<OwnedBeaconChainBlock>,
102{
103 fn ancestor_header(
110 &self,
111 ancestor_block_number: BlockNumber,
112 descendant_block_root: &BlockRoot,
113 ) -> Option<OwnedBeaconChainHeader> {
114 if let Some(header) = self
117 .chain_info
118 .ancestor_header(ancestor_block_number, descendant_block_root)
119 {
120 return Some(header);
121 }
122
123 let mut block_root = *descendant_block_root;
126 while let Some(importing_entry) = self.importing_blocks.get(&block_root) {
127 let header = importing_entry.header().header();
128
129 if header.prefix.number == ancestor_block_number {
130 return Some(importing_entry.header().clone());
131 }
132
133 block_root = header.prefix.parent_root;
134 }
135
136 self.chain_info
140 .ancestor_header(ancestor_block_number, &block_root)
141 }
142}
143
144#[derive(Debug)]
145pub struct BeaconChainBlockImport<PosTable, CI, BV> {
146 chain_info: CI,
147 block_verification: BV,
148 importing_blocks: ImportingBlocks<OwnedBeaconChainHeader>,
149 block_importing_notification_sender: mpsc::Sender<BlockImportingNotification>,
150 super_segments_sender: AsyncMutex<mpsc::Sender<SuperSegment>>,
151 block_import_notification_sender: mpsc::Sender<OwnedBeaconChainBlock>,
152 _pos_table: PhantomData<PosTable>,
153}
154
155impl<PosTable, CI, BV> BlockImport<OwnedBeaconChainBlock>
156 for BeaconChainBlockImport<PosTable, CI, BV>
157where
158 PosTable: Table,
159 CI: BeaconChainInfoWrite,
160 BV: BlockVerification<OwnedBeaconChainBlock, Option<SuperSegment>>,
161{
162 fn import(
163 &self,
164 block: OwnedBeaconChainBlock,
165 origin: BlockOrigin,
166 ) -> Result<impl Future<Output = Result<(), BlockImportError>> + Send, BlockImportError> {
167 let parent_root = &block.header.header().prefix.parent_root;
168
169 let (parent_header, parent_block_mmr, parent_block_import_status) =
170 if let Some((parent_header, parent_block_details)) =
171 self.chain_info.header_with_details(parent_root)
172 {
173 (
174 parent_header,
175 parent_block_details.mmr_with_block,
176 ParentBlockImportStatus::Imported {
177 system_contract_states: parent_block_details.system_contract_states,
178 },
179 )
180 } else if let Some(importing_entry) = self.importing_blocks.get(parent_root) {
181 (
182 importing_entry.header().clone(),
183 Arc::clone(importing_entry.mmr()),
184 ParentBlockImportStatus::Importing {
185 entry: importing_entry,
186 },
187 )
188 } else {
189 return Err(BlockImportError::UnknownParentBlock {
190 block_root: *parent_root,
191 });
192 };
193
194 let parent_block_mmr_root = Blake3Hash::from(
195 parent_block_mmr
196 .root()
197 .ok_or(BlockImportError::ParentBlockMmrInvalid)?,
198 );
199 let mut block_mmr = *parent_block_mmr;
200
201 if !block_mmr.add_leaf(&block.header.header().root()) {
202 return Err(BlockImportError::CantExtendMmr);
203 }
204
205 let importing_handle = self
206 .importing_blocks
207 .insert(block.header.clone(), Arc::new(block_mmr))
208 .ok_or(BlockImportError::AlreadyImporting)?;
209
210 if self
211 .chain_info
212 .header(&block.header.header().root())
213 .is_some()
214 {
215 return Err(BlockImportError::AlreadyImported);
216 }
217
218 Ok(self.import(
219 parent_header,
220 parent_block_mmr_root,
221 block,
222 origin,
223 importing_handle,
224 parent_block_import_status,
225 ))
226 }
227}
228
229impl<PosTable, CI, BV> BeaconChainBlockImport<PosTable, CI, BV>
230where
231 PosTable: Table,
232 CI: BeaconChainInfoWrite,
233 BV: BlockVerification<OwnedBeaconChainBlock, Option<SuperSegment>>,
234{
235 #[inline(always)]
237 pub fn new(
238 chain_info: CI,
239 block_verification: BV,
240 block_importing_notification_sender: mpsc::Sender<BlockImportingNotification>,
241 super_segments_sender: mpsc::Sender<SuperSegment>,
242 block_import_notification_sender: mpsc::Sender<OwnedBeaconChainBlock>,
243 ) -> Self {
244 Self {
245 chain_info,
246 block_verification,
247 importing_blocks: ImportingBlocks::new(),
248 block_importing_notification_sender,
249 super_segments_sender: AsyncMutex::new(super_segments_sender),
250 block_import_notification_sender,
251 _pos_table: PhantomData,
252 }
253 }
254
255 async fn import(
256 &self,
257 parent_header: OwnedBeaconChainHeader,
258 parent_block_mmr_root: Blake3Hash,
259 block: OwnedBeaconChainBlock,
260 origin: BlockOrigin,
261 importing_handle: ImportingBlockHandle<OwnedBeaconChainHeader>,
262 parent_block_import_status: ParentBlockImportStatus<OwnedBeaconChainHeader>,
263 ) -> Result<(), BlockImportError> {
264 let parent_header = parent_header.header();
265 let header = block.header.header();
266
267 #[expect(
268 clippy::rest_pattern_accessible_field,
269 reason = "Do not care about fields"
270 )]
271 let log_block_import = match origin {
272 BlockOrigin::LocalBlockBuilder { .. } => true,
273 BlockOrigin::Sync => false,
274 BlockOrigin::Broadcast => true,
275 };
276
277 self.block_verification
280 .verify_concurrent(
281 parent_header,
282 &parent_block_mmr_root,
283 &block,
284 &origin,
285 &VerificationChainInfo {
286 chain_info: &self.chain_info,
287 importing_blocks: &self.importing_blocks,
288 },
289 )
290 .send()
291 .await
292 .map_err(BeaconChainBlockImportError::from)?;
293
294 let Some(system_contract_states) = parent_block_import_status.wait().await else {
295 return Err(BlockImportError::ParentBlockImportFailed);
296 };
297
298 let maybe_super_segment = self
301 .block_verification
302 .verify_sequential(parent_header, &parent_block_mmr_root, &block, &origin)
303 .send()
304 .await
305 .map_err(BeaconChainBlockImportError::from)?;
306
307 let global_state = GlobalState::new(&system_contract_states);
308
309 let state_root = global_state.root();
312
313 if header.result.state_root != state_root {
314 return Err(BlockImportError::InvalidStateRoot {
315 expected: state_root,
316 actual: header.result.state_root,
317 });
318 }
319
320 let system_contract_states = global_state.to_system_contract_states();
321
322 let (acknowledgement_sender, mut acknowledgement_receiver) = mpsc::channel(0);
323 if let Err(error) = self
324 .block_importing_notification_sender
325 .clone()
326 .send(BlockImportingNotification {
327 block_number: header.prefix.number,
328 acknowledgement_sender,
329 })
330 .await
331 {
332 warn!(%error, "Failed to send block importing notification");
333 }
334
335 while acknowledgement_receiver.next().await.is_some() {
336 }
338
339 let number = header.prefix.number;
340 let root = *header.root();
341
342 if let Some(super_segment) = maybe_super_segment {
343 let mut super_segment_sender = self.super_segments_sender.lock().await;
344 if self
345 .chain_info
346 .persist_super_segment_header(super_segment.header)
347 .await
348 .map_err(
349 |error| BeaconChainBlockImportError::PersistSuperSegmentHeaders { error },
350 )?
351 && let Err(error) = super_segment_sender.send(super_segment).await
352 {
353 warn!(%error, "Failed to send a super segment notification");
354 }
355 }
356
357 self.chain_info
358 .persist_block(
359 block.clone(),
360 BlockDetails {
361 mmr_with_block: Arc::clone(importing_handle.mmr()),
362 system_contract_states: Arc::clone(&system_contract_states),
363 },
364 )
365 .await?;
366
367 importing_handle.set_success(system_contract_states);
368
369 if let Err(error) = self
370 .block_import_notification_sender
371 .clone()
372 .send(block)
373 .await
374 {
375 warn!(
376 %error,
377 block_number = %number,
378 block_root = %root,
379 "Failed to send block import notification"
380 );
381 }
382
383 if log_block_import {
384 info!(
385 %number,
386 %root,
387 "🏆 Imported block",
388 );
389 }
390
391 Ok(())
392 }
393}