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