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,
|
data_dir,
|
||||||
chain: base_config.chain,
|
chain: base_config.chain,
|
||||||
forks: base_config.forks,
|
forks: base_config.forks,
|
||||||
|
max_checkpoint_age: base_config.max_checkpoint_age,
|
||||||
};
|
};
|
||||||
|
|
||||||
Client::new(config)
|
Client::new(config)
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
#![feature(map_first_last)]
|
|
||||||
|
|
||||||
mod client;
|
mod client;
|
||||||
pub use crate::client::*;
|
pub use crate::client::*;
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ pub struct Config {
|
||||||
pub data_dir: Option<PathBuf>,
|
pub data_dir: Option<PathBuf>,
|
||||||
pub chain: ChainConfig,
|
pub chain: ChainConfig,
|
||||||
pub forks: Forks,
|
pub forks: Forks,
|
||||||
|
pub max_checkpoint_age: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
|
@ -93,6 +94,7 @@ impl Config {
|
||||||
checkpoint: self.checkpoint.clone(),
|
checkpoint: self.checkpoint.clone(),
|
||||||
chain: self.chain.clone(),
|
chain: self.chain.clone(),
|
||||||
forks: self.forks.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 checkpoint: Vec<u8>,
|
||||||
pub chain: ChainConfig,
|
pub chain: ChainConfig,
|
||||||
pub forks: Forks,
|
pub forks: Forks,
|
||||||
|
pub max_checkpoint_age: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mainnet() -> BaseConfig {
|
pub fn mainnet() -> BaseConfig {
|
||||||
|
@ -60,6 +61,7 @@ pub fn mainnet() -> BaseConfig {
|
||||||
fork_version: hex_str_to_bytes("0x02000000").unwrap(),
|
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(),
|
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
|
.await
|
||||||
.map_err(|_| eyre!("could not fetch bootstrap"))?;
|
.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(
|
let committee_valid = is_current_committee_proof_valid(
|
||||||
&bootstrap.header,
|
&bootstrap.header,
|
||||||
&mut bootstrap.current_sync_committee,
|
&mut bootstrap.current_sync_committee,
|
||||||
|
@ -493,6 +498,17 @@ impl<R: ConsensusRpc> ConsensusClient<R> {
|
||||||
|
|
||||||
Duration::seconds(next_update as i64)
|
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(
|
fn get_participating_keys(
|
||||||
|
@ -574,13 +590,14 @@ mod tests {
|
||||||
};
|
};
|
||||||
use config::{networks, Config};
|
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 base_config = networks::goerli();
|
||||||
let config = Config {
|
let config = Config {
|
||||||
consensus_rpc: String::new(),
|
consensus_rpc: String::new(),
|
||||||
execution_rpc: String::new(),
|
execution_rpc: String::new(),
|
||||||
chain: base_config.chain,
|
chain: base_config.chain,
|
||||||
forks: base_config.forks,
|
forks: base_config.forks,
|
||||||
|
max_checkpoint_age: if large_checkpoint_age { 123123123 } else { 123 },
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -590,13 +607,12 @@ mod tests {
|
||||||
|
|
||||||
let mut client = ConsensusClient::new("testdata/", &checkpoint, Arc::new(config)).unwrap();
|
let mut client = ConsensusClient::new("testdata/", &checkpoint, Arc::new(config)).unwrap();
|
||||||
client.bootstrap().await.unwrap();
|
client.bootstrap().await.unwrap();
|
||||||
|
|
||||||
client
|
client
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_verify_update() {
|
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 period = calc_sync_period(client.store.finalized_header.slot);
|
||||||
let updates = client
|
let updates = client
|
||||||
.rpc
|
.rpc
|
||||||
|
@ -610,7 +626,7 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_verify_update_invalid_committee() {
|
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 period = calc_sync_period(client.store.finalized_header.slot);
|
||||||
let updates = client
|
let updates = client
|
||||||
.rpc
|
.rpc
|
||||||
|
@ -630,7 +646,7 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_verify_update_invalid_finality() {
|
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 period = calc_sync_period(client.store.finalized_header.slot);
|
||||||
let updates = client
|
let updates = client
|
||||||
.rpc
|
.rpc
|
||||||
|
@ -650,7 +666,7 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_verify_update_invalid_sig() {
|
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 period = calc_sync_period(client.store.finalized_header.slot);
|
||||||
let updates = client
|
let updates = client
|
||||||
.rpc
|
.rpc
|
||||||
|
@ -670,7 +686,7 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_verify_finality() {
|
async fn test_verify_finality() {
|
||||||
let mut client = get_client().await;
|
let mut client = get_client(true).await;
|
||||||
client.sync().await.unwrap();
|
client.sync().await.unwrap();
|
||||||
|
|
||||||
let update = client.rpc.get_finality_update().await.unwrap();
|
let update = client.rpc.get_finality_update().await.unwrap();
|
||||||
|
@ -680,7 +696,7 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_verify_finality_invalid_finality() {
|
async fn test_verify_finality_invalid_finality() {
|
||||||
let mut client = get_client().await;
|
let mut client = get_client(true).await;
|
||||||
client.sync().await.unwrap();
|
client.sync().await.unwrap();
|
||||||
|
|
||||||
let mut update = client.rpc.get_finality_update().await.unwrap();
|
let mut update = client.rpc.get_finality_update().await.unwrap();
|
||||||
|
@ -695,7 +711,7 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_verify_finality_invalid_sig() {
|
async fn test_verify_finality_invalid_sig() {
|
||||||
let mut client = get_client().await;
|
let mut client = get_client(true).await;
|
||||||
client.sync().await.unwrap();
|
client.sync().await.unwrap();
|
||||||
|
|
||||||
let mut update = client.rpc.get_finality_update().await.unwrap();
|
let mut update = client.rpc.get_finality_update().await.unwrap();
|
||||||
|
@ -710,7 +726,7 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_verify_optimistic() {
|
async fn test_verify_optimistic() {
|
||||||
let mut client = get_client().await;
|
let mut client = get_client(true).await;
|
||||||
client.sync().await.unwrap();
|
client.sync().await.unwrap();
|
||||||
|
|
||||||
let update = client.rpc.get_optimistic_update().await.unwrap();
|
let update = client.rpc.get_optimistic_update().await.unwrap();
|
||||||
|
@ -719,7 +735,7 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_verify_optimistic_invalid_sig() {
|
async fn test_verify_optimistic_invalid_sig() {
|
||||||
let mut client = get_client().await;
|
let mut client = get_client(true).await;
|
||||||
client.sync().await.unwrap();
|
client.sync().await.unwrap();
|
||||||
|
|
||||||
let mut update = client.rpc.get_optimistic_update().await.unwrap();
|
let mut update = client.rpc.get_optimistic_update().await.unwrap();
|
||||||
|
@ -731,4 +747,10 @@ mod tests {
|
||||||
ConsensusError::InvalidSignature.to_string()
|
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),
|
InvalidHeaderHash(String, String),
|
||||||
#[error("payload not found for slot: {0}")]
|
#[error("payload not found for slot: {0}")]
|
||||||
PayloadNotFound(u64),
|
PayloadNotFound(u64),
|
||||||
|
#[error("checkpoint is too old")]
|
||||||
|
CheckpointTooOld,
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ async fn setup() -> ConsensusClient<MockRpc> {
|
||||||
execution_rpc: String::new(),
|
execution_rpc: String::new(),
|
||||||
chain: base_config.chain,
|
chain: base_config.chain,
|
||||||
forks: base_config.forks,
|
forks: base_config.forks,
|
||||||
|
max_checkpoint_age: 123123123,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue