Skip to main content

ab_client_block_import/
beacon_chain.rs

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