From c1d01d3ba1f5faf943ae480364e641d4a011f979 Mon Sep 17 00:00:00 2001 From: ss-es <155648797+ss-es@users.noreply.github.com> Date: Wed, 27 Mar 2024 13:06:42 -0400 Subject: [PATCH] add upgrade cert to leaf + cleanup --- crates/example-types/src/block_types.rs | 2 +- crates/hotshot/src/lib.rs | 2 +- crates/task-impls/src/consensus.rs | 104 +++++++++++------------- crates/task-impls/src/transactions.rs | 2 +- crates/testing/src/block_builder.rs | 2 +- crates/testing/src/task_helpers.rs | 65 +++------------ crates/testing/src/view_generator.rs | 37 +++------ crates/types/src/data.rs | 65 +++++++++------ crates/types/src/simple_certificate.rs | 18 +++- crates/types/src/simple_vote.rs | 10 +-- 10 files changed, 137 insertions(+), 170 deletions(-) diff --git a/crates/example-types/src/block_types.rs b/crates/example-types/src/block_types.rs index 6d28a0e1b3..af1c37116d 100644 --- a/crates/example-types/src/block_types.rs +++ b/crates/example-types/src/block_types.rs @@ -197,7 +197,7 @@ impl> Block payload_commitment: VidCommitment, _metadata: ::Metadata, ) -> Self { - let parent = &parent_leaf.block_header; + let parent = parent_leaf.get_block_header(); let mut timestamp = OffsetDateTime::now_utc().unix_timestamp() as u64; if timestamp < parent.timestamp { diff --git a/crates/hotshot/src/lib.rs b/crates/hotshot/src/lib.rs index 78bea4ce58..7177030b43 100644 --- a/crates/hotshot/src/lib.rs +++ b/crates/hotshot/src/lib.rs @@ -182,7 +182,7 @@ impl> SystemContext { let validated_state = match initializer.validated_state { Some(state) => state, None => Arc::new(TYPES::ValidatedState::from_header( - &anchored_leaf.block_header, + anchored_leaf.get_block_header(), )), }; diff --git a/crates/task-impls/src/consensus.rs b/crates/task-impls/src/consensus.rs index df5102fe35..872903a15b 100644 --- a/crates/task-impls/src/consensus.rs +++ b/crates/task-impls/src/consensus.rs @@ -195,13 +195,12 @@ impl, A: ConsensusApi + }; let parent_commitment = parent.commit(); - let leaf: Leaf<_> = Leaf { - view_number: view, - justify_qc: proposal.justify_qc.clone(), - parent_commitment, - block_header: proposal.block_header.clone(), - block_payload: None, - }; + let proposed_leaf = Leaf::from_quorum_proposal(proposal); + + if proposed_leaf.get_parent_commitment() != parent_commitment { + info!("Quorum proposal's parent commitment does not match expected parent commitment!"); + return false; + } // Validate the DAC. let message = if cert.is_valid_cert(self.committee_membership.as_ref()) { @@ -215,7 +214,7 @@ impl, A: ConsensusApi + } if let Ok(vote) = QuorumVote::::create_signed_vote( QuorumData { - leaf_commit: leaf.commit(), + leaf_commit: proposed_leaf.commit(), }, view, &self.public_key, @@ -501,7 +500,7 @@ impl, A: ConsensusApi + { Some(leaf) => { if let (Some(state), _) = - consensus.get_state_and_delta(leaf.view_number) + consensus.get_state_and_delta(leaf.get_view_number()) { Some((leaf, state.clone())) } else { @@ -539,13 +538,8 @@ impl, A: ConsensusApi + "Proposal's parent missing from storage with commitment: {:?}", justify_qc.get_data().leaf_commit ); - let leaf = Leaf { - view_number: view, - justify_qc: justify_qc.clone(), - parent_commitment: justify_qc.get_data().leaf_commit, - block_header: proposal.data.block_header.clone(), - block_payload: None, - }; + let leaf = Leaf::from_proposal(proposal); + let state = Arc::new( >::from_header( &proposal.data.block_header, @@ -626,20 +620,20 @@ impl, A: ConsensusApi + let state = Arc::new(validated_state); let delta = Arc::new(state_delta); let parent_commitment = parent_leaf.commit(); - let leaf: Leaf<_> = Leaf { - view_number: view, - justify_qc: justify_qc.clone(), - parent_commitment, - block_header: proposal.data.block_header.clone(), - block_payload: None, - }; - let leaf_commitment = leaf.commit(); + + let proposed_leaf = Leaf::from_quorum_proposal(&proposal.data); + + if proposed_leaf.get_parent_commitment() != parent_commitment { + info!("Quorum proposal's parent commitment does not match expected parent commitment!"); + return; + } // Validate the signature. This should also catch if the leaf_commitment does not equal our calculated parent commitment - if !view_leader_key.validate(&proposal.signature, leaf_commitment.as_ref()) { + if !view_leader_key.validate(&proposal.signature, proposed_leaf.commit().as_ref()) { error!(?proposal.signature, "Could not verify proposal."); return; } + // Create a positive vote if either liveness or safety check // passes. @@ -655,7 +649,7 @@ impl, A: ConsensusApi + |leaf, _, _| { // if leaf view no == locked view no then we're done, report success by // returning true - leaf.view_number != consensus.locked_view + leaf.get_view_number() != consensus.locked_view }, ); let safety_check = outcome.is_ok(); @@ -703,7 +697,7 @@ impl, A: ConsensusApi + let mut leafs_decided = Vec::new(); let mut included_txns = HashSet::new(); let old_anchor_view = consensus.last_decided_view; - let parent_view = leaf.justify_qc.get_view_number(); + let parent_view = proposed_leaf.get_justify_qc().get_view_number(); let mut current_chain_length = 0usize; if parent_view + 1 == view { current_chain_length += 1; @@ -713,17 +707,17 @@ impl, A: ConsensusApi + true, |leaf, state, delta| { if !new_decide_reached { - if last_view_number_visited == leaf.view_number + 1 { - last_view_number_visited = leaf.view_number; + if last_view_number_visited == leaf.get_view_number() + 1 { + last_view_number_visited = leaf.get_view_number(); current_chain_length += 1; if current_chain_length == 2 { - new_locked_view = leaf.view_number; + new_locked_view = leaf.get_view_number(); new_commit_reached = true; // The next leaf in the chain, if there is one, is decided, so this // leaf's justify_qc would become the QC for the decided chain. - new_decide_qc = Some(leaf.justify_qc.clone()); + new_decide_qc = Some(leaf.get_justify_qc().clone()); } else if current_chain_length == 3 { - new_anchor_view = leaf.view_number; + new_anchor_view = leaf.get_view_number(); new_decide_reached = true; } } else { @@ -734,7 +728,7 @@ impl, A: ConsensusApi + // starting from the first iteration with a three chain, e.g. right after the else if case nested in the if case above if new_decide_reached { let mut leaf = leaf.clone(); - if leaf.view_number == new_anchor_view { + if leaf.get_view_number() == new_anchor_view { consensus .metrics .last_synced_block_height @@ -768,7 +762,7 @@ impl, A: ConsensusApi + leaf_views.push(LeafInfo::new(leaf.clone(), state.clone(), delta.clone(), vid)); leafs_decided.push(leaf.clone()); - if let Some(ref payload) = leaf.block_payload { + if let Some(ref payload) = leaf.get_block_payload() { for txn in payload .transaction_commitments(leaf.get_block_header().metadata()) { @@ -801,13 +795,13 @@ impl, A: ConsensusApi + view, View { view_inner: ViewInner::Leaf { - leaf: leaf.commit(), + leaf: proposed_leaf.commit(), state: state.clone(), delta: Some(delta.clone()), }, }, ); - consensus.saved_leaves.insert(leaf.commit(), leaf.clone()); + consensus.saved_leaves.insert(proposed_leaf.commit(), proposed_leaf.clone()); if let Err(e) = self .storage @@ -1339,7 +1333,7 @@ impl, A: ConsensusApi + error!("Failed to find high QC of parent."); return false; }; - if leaf.view_number == consensus.last_decided_view { + if leaf.get_view_number() == consensus.last_decided_view { reached_decided = true; } @@ -1353,10 +1347,10 @@ impl, A: ConsensusApi + if !reached_decided { debug!("We have not reached decide from view {:?}", self.cur_view); while let Some(next_parent_leaf) = consensus.saved_leaves.get(&next_parent_hash) { - if next_parent_leaf.view_number <= consensus.last_decided_view { + if next_parent_leaf.get_view_number() <= consensus.last_decided_view { break; } - next_parent_hash = next_parent_leaf.parent_commitment; + next_parent_hash = next_parent_leaf.get_parent_commitment(); } debug!("updated saved leaves"); // TODO do some sort of sanity check on the view number that it matches decided @@ -1371,21 +1365,6 @@ impl, A: ConsensusApi + commit_and_metadata.metadata.clone(), ) .await; - let leaf = Leaf { - view_number: view, - justify_qc: consensus.high_qc.clone(), - parent_commitment: parent_leaf.commit(), - block_header: block_header.clone(), - block_payload: None, - }; - - let Ok(signature) = - TYPES::SignatureKey::sign(&self.private_key, leaf.commit().as_ref()) - else { - error!("Failed to sign leaf.commit()!"); - return false; - }; - let upgrade_cert = if self .upgrade_cert .as_ref() @@ -1410,11 +1389,20 @@ impl, A: ConsensusApi + // TODO: DA cert is sent as part of the proposal here, we should split this out so we don't have to wait for it. let proposal = QuorumProposal { - block_header, - view_number: leaf.view_number, + block_header: block_header.clone(), + view_number: view, justify_qc: consensus.high_qc.clone(), proposal_certificate, - upgrade_certificate: upgrade_cert, + upgrade_certificate: upgrade_cert.clone(), + }; + + let new_leaf = Leaf::from_quorum_proposal(&proposal); + + let Ok(signature) = + TYPES::SignatureKey::sign(&self.private_key, new_leaf.commit().as_ref()) + else { + error!("Failed to sign new_leaf.commit()!"); + return false; }; self.proposal_cert = None; @@ -1423,7 +1411,7 @@ impl, A: ConsensusApi + signature, _pd: PhantomData, }; - debug!("Sending proposal for view {:?}", leaf.view_number); + debug!("Sending proposal for view {:?}", view); broadcast_event( Arc::new(HotShotEvent::QuorumProposalSend( diff --git a/crates/task-impls/src/transactions.rs b/crates/task-impls/src/transactions.rs index 5672744f68..4d8be0e409 100644 --- a/crates/task-impls/src/transactions.rs +++ b/crates/task-impls/src/transactions.rs @@ -127,7 +127,7 @@ impl, A: ConsensusApi + let mut included_txn_size = 0; let mut included_txn_count = 0; for leaf in leaf_chain { - if let Some(ref payload) = leaf.block_payload { + if let Some(ref payload) = leaf.get_block_payload() { for txn in payload.transaction_commitments(leaf.get_block_header().metadata()) { diff --git a/crates/testing/src/block_builder.rs b/crates/testing/src/block_builder.rs index 42c73f469c..7d250c3443 100644 --- a/crates/testing/src/block_builder.rs +++ b/crates/testing/src/block_builder.rs @@ -348,7 +348,7 @@ impl TaskState for SimpleBuilderTask { HotShotEvent::LeafDecided(leaf_chain) => { let mut queue = this.transactions.write().await; for leaf in leaf_chain.iter() { - if let Some(ref payload) = leaf.block_payload { + if let Some(ref payload) = leaf.get_block_payload() { for txn in payload.transaction_commitments(&()) { queue.remove(&txn); } diff --git a/crates/testing/src/task_helpers.rs b/crates/testing/src/task_helpers.rs index 67273199c7..c77d0d8b99 100644 --- a/crates/testing/src/task_helpers.rs +++ b/crates/testing/src/task_helpers.rs @@ -240,7 +240,7 @@ async fn build_quorum_proposal_and_signature( handle.hotshot.memberships.quorum_membership.total_nodes(), ); let mut parent_state = Arc::new( - >::from_header(&parent_leaf.block_header), + >::from_header(parent_leaf.get_block_header()), ); let block_header = TestBlockHeader::new( &*parent_state, @@ -250,17 +250,6 @@ async fn build_quorum_proposal_and_signature( (), ) .await; - // current leaf that can be re-assigned everytime when entering a new view - let mut leaf = Leaf { - view_number: ViewNumber::new(1), - justify_qc: consensus.high_qc.clone(), - parent_commitment: parent_leaf.commit(), - block_header: block_header.clone(), - block_payload: None, - }; - - let mut signature = ::sign(private_key, leaf.commit().as_ref()) - .expect("Failed to sign leaf commitment!"); let mut proposal = QuorumProposal:: { block_header: block_header.clone(), view_number: ViewNumber::new(1), @@ -268,6 +257,11 @@ async fn build_quorum_proposal_and_signature( upgrade_certificate: None, proposal_certificate: None, }; + // current leaf that can be re-assigned everytime when entering a new view + let mut leaf = Leaf::from_quorum_proposal(&proposal); + + let mut signature = ::sign(private_key, leaf.commit().as_ref()) + .expect("Failed to sign leaf commitment!"); // Only view 2 is tested, higher views are not tested for cur_view in 2..=view { @@ -307,17 +301,6 @@ async fn build_quorum_proposal_and_signature( private_key, ); // create a new leaf for the current view - let parent_leaf = leaf.clone(); - let leaf_new_view = Leaf { - view_number: ViewNumber::new(cur_view), - justify_qc: created_qc.clone(), - parent_commitment: parent_leaf.commit(), - block_header: block_header.clone(), - block_payload: None, - }; - let signature_new_view = - ::sign(private_key, leaf_new_view.commit().as_ref()) - .expect("Failed to sign leaf commitment!"); let proposal_new_view = QuorumProposal:: { block_header: block_header.clone(), view_number: ViewNumber::new(cur_view), @@ -325,6 +308,10 @@ async fn build_quorum_proposal_and_signature( upgrade_certificate: None, proposal_certificate: None, }; + let leaf_new_view = Leaf::from_quorum_proposal(&proposal_new_view); + let signature_new_view = + ::sign(private_key, leaf_new_view.commit().as_ref()) + .expect("Failed to sign leaf commitment!"); proposal = proposal_new_view; signature = signature_new_view; leaf = leaf_new_view; @@ -443,39 +430,9 @@ pub async fn build_vote( handle: &SystemContextHandle, proposal: QuorumProposal, ) -> GeneralConsensusMessage { - let consensus_lock = handle.get_consensus(); - let consensus = consensus_lock.read().await; - - let justify_qc = proposal.justify_qc.clone(); let view = ViewNumber::new(*proposal.view_number); - let parent = if justify_qc.is_genesis { - let Some(genesis_view) = consensus.validated_state_map.get(&ViewNumber::new(0)) else { - panic!("Couldn't find genesis view in state map."); - }; - let Some(leaf) = genesis_view.get_leaf_commitment() else { - panic!("Genesis view points to a view without a leaf"); - }; - let Some(leaf) = consensus.saved_leaves.get(&leaf) else { - panic!("Failed to find genesis leaf."); - }; - leaf.clone() - } else { - consensus - .saved_leaves - .get(&justify_qc.get_data().leaf_commit) - .cloned() - .unwrap() - }; - - let parent_commitment = parent.commit(); - let leaf: Leaf<_> = Leaf { - view_number: view, - justify_qc: proposal.justify_qc.clone(), - parent_commitment, - block_header: proposal.block_header, - block_payload: None, - }; + let leaf: Leaf<_> = Leaf::from_quorum_proposal(&proposal); let vote = QuorumVote::::create_signed_vote( QuorumData { leaf_commit: leaf.commit(), diff --git a/crates/testing/src/view_generator.rs b/crates/testing/src/view_generator.rs index 2e9c904c28..563a58f788 100644 --- a/crates/testing/src/view_generator.rs +++ b/crates/testing/src/view_generator.rs @@ -3,7 +3,6 @@ use std::{cmp::max, marker::PhantomData}; use hotshot_example_types::{ block_types::{TestBlockHeader, TestBlockPayload, TestTransaction}, node_types::{MemoryImpl, TestTypes}, - state_types::TestInstanceState, }; use sha2::{Digest, Sha256}; @@ -113,16 +112,10 @@ impl TestView { _pd: PhantomData, }; - let leaf = Leaf { - view_number: genesis_view, - justify_qc: QuorumCertificate::genesis(), - parent_commitment: Leaf::genesis(&TestInstanceState {}).commit(), - block_header: block_header.clone(), - // Note: this field is not relevant in calculating the leaf commitment. - block_payload: Some(TestBlockPayload { - transactions: transactions.clone(), - }), - }; + let mut leaf = Leaf::from_quorum_proposal(&quorum_proposal_inner); + leaf.fill_block_payload_unchecked(TestBlockPayload { + transactions: transactions.clone(), + }); let signature = ::sign(&private_key, leaf.commit().as_ref()) .expect("Failed to sign leaf commitment!"); @@ -274,20 +267,6 @@ impl TestView { payload_commitment, }; - let leaf = Leaf { - view_number: next_view, - justify_qc: quorum_certificate.clone(), - parent_commitment: old.leaf.commit(), - block_header: block_header.clone(), - // Note: this field is not relevant in calculating the leaf commitment. - block_payload: Some(TestBlockPayload { - transactions: transactions.clone(), - }), - }; - - let signature = ::sign(&private_key, leaf.commit().as_ref()) - .expect("Failed to sign leaf commitment."); - let proposal = QuorumProposal:: { block_header: block_header.clone(), view_number: next_view, @@ -296,6 +275,14 @@ impl TestView { proposal_certificate, }; + let mut leaf = Leaf::from_quorum_proposal(&proposal); + leaf.fill_block_payload_unchecked(TestBlockPayload { + transactions: transactions.clone(), + }); + + let signature = ::sign(&private_key, leaf.commit().as_ref()) + .expect("Failed to sign leaf commitment."); + let quorum_proposal = Proposal { data: proposal, signature, diff --git a/crates/types/src/data.rs b/crates/types/src/data.rs index cf330b6b56..296bd293cb 100644 --- a/crates/types/src/data.rs +++ b/crates/types/src/data.rs @@ -373,22 +373,25 @@ pub trait TestableLeaf { #[serde(bound(deserialize = ""))] pub struct Leaf { /// CurView from leader when proposing leaf - pub view_number: TYPES::Time, + view_number: TYPES::Time, /// Per spec, justification - pub justify_qc: QuorumCertificate, + justify_qc: QuorumCertificate, /// The hash of the parent `Leaf` /// So we can ask if it extends - pub parent_commitment: Commitment, + parent_commitment: Commitment, /// Block header. - pub block_header: TYPES::BlockHeader, + block_header: TYPES::BlockHeader, + + /// Optional upgrade certificate, if one was attached to the quorum proposal for this view. + upgrade_certificate: Option>, /// Optional block payload. /// /// It may be empty for nodes not in the DA committee. - pub block_payload: Option, + block_payload: Option, } impl PartialEq for Leaf { @@ -442,6 +445,7 @@ impl Leaf { view_number: TYPES::Time::genesis(), justify_qc: QuorumCertificate::::genesis(), parent_commitment: fake_commitment(), + upgrade_certificate: None, block_header: block_header.clone(), block_payload: Some(payload), } @@ -461,6 +465,10 @@ impl Leaf { pub fn get_justify_qc(&self) -> QuorumCertificate { self.justify_qc.clone() } + /// The QC linking this leaf to its parent in the chain. + pub fn get_upgrade_certificate(&self) -> Option> { + self.upgrade_certificate.clone() + } /// Commitment to this leaf's parent. pub fn get_parent_commitment(&self) -> Commitment { self.parent_commitment @@ -567,29 +575,40 @@ pub fn serialize_signature2( impl Committable for Leaf { fn commit(&self) -> commit::Commitment { - let signatures_bytes = if self.justify_qc.is_genesis { - let mut bytes = vec![]; - bytes.extend("genesis".as_bytes()); - bytes - } else { - serialize_signature2::(self.justify_qc.signatures.as_ref().unwrap()) - }; - // Skip the transaction commitments, so that the repliacs can reconstruct the leaf. RawCommitmentBuilder::new("leaf commitment") .u64_field("view number", *self.view_number) .u64_field("block number", self.get_height()) .field("parent Leaf commitment", self.parent_commitment) - .constant_str("block payload commitment") - .fixed_size_bytes(self.get_payload_commitment().as_ref().as_ref()) - .constant_str("justify_qc view number") - .u64(*self.justify_qc.view_number) - .field( - "justify_qc leaf commitment", - self.justify_qc.get_data().leaf_commit, - ) - .constant_str("justify_qc signatures") - .var_size_bytes(&signatures_bytes) + .fixed_size_field("block payload commitment", self.get_payload_commitment().as_ref().as_ref()) + .field("justify qc", self.justify_qc.commit()) + .optional("upgrade certificate", &self.upgrade_certificate) .finalize() } } + + +impl Leaf { + pub fn from_proposal(proposal: &Proposal>) -> Self { + Self::from_quorum_proposal(&proposal.data) + } + + pub fn from_quorum_proposal(quorum_proposal: &QuorumProposal) -> Self { + // WARNING: Do NOT change this to a wildcard match, or reference the fields directly in the construction of the leaf. + // The point of this match is that we will get a compile-time error if we add a field without updating this. + let QuorumProposal { view_number, justify_qc, block_header, upgrade_certificate, proposal_certificate: _ } = quorum_proposal; + Leaf { + view_number: *view_number, + justify_qc: justify_qc.clone(), + parent_commitment: justify_qc.get_data().leaf_commit, + block_header: block_header.clone(), + upgrade_certificate: upgrade_certificate.clone(), + block_payload: None, + } + } + + pub fn commit_from_proposal(proposal: &Proposal>) -> Commitment { + Leaf::from_proposal(proposal).commit() + } +} + diff --git a/crates/types/src/simple_certificate.rs b/crates/types/src/simple_certificate.rs index 1592583e90..0031f3b644 100644 --- a/crates/types/src/simple_certificate.rs +++ b/crates/types/src/simple_certificate.rs @@ -10,7 +10,7 @@ use commit::{Commitment, CommitmentBoundsArkless, Committable}; use ethereum_types::U256; use crate::{ - data::Leaf, + data::{Leaf, serialize_signature2}, simple_vote::{ DAData, QuorumData, TimeoutData, UpgradeProposalData, ViewSyncCommitData, ViewSyncFinalizeData, ViewSyncPreCommitData, Voteable, @@ -77,6 +77,22 @@ pub struct SimpleCertificate, } +impl> Committable for SimpleCertificate { + fn commit(&self) -> Commitment { + let signature_bytes = match self.signatures.as_ref() { + Some(sigs) => serialize_signature2::(sigs), + None => vec![], + }; + commit::RawCommitmentBuilder::new("Certificate") + .field("data", self.data.commit()) + .field("vote_commitment", self.vote_commitment) + .field("view number", self.view_number.commit()) + .var_size_field("signatures", &signature_bytes) + .fixed_size_field("is genesis", &[self.is_genesis as u8]) + .finalize() + } +} + impl> Certificate for SimpleCertificate { diff --git a/crates/types/src/simple_vote.rs b/crates/types/src/simple_vote.rs index 3573ad4628..faa55c870b 100644 --- a/crates/types/src/simple_vote.rs +++ b/crates/types/src/simple_vote.rs @@ -162,7 +162,7 @@ impl SimpleVote { impl Committable for QuorumData { fn commit(&self) -> Commitment { - commit::RawCommitmentBuilder::new("Yes Vote") + commit::RawCommitmentBuilder::new("Quorum data") .var_size_bytes(self.leaf_commit.as_ref()) .finalize() } @@ -170,7 +170,7 @@ impl Committable for QuorumData { impl Committable for TimeoutData { fn commit(&self) -> Commitment { - commit::RawCommitmentBuilder::new("Timeout Vote") + commit::RawCommitmentBuilder::new("Timeout data") .u64(*self.view) .finalize() } @@ -178,7 +178,7 @@ impl Committable for TimeoutData { impl Committable for DAData { fn commit(&self) -> Commitment { - commit::RawCommitmentBuilder::new("DA Vote") + commit::RawCommitmentBuilder::new("DA data") .var_size_bytes(self.payload_commit.as_ref()) .finalize() } @@ -186,7 +186,7 @@ impl Committable for DAData { impl Committable for VIDData { fn commit(&self) -> Commitment { - commit::RawCommitmentBuilder::new("VID Vote") + commit::RawCommitmentBuilder::new("VID data") .var_size_bytes(self.payload_commit.as_ref()) .finalize() } @@ -194,7 +194,7 @@ impl Committable for VIDData { impl Committable for UpgradeProposalData { fn commit(&self) -> Commitment { - let builder = commit::RawCommitmentBuilder::new("Upgrade Vote"); + let builder = commit::RawCommitmentBuilder::new("Upgrade data"); builder .u64(*self.new_version_first_block) .u64(*self.old_version_last_block)