1use crate::{ConsensusConstants, PotConsensusConstants};
2use ab_client_api::{BeaconChainInfo, ChainInfo, ShardSegmentRoot, ShardSegmentRootsError};
3use ab_core_primitives::block::header::{
4 BeaconChainHeader, BlockHeaderConsensusInfo, BlockHeaderConsensusParameters,
5 BlockHeaderFixedConsensusParameters, BlockHeaderPotParametersChange,
6};
7use ab_core_primitives::block::owned::OwnedBeaconChainBlock;
8use ab_core_primitives::block::{BlockNumber, BlockRoot};
9use ab_core_primitives::hashes::Blake3Hash;
10use ab_core_primitives::pieces::RecordChunk;
11use ab_core_primitives::pot::{PotOutput, PotParametersChange, SlotNumber};
12use ab_core_primitives::segments::{
13 LocalSegmentIndex, SegmentIndex, SegmentPosition, SegmentRoot, ShardSegmentRootWithPosition,
14 SuperSegment, SuperSegmentHeader, SuperSegmentIndex, SuperSegmentRoot,
15};
16use ab_core_primitives::shard::ShardIndex;
17use ab_core_primitives::solutions::{ShardMembershipEntropy, SolutionRange};
18use std::collections::HashMap;
19use std::num::NonZeroU32;
20use std::sync::Arc as StdArc;
21use triomphe::Arc;
22
23struct SolutionRanges {
24 current: SolutionRange,
25 next: Option<SolutionRange>,
26}
27
28struct PotInfo {
29 slot_iterations: NonZeroU32,
30 parameters_change: Option<PotParametersChange>,
31}
32
33#[derive(Debug, Copy, Clone)]
38pub struct DerivedConsensusParameters {
39 pub fixed_parameters: BlockHeaderFixedConsensusParameters,
41 pub next_solution_range: Option<SolutionRange>,
43 pub pot_parameters_change: Option<BlockHeaderPotParametersChange>,
45}
46
47#[derive(Debug, thiserror::Error)]
49pub enum DeriveConsensusParametersError {
50 #[error("Failed to get ancestor header")]
52 GetAncestorHeader,
53}
54
55#[derive(Debug, Clone, Copy)]
57pub struct DeriveConsensusParametersConsensusInfo {
58 pub slot: SlotNumber,
60 pub proof_of_time: PotOutput,
62 pub solution_record_chunk: RecordChunk,
64}
65
66impl DeriveConsensusParametersConsensusInfo {
67 pub fn from_consensus_info(consensus_info: &BlockHeaderConsensusInfo) -> Self {
68 Self {
69 slot: consensus_info.slot,
70 proof_of_time: consensus_info.proof_of_time,
71 solution_record_chunk: consensus_info.solution.chunk,
72 }
73 }
74}
75
76pub trait DeriveConsensusParametersChainInfo: Send + Sync {
80 fn ancestor_header_consensus_info(
82 &self,
83 ancestor_block_number: BlockNumber,
84 descendant_block_root: &BlockRoot,
85 ) -> Option<DeriveConsensusParametersConsensusInfo>;
86}
87
88impl<T> DeriveConsensusParametersChainInfo for T
89where
90 T: ChainInfo<OwnedBeaconChainBlock>,
91{
92 fn ancestor_header_consensus_info(
93 &self,
94 ancestor_block_number: BlockNumber,
95 descendant_block_root: &BlockRoot,
96 ) -> Option<DeriveConsensusParametersConsensusInfo> {
97 let header = self.ancestor_header(ancestor_block_number, descendant_block_root)?;
98
99 Some(DeriveConsensusParametersConsensusInfo::from_consensus_info(
100 header.header().consensus_info,
101 ))
102 }
103}
104
105pub fn derive_consensus_parameters<BCI>(
106 consensus_constants: &ConsensusConstants,
107 beacon_chain_info: &BCI,
108 parent_block_root: &BlockRoot,
109 parent_consensus_parameters: &BlockHeaderConsensusParameters<'_>,
110 parent_slot: SlotNumber,
111 block_number: BlockNumber,
112 slot: SlotNumber,
113) -> Result<DerivedConsensusParameters, DeriveConsensusParametersError>
114where
115 BCI: DeriveConsensusParametersChainInfo,
116{
117 let solution_ranges = derive_solution_ranges(
118 consensus_constants.retarget_interval,
119 consensus_constants.slot_probability,
120 beacon_chain_info,
121 parent_block_root,
122 parent_consensus_parameters.fixed_parameters.solution_range,
123 parent_consensus_parameters.next_solution_range,
124 block_number,
125 slot,
126 )?;
127 let pot_info = derive_pot_info(
128 &consensus_constants.pot,
129 beacon_chain_info,
130 parent_block_root,
131 parent_slot,
132 parent_consensus_parameters.fixed_parameters.slot_iterations,
133 parent_consensus_parameters
134 .pot_parameters_change
135 .copied()
136 .map(PotParametersChange::from),
137 block_number,
138 slot,
139 )?;
140
141 Ok(DerivedConsensusParameters {
142 fixed_parameters: BlockHeaderFixedConsensusParameters {
143 solution_range: solution_ranges.current,
144 slot_iterations: pot_info.slot_iterations,
145 num_shards: parent_consensus_parameters.fixed_parameters.num_shards,
146 },
147 next_solution_range: solution_ranges.next,
148 pot_parameters_change: pot_info
149 .parameters_change
150 .map(BlockHeaderPotParametersChange::from),
151 })
152}
153
154#[expect(
155 clippy::too_many_arguments,
156 reason = "Explicit minimal input for better testability"
157)]
158fn derive_solution_ranges<BCI>(
159 retarget_interval: BlockNumber,
160 slot_probability: (u64, u64),
161 beacon_chain_info: &BCI,
162 parent_block_root: &BlockRoot,
163 solution_range: SolutionRange,
164 next_solution_range: Option<SolutionRange>,
165 block_number: BlockNumber,
166 slot: SlotNumber,
167) -> Result<SolutionRanges, DeriveConsensusParametersError>
168where
169 BCI: DeriveConsensusParametersChainInfo,
170{
171 if let Some(next_solution_range) = next_solution_range {
172 return Ok(SolutionRanges {
173 current: next_solution_range,
174 next: None,
175 });
176 }
177
178 let next_solution_range = if u64::from(block_number)
179 .is_multiple_of(u64::from(retarget_interval))
180 && block_number > retarget_interval
181 {
182 let interval_start_block = block_number.saturating_sub(retarget_interval);
183 let interval_start_slot = beacon_chain_info
184 .ancestor_header_consensus_info(interval_start_block, parent_block_root)
185 .ok_or(DeriveConsensusParametersError::GetAncestorHeader)?
186 .slot;
187
188 Some(solution_range.derive_next(
189 slot.saturating_sub(interval_start_slot),
190 slot_probability,
191 retarget_interval,
192 ))
193 } else {
194 None
195 };
196
197 Ok(SolutionRanges {
198 current: solution_range,
199 next: next_solution_range,
200 })
201}
202
203#[expect(
204 clippy::too_many_arguments,
205 reason = "Explicit minimal input for better testability"
206)]
207fn derive_pot_info<BCI>(
208 pot_consensus_constants: &PotConsensusConstants,
209 beacon_chain_info: &BCI,
210 parent_block_root: &BlockRoot,
211 parent_slot: SlotNumber,
212 parent_slot_iterations: NonZeroU32,
213 parent_parameters_change: Option<PotParametersChange>,
214 block_number: BlockNumber,
215 slot: SlotNumber,
216) -> Result<PotInfo, DeriveConsensusParametersError>
217where
218 BCI: DeriveConsensusParametersChainInfo,
219{
220 let pot_entropy_injection_interval = pot_consensus_constants.entropy_injection_interval;
221 let pot_entropy_injection_lookback_depth =
222 pot_consensus_constants.entropy_injection_lookback_depth;
223 let pot_entropy_injection_delay = pot_consensus_constants.entropy_injection_delay;
224
225 let slot_iterations = if let Some(change) = &parent_parameters_change
227 && change.slot <= parent_slot.saturating_add(SlotNumber::ONE)
228 {
229 change.slot_iterations
230 } else {
231 parent_slot_iterations
232 };
233
234 let parameters_change = if let Some(change) = parent_parameters_change
235 && change.slot > slot
236 {
237 Some(change)
239 } else {
240 let lookback_in_blocks = BlockNumber::from(
241 u64::from(pot_entropy_injection_interval)
242 * u64::from(pot_entropy_injection_lookback_depth),
243 );
244 let last_entropy_injection_block_number = BlockNumber::from(
245 u64::from(block_number) / u64::from(pot_entropy_injection_interval)
246 * u64::from(pot_entropy_injection_interval),
247 );
248 let maybe_entropy_source_block_number =
249 last_entropy_injection_block_number.checked_sub(lookback_in_blocks);
250
251 if last_entropy_injection_block_number == block_number
253 && let Some(entropy_source_block_number) = maybe_entropy_source_block_number
254 && entropy_source_block_number > BlockNumber::ZERO
255 {
256 let entropy = {
257 let consensus_info = beacon_chain_info
258 .ancestor_header_consensus_info(entropy_source_block_number, parent_block_root)
259 .ok_or(DeriveConsensusParametersError::GetAncestorHeader)?;
260
261 consensus_info
262 .proof_of_time
263 .derive_pot_entropy(&consensus_info.solution_record_chunk)
264 };
265
266 let target_slot = slot
267 .checked_add(pot_entropy_injection_delay)
268 .unwrap_or(SlotNumber::MAX);
269
270 Some(PotParametersChange {
271 slot: target_slot,
272 slot_iterations,
276 entropy,
277 })
278 } else {
279 None
280 }
281 };
282
283 Ok(PotInfo {
284 slot_iterations,
285 parameters_change,
286 })
287}
288
289pub trait ShardMembershipEntropySourceChainInfo: Send + Sync {
293 fn ancestor_header_proof_of_time(
294 &self,
295 ancestor_block_number: BlockNumber,
296 descendant_block_root: &BlockRoot,
297 ) -> Option<PotOutput>;
298}
299
300impl<T> ShardMembershipEntropySourceChainInfo for T
301where
302 T: ChainInfo<OwnedBeaconChainBlock>,
303{
304 fn ancestor_header_proof_of_time(
305 &self,
306 ancestor_block_number: BlockNumber,
307 descendant_block_root: &BlockRoot,
308 ) -> Option<PotOutput> {
309 let header =
310 ChainInfo::ancestor_header(self, ancestor_block_number, descendant_block_root)?;
311 Some(header.header().consensus_info.proof_of_time)
312 }
313}
314
315#[derive(Debug, thiserror::Error)]
317pub enum ShardMembershipEntropySourceError {
318 #[error(
320 "Failed to find a beacon chain block {block_number} with the shard membership entropy \
321 source"
322 )]
323 FailedToFindBeaconChainBlock {
324 block_number: BlockNumber,
326 },
327}
328
329pub fn shard_membership_entropy_source<BCI>(
331 block_number: BlockNumber,
332 best_beacon_chain_header: &BeaconChainHeader<'_>,
333 shard_rotation_interval: BlockNumber,
334 shard_rotation_delay: BlockNumber,
335 beacon_chain_info: &BCI,
336) -> Result<ShardMembershipEntropy, ShardMembershipEntropySourceError>
337where
338 BCI: ShardMembershipEntropySourceChainInfo,
339{
340 let entropy_source_block_number = BlockNumber::from(
341 u64::from(block_number.saturating_sub(shard_rotation_delay))
342 / u64::from(shard_rotation_interval)
343 * u64::from(shard_rotation_interval),
344 );
345
346 let proof_of_time = beacon_chain_info
347 .ancestor_header_proof_of_time(
348 entropy_source_block_number,
349 &best_beacon_chain_header.root(),
350 )
351 .ok_or(
352 ShardMembershipEntropySourceError::FailedToFindBeaconChainBlock {
353 block_number: entropy_source_block_number,
354 },
355 )?;
356
357 Ok(proof_of_time.shard_membership_entropy())
358}
359
360#[derive(Debug, thiserror::Error)]
362pub enum DeriveSuperSegmentForBlockError {
363 #[error("Genesis beacon chain segment header not found")]
365 GenesisBeaconChainSegmentHeaderNotFound,
366 #[error("Parent super segment header not found for block {block_number}")]
368 ParentSuperSegmentHeaderNotFound {
369 block_number: BlockNumber,
371 },
372 #[error("Shard segment roots error: {error}")]
374 ShardSegmentRootsError {
375 #[from]
377 error: ShardSegmentRootsError,
378 },
379 #[error("Too many segments: {extra_segment_roots} extra segment roots")]
381 TooManySegments {
382 extra_segment_roots: usize,
384 },
385}
386
387pub trait DeriveSuperSegmentsForBlockChainInfo: Send + Sync {
391 fn get_genesis_segment_root(&self) -> Result<SegmentRoot, DeriveSuperSegmentForBlockError>;
393
394 fn segment_roots_for_block(
396 &self,
397 block_number: BlockNumber,
398 ) -> impl ExactSizeIterator<Item = ShardSegmentRoot> + Send + Sync + 'static;
399
400 fn previous_super_segment_header(
402 &self,
403 block_number: BlockNumber,
404 ) -> Option<SuperSegmentHeader>;
405
406 fn shard_segment_roots(
411 &self,
412 block_number: BlockNumber,
413 ) -> Result<Arc<[ShardSegmentRoot]>, ShardSegmentRootsError>;
414}
415
416impl<T> DeriveSuperSegmentsForBlockChainInfo for T
417where
418 T: BeaconChainInfo,
419{
420 #[inline]
421 fn get_genesis_segment_root(&self) -> Result<SegmentRoot, DeriveSuperSegmentForBlockError> {
422 Ok(self
423 .get_segment_header(LocalSegmentIndex::ZERO)
424 .ok_or(DeriveSuperSegmentForBlockError::GenesisBeaconChainSegmentHeaderNotFound)?
425 .root)
426 }
427
428 #[inline]
429 fn segment_roots_for_block(
430 &self,
431 block_number: BlockNumber,
432 ) -> impl ExactSizeIterator<Item = ShardSegmentRoot> + Send + Sync + 'static {
433 self.segment_headers_for_block(block_number)
434 .into_iter()
435 .map(|segment_header| ShardSegmentRoot {
436 shard_index: ShardIndex::BEACON_CHAIN,
437 segment_index: segment_header.index.as_inner(),
438 segment_root: segment_header.root,
439 })
440 }
441
442 #[inline(always)]
443 fn previous_super_segment_header(
444 &self,
445 block_number: BlockNumber,
446 ) -> Option<SuperSegmentHeader> {
447 BeaconChainInfo::previous_super_segment_header(self, block_number)
448 }
449
450 #[inline(always)]
451 fn shard_segment_roots(
452 &self,
453 block_number: BlockNumber,
454 ) -> Result<Arc<[ShardSegmentRoot]>, ShardSegmentRootsError> {
455 BeaconChainInfo::shard_segment_roots(self, block_number)
456 }
457}
458
459pub fn derive_super_segments_for_block<BCI>(
461 chain_info: &BCI,
462 parent_block_number: BlockNumber,
463 block_confirmation_depth: BlockNumber,
464 shard_confirmation_depth: BlockNumber,
465) -> Result<Option<SuperSegment>, DeriveSuperSegmentForBlockError>
466where
467 BCI: DeriveSuperSegmentsForBlockChainInfo,
468{
469 if parent_block_number == BlockNumber::ZERO {
470 let shard_segment_root = ShardSegmentRootWithPosition {
471 shard_index: ShardIndex::BEACON_CHAIN,
472 segment_position: SegmentPosition::from(0),
473 local_segment_index: LocalSegmentIndex::ZERO,
474 segment_root: chain_info.get_genesis_segment_root()?,
475 };
476
477 let mut super_segment = SuperSegment::new(
478 &SuperSegmentHeader {
479 index: SuperSegmentIndex::ZERO.into(),
481 root: SuperSegmentRoot::default(),
482 prev_super_segment_header_hash: Blake3Hash::default(),
483 max_segment_index: SegmentIndex::ZERO.into(),
485 target_beacon_chain_block_number: BlockNumber::ZERO.into(),
486 num_segments: 0,
487 },
488 BlockNumber::ONE,
489 StdArc::new([shard_segment_root]),
490 )
491 .expect("Genesis super segment is always valid; qed");
492
493 super_segment.header = SuperSegmentHeader {
494 index: SuperSegmentIndex::ZERO.into(),
495 max_segment_index: SegmentIndex::ZERO.into(),
496 prev_super_segment_header_hash: Blake3Hash::default(),
497 ..super_segment.header
498 };
499
500 return Ok(Some(super_segment));
501 }
502
503 let target_block_number = parent_block_number + BlockNumber::ONE;
504
505 let own_segment_roots = chain_info.segment_roots_for_block(target_block_number);
506
507 let shard_segment_roots = if let Some(base_shard_segment_roots_depth) = target_block_number
508 .checked_sub(block_confirmation_depth.saturating_add(shard_confirmation_depth))
509 {
510 let shard_segment_roots = chain_info.shard_segment_roots(base_shard_segment_roots_depth)?;
511 let mut shard_segment_roots_map =
512 HashMap::<ShardIndex, Vec<ShardSegmentRoot>>::with_capacity(shard_segment_roots.len());
513
514 for &shard_segment_root in shard_segment_roots.iter() {
516 shard_segment_roots_map
519 .entry(shard_segment_root.shard_index)
520 .or_default()
521 .push(shard_segment_root);
522 }
523
524 for block_number_to_check in (base_shard_segment_roots_depth + BlockNumber::ONE..)
527 .take(u64::from(shard_confirmation_depth) as usize)
528 {
529 for shard_segment_root in chain_info
530 .shard_segment_roots(block_number_to_check)?
531 .iter()
532 {
533 if let Some(shard_segments) =
534 shard_segment_roots_map.get_mut(&shard_segment_root.shard_index)
535 && let Some(first_shard_segment) = shard_segments.first()
536 && let Some(offset) = shard_segment_root
537 .segment_index
538 .checked_sub(first_shard_segment.segment_index)
539 {
540 shard_segments.truncate(u64::from(offset) as usize);
542 }
543 }
544 }
545
546 Some(
548 shard_segment_roots
549 .iter()
550 .filter(|shard_segment_root| {
551 if let Some(shard_segments) =
552 shard_segment_roots_map.get(&shard_segment_root.shard_index)
553 && let Some(first_shard_segment) = shard_segments.first()
554 && let Some(offset) = shard_segment_root
555 .segment_index
556 .checked_sub(first_shard_segment.segment_index)
557 {
558 (u64::from(offset) as usize) < shard_segments.len()
559 } else {
560 false
561 }
562 })
563 .copied()
564 .collect::<Vec<_>>(),
565 )
566 } else {
567 None
568 };
569 let shard_segment_roots = shard_segment_roots.into_flat_iter();
570
571 let segment_roots = own_segment_roots
572 .chain(shard_segment_roots)
573 .zip(0..)
574 .map(
575 |(shard_segment_root, segment_position)| ShardSegmentRootWithPosition {
576 shard_index: shard_segment_root.shard_index,
577 segment_position: SegmentPosition::from(segment_position),
578 local_segment_index: shard_segment_root.segment_index,
579 segment_root: shard_segment_root.segment_root,
580 },
581 )
582 .collect::<StdArc<_>>();
583
584 if segment_roots.is_empty() {
585 return Ok(None);
586 }
587
588 let num_segments = segment_roots.len();
589
590 let previous_super_segment_header = chain_info
591 .previous_super_segment_header(target_block_number)
592 .ok_or(
593 DeriveSuperSegmentForBlockError::ParentSuperSegmentHeaderNotFound {
594 block_number: target_block_number,
595 },
596 )?;
597
598 SuperSegment::new(
599 &previous_super_segment_header,
600 target_block_number,
601 segment_roots,
602 )
603 .ok_or({
604 DeriveSuperSegmentForBlockError::TooManySegments {
609 extra_segment_roots: num_segments - SuperSegmentRoot::MAX_SEGMENTS as usize,
610 }
611 })
612 .map(Some)
613}