Skip to main content

ab_client_block_import/
beacon_chain.rs

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/// Errors for [`BeaconChainBlockImport`]
32#[derive(Debug, thiserror::Error)]
33pub enum BeaconChainBlockImportError {
34    /// Block verification error
35    #[error("Block verification error: {error}")]
36    VerificationError {
37        /// Block verification error
38        #[from]
39        error: BlockVerificationError,
40    },
41    /// Failed to persist a new super segment header
42    #[error("Failed to persist a new super segment header: {error}")]
43    PersistSuperSegmentHeaders {
44        /// Low-level error
45        #[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/// A custom wrapper that will query chain info from either already persisted blocks or blocks that
60/// are currently queued for import
61#[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    /// Like [`ChainInfo::ancestor_header()`], but the chain may end with blocks that are being
104    /// imported.
105    ///
106    /// Block numbers of blocks being imported aren't verified yet. On a chain where one of them is
107    /// invalid, the result may not match the description of [`ChainInfo::ancestor_header()`], which
108    /// only changes the error that verification of that chain fails with.
109    fn ancestor_header(
110        &self,
111        ancestor_block_number: BlockNumber,
112        descendant_block_root: &BlockRoot,
113    ) -> Option<OwnedBeaconChainHeader> {
114        // The descendant is usually already in the database, so this is the shortest path to the
115        // ancestor
116        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        // Walk down the blocks that are being imported, it ends because block roots are hashes of
124        // headers, so parent roots can't form a cycle
125        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        // Blocks are removed from importing blocks once they are persisted or their import failed,
137        // so the block where the walk stopped is either in the database or unknown. This finds
138        // ancestors below the blocks being imported and blocks persisted since the first query.
139        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    /// Create a new instance
236    #[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        // TODO: `.send()` is a hack for compiler bug, see:
278        //  https://github.com/rust-lang/rust/issues/100013#issuecomment-2210995259
279        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        // TODO: `.send()` is a hack for compiler bug, see:
299        //  https://github.com/rust-lang/rust/issues/100013#issuecomment-2210995259
300        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        // TODO: Execute block
310
311        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            // Wait for all the acknowledgements to arrive
337        }
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}