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