1pub mod attestations;
114pub mod balances;
115pub mod eth1_data_votes;
116pub mod historical_log;
117pub mod inactivity_scores;
118pub mod participation;
119pub mod pending_queue;
120pub mod randao_mixes;
121pub mod recent_roots;
122pub mod slashings;
123pub mod sync_committee;
124pub mod types;
125pub mod validators;
126
127pub mod error;
128use error::Error;
129
130use rkyv::{Archive, Deserialize, Serialize};
131
132use crate::{
133 types::{
134 AttestationsDiff, BalancesDiff, Eth1DataVotesDiff, HistoricalLogDiff, InactivityDiff,
135 ParticipationDiff, QueueDiff, RandaoDiff, RootsDiff, SlashingsDiff, SyncCommitteeDiff,
136 ValidatorsDiff, HISTORICAL_ROOTS_SSZ_SIZE, HISTORICAL_SUMMARIES_SSZ_SIZE,
137 },
138 validators::{ValidatorMutTarget, ValidatorSnapshot},
139};
140
141#[derive(Archive, Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
160#[repr(u8)]
161pub enum ForkName {
162 Phase0 = 0,
164 Altair = 1,
166 Bellatrix = 2,
168 Capella = 3,
170 Deneb = 4,
172 Electra = 5,
174 Fulu = 6,
176 Gloas = 7,
178 Heze = 8,
180}
181
182pub trait DiffSource {
220 fn fork(&self) -> ForkName;
221 fn slot(&self) -> (u64, u64);
222 fn capella_fork_slot(&self) -> u64; fn scalar_header(&self) -> Vec<u8>;
249
250 fn balances(
252 &self,
253 ) -> (
254 impl ExactSizeIterator<Item = u64>,
255 impl ExactSizeIterator<Item = u64>,
256 );
257 fn validators(
258 &self,
259 ) -> (
260 impl ExactSizeIterator<Item = impl ValidatorSnapshot>,
261 impl ExactSizeIterator<Item = impl ValidatorSnapshot>,
262 );
263 fn block_roots(&self) -> &[[u8; 32]];
264 fn state_roots(&self) -> &[[u8; 32]];
265 fn randao_mixes(&self) -> &[[u8; 32]];
266 fn slashings(&self) -> (&[u64], &[u64]);
267 fn eth1_data_votes(&self) -> (&[u8], &[u8]);
268 fn historical_roots(&self) -> Option<&[u8]>;
269
270 fn previous_epoch_attestations(&self) -> Option<(&[u8], &[u8])>;
272 fn current_epoch_attestations(&self) -> Option<(&[u8], &[u8])>;
273
274 fn previous_participation(
276 &self,
277 ) -> Option<(
278 impl ExactSizeIterator<Item = u8>,
279 impl ExactSizeIterator<Item = u8>,
280 )>;
281 fn current_participation(
282 &self,
283 ) -> Option<(
284 impl ExactSizeIterator<Item = u8>,
285 impl ExactSizeIterator<Item = u8>,
286 )>;
287 fn inactivity_scores(&self) -> Option<(&[u64], &[u64])>;
288 fn current_sync_committee(&self) -> Option<(&[u8], &[u8])>;
289 fn next_sync_committee(&self) -> Option<(&[u8], &[u8])>;
290
291 fn historical_summaries(&self) -> Option<&[u8]>;
293
294 fn pending_deposits(&self) -> Option<(&[u8], &[u8])>;
296 fn pending_partial_withdrawals(&self) -> Option<(&[u8], &[u8])>;
297 fn pending_consolidations(&self) -> Option<(&[u8], &[u8])>;
298}
299
300pub trait DiffTarget {
327 fn get_fork(&self) -> ForkName;
332
333 fn scalar_header_mut(&mut self) -> &mut Vec<u8>;
338
339 fn balances_mut(&mut self) -> &mut impl ListMutTarget<u64>;
341 fn validators_mut(&mut self) -> &mut impl ValidatorMutTarget;
342 fn block_roots_mut(&mut self) -> &mut [[u8; 32]];
343 fn state_roots_mut(&mut self) -> &mut [[u8; 32]];
344 fn randao_mixes_mut(&mut self) -> &mut [[u8; 32]];
345 fn slashings_mut(&mut self) -> &mut [u64];
346 fn eth1_data_votes_mut(&mut self) -> &mut Vec<u8>;
347 fn historical_roots_mut(&mut self) -> Option<&mut Vec<u8>>;
348
349 fn previous_epoch_attestations_mut(&mut self) -> Option<&mut Vec<u8>>;
355
356 fn current_epoch_attestations_mut(&mut self) -> Option<&mut Vec<u8>>;
360
361 fn previous_participation_mut(&mut self) -> Option<&mut impl ListMutTarget<u8>>;
363 fn current_participation_mut(&mut self) -> Option<&mut impl ListMutTarget<u8>>;
364 fn inactivity_scores_mut(&mut self) -> Option<&mut Vec<u64>>;
365 fn current_sync_committee_mut(&mut self) -> Option<&mut Vec<u8>>;
366 fn next_sync_committee_mut(&mut self) -> Option<&mut Vec<u8>>;
367
368 fn historical_summaries_mut(&mut self) -> Option<&mut Vec<u8>>;
370
371 fn pending_deposits_mut(&mut self) -> Option<&mut Vec<u8>>;
373 fn pending_partial_withdrawals_mut(&mut self) -> Option<&mut Vec<u8>>;
374 fn pending_consolidations_mut(&mut self) -> Option<&mut Vec<u8>>;
375}
376
377#[derive(Archive, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
438pub struct BeaconStateDelta {
439 pub fork: ForkName,
440 pub base_slot: u64,
441 pub scalar_header: Vec<u8>,
442
443 pub balances: BalancesDiff,
445 pub validators: ValidatorsDiff,
446 pub block_roots: RootsDiff,
447 pub state_roots: RootsDiff,
448 pub randao_mixes: RandaoDiff,
449 pub slashings: SlashingsDiff,
450 pub eth1_data_votes: Eth1DataVotesDiff,
451 pub historical_roots: Option<HistoricalLogDiff>,
452
453 pub previous_epoch_attestations: Option<AttestationsDiff>,
456 pub current_epoch_attestations: Option<AttestationsDiff>,
457
458 pub previous_participation: Option<ParticipationDiff>,
461 pub current_participation: Option<ParticipationDiff>,
462 pub inactivity_scores: Option<InactivityDiff>,
463 pub current_sync_committee: Option<SyncCommitteeDiff>,
464 pub next_sync_committee: Option<SyncCommitteeDiff>,
465
466 pub historical_summaries: Option<HistoricalLogDiff>,
469
470 pub pending_deposits: Option<QueueDiff>,
473 pub pending_partial_withdrawals: Option<QueueDiff>,
474 pub pending_consolidations: Option<QueueDiff>,
475}
476
477pub fn create<R: DiffSource>(state: &R) -> BeaconStateDelta {
513 let (base_slot, target_slot) = state.slot();
514
515 let delta = BeaconStateDelta {
516 fork: state.fork(),
517 base_slot,
518 scalar_header: state.scalar_header(),
519
520 balances: balances::diff_balances_iter(state.balances().0, state.balances().1),
522 validators: validators::diff_validators_iter(state.validators().0, state.validators().1),
523 block_roots: recent_roots::diff_roots(base_slot, target_slot, state.block_roots()),
524 state_roots: recent_roots::diff_roots(base_slot, target_slot, state.state_roots()),
525 randao_mixes: randao_mixes::diff_randao(base_slot, target_slot, state.randao_mixes()),
526 slashings: slashings::diff_slashings(
527 base_slot,
528 target_slot,
529 state.slashings().0,
530 state.slashings().1,
531 ),
532 eth1_data_votes: eth1_data_votes::diff_eth1_votes(
533 state.eth1_data_votes().0,
534 state.eth1_data_votes().1,
535 ),
536 historical_roots: state.historical_roots().map(|t| {
537 historical_log::diff_historical_log(
538 base_slot,
539 target_slot,
540 t,
541 HISTORICAL_ROOTS_SSZ_SIZE,
542 None,
543 )
544 }),
545
546 previous_epoch_attestations: state
548 .previous_epoch_attestations()
549 .map(|(b, t)| attestations::diff_attestations(b, t)),
550 current_epoch_attestations: state
551 .current_epoch_attestations()
552 .map(|(b, t)| attestations::diff_attestations(b, t)),
553
554 previous_participation: state
556 .previous_participation()
557 .map(|(b, t)| participation::diff_participation_iter(b, t)),
558 current_participation: state
559 .current_participation()
560 .map(|(b, t)| participation::diff_participation_iter(b, t)),
561 inactivity_scores: state
562 .inactivity_scores()
563 .map(|(b, t)| inactivity_scores::diff_inactivity(b, t)),
564 current_sync_committee: state
565 .current_sync_committee()
566 .map(|(b, t)| sync_committee::diff_sync_committee(b, t)),
567 next_sync_committee: state
568 .next_sync_committee()
569 .map(|(b, t)| sync_committee::diff_sync_committee(b, t)),
570
571 historical_summaries: state.historical_summaries().map(|t| {
573 historical_log::diff_historical_log(
574 base_slot,
575 target_slot,
576 t,
577 HISTORICAL_SUMMARIES_SSZ_SIZE,
578 Some(state.capella_fork_slot()),
579 )
580 }),
581
582 pending_deposits: state
584 .pending_deposits()
585 .map(|(b, t)| pending_queue::diff_queue(b, t, PENDING_DEPOSIT_SSZ_SIZE)),
586 pending_partial_withdrawals: state
587 .pending_partial_withdrawals()
588 .map(|(b, t)| pending_queue::diff_queue(b, t, PARTIAL_WITHDRAWAL_SSZ_SIZE)),
589 pending_consolidations: state
590 .pending_consolidations()
591 .map(|(b, t)| pending_queue::diff_queue(b, t, PENDING_CONSOLIDATION_SSZ_SIZE)),
592 };
593
594 debug_assert_eq!(
595 delta.previous_participation.is_some(),
596 delta.fork >= ForkName::Altair,
597 "DiffSource bug: previous_participation must exist iff fork >= Altair (got {:?})",
598 delta.fork
599 );
600
601 debug_assert_eq!(
602 delta.current_participation.is_some(),
603 delta.fork >= ForkName::Altair,
604 "DiffSource bug: current_participation must exist iff fork >= Altair (got {:?})",
605 delta.fork
606 );
607
608 debug_assert_eq!(
609 delta.inactivity_scores.is_some(),
610 delta.fork >= ForkName::Altair,
611 "DiffSource bug: inactivity_scores must exist iff fork >= Altair (got {:?})",
612 delta.fork
613 );
614
615 debug_assert_eq!(
616 delta.current_sync_committee.is_some(),
617 delta.fork >= ForkName::Altair,
618 "DiffSource bug: current_sync_committee must exist iff fork >= Altair (got {:?})",
619 delta.fork
620 );
621
622 debug_assert_eq!(
623 delta.next_sync_committee.is_some(),
624 delta.fork >= ForkName::Altair,
625 "DiffSource bug: next_sync_committee must exist iff fork >= Altair (got {:?})",
626 delta.fork
627 );
628
629 debug_assert_eq!(
630 delta.historical_summaries.is_some(),
631 delta.fork >= ForkName::Capella,
632 "DiffSource bug: historical_summaries must exist iff fork >= Capella (got {:?})",
633 delta.fork
634 );
635
636 debug_assert_eq!(
637 delta.historical_roots.is_some(),
638 delta.fork < ForkName::Capella,
639 "DiffSource bug: historical_roots must exist iff fork < Capella (got {:?})",
640 delta.fork
641 );
642
643 debug_assert_eq!(
644 delta.pending_deposits.is_some(),
645 delta.fork >= ForkName::Electra,
646 "DiffSource bug: pending_deposits must exist iff fork >= Electra (got {:?})",
647 delta.fork
648 );
649
650 debug_assert_eq!(
651 delta.pending_partial_withdrawals.is_some(),
652 delta.fork >= ForkName::Electra,
653 "DiffSource bug: pending_partial_withdrawals must exist iff fork >= Electra (got {:?})",
654 delta.fork
655 );
656
657 debug_assert_eq!(
658 delta.pending_consolidations.is_some(),
659 delta.fork >= ForkName::Electra,
660 "DiffSource bug: pending_consolidations must exist iff fork >= Electra (got {:?})",
661 delta.fork
662 );
663
664 delta
665}
666
667pub fn apply<M: DiffTarget>(mut state: M, delta: &ArchivedBeaconStateDelta) -> Result<M, Error> {
702 use rkyv::deserialize;
703
704 let delta_fork: ForkName = deserialize::<ForkName, rkyv::rancor::Error>(&delta.fork)
705 .map_err(|e| Error::MalformedDelta(format!("failed to deserialize fork: {e}")))?;
706
707 let state_fork = state.get_fork();
708 if state_fork != delta_fork {
709 return Err(Error::ForkMismatch {
710 state_fork,
711 delta_fork,
712 });
713 }
714
715 macro_rules! validate_removed_field {
716 ($field:ident, $removed_in:expr) => {
717 if delta.$field.is_some() && delta_fork >= $removed_in {
718 return Err(Error::InvalidFieldForFork {
719 field: stringify!($field),
720 fork: delta_fork,
721 });
722 }
723 };
724 }
725
726 macro_rules! validate_field {
727 ($field:ident, $fork:expr) => {
728 if delta.$field.is_some() && delta_fork < $fork {
729 return Err(Error::InvalidFieldForFork {
730 field: stringify!($field),
731 fork: delta_fork,
732 });
733 }
734 };
735 }
736
737 validate_field!(previous_participation, ForkName::Altair);
738 validate_field!(current_participation, ForkName::Altair);
739 validate_field!(inactivity_scores, ForkName::Altair);
740 validate_field!(current_sync_committee, ForkName::Altair);
741 validate_field!(next_sync_committee, ForkName::Altair);
742
743 validate_field!(historical_summaries, ForkName::Capella);
744
745 validate_field!(pending_deposits, ForkName::Electra);
746 validate_field!(pending_partial_withdrawals, ForkName::Electra);
747 validate_field!(pending_consolidations, ForkName::Electra);
748
749 validate_removed_field!(previous_epoch_attestations, ForkName::Altair);
750 validate_removed_field!(current_epoch_attestations, ForkName::Altair);
751
752 validate_removed_field!(historical_roots, ForkName::Capella);
753
754 let base_slot = delta.base_slot.to_native();
755
756 *state.scalar_header_mut() = delta.scalar_header.as_slice().to_vec();
757
758 balances::apply_balances_iter(state.balances_mut(), &delta.balances)?;
760 validators::apply_validators_iter(state.validators_mut(), &delta.validators)?;
761 recent_roots::apply_roots(base_slot, state.block_roots_mut(), &delta.block_roots)?;
762 recent_roots::apply_roots(base_slot, state.state_roots_mut(), &delta.state_roots)?;
763 randao_mixes::apply_randao(base_slot, state.randao_mixes_mut(), &delta.randao_mixes)?;
764 slashings::apply_slashings(state.slashings_mut(), &delta.slashings)?;
765 eth1_data_votes::apply_eth1_votes(state.eth1_data_votes_mut(), &delta.eth1_data_votes);
766
767 if let (Some(s), Some(d)) = (
768 state.historical_roots_mut(),
769 delta.historical_roots.as_ref(),
770 ) {
771 historical_log::apply_historical_log(s, d);
772 }
773
774 if let (Some(s), Some(d)) = (
775 state.previous_epoch_attestations_mut(),
776 delta.previous_epoch_attestations.as_ref(),
777 ) {
778 attestations::apply_attestations(s, d);
779 }
780
781 if let (Some(s), Some(d)) = (
782 state.current_epoch_attestations_mut(),
783 delta.current_epoch_attestations.as_ref(),
784 ) {
785 attestations::apply_attestations(s, d);
786 }
787
788 if let (Some(s), Some(d)) = (
789 state.previous_participation_mut(),
790 delta.previous_participation.as_ref(),
791 ) {
792 participation::apply_participation_iter(s, d)?;
793 }
794
795 if let (Some(s), Some(d)) = (
796 state.current_participation_mut(),
797 delta.current_participation.as_ref(),
798 ) {
799 participation::apply_participation_iter(s, d)?;
800 }
801
802 if let (Some(s), Some(d)) = (
803 state.inactivity_scores_mut(),
804 delta.inactivity_scores.as_ref(),
805 ) {
806 inactivity_scores::apply_inactivity(s, d)?;
807 }
808
809 if let (Some(s), Some(d)) = (
810 state.current_sync_committee_mut(),
811 delta.current_sync_committee.as_ref(),
812 ) {
813 sync_committee::apply_sync_committee(s, d);
814 }
815
816 if let (Some(s), Some(d)) = (
817 state.next_sync_committee_mut(),
818 delta.next_sync_committee.as_ref(),
819 ) {
820 sync_committee::apply_sync_committee(s, d);
821 }
822
823 if let (Some(s), Some(d)) = (
824 state.historical_summaries_mut(),
825 delta.historical_summaries.as_ref(),
826 ) {
827 historical_log::apply_historical_log(s, d);
828 }
829
830 if let (Some(s), Some(d)) = (
831 state.pending_deposits_mut(),
832 delta.pending_deposits.as_ref(),
833 ) {
834 pending_queue::apply_queue(s, d, PENDING_DEPOSIT_SSZ_SIZE)?;
835 }
836
837 if let (Some(s), Some(d)) = (
838 state.pending_partial_withdrawals_mut(),
839 delta.pending_partial_withdrawals.as_ref(),
840 ) {
841 pending_queue::apply_queue(s, d, PARTIAL_WITHDRAWAL_SSZ_SIZE)?;
842 }
843
844 if let (Some(s), Some(d)) = (
845 state.pending_consolidations_mut(),
846 delta.pending_consolidations.as_ref(),
847 ) {
848 pending_queue::apply_queue(s, d, PENDING_CONSOLIDATION_SSZ_SIZE)?;
849 }
850
851 Ok(state)
852}
853
854pub trait ListMutTarget<T: Copy> {
906 fn len(&self) -> usize;
908
909 fn is_empty(&self) -> bool {
911 self.len() == 0
912 }
913
914 fn get_mut(&mut self, index: usize) -> Option<&mut T>;
918
919 fn push(&mut self, value: T);
921}
922
923impl ListMutTarget<u64> for Vec<u64> {
924 #[inline]
925 fn len(&self) -> usize {
926 self.len()
927 }
928
929 #[inline]
930 fn get_mut(&mut self, index: usize) -> Option<&mut u64> {
931 self.as_mut_slice().get_mut(index)
932 }
933
934 #[inline]
935 fn push(&mut self, value: u64) {
936 self.push(value);
937 }
938}
939
940impl ListMutTarget<u8> for Vec<u8> {
941 #[inline]
942 fn len(&self) -> usize {
943 self.len()
944 }
945
946 #[inline]
947 fn get_mut(&mut self, index: usize) -> Option<&mut u8> {
948 self.as_mut_slice().get_mut(index)
949 }
950
951 #[inline]
952 fn push(&mut self, value: u8) {
953 self.push(value);
954 }
955}
956
957const PENDING_DEPOSIT_SSZ_SIZE: usize = 192;
958const PARTIAL_WITHDRAWAL_SSZ_SIZE: usize = 24;
959const PENDING_CONSOLIDATION_SSZ_SIZE: usize = 16;
960
961#[cfg(test)]
962mod tests {
963 use super::*;
964
965 #[test]
966 fn fork_name_ordering_is_correct() {
967 assert!(ForkName::Phase0 < ForkName::Altair);
969 assert!(ForkName::Altair < ForkName::Bellatrix);
970 assert!(ForkName::Bellatrix < ForkName::Capella);
971 assert!(ForkName::Capella < ForkName::Deneb);
972 assert!(ForkName::Deneb < ForkName::Electra);
973
974 assert_eq!(ForkName::Capella, ForkName::Capella);
976 }
977
978 #[test]
979 fn list_mut_target_vec_u64_works() {
980 let mut v = vec![100u64, 200, 300];
981
982 {
983 let target: &mut dyn ListMutTarget<u64> = &mut v;
984
985 assert_eq!(target.len(), 3);
986 assert!(!target.is_empty());
987
988 *target.get_mut(1).expect("index 1 exists") = 250;
989
990 assert!(target.get_mut(10).is_none());
991
992 target.push(400);
993
994 assert_eq!(target.len(), 4);
995 }
996
997 assert_eq!(v[1], 250);
998 assert_eq!(v[3], 400);
999 }
1000
1001 #[test]
1002 fn list_mut_target_vec_u8_works() {
1003 let mut v = vec![1u8, 2, 3];
1004
1005 {
1006 let target: &mut dyn ListMutTarget<u8> = &mut v;
1007
1008 assert_eq!(target.len(), 3);
1009
1010 *target.get_mut(0).expect("index 0 exists") = 9;
1011
1012 target.push(4);
1013 }
1014
1015 assert_eq!(v[0], 9);
1016 assert_eq!(v[3], 4);
1017 }
1018}
1019
1020#[cfg(test)]
1021mod integration_tests {
1022 use super::*;
1023 use crate::types::{MIN_VALIDATOR_WITHDRAWABILITY_DELAY, VALIDATOR_SSZ_SIZE};
1024 use crate::validators::{ValidatorMut, ValidatorMutTarget, ValidatorSnapshot};
1025
1026 const SLOTS_PER_EPOCH: u64 = 32;
1027 const SLOTS_PER_HISTORICAL_ROOT: usize = 8192;
1028 const EPOCHS_PER_HISTORICAL_ROOT: usize = 256;
1029 const EPOCHS_PER_SLASHINGS_VECTOR: usize = 8192;
1030
1031 #[derive(Clone, Debug, PartialEq, Eq)]
1032 struct MockValidator {
1033 withdrawal_credentials: [u8; 32],
1034 effective_balance: u64,
1035 slashed: bool,
1036 activation_eligibility_epoch: u64,
1037 activation_epoch: u64,
1038 exit_epoch: u64,
1039 withdrawable_epoch: u64,
1040 }
1041
1042 impl MockValidator {
1043 fn new(id: u8) -> Self {
1044 Self {
1045 withdrawal_credentials: [id; 32],
1046 effective_balance: 32_000_000_000,
1047 slashed: false,
1048 activation_eligibility_epoch: 0,
1049 activation_epoch: 0,
1050 exit_epoch: u64::MAX,
1051 withdrawable_epoch: u64::MAX,
1052 }
1053 }
1054
1055 fn from_ssz_bytes(b: &[u8]) -> Self {
1056 assert!(b.len() >= VALIDATOR_SSZ_SIZE, "ssz too short");
1057 Self {
1058 withdrawal_credentials: b[48..80].try_into().unwrap(),
1059 effective_balance: u64::from_le_bytes(b[80..88].try_into().unwrap()),
1060 slashed: b[88] != 0,
1061 activation_eligibility_epoch: u64::from_le_bytes(b[89..97].try_into().unwrap()),
1062 activation_epoch: u64::from_le_bytes(b[97..105].try_into().unwrap()),
1063 exit_epoch: u64::from_le_bytes(b[105..113].try_into().unwrap()),
1064 withdrawable_epoch: u64::from_le_bytes(b[113..121].try_into().unwrap()),
1065 }
1066 }
1067 }
1068
1069 impl ValidatorSnapshot for MockValidator {
1070 fn withdrawal_credentials(&self) -> &[u8; 32] {
1071 &self.withdrawal_credentials
1072 }
1073
1074 fn effective_balance(&self) -> u64 {
1075 self.effective_balance
1076 }
1077
1078 fn is_slashed(&self) -> bool {
1079 self.slashed
1080 }
1081
1082 fn activation_eligibility_epoch(&self) -> u64 {
1083 self.activation_eligibility_epoch
1084 }
1085
1086 fn activation_epoch(&self) -> u64 {
1087 self.activation_epoch
1088 }
1089
1090 fn exit_epoch(&self) -> u64 {
1091 self.exit_epoch
1092 }
1093
1094 fn withdrawable_epoch(&self) -> u64 {
1095 self.withdrawable_epoch
1096 }
1097
1098 fn to_ssz_bytes(&self) -> Vec<u8> {
1099 let mut b = vec![0u8; VALIDATOR_SSZ_SIZE];
1100 b[48..80].copy_from_slice(&self.withdrawal_credentials);
1101 b[80..88].copy_from_slice(&self.effective_balance.to_le_bytes());
1102 b[88] = self.slashed as u8;
1103 b[89..97].copy_from_slice(&self.activation_eligibility_epoch.to_le_bytes());
1104 b[97..105].copy_from_slice(&self.activation_epoch.to_le_bytes());
1105 b[105..113].copy_from_slice(&self.exit_epoch.to_le_bytes());
1106 b[113..121].copy_from_slice(&self.withdrawable_epoch.to_le_bytes());
1107 b
1108 }
1109 }
1110
1111 struct MockMutVal<'a>(&'a mut MockValidator);
1112
1113 impl ValidatorMut for MockMutVal<'_> {
1114 fn is_slashed(&self) -> bool {
1115 self.0.slashed
1116 }
1117
1118 fn set_withdrawal_credentials(&mut self, v: &[u8; 32]) {
1119 self.0.withdrawal_credentials = *v;
1120 }
1121
1122 fn set_effective_balance(&mut self, v: u64) {
1123 self.0.effective_balance = v;
1124 }
1125
1126 fn set_slashed(&mut self, v: bool) {
1127 self.0.slashed = v;
1128 }
1129
1130 fn set_activation_eligibility_epoch(&mut self, v: u64) {
1131 self.0.activation_eligibility_epoch = v;
1132 }
1133
1134 fn set_activation_epoch(&mut self, v: u64) {
1135 self.0.activation_epoch = v;
1136 }
1137
1138 fn set_exit_epoch(&mut self, v: u64) {
1139 self.0.exit_epoch = v;
1140 }
1141
1142 fn set_withdrawable_epoch(&mut self, v: u64) {
1143 self.0.withdrawable_epoch = v;
1144 }
1145 }
1146
1147 impl ValidatorMutTarget for Vec<MockValidator> {
1148 type Validator<'a>
1149 = MockMutVal<'a>
1150 where
1151 Self: 'a;
1152
1153 fn get_mut(&mut self, i: usize) -> Option<Self::Validator<'_>> {
1154 self.as_mut_slice().get_mut(i).map(MockMutVal)
1155 }
1156
1157 fn push_from_ssz(&mut self, b: &[u8]) {
1158 self.push(MockValidator::from_ssz_bytes(b));
1159 }
1160 }
1161
1162 #[derive(Clone, Debug, PartialEq, Eq)]
1163 struct MockState {
1164 fork: ForkName,
1165 slot: u64,
1166 capella_fork_slot: u64,
1167 scalar_header: Vec<u8>,
1168
1169 balances: Vec<u64>,
1170 validators: Vec<MockValidator>,
1171 block_roots: Vec<[u8; 32]>,
1172 state_roots: Vec<[u8; 32]>,
1173 randao_mixes: Vec<[u8; 32]>,
1174 slashings: Vec<u64>,
1175 eth1_data_votes: Vec<u8>,
1176
1177 historical_roots: Option<Vec<u8>>,
1178
1179 previous_epoch_attestations: Option<Vec<u8>>,
1180 current_epoch_attestations: Option<Vec<u8>>,
1181
1182 previous_participation: Option<Vec<u8>>,
1183 current_participation: Option<Vec<u8>>,
1184 inactivity_scores: Option<Vec<u64>>,
1185 current_sync_committee: Option<Vec<u8>>,
1186 next_sync_committee: Option<Vec<u8>>,
1187
1188 historical_summaries: Option<Vec<u8>>,
1189
1190 pending_deposits: Option<Vec<u8>>,
1191 pending_partial_withdrawals: Option<Vec<u8>>,
1192 pending_consolidations: Option<Vec<u8>>,
1193 }
1194
1195 impl MockState {
1196 fn at_fork(fork: ForkName, slot: u64, capella_fork_slot: u64) -> Self {
1197 let mut state = Self {
1198 fork: fork.clone(),
1199 slot,
1200 capella_fork_slot,
1201 scalar_header: vec![],
1202
1203 balances: vec![],
1204 validators: vec![],
1205
1206 block_roots: vec![[0; 32]; SLOTS_PER_HISTORICAL_ROOT],
1207 state_roots: vec![[0; 32]; SLOTS_PER_HISTORICAL_ROOT],
1208 randao_mixes: vec![[0; 32]; EPOCHS_PER_HISTORICAL_ROOT],
1209 slashings: vec![0; EPOCHS_PER_SLASHINGS_VECTOR],
1210 eth1_data_votes: vec![],
1211
1212 historical_roots: None,
1213
1214 previous_epoch_attestations: None,
1215 current_epoch_attestations: None,
1216
1217 previous_participation: None,
1218 current_participation: None,
1219 inactivity_scores: None,
1220 current_sync_committee: None,
1221 next_sync_committee: None,
1222
1223 historical_summaries: None,
1224
1225 pending_deposits: None,
1226 pending_partial_withdrawals: None,
1227 pending_consolidations: None,
1228 };
1229
1230 if fork == ForkName::Phase0 {
1231 state.previous_epoch_attestations = Some(vec![]);
1232 state.current_epoch_attestations = Some(vec![]);
1233 }
1234
1235 if fork < ForkName::Capella {
1236 state.historical_roots = Some(vec![]);
1237 }
1238
1239 if fork >= ForkName::Altair {
1240 state.previous_participation = Some(vec![]);
1241 state.current_participation = Some(vec![]);
1242 state.inactivity_scores = Some(vec![]);
1243 state.current_sync_committee = Some(vec![0; 48 * 512]);
1244 state.next_sync_committee = Some(vec![0; 48 * 512]);
1245 }
1246
1247 if fork >= ForkName::Capella {
1248 state.historical_summaries = Some(vec![]);
1249 }
1250
1251 if fork >= ForkName::Electra {
1252 state.pending_deposits = Some(vec![]);
1253 state.pending_partial_withdrawals = Some(vec![]);
1254 state.pending_consolidations = Some(vec![]);
1255 }
1256
1257 state
1258 }
1259 }
1260
1261 struct MockSource<'a> {
1262 base: &'a MockState,
1263 target: &'a MockState,
1264 }
1265
1266 fn pair_ref<'a, T>(base: &'a Option<T>, target: &'a Option<T>) -> Option<(&'a T, &'a T)> {
1267 match (base, target) {
1268 (Some(base), Some(target)) => Some((base, target)),
1269 _ => None,
1270 }
1271 }
1272
1273 impl DiffSource for MockSource<'_> {
1274 fn fork(&self) -> ForkName {
1275 self.target.fork.clone()
1276 }
1277
1278 fn slot(&self) -> (u64, u64) {
1279 (self.base.slot, self.target.slot)
1280 }
1281
1282 fn capella_fork_slot(&self) -> u64 {
1283 self.target.capella_fork_slot
1284 }
1285
1286 fn scalar_header(&self) -> Vec<u8> {
1287 self.target.scalar_header.clone()
1288 }
1289
1290 fn balances(
1291 &self,
1292 ) -> (
1293 impl ExactSizeIterator<Item = u64>,
1294 impl ExactSizeIterator<Item = u64>,
1295 ) {
1296 (
1297 self.base.balances.clone().into_iter(),
1298 self.target.balances.clone().into_iter(),
1299 )
1300 }
1301
1302 fn validators(
1303 &self,
1304 ) -> (
1305 impl ExactSizeIterator<Item = impl ValidatorSnapshot>,
1306 impl ExactSizeIterator<Item = impl ValidatorSnapshot>,
1307 ) {
1308 (
1309 self.base.validators.clone().into_iter(),
1310 self.target.validators.clone().into_iter(),
1311 )
1312 }
1313
1314 fn block_roots(&self) -> &[[u8; 32]] {
1315 &self.target.block_roots
1316 }
1317
1318 fn state_roots(&self) -> &[[u8; 32]] {
1319 &self.target.state_roots
1320 }
1321
1322 fn randao_mixes(&self) -> &[[u8; 32]] {
1323 &self.target.randao_mixes
1324 }
1325
1326 fn slashings(&self) -> (&[u64], &[u64]) {
1327 (&self.base.slashings, &self.target.slashings)
1328 }
1329
1330 fn eth1_data_votes(&self) -> (&[u8], &[u8]) {
1331 (&self.base.eth1_data_votes, &self.target.eth1_data_votes)
1332 }
1333
1334 fn historical_roots(&self) -> Option<&[u8]> {
1335 self.target.historical_roots.as_deref()
1336 }
1337
1338 fn previous_epoch_attestations(&self) -> Option<(&[u8], &[u8])> {
1339 pair_ref(
1340 &self.base.previous_epoch_attestations,
1341 &self.target.previous_epoch_attestations,
1342 )
1343 .map(|(base, target)| (base.as_slice(), target.as_slice()))
1344 }
1345
1346 fn current_epoch_attestations(&self) -> Option<(&[u8], &[u8])> {
1347 pair_ref(
1348 &self.base.current_epoch_attestations,
1349 &self.target.current_epoch_attestations,
1350 )
1351 .map(|(base, target)| (base.as_slice(), target.as_slice()))
1352 }
1353
1354 fn previous_participation(
1355 &self,
1356 ) -> Option<(
1357 impl ExactSizeIterator<Item = u8>,
1358 impl ExactSizeIterator<Item = u8>,
1359 )> {
1360 pair_ref(
1361 &self.base.previous_participation,
1362 &self.target.previous_participation,
1363 )
1364 .map(|(base, target)| (base.clone().into_iter(), target.clone().into_iter()))
1365 }
1366
1367 fn current_participation(
1368 &self,
1369 ) -> Option<(
1370 impl ExactSizeIterator<Item = u8>,
1371 impl ExactSizeIterator<Item = u8>,
1372 )> {
1373 pair_ref(
1374 &self.base.current_participation,
1375 &self.target.current_participation,
1376 )
1377 .map(|(base, target)| (base.clone().into_iter(), target.clone().into_iter()))
1378 }
1379
1380 fn inactivity_scores(&self) -> Option<(&[u64], &[u64])> {
1381 pair_ref(&self.base.inactivity_scores, &self.target.inactivity_scores)
1382 .map(|(base, target)| (base.as_slice(), target.as_slice()))
1383 }
1384
1385 fn current_sync_committee(&self) -> Option<(&[u8], &[u8])> {
1386 pair_ref(
1387 &self.base.current_sync_committee,
1388 &self.target.current_sync_committee,
1389 )
1390 .map(|(base, target)| (base.as_slice(), target.as_slice()))
1391 }
1392
1393 fn next_sync_committee(&self) -> Option<(&[u8], &[u8])> {
1394 pair_ref(
1395 &self.base.next_sync_committee,
1396 &self.target.next_sync_committee,
1397 )
1398 .map(|(base, target)| (base.as_slice(), target.as_slice()))
1399 }
1400
1401 fn historical_summaries(&self) -> Option<&[u8]> {
1402 self.target.historical_summaries.as_deref()
1403 }
1404
1405 fn pending_deposits(&self) -> Option<(&[u8], &[u8])> {
1406 pair_ref(&self.base.pending_deposits, &self.target.pending_deposits)
1407 .map(|(base, target)| (base.as_slice(), target.as_slice()))
1408 }
1409
1410 fn pending_partial_withdrawals(&self) -> Option<(&[u8], &[u8])> {
1411 pair_ref(
1412 &self.base.pending_partial_withdrawals,
1413 &self.target.pending_partial_withdrawals,
1414 )
1415 .map(|(base, target)| (base.as_slice(), target.as_slice()))
1416 }
1417
1418 fn pending_consolidations(&self) -> Option<(&[u8], &[u8])> {
1419 pair_ref(
1420 &self.base.pending_consolidations,
1421 &self.target.pending_consolidations,
1422 )
1423 .map(|(base, target)| (base.as_slice(), target.as_slice()))
1424 }
1425 }
1426
1427 impl DiffTarget for MockState {
1428 fn get_fork(&self) -> ForkName {
1429 self.fork.clone()
1430 }
1431
1432 fn scalar_header_mut(&mut self) -> &mut Vec<u8> {
1433 &mut self.scalar_header
1434 }
1435
1436 fn balances_mut(&mut self) -> &mut impl ListMutTarget<u64> {
1437 &mut self.balances
1438 }
1439
1440 fn validators_mut(&mut self) -> &mut impl ValidatorMutTarget {
1441 &mut self.validators
1442 }
1443
1444 fn block_roots_mut(&mut self) -> &mut [[u8; 32]] {
1445 self.block_roots.as_mut_slice()
1446 }
1447
1448 fn state_roots_mut(&mut self) -> &mut [[u8; 32]] {
1449 self.state_roots.as_mut_slice()
1450 }
1451
1452 fn randao_mixes_mut(&mut self) -> &mut [[u8; 32]] {
1453 self.randao_mixes.as_mut_slice()
1454 }
1455
1456 fn slashings_mut(&mut self) -> &mut [u64] {
1457 self.slashings.as_mut_slice()
1458 }
1459
1460 fn eth1_data_votes_mut(&mut self) -> &mut Vec<u8> {
1461 &mut self.eth1_data_votes
1462 }
1463
1464 fn historical_roots_mut(&mut self) -> Option<&mut Vec<u8>> {
1465 self.historical_roots.as_mut()
1466 }
1467
1468 fn previous_epoch_attestations_mut(&mut self) -> Option<&mut Vec<u8>> {
1469 self.previous_epoch_attestations.as_mut()
1470 }
1471
1472 fn current_epoch_attestations_mut(&mut self) -> Option<&mut Vec<u8>> {
1473 self.current_epoch_attestations.as_mut()
1474 }
1475
1476 fn previous_participation_mut(&mut self) -> Option<&mut impl ListMutTarget<u8>> {
1477 self.previous_participation.as_mut()
1478 }
1479
1480 fn current_participation_mut(&mut self) -> Option<&mut impl ListMutTarget<u8>> {
1481 self.current_participation.as_mut()
1482 }
1483
1484 fn inactivity_scores_mut(&mut self) -> Option<&mut Vec<u64>> {
1485 self.inactivity_scores.as_mut()
1486 }
1487
1488 fn current_sync_committee_mut(&mut self) -> Option<&mut Vec<u8>> {
1489 self.current_sync_committee.as_mut()
1490 }
1491
1492 fn next_sync_committee_mut(&mut self) -> Option<&mut Vec<u8>> {
1493 self.next_sync_committee.as_mut()
1494 }
1495
1496 fn historical_summaries_mut(&mut self) -> Option<&mut Vec<u8>> {
1497 self.historical_summaries.as_mut()
1498 }
1499
1500 fn pending_deposits_mut(&mut self) -> Option<&mut Vec<u8>> {
1501 self.pending_deposits.as_mut()
1502 }
1503
1504 fn pending_partial_withdrawals_mut(&mut self) -> Option<&mut Vec<u8>> {
1505 self.pending_partial_withdrawals.as_mut()
1506 }
1507
1508 fn pending_consolidations_mut(&mut self) -> Option<&mut Vec<u8>> {
1509 self.pending_consolidations.as_mut()
1510 }
1511 }
1512
1513 fn archive_delta(delta: &BeaconStateDelta) -> Vec<u8> {
1514 rkyv::to_bytes::<rkyv::rancor::Error>(delta)
1515 .expect("serialize delta")
1516 .to_vec()
1517 }
1518
1519 fn access_archived(bytes: &[u8]) -> &ArchivedBeaconStateDelta {
1520 rkyv::access::<ArchivedBeaconStateDelta, rkyv::rancor::Error>(bytes)
1521 .expect("access archived delta")
1522 }
1523
1524 fn roundtrip(base: MockState, target: MockState) {
1525 assert_eq!(
1526 base.fork, target.fork,
1527 "roundtrip requires base and target to use the same fork"
1528 );
1529
1530 let source = MockSource {
1531 base: &base,
1532 target: &target,
1533 };
1534
1535 let delta = create(&source);
1536 let bytes = archive_delta(&delta);
1537 let archived = access_archived(&bytes);
1538
1539 let mut reconstructed = apply(base.clone(), archived).expect("apply");
1540
1541 reconstructed.slot = target.slot;
1545
1546 assert_eq!(reconstructed.fork, target.fork, "fork mismatch");
1547 assert_eq!(
1548 reconstructed.scalar_header, target.scalar_header,
1549 "scalar_header mismatch"
1550 );
1551 assert_eq!(reconstructed.balances, target.balances, "balances mismatch");
1552 assert_eq!(
1553 reconstructed.validators, target.validators,
1554 "validators mismatch"
1555 );
1556 assert_eq!(
1557 reconstructed.block_roots, target.block_roots,
1558 "block_roots mismatch"
1559 );
1560 assert_eq!(
1561 reconstructed.state_roots, target.state_roots,
1562 "state_roots mismatch"
1563 );
1564 assert_eq!(
1565 reconstructed.randao_mixes, target.randao_mixes,
1566 "randao_mixes mismatch"
1567 );
1568 assert_eq!(
1569 reconstructed.slashings, target.slashings,
1570 "slashings mismatch"
1571 );
1572 assert_eq!(
1573 reconstructed.eth1_data_votes, target.eth1_data_votes,
1574 "eth1_data_votes mismatch"
1575 );
1576 assert_eq!(
1577 reconstructed.historical_roots, target.historical_roots,
1578 "historical_roots mismatch"
1579 );
1580 assert_eq!(
1581 reconstructed.previous_epoch_attestations, target.previous_epoch_attestations,
1582 "previous_epoch_attestations mismatch"
1583 );
1584 assert_eq!(
1585 reconstructed.current_epoch_attestations, target.current_epoch_attestations,
1586 "current_epoch_attestations mismatch"
1587 );
1588 assert_eq!(
1589 reconstructed.previous_participation, target.previous_participation,
1590 "previous_participation mismatch"
1591 );
1592 assert_eq!(
1593 reconstructed.current_participation, target.current_participation,
1594 "current_participation mismatch"
1595 );
1596 assert_eq!(
1597 reconstructed.inactivity_scores, target.inactivity_scores,
1598 "inactivity_scores mismatch"
1599 );
1600 assert_eq!(
1601 reconstructed.current_sync_committee, target.current_sync_committee,
1602 "current_sync_committee mismatch"
1603 );
1604 assert_eq!(
1605 reconstructed.next_sync_committee, target.next_sync_committee,
1606 "next_sync_committee mismatch"
1607 );
1608 assert_eq!(
1609 reconstructed.historical_summaries, target.historical_summaries,
1610 "historical_summaries mismatch"
1611 );
1612 assert_eq!(
1613 reconstructed.pending_deposits, target.pending_deposits,
1614 "pending_deposits mismatch"
1615 );
1616 assert_eq!(
1617 reconstructed.pending_partial_withdrawals, target.pending_partial_withdrawals,
1618 "pending_partial_withdrawals mismatch"
1619 );
1620 assert_eq!(
1621 reconstructed.pending_consolidations, target.pending_consolidations,
1622 "pending_consolidations mismatch"
1623 );
1624 }
1625
1626 #[test]
1627 fn phase0_empty_state_no_changes() {
1628 let base = MockState::at_fork(ForkName::Phase0, 100, 32_000);
1629 let target = MockState::at_fork(ForkName::Phase0, 105, 32_000);
1630
1631 roundtrip(base, target);
1632 }
1633
1634 #[test]
1635 fn phase0_balances_validators_roots_slashings_votes() {
1636 let mut base = MockState::at_fork(ForkName::Phase0, 96, 32_000);
1637 let mut target = MockState::at_fork(ForkName::Phase0, 128, 32_000);
1638
1639 base.validators = vec![MockValidator::new(1), MockValidator::new(2)];
1640 base.balances = vec![32_000_000_000, 32_000_000_000];
1641
1642 target.validators = base.validators.clone();
1643 target.balances = base.balances.clone();
1644
1645 target.balances[1] = 31_000_000_000;
1647
1648 target.validators[0].effective_balance = 31_000_000_000;
1650
1651 target.validators.push(MockValidator::new(3));
1653 target.balances.push(32_000_000_000);
1654
1655 for slot in 96u64..128 {
1657 let index = (slot as usize) % SLOTS_PER_HISTORICAL_ROOT;
1658
1659 target.block_roots[index] = [slot as u8; 32];
1660 target.state_roots[index] = [(slot + 1) as u8; 32];
1661 }
1662
1663 let slashing_index = (target.slot / SLOTS_PER_EPOCH) as usize % EPOCHS_PER_SLASHINGS_VECTOR;
1665 target.slashings[slashing_index] = 1_000_000_000;
1666
1667 target.eth1_data_votes = vec![0xAA, 0xBB, 0xCC];
1669
1670 base.scalar_header = vec![0x11; 16];
1672 target.scalar_header = vec![0x22; 16];
1673
1674 roundtrip(base, target);
1675 }
1676
1677 #[test]
1678 fn capella_with_altair_fields_and_slashed_validator() {
1679 let mut base = MockState::at_fork(ForkName::Capella, 32, 32);
1680 let mut target = MockState::at_fork(ForkName::Capella, 8224, 32);
1681
1682 base.validators.clone_from(&vec![
1683 MockValidator::new(1),
1684 MockValidator::new(2),
1685 MockValidator::new(3),
1686 ]);
1687
1688 target.validators.clone_from(&base.validators);
1689
1690 base.balances
1691 .clone_from(&vec![32_000_000_000, 32_000_000_000, 32_000_000_000]);
1692
1693 target.balances.clone_from(&base.balances);
1694
1695 target.validators[1].slashed = true;
1696 target.validators[1].exit_epoch = 200;
1697 target.validators[1].withdrawable_epoch = 200 + MIN_VALIDATOR_WITHDRAWABILITY_DELAY;
1698
1699 target.balances[2] -= 1_000_000_000;
1700
1701 for i in 0..5 {
1702 target.block_roots[100 + i] = [i as u8; 32];
1703 target.state_roots[100 + i] = [(i + 10) as u8; 32];
1704 }
1705
1706 target.previous_participation = Some(vec![1, 3, 7]);
1707 target.current_participation = Some(vec![2, 4, 8]);
1708 target.inactivity_scores = Some(vec![0, 1, 2]);
1709 target.current_sync_committee = Some(vec![0xFF; 48 * 512]);
1710 target.next_sync_committee = Some(vec![0xEE; 48 * 512]);
1711
1712 target.historical_summaries = Some(vec![0xAB; 64]);
1716
1717 target.scalar_header = vec![0x44];
1718 roundtrip(base, target);
1721 }
1722
1723 #[test]
1724 fn electra_with_pending_queues() {
1725 let mut base = MockState::at_fork(ForkName::Electra, 100, 32);
1726 let mut target = MockState::at_fork(ForkName::Electra, 105, 32);
1727
1728 base.validators = vec![MockValidator::new(1)];
1729 base.balances = vec![32_000_000_000];
1730
1731 target.validators = base.validators.clone();
1732 target.balances = base.balances.clone();
1733
1734 target.pending_deposits = Some(vec![0xAB; PENDING_DEPOSIT_SSZ_SIZE]);
1736
1737 target.pending_partial_withdrawals = Some(vec![0xCD; PARTIAL_WITHDRAWAL_SSZ_SIZE]);
1739
1740 target.pending_consolidations = Some(vec![0xEF; PENDING_CONSOLIDATION_SSZ_SIZE]);
1742
1743 base.scalar_header = vec![0x55; 16];
1744 target.scalar_header = vec![0x66; 16];
1745
1746 roundtrip(base, target);
1747 }
1748
1749 #[test]
1824 fn fork_mismatch_rejected() {
1825 let base = MockState::at_fork(ForkName::Capella, 100, 32);
1826 let target = MockState::at_fork(ForkName::Capella, 105, 32);
1827
1828 let source = MockSource {
1829 base: &base,
1830 target: &target,
1831 };
1832
1833 let delta = create(&source);
1834 assert_eq!(delta.fork, ForkName::Capella);
1835
1836 let bytes = archive_delta(&delta);
1837 let archived = access_archived(&bytes);
1838
1839 let state = MockState::at_fork(ForkName::Phase0, 100, 32_000);
1840
1841 let result = apply(state, archived);
1842
1843 assert!(
1844 matches!(
1845 result,
1846 Err(Error::ForkMismatch {
1847 state_fork: ForkName::Phase0,
1848 delta_fork: ForkName::Capella,
1849 })
1850 ),
1851 "expected ForkMismatch, got {result:?}"
1852 );
1853 }
1854
1855 #[test]
1856 fn historical_roots_on_capella_rejected_by_apply() {
1857 let base = MockState::at_fork(ForkName::Phase0, 100, 32_000);
1858 let target = MockState::at_fork(ForkName::Phase0, 105, 32_000);
1859
1860 let source = MockSource {
1861 base: &base,
1862 target: &target,
1863 };
1864
1865 let phase0_delta = create(&source);
1866 assert!(phase0_delta.historical_roots.is_some());
1867
1868 let capella_base = MockState::at_fork(ForkName::Capella, 100, 32);
1869 let capella_target = MockState::at_fork(ForkName::Capella, 105, 32);
1870
1871 let capella_source = MockSource {
1872 base: &capella_base,
1873 target: &capella_target,
1874 };
1875
1876 let mut delta = create(&capella_source);
1877 assert!(delta.historical_roots.is_none());
1878
1879 delta.historical_roots = phase0_delta.historical_roots;
1881 assert!(delta.historical_roots.is_some());
1882
1883 let bytes = archive_delta(&delta);
1884 let archived = access_archived(&bytes);
1885
1886 let state = MockState::at_fork(ForkName::Capella, 100, 32);
1887
1888 let result = apply(state, archived);
1889
1890 assert!(
1891 matches!(
1892 result,
1893 Err(Error::InvalidFieldForFork {
1894 field: "historical_roots",
1895 fork: ForkName::Capella,
1896 })
1897 ),
1898 "expected InvalidFieldForFork, got {result:?}"
1899 );
1900 }
1901
1902 #[test]
1903 fn altair_field_on_phase0_rejected_by_apply() {
1904 let altair_base = MockState::at_fork(ForkName::Altair, 100, 32_000);
1905 let mut altair_target = MockState::at_fork(ForkName::Altair, 105, 32_000);
1906
1907 altair_target.previous_participation = Some(vec![1, 2, 3]);
1908
1909 let altair_source = MockSource {
1910 base: &altair_base,
1911 target: &altair_target,
1912 };
1913
1914 let altair_delta = create(&altair_source);
1915 assert!(altair_delta.previous_participation.is_some());
1916
1917 let phase0_base = MockState::at_fork(ForkName::Phase0, 100, 32_000);
1918 let phase0_target = MockState::at_fork(ForkName::Phase0, 105, 32_000);
1919
1920 let phase0_source = MockSource {
1921 base: &phase0_base,
1922 target: &phase0_target,
1923 };
1924
1925 let mut delta = create(&phase0_source);
1926 assert!(delta.previous_participation.is_none());
1927
1928 delta.previous_participation = altair_delta.previous_participation;
1930 assert!(delta.previous_participation.is_some());
1931
1932 let bytes = archive_delta(&delta);
1933 let archived = access_archived(&bytes);
1934
1935 let state = MockState::at_fork(ForkName::Phase0, 100, 32_000);
1936
1937 let result = apply(state, archived);
1938
1939 assert!(
1940 matches!(
1941 result,
1942 Err(Error::InvalidFieldForFork {
1943 field: "previous_participation",
1944 fork: ForkName::Phase0,
1945 })
1946 ),
1947 "expected InvalidFieldForFork, got {result:?}"
1948 );
1949 }
1950
1951 #[test]
1952 fn phase0_attestations_on_altair_rejected_by_apply() {
1953 let phase0_base = MockState::at_fork(ForkName::Phase0, 100, 32_000);
1954 let mut phase0_target = MockState::at_fork(ForkName::Phase0, 105, 32_000);
1955
1956 phase0_target.previous_epoch_attestations = Some(vec![0xAA]);
1957 phase0_target.current_epoch_attestations = Some(vec![0xBB]);
1958
1959 let phase0_source = MockSource {
1960 base: &phase0_base,
1961 target: &phase0_target,
1962 };
1963
1964 let phase0_delta = create(&phase0_source);
1965 assert!(phase0_delta.previous_epoch_attestations.is_some());
1966 assert!(phase0_delta.current_epoch_attestations.is_some());
1967
1968 let altair_base = MockState::at_fork(ForkName::Altair, 100, 32_000);
1969 let altair_target = MockState::at_fork(ForkName::Altair, 105, 32_000);
1970
1971 let altair_source = MockSource {
1972 base: &altair_base,
1973 target: &altair_target,
1974 };
1975
1976 let mut delta = create(&altair_source);
1977 assert!(delta.previous_epoch_attestations.is_none());
1978 assert!(delta.current_epoch_attestations.is_none());
1979
1980 delta.previous_epoch_attestations = phase0_delta.previous_epoch_attestations;
1982 delta.current_epoch_attestations = phase0_delta.current_epoch_attestations;
1983
1984 let bytes = archive_delta(&delta);
1985 let archived = access_archived(&bytes);
1986
1987 let state = MockState::at_fork(ForkName::Altair, 100, 32_000);
1988
1989 let result = apply(state, archived);
1990
1991 assert!(
1992 matches!(
1993 result,
1994 Err(Error::InvalidFieldForFork {
1995 field: "previous_epoch_attestations",
1996 fork: ForkName::Altair,
1997 })
1998 ),
1999 "expected InvalidFieldForFork, got {result:?}"
2000 );
2001 }
2002}