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