1use crate::block::BlockValidityError::{
2 InvalidChallengeRoot, InvalidChunkHeaderRoot, InvalidChunkMask, InvalidReceiptRoot,
3 InvalidStateRoot, InvalidTransactionRoot,
4};
5pub use crate::block_header::*;
6use crate::challenge::{Challenges, ChallengesResult};
7use crate::checked_feature;
8use crate::hash::{hash, CryptoHash};
9use crate::merkle::{merklize, verify_path, MerklePath};
10use crate::num_rational::Rational32;
11use crate::sharding::{
12 ChunkHashHeight, EncodedShardChunk, ReedSolomonWrapper, ShardChunk, ShardChunkHeader,
13 ShardChunkHeaderV1,
14};
15use crate::static_clock::StaticClock;
16use crate::types::{Balance, BlockHeight, EpochId, Gas, NumBlocks, StateRoot};
17use crate::utils::to_timestamp;
18use crate::validator_signer::{EmptyValidatorSigner, ValidatorSigner};
19use crate::version::{ProtocolVersion, SHARD_CHUNK_HEADER_UPGRADE_VERSION};
20use borsh::{BorshDeserialize, BorshSerialize};
21use chrono::{DateTime, Utc};
22use primitive_types::U256;
23use std::ops::Index;
24use std::sync::Arc;
25use unc_crypto::Signature;
26use unc_primitives_core::types::ShardId;
27
28#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq, Default)]
29pub struct GenesisId {
30 pub chain_id: String,
32 pub hash: CryptoHash,
34}
35
36#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)]
37pub enum BlockValidityError {
38 InvalidStateRoot,
39 InvalidReceiptRoot,
40 InvalidChunkHeaderRoot,
41 InvalidTransactionRoot,
42 InvalidChunkMask,
43 InvalidChallengeRoot,
44}
45
46#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, Eq, PartialEq)]
47pub struct BlockV1 {
48 pub header: BlockHeader,
49 pub chunks: Vec<ShardChunkHeaderV1>,
50 pub challenges: Challenges,
51
52 pub vrf_value: unc_crypto::vrf::Value,
54 pub vrf_proof: unc_crypto::vrf::Proof,
55}
56
57#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, Eq, PartialEq)]
58pub struct BlockV2 {
59 pub header: BlockHeader,
60 pub chunks: Vec<ShardChunkHeader>,
61 pub challenges: Challenges,
62
63 pub vrf_value: unc_crypto::vrf::Value,
65 pub vrf_proof: unc_crypto::vrf::Proof,
66}
67
68#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, Eq, PartialEq)]
70pub struct BlockV3 {
71 pub header: BlockHeader,
72 pub body: BlockBody,
73}
74
75#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, Eq, PartialEq)]
76pub struct BlockBody {
77 pub chunks: Vec<ShardChunkHeader>,
78 pub challenges: Challenges,
79
80 pub vrf_value: unc_crypto::vrf::Value,
82 pub vrf_proof: unc_crypto::vrf::Proof,
83}
84
85#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, Eq, PartialEq)]
88pub enum Block {
89 BlockV1(Arc<BlockV1>),
90 BlockV2(Arc<BlockV2>),
91 BlockV3(Arc<BlockV3>),
92}
93
94pub fn genesis_chunks(
95 state_roots: Vec<StateRoot>,
96 shard_ids: &[ShardId],
97 initial_gas_limit: Gas,
98 genesis_height: BlockHeight,
99 genesis_protocol_version: ProtocolVersion,
100) -> Vec<ShardChunk> {
101 let mut rs = ReedSolomonWrapper::new(1, 2);
102 let state_roots = if state_roots.len() == shard_ids.len() {
103 state_roots
104 } else {
105 assert_eq!(state_roots.len(), 1);
106 std::iter::repeat(state_roots[0]).take(shard_ids.len()).collect()
107 };
108
109 shard_ids
110 .into_iter()
111 .zip(state_roots)
112 .map(|(&shard_id, state_root)| {
113 let (encoded_chunk, _) = EncodedShardChunk::new(
114 CryptoHash::default(),
115 state_root,
116 CryptoHash::default(),
117 genesis_height,
118 shard_id,
119 &mut rs,
120 0,
121 initial_gas_limit,
122 0,
123 CryptoHash::default(),
124 vec![],
125 vec![],
126 vec![],
127 &[],
128 CryptoHash::default(),
129 &EmptyValidatorSigner::default(),
130 genesis_protocol_version,
131 )
132 .expect("Failed to decode genesis chunk");
133 let mut chunk = encoded_chunk.decode_chunk(1).expect("Failed to decode genesis chunk");
134 chunk.set_height_included(genesis_height);
135 chunk
136 })
137 .collect()
138}
139
140impl Block {
141 fn block_from_protocol_version(
142 this_epoch_protocol_version: ProtocolVersion,
143 next_epoch_protocol_version: ProtocolVersion,
144 header: BlockHeader,
145 body: BlockBody,
146 ) -> Block {
147 if next_epoch_protocol_version < SHARD_CHUNK_HEADER_UPGRADE_VERSION {
148 let legacy_chunks = body
149 .chunks
150 .into_iter()
151 .map(|chunk| match chunk {
152 ShardChunkHeader::V1(header) => header,
153 ShardChunkHeader::V2(_) => panic!(
154 "Attempted to include VersionedShardChunkHeaderV2 in old protocol version"
155 ),
156 ShardChunkHeader::V3(_) => panic!(
157 "Attempted to include VersionedShardChunkHeaderV3 in old protocol version"
158 ),
159 })
160 .collect();
161
162 Block::BlockV1(Arc::new(BlockV1 {
163 header,
164 chunks: legacy_chunks,
165 challenges: body.challenges,
166 vrf_value: body.vrf_value,
167 vrf_proof: body.vrf_proof,
168 }))
169 } else if !checked_feature!("stable", BlockHeaderV4, this_epoch_protocol_version) {
170 Block::BlockV2(Arc::new(BlockV2 {
171 header,
172 chunks: body.chunks,
173 challenges: body.challenges,
174 vrf_value: body.vrf_value,
175 vrf_proof: body.vrf_proof,
176 }))
177 } else {
178 Block::BlockV3(Arc::new(BlockV3 { header, body }))
179 }
180 }
181
182 pub fn genesis(
184 genesis_protocol_version: ProtocolVersion,
185 chunks: Vec<ShardChunkHeader>,
186 timestamp: DateTime<Utc>,
187 height: BlockHeight,
188 initial_gas_price: Balance,
189 initial_total_supply: Balance,
190 next_bp_hash: CryptoHash,
191 ) -> Self {
192 let challenges = vec![];
193 for chunk in &chunks {
194 assert_eq!(chunk.height_included(), height);
195 }
196 let vrf_value = unc_crypto::vrf::Value([0; 32]);
197 let vrf_proof = unc_crypto::vrf::Proof([0; 64]);
198 let body = BlockBody { chunks, challenges, vrf_value, vrf_proof };
199 let header = BlockHeader::genesis(
200 genesis_protocol_version,
201 height,
202 Block::compute_state_root(&body.chunks),
203 Block::compute_block_body_hash_impl(&body),
204 Block::compute_chunk_prev_outgoing_receipts_root(&body.chunks),
205 Block::compute_chunk_headers_root(&body.chunks).0,
206 Block::compute_chunk_tx_root(&body.chunks),
207 body.chunks.len() as u64,
208 Block::compute_challenges_root(&body.challenges),
209 timestamp,
210 initial_gas_price,
211 initial_total_supply,
212 next_bp_hash,
213 );
214
215 Self::block_from_protocol_version(
216 genesis_protocol_version,
217 genesis_protocol_version,
218 header,
219 body,
220 )
221 }
222
223 pub fn produce(
225 this_epoch_protocol_version: ProtocolVersion,
226 next_epoch_protocol_version: ProtocolVersion,
227 prev: &BlockHeader,
228 height: BlockHeight,
229 block_ordinal: NumBlocks,
230 chunks: Vec<ShardChunkHeader>,
231 epoch_id: EpochId,
232 next_epoch_id: EpochId,
233 epoch_sync_data_hash: Option<CryptoHash>,
234 approvals: Vec<Option<Box<Signature>>>,
235 gas_price_adjustment_rate: Rational32,
236 min_gas_price: Balance,
237 max_gas_price: Balance,
238 minted_amount: Option<Balance>,
239 challenges_result: ChallengesResult,
240 challenges: Challenges,
241 signer: &dyn ValidatorSigner,
242 next_bp_hash: CryptoHash,
243 block_merkle_root: CryptoHash,
244 timestamp_override: Option<DateTime<chrono::Utc>>,
245 ) -> Self {
246 let mut prev_validator_power_proposals = vec![];
248 let mut prev_validator_pledge_proposals = vec![];
249 let mut gas_used = 0;
250 let mut chunk_mask = vec![];
252 let mut balance_burnt = 0;
253 let mut gas_limit = 0;
254 for chunk in chunks.iter() {
255 if chunk.height_included() == height {
256 prev_validator_power_proposals.extend(chunk.prev_validator_power_proposals());
257 prev_validator_pledge_proposals.extend(chunk.prev_validator_pledge_proposals());
258 gas_used += chunk.prev_gas_used();
259 gas_limit += chunk.gas_limit();
260 balance_burnt += chunk.prev_balance_burnt();
261 chunk_mask.push(true);
262 } else {
263 chunk_mask.push(false);
264 }
265 }
266 let next_gas_price = Self::compute_next_gas_price(
267 prev.next_gas_price(),
268 gas_used,
269 gas_limit,
270 gas_price_adjustment_rate,
271 min_gas_price,
272 max_gas_price,
273 );
274
275 let new_total_supply = prev.total_supply() + minted_amount.unwrap_or(0) - balance_burnt;
276 let now = to_timestamp(timestamp_override.unwrap_or_else(StaticClock::utc));
277 let time = if now <= prev.raw_timestamp() { prev.raw_timestamp() + 1 } else { now };
278
279 let (vrf_value, vrf_proof) = signer.compute_vrf_with_proof(prev.random_value().as_ref());
280 let random_value = hash(vrf_value.0.as_ref());
281
282 let last_ds_final_block =
283 if height == prev.height() + 1 { prev.hash() } else { prev.last_ds_final_block() };
284
285 let last_final_block =
286 if height == prev.height() + 1 && prev.last_ds_final_block() == prev.prev_hash() {
287 prev.prev_hash()
288 } else {
289 prev.last_final_block()
290 };
291
292 match prev {
293 BlockHeader::BlockHeaderV1(_) => debug_assert_eq!(prev.block_ordinal(), 0),
294 BlockHeader::BlockHeaderV2(_) => debug_assert_eq!(prev.block_ordinal(), 0),
295 BlockHeader::BlockHeaderV3(_) => {
296 debug_assert_eq!(prev.block_ordinal() + 1, block_ordinal)
297 }
298 BlockHeader::BlockHeaderV4(_) => {
299 debug_assert_eq!(prev.block_ordinal() + 1, block_ordinal)
300 }
301 };
302
303 let body = BlockBody { chunks, challenges, vrf_value, vrf_proof };
304 let header = BlockHeader::new(
305 this_epoch_protocol_version,
306 next_epoch_protocol_version,
307 height,
308 *prev.hash(),
309 Block::compute_block_body_hash_impl(&body),
310 Block::compute_state_root(&body.chunks),
311 Block::compute_chunk_prev_outgoing_receipts_root(&body.chunks),
312 Block::compute_chunk_headers_root(&body.chunks).0,
313 Block::compute_chunk_tx_root(&body.chunks),
314 Block::compute_outcome_root(&body.chunks),
315 time,
316 Block::compute_challenges_root(&body.challenges),
317 random_value,
318 prev_validator_power_proposals,
319 prev_validator_pledge_proposals,
320 chunk_mask,
321 block_ordinal,
322 epoch_id,
323 next_epoch_id,
324 next_gas_price,
325 new_total_supply,
326 challenges_result,
327 signer,
328 *last_final_block,
329 *last_ds_final_block,
330 epoch_sync_data_hash,
331 approvals,
332 next_bp_hash,
333 block_merkle_root,
334 prev.height(),
335 );
336
337 Self::block_from_protocol_version(
338 this_epoch_protocol_version,
339 next_epoch_protocol_version,
340 header,
341 body,
342 )
343 }
344
345 pub fn verify_total_supply(
346 &self,
347 prev_total_supply: Balance,
348 minted_amount: Option<Balance>,
349 ) -> bool {
350 let mut balance_burnt = 0;
351
352 for chunk in self.chunks().iter() {
353 if chunk.height_included() == self.header().height() {
354 balance_burnt += chunk.prev_balance_burnt();
355 }
356 }
357
358 let new_total_supply = prev_total_supply + minted_amount.unwrap_or(0) - balance_burnt;
359 self.header().total_supply() == new_total_supply
360 }
361
362 pub fn verify_gas_price(
363 &self,
364 gas_price: Balance,
365 min_gas_price: Balance,
366 max_gas_price: Balance,
367 gas_price_adjustment_rate: Rational32,
368 ) -> bool {
369 let gas_used = Self::compute_gas_used(self.chunks().iter(), self.header().height());
370 let gas_limit = Self::compute_gas_limit(self.chunks().iter(), self.header().height());
371 let expected_price = Self::compute_next_gas_price(
372 gas_price,
373 gas_used,
374 gas_limit,
375 gas_price_adjustment_rate,
376 min_gas_price,
377 max_gas_price,
378 );
379 self.header().next_gas_price() == expected_price
380 }
381
382 pub fn compute_next_gas_price(
386 gas_price: Balance,
387 gas_used: Gas,
388 gas_limit: Gas,
389 gas_price_adjustment_rate: Rational32,
390 min_gas_price: Balance,
391 max_gas_price: Balance,
392 ) -> Balance {
393 if gas_limit == 0 {
395 return gas_price;
396 }
397
398 let gas_used = u128::from(gas_used);
399 let gas_limit = u128::from(gas_limit);
400 let adjustment_rate_numer = *gas_price_adjustment_rate.numer() as u128;
401 let adjustment_rate_denom = *gas_price_adjustment_rate.denom() as u128;
402
403 let numerator = 2 * adjustment_rate_denom * gas_limit
406 + 2 * adjustment_rate_numer * gas_used
407 - adjustment_rate_numer * gas_limit;
408 let denominator = 2 * adjustment_rate_denom * gas_limit;
409 let next_gas_price =
410 U256::from(gas_price) * U256::from(numerator) / U256::from(denominator);
411
412 next_gas_price.clamp(U256::from(min_gas_price), U256::from(max_gas_price)).as_u128()
413 }
414
415 pub fn compute_state_root<'a, T: IntoIterator<Item = &'a ShardChunkHeader>>(
416 chunks: T,
417 ) -> CryptoHash {
418 merklize(
419 &chunks.into_iter().map(|chunk| chunk.prev_state_root()).collect::<Vec<CryptoHash>>(),
420 )
421 .0
422 }
423
424 pub fn compute_block_body_hash_impl(body: &BlockBody) -> CryptoHash {
425 CryptoHash::hash_borsh(body)
426 }
427
428 pub fn compute_chunk_prev_outgoing_receipts_root<
429 'a,
430 T: IntoIterator<Item = &'a ShardChunkHeader>,
431 >(
432 chunks: T,
433 ) -> CryptoHash {
434 merklize(
435 &chunks
436 .into_iter()
437 .map(|chunk| chunk.prev_outgoing_receipts_root())
438 .collect::<Vec<CryptoHash>>(),
439 )
440 .0
441 }
442
443 pub fn compute_chunk_headers_root<'a, T: IntoIterator<Item = &'a ShardChunkHeader>>(
444 chunks: T,
445 ) -> (CryptoHash, Vec<MerklePath>) {
446 merklize(
447 &chunks
448 .into_iter()
449 .map(|chunk| ChunkHashHeight(chunk.chunk_hash(), chunk.height_included()))
450 .collect::<Vec<ChunkHashHeight>>(),
451 )
452 }
453
454 pub fn compute_chunk_tx_root<'a, T: IntoIterator<Item = &'a ShardChunkHeader>>(
455 chunks: T,
456 ) -> CryptoHash {
457 merklize(&chunks.into_iter().map(|chunk| chunk.tx_root()).collect::<Vec<CryptoHash>>()).0
458 }
459
460 pub fn compute_outcome_root<'a, T: IntoIterator<Item = &'a ShardChunkHeader>>(
461 chunks: T,
462 ) -> CryptoHash {
463 merklize(
464 &chunks.into_iter().map(|chunk| chunk.prev_outcome_root()).collect::<Vec<CryptoHash>>(),
465 )
466 .0
467 }
468
469 pub fn compute_challenges_root(challenges: &Challenges) -> CryptoHash {
470 merklize(&challenges.iter().map(|challenge| challenge.hash).collect::<Vec<CryptoHash>>()).0
471 }
472
473 pub fn compute_gas_used<'a, T: IntoIterator<Item = &'a ShardChunkHeader>>(
474 chunks: T,
475 height: BlockHeight,
476 ) -> Gas {
477 chunks.into_iter().fold(0, |acc, chunk| {
478 if chunk.height_included() == height {
479 acc + chunk.prev_gas_used()
480 } else {
481 acc
482 }
483 })
484 }
485
486 pub fn compute_gas_limit<'a, T: IntoIterator<Item = &'a ShardChunkHeader>>(
487 chunks: T,
488 height: BlockHeight,
489 ) -> Gas {
490 chunks.into_iter().fold(0, |acc, chunk| {
491 if chunk.height_included() == height {
492 acc + chunk.gas_limit()
493 } else {
494 acc
495 }
496 })
497 }
498
499 pub fn validate_chunk_header_proof(
500 chunk: &ShardChunkHeader,
501 chunk_root: &CryptoHash,
502 merkle_path: &MerklePath,
503 ) -> bool {
504 verify_path(
505 *chunk_root,
506 merkle_path,
507 &ChunkHashHeight(chunk.chunk_hash(), chunk.height_included()),
508 )
509 }
510
511 #[inline]
512 pub fn header(&self) -> &BlockHeader {
513 match self {
514 Block::BlockV1(block) => &block.header,
515 Block::BlockV2(block) => &block.header,
516 Block::BlockV3(block) => &block.header,
517 }
518 }
519
520 pub fn chunks(&self) -> ChunksCollection {
521 match self {
522 Block::BlockV1(block) => ChunksCollection::V1(
523 block.chunks.iter().map(|h| ShardChunkHeader::V1(h.clone())).collect(),
524 ),
525 Block::BlockV2(block) => ChunksCollection::V2(&block.chunks),
526 Block::BlockV3(block) => ChunksCollection::V2(&block.body.chunks),
527 }
528 }
529
530 #[inline]
531 pub fn challenges(&self) -> &Challenges {
532 match self {
533 Block::BlockV1(block) => &block.challenges,
534 Block::BlockV2(block) => &block.challenges,
535 Block::BlockV3(block) => &block.body.challenges,
536 }
537 }
538
539 #[inline]
540 pub fn vrf_value(&self) -> &unc_crypto::vrf::Value {
541 match self {
542 Block::BlockV1(block) => &block.vrf_value,
543 Block::BlockV2(block) => &block.vrf_value,
544 Block::BlockV3(block) => &block.body.vrf_value,
545 }
546 }
547
548 #[inline]
549 pub fn vrf_proof(&self) -> &unc_crypto::vrf::Proof {
550 match self {
551 Block::BlockV1(block) => &block.vrf_proof,
552 Block::BlockV2(block) => &block.vrf_proof,
553 Block::BlockV3(block) => &block.body.vrf_proof,
554 }
555 }
556
557 pub fn hash(&self) -> &CryptoHash {
558 self.header().hash()
559 }
560
561 pub fn compute_block_body_hash(&self) -> Option<CryptoHash> {
562 match self {
563 Block::BlockV1(_) => None,
564 Block::BlockV2(_) => None,
565 Block::BlockV3(block) => Some(Self::compute_block_body_hash_impl(&block.body)),
566 }
567 }
568
569 pub fn check_validity(&self) -> Result<(), BlockValidityError> {
571 let state_root = Block::compute_state_root(self.chunks().iter());
573 if self.header().prev_state_root() != &state_root {
574 return Err(InvalidStateRoot);
575 }
576
577 let chunk_receipts_root =
579 Block::compute_chunk_prev_outgoing_receipts_root(self.chunks().iter());
580 if self.header().prev_chunk_outgoing_receipts_root() != &chunk_receipts_root {
581 return Err(InvalidReceiptRoot);
582 }
583
584 let chunk_headers_root = Block::compute_chunk_headers_root(self.chunks().iter()).0;
586 if self.header().chunk_headers_root() != &chunk_headers_root {
587 return Err(InvalidChunkHeaderRoot);
588 }
589
590 let chunk_tx_root = Block::compute_chunk_tx_root(self.chunks().iter());
592 if self.header().chunk_tx_root() != &chunk_tx_root {
593 return Err(InvalidTransactionRoot);
594 }
595
596 let outcome_root = Block::compute_outcome_root(self.chunks().iter());
597 if self.header().outcome_root() != &outcome_root {
598 return Err(InvalidTransactionRoot);
599 }
600
601 let chunk_mask: Vec<bool> = self
603 .chunks()
604 .iter()
605 .map(|chunk| chunk.height_included() == self.header().height())
606 .collect();
607 if self.header().chunk_mask() != &chunk_mask[..] {
608 return Err(InvalidChunkMask);
609 }
610
611 let challenges_root = Block::compute_challenges_root(self.challenges());
613 if self.header().challenges_root() != &challenges_root {
614 return Err(InvalidChallengeRoot);
615 }
616
617 Ok(())
618 }
619}
620
621pub enum ChunksCollection<'a> {
622 V1(Vec<ShardChunkHeader>),
623 V2(&'a [ShardChunkHeader]),
624}
625
626pub struct VersionedChunksIter<'a> {
627 chunks: &'a [ShardChunkHeader],
628 curr_index: usize,
629 len: usize,
630}
631
632impl<'a> VersionedChunksIter<'a> {
633 fn new(chunks: &'a [ShardChunkHeader]) -> Self {
634 Self { chunks, curr_index: 0, len: chunks.len() }
635 }
636}
637
638impl<'a> Iterator for VersionedChunksIter<'a> {
639 type Item = &'a ShardChunkHeader;
640
641 fn next(&mut self) -> Option<Self::Item> {
642 if self.curr_index < self.len {
643 let item = &self.chunks[self.curr_index];
644 self.curr_index += 1;
645 Some(item)
646 } else {
647 None
648 }
649 }
650}
651
652impl<'a> ExactSizeIterator for VersionedChunksIter<'a> {
653 fn len(&self) -> usize {
654 self.len - self.curr_index
655 }
656}
657
658impl<'a> Index<usize> for ChunksCollection<'a> {
659 type Output = ShardChunkHeader;
660
661 fn index(&self, index: usize) -> &Self::Output {
662 match self {
663 ChunksCollection::V1(chunks) => &chunks[index],
664 ChunksCollection::V2(chunks) => &chunks[index],
665 }
666 }
667}
668
669impl<'a> ChunksCollection<'a> {
670 pub fn len(&self) -> usize {
671 match self {
672 ChunksCollection::V1(chunks) => chunks.len(),
673 ChunksCollection::V2(chunks) => chunks.len(),
674 }
675 }
676
677 pub fn iter(&'a self) -> VersionedChunksIter<'a> {
678 match self {
679 ChunksCollection::V1(chunks) => VersionedChunksIter::new(chunks),
680 ChunksCollection::V2(chunks) => VersionedChunksIter::new(chunks),
681 }
682 }
683
684 pub fn get(&self, index: usize) -> Option<&ShardChunkHeader> {
685 match self {
686 ChunksCollection::V1(chunks) => chunks.get(index),
687 ChunksCollection::V2(chunks) => chunks.get(index),
688 }
689 }
690}
691
692#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq, serde::Serialize)]
696pub struct Tip {
697 pub height: BlockHeight,
699 pub last_block_hash: CryptoHash,
701 pub prev_block_hash: CryptoHash,
703 pub epoch_id: EpochId,
705 pub next_epoch_id: EpochId,
707}
708
709impl Tip {
710 pub fn from_header(header: &BlockHeader) -> Tip {
712 Tip {
713 height: header.height(),
714 last_block_hash: *header.hash(),
715 prev_block_hash: *header.prev_hash(),
716 epoch_id: header.epoch_id().clone(),
717 next_epoch_id: header.next_epoch_id().clone(),
718 }
719 }
720}