Skip to content

Commit

Permalink
fix sync committee rotation
Browse files Browse the repository at this point in the history
  • Loading branch information
Wizdave97 committed Oct 9, 2023
1 parent 82fc7a1 commit cc7b436
Show file tree
Hide file tree
Showing 7 changed files with 242 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ pub mod mainnet {
pub mod devnet {
use super::*;
use hex_literal::hex;
pub const SLOTS_PER_EPOCH: Slot = 6;
pub const SLOTS_PER_EPOCH: Slot = 12;
pub const GENESIS_VALIDATORS_ROOT: [u8; 32] =
hex_literal::hex!("83431ec7fcf92cfc44947fc0418e831c25e1d0806590231c439830db7ad54fda");
pub const BELLATRIX_FORK_VERSION: Version = hex!("52525502");
Expand All @@ -130,5 +130,5 @@ pub mod devnet {
pub const BELLATRIX_FORK_EPOCH: Epoch = 0;
pub const CAPELLA_FORK_EPOCH: Epoch = 2;
pub const CAPELLA_FORK_VERSION: Version = hex!("52525503");
pub const EPOCHS_PER_SYNC_COMMITTEE_PERIOD: Epoch = 4;
pub const EPOCHS_PER_SYNC_COMMITTEE_PERIOD: Epoch = 3;
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ pub struct VerifierState {
pub latest_finalized_epoch: u64,
/// Sync committees corresponding to the finalized header
pub current_sync_committee: SyncCommittee<SYNC_COMMITTEE_SIZE>,
/// Committee for the next sync period
pub next_sync_committee: SyncCommittee<SYNC_COMMITTEE_SIZE>,
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
consensus_types::ForkData,
constants::{
Domain, Root, Slot, Version, ALTAIR_FORK_EPOCH, ALTAIR_FORK_VERSION, BELLATRIX_FORK_EPOCH,
Domain, Root, Version, ALTAIR_FORK_EPOCH, ALTAIR_FORK_VERSION, BELLATRIX_FORK_EPOCH,
BELLATRIX_FORK_VERSION, CAPELLA_FORK_EPOCH, CAPELLA_FORK_VERSION,
EPOCHS_PER_SYNC_COMMITTEE_PERIOD, GENESIS_FORK_VERSION, SLOTS_PER_EPOCH,
},
Expand All @@ -11,10 +11,9 @@ use alloc::{vec, vec::Vec};
use anyhow::anyhow;
use ssz_rs::prelude::*;

/// Returns true if the next epoch is the start of a new sync committee period
pub fn should_get_sync_committee_update(slot: Slot) -> bool {
let next_epoch = compute_epoch_at_slot(slot) + 1;
next_epoch % EPOCHS_PER_SYNC_COMMITTEE_PERIOD == 0
/// Returns true if sync committee update is required
pub fn should_have_sync_committee_update(state_period: u64, signature_period: u64) -> bool {
state_period != signature_period
}

/// Return the sync committee period at the given ``epoch``
Expand Down
101 changes: 94 additions & 7 deletions parachain/modules/consensus/sync-committee/prover/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use sync_committee_primitives::{
consensus_types::{
BeaconBlock, BeaconBlockHeader, BeaconState, Checkpoint, SyncCommittee, Validator,
},
constants::EPOCHS_PER_SYNC_COMMITTEE_PERIOD,
types::VerifierState,
};

Expand Down Expand Up @@ -44,7 +45,7 @@ use sync_committee_primitives::{
},
util::{
compute_epoch_at_slot, compute_sync_committee_period_at_slot,
should_get_sync_committee_update,
should_have_sync_committee_update,
},
};

Expand Down Expand Up @@ -223,10 +224,14 @@ impl SyncCommitteeProver {
format!("{}{}", self.node_url.clone(), path)
}

/// Fetches the latest finality update that can be verified by (state_period..=state_period+1)
/// latest_block_id is an optional block id where to start the signature block search, if absent
/// we use `head`
pub async fn fetch_light_client_update(
&self,
client_state: VerifierState,
finality_checkpoint: Checkpoint,
latest_block_id: Option<&str>,
debug_target: &str,
) -> Result<Option<VerifierStateUpdate>, anyhow::Error> {
if finality_checkpoint.root == Node::default() ||
Expand All @@ -237,7 +242,7 @@ impl SyncCommitteeProver {

debug!(target: debug_target, "A new epoch has been finalized {}", finality_checkpoint.epoch);
// Find the highest block with the a threshhold number of sync committee signatures
let latest_header = self.fetch_header("head").await?;
let latest_header = self.fetch_header(latest_block_id.unwrap_or("head")).await?;
let latest_root = latest_header.clone().hash_tree_root()?;
let get_block_id = |root: Root| {
let mut block_id = hex::encode(root.0.to_vec());
Expand All @@ -257,7 +262,7 @@ impl SyncCommitteeProver {
let parent_block_finality_checkpoint =
self.fetch_finalized_checkpoint(Some(&parent_state_id)).await?.finalized;
if parent_block_finality_checkpoint.epoch <= client_state.latest_finalized_epoch {
debug!(target: "prover", "Signature block search has reached an invalid epoch {} finalized_block_epoch {}", compute_epoch_at_slot(block.slot), finality_checkpoint.epoch);
debug!(target: "prover", "Signature block search has reached an invalid epoch {} latest finalized_block_epoch {}", parent_block_finality_checkpoint.epoch, client_state.latest_finalized_epoch);
return Ok(None)
}

Expand Down Expand Up @@ -292,14 +297,96 @@ impl SyncCommitteeProver {

let execution_payload_proof = prove_execution_payload(&mut finalized_state)?;

let sync_committee_update = if should_get_sync_committee_update(attested_state.slot) {
let signature_period = compute_sync_committee_period_at_slot(block.slot);
let sync_committee_update =
if should_have_sync_committee_update(state_period, signature_period) {
let sync_committee_proof = prove_sync_committee_update(&mut attested_state)?;
Some(SyncCommitteeUpdate {
next_sync_committee: attested_state.next_sync_committee,
next_sync_committee_branch: sync_committee_proof,
})
} else {
None
};

// construct light client
let light_client_update = VerifierStateUpdate {
attested_header,
sync_committee_update,
finalized_header,
execution_payload: execution_payload_proof,
finality_proof,
sync_aggregate: block.body.sync_aggregate,
signature_slot: block.slot,
};

Ok(Some(light_client_update))
}

pub async fn latest_update_for_period(
&self,
period: u64,
) -> Result<VerifierStateUpdate, anyhow::Error> {
let mut higest_slot_in_epoch = ((period * EPOCHS_PER_SYNC_COMMITTEE_PERIOD) *
SLOTS_PER_EPOCH) +
(EPOCHS_PER_SYNC_COMMITTEE_PERIOD * SLOTS_PER_EPOCH) -
1;
let mut count = 0;
// Some slots are empty so we'll use a loop to fetch the highest available slot in an epoch
let mut block = loop {
// Prevent an infinite loop
if count == 100 {
return Err(anyhow!("Error fetching blocks from selected epoch"))
}

if let Ok(block) = self.fetch_block(&higest_slot_in_epoch.to_string()).await {
break block
} else {
higest_slot_in_epoch -= 1;
count += 1;
}
};
let min_signatures = (2 * SYNC_COMMITTEE_SIZE) / 3;
let get_block_id = |root: Root| {
let mut block_id = hex::encode(root.0.to_vec());
block_id.insert_str(0, "0x");
block_id
};
loop {
let num_signatures = block.body.sync_aggregate.sync_committee_bits.count_ones();
if num_signatures >= min_signatures {
break
}

let parent_root = block.parent_root;
let parent_block_id = get_block_id(parent_root);
let parent_block = self.fetch_block(&parent_block_id).await?;

block = parent_block;
}

let attested_block_id = get_block_id(block.parent_root);

let attested_header = self.fetch_header(&attested_block_id).await?;
let mut attested_state =
self.fetch_beacon_state(&get_block_id(attested_header.state_root)).await?;
let finalized_block_id = get_block_id(attested_state.finalized_checkpoint.root);
let finalized_header = self.fetch_header(&finalized_block_id).await?;
let mut finalized_state =
self.fetch_beacon_state(&get_block_id(finalized_header.state_root)).await?;
let finality_proof = FinalityProof {
epoch: attested_state.finalized_checkpoint.epoch,
finality_branch: prove_finalized_header(&mut attested_state)?,
};

let execution_payload_proof = prove_execution_payload(&mut finalized_state)?;

let sync_committee_update = {
let sync_committee_proof = prove_sync_committee_update(&mut attested_state)?;
Some(SyncCommitteeUpdate {
next_sync_committee: attested_state.next_sync_committee,
next_sync_committee_branch: sync_committee_proof,
})
} else {
None
};

// construct light client
Expand All @@ -313,7 +400,7 @@ impl SyncCommitteeProver {
signature_slot: block.slot,
};

Ok(Some(light_client_update))
Ok(light_client_update)
}
}

Expand Down
132 changes: 128 additions & 4 deletions parachain/modules/consensus/sync-committee/prover/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,125 @@ async fn test_sync_committee_update_proof() {
assert!(is_merkle_branch_valid);
}

#[cfg(test)]
#[allow(non_snake_case)]
#[tokio::test]
async fn test_client_sync() {
let sync_committee_prover = setup_prover();
let start_period = 810;
let end_period = 815;
let starting_slot = ((start_period * EPOCHS_PER_SYNC_COMMITTEE_PERIOD) * SLOTS_PER_EPOCH) +
(EPOCHS_PER_SYNC_COMMITTEE_PERIOD * SLOTS_PER_EPOCH) -
1;
let block_header =
sync_committee_prover.fetch_header(&starting_slot.to_string()).await.unwrap();

let state = sync_committee_prover
.fetch_beacon_state(&block_header.slot.to_string())
.await
.unwrap();

let mut client_state = VerifierState {
finalized_header: block_header.clone(),
latest_finalized_epoch: compute_epoch_at_slot(block_header.slot),
current_sync_committee: state.current_sync_committee,
next_sync_committee: state.next_sync_committee,
};

let mut next_period = start_period + 1;
loop {
if next_period > end_period {
break
}
let update = sync_committee_prover.latest_update_for_period(next_period).await.unwrap();
dbg!(&update);
client_state = verify_sync_committee_attestation(client_state, update).unwrap();
next_period += 1;
}

println!("Sync completed");
}

#[cfg(test)]
#[allow(non_snake_case)]
#[tokio::test]
async fn test_sync_committee_hand_offs() {
let sync_committee_prover = setup_prover();
let state_period = 805;
let signature_period = 806;
let starting_slot = ((state_period * EPOCHS_PER_SYNC_COMMITTEE_PERIOD) * SLOTS_PER_EPOCH) + 1;
let block_header =
sync_committee_prover.fetch_header(&starting_slot.to_string()).await.unwrap();

let state = sync_committee_prover
.fetch_beacon_state(&block_header.slot.to_string())
.await
.unwrap();

let mut client_state = VerifierState {
finalized_header: block_header.clone(),
latest_finalized_epoch: compute_epoch_at_slot(block_header.slot),
current_sync_committee: state.current_sync_committee,
next_sync_committee: state.next_sync_committee,
};

// Verify an update from state_period + 1
let latest_block_id = {
let slot = ((signature_period * EPOCHS_PER_SYNC_COMMITTEE_PERIOD) * SLOTS_PER_EPOCH) +
((EPOCHS_PER_SYNC_COMMITTEE_PERIOD * SLOTS_PER_EPOCH) / 2);
slot.to_string()
};

let finalized_checkpoint = sync_committee_prover
.fetch_finalized_checkpoint(Some("head"))
.await
.unwrap()
.finalized;

let update = sync_committee_prover
.fetch_light_client_update(
client_state.clone(),
finalized_checkpoint.clone(),
Some(&latest_block_id),
"prover",
)
.await
.unwrap()
.unwrap();
assert!(update.sync_committee_update.is_some());
client_state = verify_sync_committee_attestation(client_state, update).unwrap();

// Verify block in the current state_period
let latest_block_id = {
let slot = ((signature_period * EPOCHS_PER_SYNC_COMMITTEE_PERIOD) * SLOTS_PER_EPOCH) +
(EPOCHS_PER_SYNC_COMMITTEE_PERIOD * SLOTS_PER_EPOCH) -
1;
slot.to_string()
};

let update = sync_committee_prover
.fetch_light_client_update(
client_state.clone(),
finalized_checkpoint,
Some(&latest_block_id),
"prover",
)
.await
.unwrap()
.unwrap();
assert!(update.sync_committee_update.is_none());
client_state = verify_sync_committee_attestation(client_state, update).unwrap();

let next_period = signature_period + 1;
let next_period_slot = ((next_period * EPOCHS_PER_SYNC_COMMITTEE_PERIOD) * SLOTS_PER_EPOCH) + 1;
let beacon_state = sync_committee_prover
.fetch_beacon_state(&next_period_slot.to_string())
.await
.unwrap();

assert_eq!(client_state.next_sync_committee, beacon_state.current_sync_committee);
}

#[cfg(test)]
#[allow(non_snake_case)]
#[tokio::test]
Expand All @@ -251,7 +370,7 @@ async fn test_prover() {
let sync_committee_prover = setup_prover();
let node_url =
format!("{}/eth/v1/events?topics=finalized_checkpoint", sync_committee_prover.node_url);
let block_header = sync_committee_prover.fetch_header("head").await.unwrap();
let block_header = sync_committee_prover.fetch_header("finalized").await.unwrap();

let state = sync_committee_prover
.fetch_beacon_state(&block_header.slot.to_string())
Expand All @@ -275,7 +394,7 @@ async fn test_prover() {
let checkpoint =
Checkpoint { epoch: message.epoch.parse().unwrap(), root: message.block };
let light_client_update = if let Some(update) = sync_committee_prover
.fetch_light_client_update(client_state.clone(), checkpoint, "prover")
.fetch_light_client_update(client_state.clone(), checkpoint, None, "prover")
.await
.unwrap()
{
Expand All @@ -284,6 +403,12 @@ async fn test_prover() {
continue
};

if light_client_update.sync_committee_update.is_some() {
println!("Sync committee update present");
dbg!(light_client_update.attested_header.slot);
dbg!(light_client_update.finalized_header.slot);
dbg!(client_state.finalized_header.slot);
}
let encoded = light_client_update.encode();
let decoded = VerifierStateUpdate::decode(&mut &*encoded).unwrap();
assert_eq!(light_client_update, decoded);
Expand Down Expand Up @@ -377,7 +502,6 @@ pub struct EventResponse {

fn setup_prover() -> SyncCommitteeProver {
dotenv::dotenv().ok();
let consensus_url =
std::env::var("CONSENSUS_NODE_URL").unwrap_or("http://localhost:3500".to_string());
let consensus_url = std::env::var("BEACON_URL").unwrap_or("http://localhost:3500".to_string());
SyncCommitteeProver::new(consensus_url)
}
Loading

0 comments on commit cc7b436

Please sign in to comment.