Skip to main content

ab_client_block_builder/
beacon_chain.rs

1//! Block building for the beacon chain
2
3use crate::{BlockBuilder, BlockBuilderError, BlockBuilderResult};
4use ab_client_api::{BeaconChainInfo, BlockDetails, BlockMerkleMountainRange, ContractSlotState};
5use ab_client_consensus_common::ConsensusConstants;
6use ab_client_consensus_common::consensus_parameters::{
7    DeriveConsensusParametersError, DeriveSuperSegmentForBlockError, derive_consensus_parameters,
8    derive_super_segments_for_block,
9};
10use ab_client_consensus_common::state::GlobalState;
11use ab_core_primitives::block::body::owned::OwnedBeaconChainBodyError;
12use ab_core_primitives::block::header::owned::{
13    GenericOwnedBlockHeader, OwnedBeaconChainHeader, OwnedBeaconChainHeaderError,
14};
15use ab_core_primitives::block::header::{
16    BeaconChainHeader, BlockHeaderConsensusInfo, BlockHeaderPrefix,
17    OwnedBlockHeaderConsensusParameters, OwnedBlockHeaderSeal,
18};
19use ab_core_primitives::block::owned::OwnedBeaconChainBlock;
20use ab_core_primitives::block::{BlockNumber, BlockRoot, BlockTimestamp};
21use ab_core_primitives::hashes::Blake3Hash;
22use ab_core_primitives::pot::{PotCheckpoints, SlotNumber};
23use ab_core_primitives::segments::SuperSegmentRoot;
24use ab_core_primitives::shard::ShardIndex;
25use rclite::Arc;
26use std::iter;
27use std::sync::Arc as StdArc;
28use std::time::SystemTime;
29
30/// Error for [`BeaconChainBlockBuilder`]
31#[derive(Debug, thiserror::Error)]
32pub enum BeaconChainBlockBuilderError {
33    /// Consensus parameters derivation error
34    #[error("Consensus parameters derivation error: {error}")]
35    ConsensusParametersDerivation {
36        /// Consensus parameters derivation error
37        #[from]
38        error: DeriveConsensusParametersError,
39    },
40    /// Super segment derivation error
41    #[error("Super segment derivation error: {error}")]
42    SuperSegmentDerivation {
43        /// Super segment derivation error
44        #[from]
45        error: DeriveSuperSegmentForBlockError,
46    },
47    /// Failed to create body
48    #[error("Failed to create body: {error}")]
49    FailedToCreateBody {
50        // Body creation error
51        #[from]
52        error: OwnedBeaconChainBodyError,
53    },
54    /// Failed to create a header
55    #[error("Failed to create a header: {error}")]
56    FailedToCreateHeader {
57        // Header creation error
58        #[from]
59        error: OwnedBeaconChainHeaderError,
60    },
61}
62
63impl From<BeaconChainBlockBuilderError> for BlockBuilderError {
64    #[inline(always)]
65    fn from(error: BeaconChainBlockBuilderError) -> Self {
66        Self::Custom {
67            error: error.into(),
68        }
69    }
70}
71
72// TODO: Another domain-specific abstraction over `ChainInfo`, which will be implemented for
73//  `ChainInfo`, but could also be implemented in simpler way directly for tests without dealing
74//  with complete headers, etc.
75/// Beacon chain block builder
76#[derive(Debug)]
77pub struct BeaconChainBlockBuilder<BCI> {
78    consensus_constants: ConsensusConstants,
79    chain_info: BCI,
80}
81
82impl<CI> BlockBuilder<OwnedBeaconChainBlock> for BeaconChainBlockBuilder<CI>
83where
84    CI: BeaconChainInfo,
85{
86    async fn build<SealBlock>(
87        &mut self,
88        parent_block_root: &BlockRoot,
89        parent_header: &<OwnedBeaconChainHeader as GenericOwnedBlockHeader>::Header<'_>,
90        parent_block_details: &BlockDetails,
91        consensus_info: &BlockHeaderConsensusInfo,
92        checkpoints: &[PotCheckpoints],
93        seal_block: SealBlock,
94    ) -> Result<BlockBuilderResult<OwnedBeaconChainBlock>, BlockBuilderError>
95    where
96        SealBlock: AsyncFnOnce<(Blake3Hash,), Output = Option<OwnedBlockHeaderSeal>, CallOnceFuture: Send>
97            + Send,
98    {
99        let block_number = parent_header.prefix.number + BlockNumber::ONE;
100
101        let header_prefix = self.create_header_prefix(
102            parent_block_root,
103            parent_header.prefix.timestamp,
104            &parent_block_details.mmr_with_block,
105            block_number,
106        )?;
107        // TODO: Handle super segment error caused by too many segments included in shard blocks.
108        //  While not a concern any time soon, but it is theoretically possible and should be
109        //  handled in a general case.
110        let maybe_super_segment = derive_super_segments_for_block(
111            &self.chain_info,
112            parent_header.prefix.number,
113            self.consensus_constants.block_confirmation_depth,
114            self.consensus_constants.shard_confirmation_depth,
115        )
116        .map_err(BeaconChainBlockBuilderError::from)?;
117
118        let consensus_parameters = self.derive_consensus_parameters(
119            parent_block_root,
120            parent_header,
121            block_number,
122            consensus_info.slot,
123            maybe_super_segment
124                .as_ref()
125                .map(|super_segment| super_segment.header.root),
126        )?;
127
128        let (state_root, system_contract_states) = self.execute_block(parent_block_details)?;
129
130        let block_builder = OwnedBeaconChainBlock::init(
131            self.chain_info
132                .segment_headers_for_block(block_number)
133                .into_iter()
134                .map(|segment_header| {
135                    (
136                        segment_header.segment_index.as_inner(),
137                        segment_header.segment_root,
138                    )
139                }),
140            // TODO: Real intermediate shard blocks
141            iter::empty(),
142            checkpoints,
143        )
144        .map_err(BeaconChainBlockBuilderError::from)?;
145
146        let block_unsealed = block_builder
147            .with_header(
148                &header_prefix,
149                state_root,
150                consensus_info,
151                &consensus_parameters.as_ref(),
152            )
153            .map_err(BeaconChainBlockBuilderError::from)?;
154
155        let seal = seal_block(block_unsealed.pre_seal_hash())
156            .await
157            .ok_or(BlockBuilderError::FailedToSeal)?;
158        let block = block_unsealed.with_seal(seal.as_ref());
159
160        let mut block_mmr = *parent_block_details.mmr_with_block;
161
162        if !block_mmr.add_leaf(&block.header.header().root()) {
163            return Err(BlockBuilderError::CantExtendMmr);
164        }
165
166        Ok(BlockBuilderResult {
167            block,
168            block_details: BlockDetails {
169                mmr_with_block: Arc::new(block_mmr),
170                system_contract_states,
171            },
172            extra: (),
173        })
174    }
175}
176
177impl<BCI> BeaconChainBlockBuilder<BCI>
178where
179    BCI: BeaconChainInfo,
180{
181    /// Create a new instance
182    pub fn new(consensus_constants: ConsensusConstants, chain_info: BCI) -> Self {
183        Self {
184            consensus_constants,
185            chain_info,
186        }
187    }
188
189    fn create_header_prefix(
190        &self,
191        parent_block_root: &BlockRoot,
192        parent_timestamp: BlockTimestamp,
193        mmr_with_block: &BlockMerkleMountainRange,
194        block_number: BlockNumber,
195    ) -> Result<BlockHeaderPrefix, BlockBuilderError> {
196        let timestamp = SystemTime::now()
197            .duration_since(SystemTime::UNIX_EPOCH)
198            .unwrap_or_default()
199            .as_millis();
200        let mut timestamp =
201            BlockTimestamp::from_millis(u64::try_from(timestamp).unwrap_or(u64::MAX));
202
203        if timestamp <= parent_timestamp {
204            timestamp = BlockTimestamp::from_millis(parent_timestamp.as_millis().saturating_add(1));
205        }
206
207        Ok(BlockHeaderPrefix {
208            number: block_number,
209            shard_index: ShardIndex::BEACON_CHAIN,
210            padding_0: [0; _],
211            timestamp,
212            parent_root: *parent_block_root,
213            mmr_root: Blake3Hash::new(
214                mmr_with_block
215                    .root()
216                    .ok_or(BlockBuilderError::InvalidParentMmr)?,
217            ),
218        })
219    }
220
221    fn derive_consensus_parameters(
222        &self,
223        parent_block_root: &BlockRoot,
224        parent_header: &BeaconChainHeader<'_>,
225        block_number: BlockNumber,
226        slot: SlotNumber,
227        super_segment_root: Option<SuperSegmentRoot>,
228    ) -> Result<OwnedBlockHeaderConsensusParameters, BeaconChainBlockBuilderError> {
229        let derived_consensus_parameters = derive_consensus_parameters(
230            &self.consensus_constants,
231            &self.chain_info,
232            parent_block_root,
233            parent_header.consensus_parameters(),
234            parent_header.consensus_info.slot,
235            block_number,
236            slot,
237        )?;
238
239        Ok(OwnedBlockHeaderConsensusParameters {
240            fixed_parameters: derived_consensus_parameters.fixed_parameters,
241            super_segment_root,
242            next_solution_range: derived_consensus_parameters.next_solution_range,
243            pot_parameters_change: derived_consensus_parameters.pot_parameters_change,
244        })
245    }
246
247    fn execute_block(
248        &self,
249        parent_block_details: &BlockDetails,
250    ) -> Result<(Blake3Hash, StdArc<[ContractSlotState]>), BeaconChainBlockBuilderError> {
251        let global_state = GlobalState::new(&parent_block_details.system_contract_states);
252
253        // TODO: Execute block
254
255        let state_root = global_state.root();
256        let system_contract_states = global_state.to_system_contract_states();
257
258        Ok((state_root, system_contract_states))
259    }
260}