Skip to content

Commit

Permalink
[Epoch Sync] Add versioning to EpochSyncProof. (#12336)
Browse files Browse the repository at this point in the history
Closes #12310 

Versioning is needed or else it would be difficult later to change
EpochSyncProof format at all, since it's used both for network and for
storage.

This doesn't specify *how* to deal with a new version, only that it
opens the doors for us to do that in the future.
  • Loading branch information
robin-near authored Oct 31, 2024
1 parent a8158f4 commit d73041c
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 15 deletions.
2 changes: 1 addition & 1 deletion chain/chain/src/store_validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ impl StoreValidator {
.get_ser::<EpochSyncProof>(DBCol::EpochSyncProof, &[])
.expect("Store IO error when getting EpochSyncProof")
.map(|epoch_sync_proof| {
epoch_sync_proof.current_epoch.first_block_header_in_epoch.height()
epoch_sync_proof.into_v1().current_epoch.first_block_header_in_epoch.height()
});
StoreValidator {
me,
Expand Down
23 changes: 14 additions & 9 deletions chain/client/src/sync/epoch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use near_primitives::epoch_info::EpochInfo;
use near_primitives::epoch_manager::AGGREGATOR_KEY;
use near_primitives::epoch_sync::{
CompressedEpochSyncProof, EpochSyncProof, EpochSyncProofCurrentEpochData,
EpochSyncProofEpochData, EpochSyncProofLastEpochData,
EpochSyncProofEpochData, EpochSyncProofLastEpochData, EpochSyncProofV1,
};
use near_primitives::hash::CryptoHash;
use near_primitives::merkle::PartialMerkleTree;
Expand Down Expand Up @@ -63,7 +63,8 @@ impl EpochSync {
) -> Self {
let epoch_sync_proof_we_used_to_bootstrap = store
.get_ser::<EpochSyncProof>(DBCol::EpochSyncProof, &[])
.expect("IO error querying epoch sync proof");
.expect("IO error querying epoch sync proof")
.map(|proof| proof.into_v1());
let my_own_epoch_sync_boundary_block_header = epoch_sync_proof_we_used_to_bootstrap
.map(|proof| proof.current_epoch.first_block_header_in_epoch);

Expand Down Expand Up @@ -223,8 +224,9 @@ impl EpochSync {
// If we have an existing (possibly and likely outdated) EpochSyncProof stored on disk,
// the last epoch we have a proof for is the "previous epoch" included in that EpochSyncProof.
// Otherwise, the last epoch we have a "proof" for is the genesis epoch.
let existing_epoch_sync_proof =
store.get_ser::<EpochSyncProof>(DBCol::EpochSyncProof, &[])?;
let existing_epoch_sync_proof = store
.get_ser::<EpochSyncProof>(DBCol::EpochSyncProof, &[])?
.map(|proof| proof.into_v1());
let last_epoch_we_have_proof_for = existing_epoch_sync_proof
.as_ref()
.and_then(|existing_proof| {
Expand All @@ -242,7 +244,7 @@ impl EpochSync {
// If the proof we stored is for the same epoch as current or older, then just return that.
if current_epoch_info.epoch_height() <= last_epoch_height_we_have_proof_for {
if let Some(existing_proof) = existing_epoch_sync_proof {
return Ok(existing_proof);
return Ok(EpochSyncProof::V1(existing_proof));
}
// Corner case for if the current epoch is genesis or right after genesis.
return Err(Error::Other("Not enough epochs after genesis to epoch sync".to_string()));
Expand Down Expand Up @@ -340,7 +342,7 @@ impl EpochSync {
.into_iter()
.chain(all_epochs_since_last_proof.into_iter())
.collect();
let proof = EpochSyncProof {
let proof = EpochSyncProofV1 {
all_epochs: all_epochs_including_old_proof,
last_epoch: EpochSyncProofLastEpochData {
epoch_info: prev_epoch_info,
Expand All @@ -360,7 +362,7 @@ impl EpochSync {
},
};

Ok(proof)
Ok(EpochSyncProof::V1(proof))
}

/// Get all the past epoch data needed for epoch sync, between `after_epoch` and `next_epoch`
Expand Down Expand Up @@ -646,6 +648,7 @@ impl EpochSync {
source_peer: PeerId,
epoch_manager: &dyn EpochManagerAdapter,
) -> Result<(), Error> {
let proof = proof.into_v1();
if let SyncStatus::EpochSync(status) = status {
if status.source_peer_id != source_peer {
tracing::warn!("Ignoring epoch sync proof from unexpected peer: {}", source_peer);
Expand Down Expand Up @@ -688,7 +691,9 @@ impl EpochSync {

// Store the EpochSyncProof, so that this node can derive a more recent EpochSyncProof
// to faciliate epoch sync of other nodes.
let proof = EpochSyncProof::V1(proof); // convert to avoid cloning
store_update.set_ser(DBCol::EpochSyncProof, &[], &proof)?;
let proof = proof.into_v1();

let last_header = proof.current_epoch.first_block_header_in_epoch;
let mut update = chain.mut_chain_store().store_update();
Expand Down Expand Up @@ -770,10 +775,10 @@ impl EpochSync {

fn verify_proof(
&self,
proof: &EpochSyncProof,
proof: &EpochSyncProofV1,
epoch_manager: &dyn EpochManagerAdapter,
) -> Result<(), Error> {
let EpochSyncProof { all_epochs, last_epoch, current_epoch } = proof;
let EpochSyncProofV1 { all_epochs, last_epoch, current_epoch } = proof;
if all_epochs.len() < 2 {
return Err(Error::InvalidEpochSyncProof(
"need at least two epochs in all_epochs".to_string(),
Expand Down
35 changes: 30 additions & 5 deletions core/primitives/src/epoch_sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,31 @@ use near_crypto::Signature;
use near_schema_checker_lib::ProtocolSchema;
use std::fmt::Debug;

/// Versioned enum for EpochSyncProof. Because this structure is sent over the network and also
/// persisted on disk, we want it to be deserializable even if the structure changes in the future.
/// There's no guarantee that there's any compatibility (most likely not), but being able to
/// deserialize it will allow us to modify the code in the future to properly perform any upgrades.
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, ProtocolSchema)]
pub enum EpochSyncProof {
V1(EpochSyncProofV1),
}

impl EpochSyncProof {
/// Right now this would never fail, but in the future this API can be changed.
pub fn into_v1(self) -> EpochSyncProofV1 {
match self {
EpochSyncProof::V1(v1) => v1,
}
}

/// Right now this would never fail, but in the future this API can be changed.
pub fn as_v1(&self) -> &EpochSyncProofV1 {
match self {
EpochSyncProof::V1(v1) => v1,
}
}
}

/// Proof that the blockchain history had progressed from the genesis to the
/// current epoch indicated in the proof.
///
Expand All @@ -18,8 +43,8 @@ use std::fmt::Debug;
/// - H: The last final block of the epoch
/// - H + 1: The second last block of the epoch
/// - H + 2: The last block of the epoch
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
pub struct EpochSyncProof {
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, ProtocolSchema)]
pub struct EpochSyncProofV1 {
/// All the relevant epochs, starting from the second epoch after genesis (i.e. genesis is
/// epoch EpochId::default, and then the next epoch after genesis is fully determined by
/// the genesis; after that would be the first epoch included here), to and including the
Expand Down Expand Up @@ -64,7 +89,7 @@ impl Debug for CompressedEpochSyncProof {
}

/// Data needed for each epoch covered in the epoch sync proof.
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, ProtocolSchema)]
pub struct EpochSyncProofEpochData {
/// The block producers and their stake, for this epoch. This is verified
/// against the `next_bp_hash` of the `last_final_block_header` of the epoch before this.
Expand Down Expand Up @@ -108,7 +133,7 @@ pub struct EpochSyncProofEpochData {
}

/// Data needed to initialize the epoch sync boundary.
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, ProtocolSchema)]
pub struct EpochSyncProofLastEpochData {
/// The following six fields are used to derive the epoch_sync_data_hash included in any
/// BlockHeaderV3. This is used to verify all the data we need around the epoch sync
Expand All @@ -122,7 +147,7 @@ pub struct EpochSyncProofLastEpochData {
}

/// Data needed to initialize the current epoch we're syncing to.
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, ProtocolSchema)]
pub struct EpochSyncProofCurrentEpochData {
/// The first block header that begins the epoch. It is proven using a merkle proof
/// against `last_final_block_header` in the current epoch data. Note that we cannot
Expand Down
1 change: 1 addition & 0 deletions integration-tests/src/test_loop/tests/epoch_sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,7 @@ fn sanity_check_epoch_sync_proof(
// epochs ago, i.e. the current epoch's previous previous epoch.
expected_epochs_ago: u64,
) {
let proof = proof.as_v1();
let epoch_height_of_final_block =
(final_head_height - genesis_config.genesis_height - 1) / genesis_config.epoch_length + 1;
let expected_current_epoch_height = epoch_height_of_final_block - expected_epochs_ago;
Expand Down
5 changes: 5 additions & 0 deletions tools/protocol-schema-check/res/protocol_schema.toml
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,11 @@ EpochInfoV2 = 2533281205
EpochInfoV3 = 91327628
EpochInfoV4 = 434230701
EpochSummary = 742414117
EpochSyncProof = 2656282615
EpochSyncProofCurrentEpochData = 3178856649
EpochSyncProofEpochData = 4024593770
EpochSyncProofLastEpochData = 2620439209
EpochSyncProofV1 = 3403222461
EpochValidatorInfo = 1479897921
ExecutionMetadata = 3853243413
ExecutionOutcome = 2925419955
Expand Down

0 comments on commit d73041c

Please sign in to comment.