feat: verify checkpoint has valid age (#105)
* check blockhash has valid timestamp * remove warn log * made checkpoint age req configurable * renamed method to make more sense * fixed broken tests * formatting * unit tests completed * removed needless imports * renaming vars
This commit is contained in:
parent
23bb207f1a
commit
eaf5605d4d
|
@ -166,6 +166,7 @@ impl ClientBuilder {
|
|||
data_dir,
|
||||
chain: base_config.chain,
|
||||
forks: base_config.forks,
|
||||
max_checkpoint_age: base_config.max_checkpoint_age,
|
||||
};
|
||||
|
||||
Client::new(config)
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#![feature(map_first_last)]
|
||||
|
||||
mod client;
|
||||
pub use crate::client::*;
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ pub struct Config {
|
|||
pub data_dir: Option<PathBuf>,
|
||||
pub chain: ChainConfig,
|
||||
pub forks: Forks,
|
||||
pub max_checkpoint_age: u64,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
|
@ -93,6 +94,7 @@ impl Config {
|
|||
checkpoint: self.checkpoint.clone(),
|
||||
chain: self.chain.clone(),
|
||||
forks: self.forks.clone(),
|
||||
max_checkpoint_age: self.max_checkpoint_age.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ pub struct BaseConfig {
|
|||
pub checkpoint: Vec<u8>,
|
||||
pub chain: ChainConfig,
|
||||
pub forks: Forks,
|
||||
pub max_checkpoint_age: u64,
|
||||
}
|
||||
|
||||
pub fn mainnet() -> BaseConfig {
|
||||
|
@ -60,6 +61,7 @@ pub fn mainnet() -> BaseConfig {
|
|||
fork_version: hex_str_to_bytes("0x02000000").unwrap(),
|
||||
},
|
||||
},
|
||||
max_checkpoint_age: 1_209_600, // 14 days
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -93,5 +95,6 @@ pub fn goerli() -> BaseConfig {
|
|||
fork_version: hex_str_to_bytes("0x02001020").unwrap(),
|
||||
},
|
||||
},
|
||||
max_checkpoint_age: 1_209_600, // 14 days
|
||||
}
|
||||
}
|
||||
|
|
|
@ -153,6 +153,11 @@ impl<R: ConsensusRpc> ConsensusClient<R> {
|
|||
.await
|
||||
.map_err(|_| eyre!("could not fetch bootstrap"))?;
|
||||
|
||||
let is_valid = self.is_valid_checkpoint(bootstrap.header.slot);
|
||||
if !is_valid {
|
||||
return Err(ConsensusError::CheckpointTooOld.into());
|
||||
}
|
||||
|
||||
let committee_valid = is_current_committee_proof_valid(
|
||||
&bootstrap.header,
|
||||
&mut bootstrap.current_sync_committee,
|
||||
|
@ -493,6 +498,17 @@ impl<R: ConsensusRpc> ConsensusClient<R> {
|
|||
|
||||
Duration::seconds(next_update as i64)
|
||||
}
|
||||
|
||||
// Determines blockhash_slot age and returns true if it is less than 14 days old
|
||||
fn is_valid_checkpoint(&self, blockhash_slot: u64) -> bool {
|
||||
let current_slot = self.expected_current_slot();
|
||||
let current_slot_timestamp = self.slot_timestamp(current_slot);
|
||||
let blockhash_slot_timestamp = self.slot_timestamp(blockhash_slot);
|
||||
|
||||
let slot_age = current_slot_timestamp - blockhash_slot_timestamp;
|
||||
|
||||
slot_age < self.config.max_checkpoint_age
|
||||
}
|
||||
}
|
||||
|
||||
fn get_participating_keys(
|
||||
|
@ -574,13 +590,14 @@ mod tests {
|
|||
};
|
||||
use config::{networks, Config};
|
||||
|
||||
async fn get_client() -> ConsensusClient<MockRpc> {
|
||||
async fn get_client(large_checkpoint_age: bool) -> ConsensusClient<MockRpc> {
|
||||
let base_config = networks::goerli();
|
||||
let config = Config {
|
||||
consensus_rpc: String::new(),
|
||||
execution_rpc: String::new(),
|
||||
chain: base_config.chain,
|
||||
forks: base_config.forks,
|
||||
max_checkpoint_age: if large_checkpoint_age { 123123123 } else { 123 },
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
|
@ -590,13 +607,12 @@ mod tests {
|
|||
|
||||
let mut client = ConsensusClient::new("testdata/", &checkpoint, Arc::new(config)).unwrap();
|
||||
client.bootstrap().await.unwrap();
|
||||
|
||||
client
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_verify_update() {
|
||||
let client = get_client().await;
|
||||
let client = get_client(true).await;
|
||||
let period = calc_sync_period(client.store.finalized_header.slot);
|
||||
let updates = client
|
||||
.rpc
|
||||
|
@ -610,7 +626,7 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_verify_update_invalid_committee() {
|
||||
let client = get_client().await;
|
||||
let client = get_client(true).await;
|
||||
let period = calc_sync_period(client.store.finalized_header.slot);
|
||||
let updates = client
|
||||
.rpc
|
||||
|
@ -630,7 +646,7 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_verify_update_invalid_finality() {
|
||||
let client = get_client().await;
|
||||
let client = get_client(true).await;
|
||||
let period = calc_sync_period(client.store.finalized_header.slot);
|
||||
let updates = client
|
||||
.rpc
|
||||
|
@ -650,7 +666,7 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_verify_update_invalid_sig() {
|
||||
let client = get_client().await;
|
||||
let client = get_client(true).await;
|
||||
let period = calc_sync_period(client.store.finalized_header.slot);
|
||||
let updates = client
|
||||
.rpc
|
||||
|
@ -670,7 +686,7 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_verify_finality() {
|
||||
let mut client = get_client().await;
|
||||
let mut client = get_client(true).await;
|
||||
client.sync().await.unwrap();
|
||||
|
||||
let update = client.rpc.get_finality_update().await.unwrap();
|
||||
|
@ -680,7 +696,7 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_verify_finality_invalid_finality() {
|
||||
let mut client = get_client().await;
|
||||
let mut client = get_client(true).await;
|
||||
client.sync().await.unwrap();
|
||||
|
||||
let mut update = client.rpc.get_finality_update().await.unwrap();
|
||||
|
@ -695,7 +711,7 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_verify_finality_invalid_sig() {
|
||||
let mut client = get_client().await;
|
||||
let mut client = get_client(true).await;
|
||||
client.sync().await.unwrap();
|
||||
|
||||
let mut update = client.rpc.get_finality_update().await.unwrap();
|
||||
|
@ -710,7 +726,7 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_verify_optimistic() {
|
||||
let mut client = get_client().await;
|
||||
let mut client = get_client(true).await;
|
||||
client.sync().await.unwrap();
|
||||
|
||||
let update = client.rpc.get_optimistic_update().await.unwrap();
|
||||
|
@ -719,7 +735,7 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_verify_optimistic_invalid_sig() {
|
||||
let mut client = get_client().await;
|
||||
let mut client = get_client(true).await;
|
||||
client.sync().await.unwrap();
|
||||
|
||||
let mut update = client.rpc.get_optimistic_update().await.unwrap();
|
||||
|
@ -731,4 +747,10 @@ mod tests {
|
|||
ConsensusError::InvalidSignature.to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[should_panic]
|
||||
async fn test_verify_checkpoint_age_invalid() {
|
||||
get_client(false).await;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,4 +22,6 @@ pub enum ConsensusError {
|
|||
InvalidHeaderHash(String, String),
|
||||
#[error("payload not found for slot: {0}")]
|
||||
PayloadNotFound(u64),
|
||||
#[error("checkpoint is too old")]
|
||||
CheckpointTooOld,
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ async fn setup() -> ConsensusClient<MockRpc> {
|
|||
execution_rpc: String::new(),
|
||||
chain: base_config.chain,
|
||||
forks: base_config.forks,
|
||||
max_checkpoint_age: 123123123,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue