From f52f2a61b9384f36d7fb1ff80ac99483921553e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ceyhun=20=C5=9Een?= Date: Thu, 10 Oct 2024 15:03:56 +0300 Subject: [PATCH 1/8] tmp clementine verifier --- src/clementine/mod.rs | 1 + src/clementine/verifier.rs | 204 +++++++++++++++++++++++++++++++++++++ src/config/clementine.rs | 119 ++++++++++++++++++++++ src/config/mod.rs | 2 + src/lib.rs | 1 + src/node.rs | 2 + 6 files changed, 329 insertions(+) create mode 100644 src/clementine/mod.rs create mode 100644 src/clementine/verifier.rs create mode 100644 src/config/clementine.rs diff --git a/src/clementine/mod.rs b/src/clementine/mod.rs new file mode 100644 index 0000000..9a07220 --- /dev/null +++ b/src/clementine/mod.rs @@ -0,0 +1 @@ +pub mod verifier; diff --git a/src/clementine/verifier.rs b/src/clementine/verifier.rs new file mode 100644 index 0000000..5148f2c --- /dev/null +++ b/src/clementine/verifier.rs @@ -0,0 +1,204 @@ +use std::path::PathBuf; +use std::sync::Arc; +use std::time::{Duration, Instant}; + +use anyhow::{bail, Context}; +use async_trait::async_trait; +use futures::TryStreamExt; +use tokio::process::Command; +use tokio::time::sleep; + +use crate::Result; +use crate::client::Client; +use crate::config::ClementineConfig; +use crate::docker::DockerEnv; +use crate::node::NodeKind; +use crate::traits::{ContainerSpawnOutput, LogProvider, NodeT, Restart, SpawnOutput}; + +pub struct VerifierNode { + spawn_output: SpawnOutput, + pub config: ClementineConfig, + docker_env: Arc>, + client: Client, +} + +impl VerifierNode { + pub async fn new(config: &ClementineConfig, docker: Arc>) -> Result { + let spawn_output = Self::spawn(config, &docker).await?; + + Ok(Self { + spawn_output, + config: config.clone(), + docker_env: docker, + client: Client::new(&config.client.host, config.client.port.try_into().unwrap())?, + }) + } + + async fn spawn( + config: &ClementineConfig, + docker: &Arc>, + ) -> Result { + match docker.as_ref() { + Some(docker) => docker.spawn(config.into()).await, + None => ::spawn(config), + } + } + + async fn wait_for_shutdown(&self) -> Result<()> { + let timeout_duration = Duration::from_secs(30); + let start = std::time::Instant::now(); + + while start.elapsed() < timeout_duration { + if !self.is_process_running().await? { + println!("Bridge backend has stopped successfully"); + return Ok(()); + } + sleep(Duration::from_millis(200)).await; + } + + bail!("Timeout waiting for bridge backend to stop") + } + + async fn is_process_running(&self) -> Result { + // let data_dir = &self.config.data_dir; + // let output = Command::new("pgrep") + // .args(["-f", &format!("bitcoind.*{}", data_dir.display())]) + // .output() + // .await?; + + // Ok(output.status.success()) + todo!() + } +} + +#[async_trait] +impl NodeT for VerifierNode { + type Config = ClementineConfig; + type Client = Client; + + fn spawn(config: &Self::Config) -> Result { + let env = config.get_env(); + println!("Running bridge backend with environment variables: {env:?}"); + + Command::new("npm run server:dev") + .kill_on_drop(true) + .env_clear() + .envs(env.clone()) + .spawn() + .context("Failed to spawn bridge backend server process") + .map(SpawnOutput::Child)?; + + Command::new("npm run worker:dev") + .kill_on_drop(true) + .env_clear() + .envs(env) + .spawn() + .context("Failed to spawn bridge backend worker process") + .map(SpawnOutput::Child) + } + + fn spawn_output(&mut self) -> &mut SpawnOutput { + &mut self.spawn_output + } + + async fn wait_for_ready(&self, timeout: Option) -> Result<()> { + println!("Waiting for ready"); + let start = Instant::now(); + let timeout = timeout.unwrap_or(Duration::from_secs(30)); + while start.elapsed() < timeout { + if true + // TODO: Do this check. + { + return Ok(()); + } + tokio::time::sleep(Duration::from_millis(500)).await; + } + anyhow::bail!("Node failed to become ready within the specified timeout") + } + + fn config_mut(&mut self) -> &mut Self::Config { + &mut self.config + } + + async fn stop(&mut self) -> Result<()> { + match self.spawn_output() { + SpawnOutput::Child(process) => { + process + .kill() + .await + .context("Failed to kill child process")?; + Ok(()) + } + SpawnOutput::Container(ContainerSpawnOutput { id, .. }) => { + std::println!("Stopping container {id}"); + let docker = bollard::Docker::connect_with_local_defaults() + .context("Failed to connect to Docker")?; + docker + .stop_container(id, Some(bollard::container::StopContainerOptions { t: 10 })) + .await + .context("Failed to stop Docker container")?; + Ok(()) + } + } + } + + fn client(&self) -> &Self::Client { + &self.client + } + + fn env(&self) -> Vec<(&'static str, &'static str)> { + // self.config.get_env() + todo!() + } + + fn config(&self) -> &::Config { + &self.config + } +} + +#[async_trait] +impl Restart for VerifierNode { + async fn wait_until_stopped(&mut self) -> Result<()> { + // self.client.stop().await?; + self.stop().await?; + + match &self.spawn_output { + SpawnOutput::Child(_) => self.wait_for_shutdown().await, + SpawnOutput::Container(output) => { + let Some(env) = self.docker_env.as_ref() else { + bail!("Missing docker environment") + }; + env.docker.stop_container(&output.id, None).await?; + + env.docker + .wait_container::(&output.id, None) + .try_collect::>() + .await?; + env.docker.remove_container(&output.id, None).await?; + println!("Docker container {} succesfully removed", output.id); + Ok(()) + } + } + } + + async fn start(&mut self, config: Option) -> Result<()> { + if let Some(config) = config { + self.config = config + } + self.spawn_output = Self::spawn(&self.config, &self.docker_env).await?; + + self.wait_for_ready(None).await?; + + Ok(()) + } +} + +impl LogProvider for VerifierNode { + fn kind(&self) -> NodeKind { + NodeKind::Verifier + } + + fn log_path(&self) -> PathBuf { + todo!() + } +} diff --git a/src/config/clementine.rs b/src/config/clementine.rs new file mode 100644 index 0000000..21c53fe --- /dev/null +++ b/src/config/clementine.rs @@ -0,0 +1,119 @@ +//! # Clementine Configuration Options + +use std::path::PathBuf; + +use bitcoin::{address::NetworkUnchecked, secp256k1, Amount, Network}; +use serde::{Deserialize, Serialize}; + +/// Clementine's configuration options. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ClementineClient { + /// Host of the operator or the verifier + pub host: String, + /// Port of the operator or the verifier + pub port: u16, + /// Bitcoin network to work on. + pub network: Network, + /// Secret key for the operator or the verifier. + pub secret_key: secp256k1::SecretKey, + /// Verifiers public keys. + pub verifiers_public_keys: Vec, + /// Number of verifiers. + pub num_verifiers: usize, + /// Operators x-only public keys. + pub operators_xonly_pks: Vec, + /// Operators wallet addresses. + pub operator_wallet_addresses: Vec>, + /// Number of operators. + pub num_operators: usize, + /// Operator's fee for withdrawal, in satoshis. + pub operator_withdrawal_fee_sats: Option, + /// Number of blocks after which user can take deposit back if deposit request fails. + pub user_takes_after: u32, + /// Number of blocks after which operator can take reimburse the bridge fund if they are honest. + pub operator_takes_after: u32, + /// Bridge amount in satoshis. + pub bridge_amount_sats: Amount, + /// Operator: number of kickoff UTXOs per funding transaction. + pub operator_num_kickoff_utxos_per_tx: usize, + /// Threshold for confirmation. + pub confirmation_threshold: u32, + /// Bitcoin remote procedure call URL. + pub bitcoin_rpc_url: String, + /// Bitcoin RPC user. + pub bitcoin_rpc_user: String, + /// Bitcoin RPC user password. + pub bitcoin_rpc_password: String, + /// All Secret keys. Just for testing purposes. + pub all_verifiers_secret_keys: Option>, + /// All Secret keys. Just for testing purposes. + pub all_operators_secret_keys: Option>, + /// Verifier endpoints. + pub verifier_endpoints: Option>, + /// PostgreSQL database host address. + pub db_host: String, + /// PostgreSQL database port. + pub db_port: usize, + /// PostgreSQL database user name. + pub db_user: String, + /// PostgreSQL database user password. + pub db_password: String, + /// PostgreSQL database name. + pub db_name: String, + /// Citrea RPC URL. + pub citrea_rpc_url: String, + /// Bridge contract address. + pub bridge_contract_address: String, +} + +impl Default for ClementineClient { + fn default() -> Self { + Self { + host: "127.0.0.1".to_string(), + port: 3030, + secret_key: secp256k1::SecretKey::new(&mut secp256k1::rand::thread_rng()), + verifiers_public_keys: vec![], + num_verifiers: 7, + operators_xonly_pks: vec![], + operator_wallet_addresses: vec![], + num_operators: 3, + operator_withdrawal_fee_sats: None, + user_takes_after: 5, + operator_takes_after: 5, + bridge_amount_sats: Amount::from_sat(100_000_000), + operator_num_kickoff_utxos_per_tx: 10, + confirmation_threshold: 1, + network: Network::Regtest, + bitcoin_rpc_url: "http://127.0.0.1:18443".to_string(), + bitcoin_rpc_user: "admin".to_string(), + bitcoin_rpc_password: "admin".to_string(), + all_verifiers_secret_keys: None, + all_operators_secret_keys: None, + verifier_endpoints: None, + db_host: "127.0.0.1".to_string(), + db_port: 5432, + db_user: "postgres".to_string(), + db_password: "postgres".to_string(), + db_name: "postgres".to_string(), + citrea_rpc_url: "http://127.0.0.1:12345".to_string(), + bridge_contract_address: "3100000000000000000000000000000000000002".to_string(), + } + } +} + +#[derive(Debug, Clone)] +pub struct ClementineConfig { + pub client: ClementineClient, + pub docker_image: Option, + pub data_dir: PathBuf, +} + +impl Default for ClementineConfig { + fn default() -> Self { + Self { + client: ClementineClient::default(), + docker_image: None, + data_dir: PathBuf::from("bridge_backend"), + } + } +} diff --git a/src/config/mod.rs b/src/config/mod.rs index 2d602e8..49badb2 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,4 +1,5 @@ mod bitcoin; +mod clementine; mod docker; mod rollup; mod test; @@ -7,6 +8,7 @@ mod utils; use std::path::PathBuf; +pub use clementine::{ClementineClient, ClementineConfig}; pub use bitcoin::BitcoinConfig; pub use docker::DockerConfig; pub use rollup::{default_rollup_config, RollupConfig}; diff --git a/src/lib.rs b/src/lib.rs index 3a11f88..16b28ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,5 +12,6 @@ pub mod sequencer; pub mod test_case; pub mod traits; mod utils; +pub mod clementine; pub type Result = anyhow::Result; diff --git a/src/node.rs b/src/node.rs index 8a35840..f0be166 100644 --- a/src/node.rs +++ b/src/node.rs @@ -30,6 +30,7 @@ pub enum NodeKind { LightClientProver, Sequencer, FullNode, + Verifier, } impl fmt::Display for NodeKind { @@ -40,6 +41,7 @@ impl fmt::Display for NodeKind { NodeKind::LightClientProver => write!(f, "light-client-prover"), NodeKind::Sequencer => write!(f, "sequencer"), NodeKind::FullNode => write!(f, "full-node"), + NodeKind::Verifier => write!(f, "verifier"), } } } From 04c6ea20b13911dfc3d28dcb4ba3c1de251edbfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ceyhun=20=C5=9Een?= Date: Thu, 10 Oct 2024 15:37:23 +0300 Subject: [PATCH 2/8] verifier: Add initial. --- src/clementine/mod.rs | 1 - src/clementine/verifier.rs | 204 ------------------------------------- src/config/clementine.rs | 2 +- src/config/mod.rs | 5 + src/lib.rs | 2 +- src/verifier.rs | 11 ++ 6 files changed, 18 insertions(+), 207 deletions(-) delete mode 100644 src/clementine/mod.rs delete mode 100644 src/clementine/verifier.rs create mode 100644 src/verifier.rs diff --git a/src/clementine/mod.rs b/src/clementine/mod.rs deleted file mode 100644 index 9a07220..0000000 --- a/src/clementine/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod verifier; diff --git a/src/clementine/verifier.rs b/src/clementine/verifier.rs deleted file mode 100644 index 5148f2c..0000000 --- a/src/clementine/verifier.rs +++ /dev/null @@ -1,204 +0,0 @@ -use std::path::PathBuf; -use std::sync::Arc; -use std::time::{Duration, Instant}; - -use anyhow::{bail, Context}; -use async_trait::async_trait; -use futures::TryStreamExt; -use tokio::process::Command; -use tokio::time::sleep; - -use crate::Result; -use crate::client::Client; -use crate::config::ClementineConfig; -use crate::docker::DockerEnv; -use crate::node::NodeKind; -use crate::traits::{ContainerSpawnOutput, LogProvider, NodeT, Restart, SpawnOutput}; - -pub struct VerifierNode { - spawn_output: SpawnOutput, - pub config: ClementineConfig, - docker_env: Arc>, - client: Client, -} - -impl VerifierNode { - pub async fn new(config: &ClementineConfig, docker: Arc>) -> Result { - let spawn_output = Self::spawn(config, &docker).await?; - - Ok(Self { - spawn_output, - config: config.clone(), - docker_env: docker, - client: Client::new(&config.client.host, config.client.port.try_into().unwrap())?, - }) - } - - async fn spawn( - config: &ClementineConfig, - docker: &Arc>, - ) -> Result { - match docker.as_ref() { - Some(docker) => docker.spawn(config.into()).await, - None => ::spawn(config), - } - } - - async fn wait_for_shutdown(&self) -> Result<()> { - let timeout_duration = Duration::from_secs(30); - let start = std::time::Instant::now(); - - while start.elapsed() < timeout_duration { - if !self.is_process_running().await? { - println!("Bridge backend has stopped successfully"); - return Ok(()); - } - sleep(Duration::from_millis(200)).await; - } - - bail!("Timeout waiting for bridge backend to stop") - } - - async fn is_process_running(&self) -> Result { - // let data_dir = &self.config.data_dir; - // let output = Command::new("pgrep") - // .args(["-f", &format!("bitcoind.*{}", data_dir.display())]) - // .output() - // .await?; - - // Ok(output.status.success()) - todo!() - } -} - -#[async_trait] -impl NodeT for VerifierNode { - type Config = ClementineConfig; - type Client = Client; - - fn spawn(config: &Self::Config) -> Result { - let env = config.get_env(); - println!("Running bridge backend with environment variables: {env:?}"); - - Command::new("npm run server:dev") - .kill_on_drop(true) - .env_clear() - .envs(env.clone()) - .spawn() - .context("Failed to spawn bridge backend server process") - .map(SpawnOutput::Child)?; - - Command::new("npm run worker:dev") - .kill_on_drop(true) - .env_clear() - .envs(env) - .spawn() - .context("Failed to spawn bridge backend worker process") - .map(SpawnOutput::Child) - } - - fn spawn_output(&mut self) -> &mut SpawnOutput { - &mut self.spawn_output - } - - async fn wait_for_ready(&self, timeout: Option) -> Result<()> { - println!("Waiting for ready"); - let start = Instant::now(); - let timeout = timeout.unwrap_or(Duration::from_secs(30)); - while start.elapsed() < timeout { - if true - // TODO: Do this check. - { - return Ok(()); - } - tokio::time::sleep(Duration::from_millis(500)).await; - } - anyhow::bail!("Node failed to become ready within the specified timeout") - } - - fn config_mut(&mut self) -> &mut Self::Config { - &mut self.config - } - - async fn stop(&mut self) -> Result<()> { - match self.spawn_output() { - SpawnOutput::Child(process) => { - process - .kill() - .await - .context("Failed to kill child process")?; - Ok(()) - } - SpawnOutput::Container(ContainerSpawnOutput { id, .. }) => { - std::println!("Stopping container {id}"); - let docker = bollard::Docker::connect_with_local_defaults() - .context("Failed to connect to Docker")?; - docker - .stop_container(id, Some(bollard::container::StopContainerOptions { t: 10 })) - .await - .context("Failed to stop Docker container")?; - Ok(()) - } - } - } - - fn client(&self) -> &Self::Client { - &self.client - } - - fn env(&self) -> Vec<(&'static str, &'static str)> { - // self.config.get_env() - todo!() - } - - fn config(&self) -> &::Config { - &self.config - } -} - -#[async_trait] -impl Restart for VerifierNode { - async fn wait_until_stopped(&mut self) -> Result<()> { - // self.client.stop().await?; - self.stop().await?; - - match &self.spawn_output { - SpawnOutput::Child(_) => self.wait_for_shutdown().await, - SpawnOutput::Container(output) => { - let Some(env) = self.docker_env.as_ref() else { - bail!("Missing docker environment") - }; - env.docker.stop_container(&output.id, None).await?; - - env.docker - .wait_container::(&output.id, None) - .try_collect::>() - .await?; - env.docker.remove_container(&output.id, None).await?; - println!("Docker container {} succesfully removed", output.id); - Ok(()) - } - } - } - - async fn start(&mut self, config: Option) -> Result<()> { - if let Some(config) = config { - self.config = config - } - self.spawn_output = Self::spawn(&self.config, &self.docker_env).await?; - - self.wait_for_ready(None).await?; - - Ok(()) - } -} - -impl LogProvider for VerifierNode { - fn kind(&self) -> NodeKind { - NodeKind::Verifier - } - - fn log_path(&self) -> PathBuf { - todo!() - } -} diff --git a/src/config/clementine.rs b/src/config/clementine.rs index 21c53fe..9517dcd 100644 --- a/src/config/clementine.rs +++ b/src/config/clementine.rs @@ -101,7 +101,7 @@ impl Default for ClementineClient { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct ClementineConfig { pub client: ClementineClient, pub docker_image: Option, diff --git a/src/config/mod.rs b/src/config/mod.rs index e93473d..fe138fe 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -41,6 +41,7 @@ pub struct FullL2NodeConfig { pub type FullSequencerConfig = FullL2NodeConfig; pub type FullBatchProverConfig = FullL2NodeConfig; pub type FullLightClientProverConfig = FullL2NodeConfig; +pub type FullVerifierConfig = FullL2NodeConfig; pub type FullFullNodeConfig = FullL2NodeConfig<()>; pub trait NodeKindMarker { @@ -63,6 +64,10 @@ impl NodeKindMarker for FullFullNodeConfig { const KIND: NodeKind = NodeKind::FullNode; } +impl NodeKindMarker for FullVerifierConfig { + const KIND: NodeKind = NodeKind::Verifier; +} + impl Config for FullL2NodeConfig where FullL2NodeConfig: NodeKindMarker, diff --git a/src/lib.rs b/src/lib.rs index bd64896..ffe9d63 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,6 @@ pub mod sequencer; pub mod test_case; pub mod traits; mod utils; -pub mod clementine; +pub mod verifier; pub type Result = anyhow::Result; diff --git a/src/verifier.rs b/src/verifier.rs new file mode 100644 index 0000000..6c4de8a --- /dev/null +++ b/src/verifier.rs @@ -0,0 +1,11 @@ +use std::path::PathBuf; + +use crate::{config::FullVerifierConfig, node::Node, traits::NodeT}; + +pub type Verifier = Node; + +impl Verifier { + pub fn dir(&self) -> &PathBuf { + &self.config().dir + } +} From c61cf868cd28ae94423f6e59ff9eb0b451d72f0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ceyhun=20=C5=9Een?= Date: Fri, 11 Oct 2024 11:18:26 +0300 Subject: [PATCH 3/8] config: Add clementine node to config. --- src/config/mod.rs | 86 +++++++++++++++++++++++++++++++++++++++++++---- src/node.rs | 3 +- 2 files changed, 82 insertions(+), 7 deletions(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index fe138fe..7f7348e 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -8,8 +8,8 @@ mod utils; use std::path::PathBuf; -pub use clementine::{ClementineClient, ClementineConfig}; pub use bitcoin::BitcoinConfig; +pub use clementine::{ClementineClient, ClementineConfig}; pub use docker::DockerConfig; pub use rollup::{default_rollup_config, RollupConfig}; use serde::Serialize; @@ -41,7 +41,6 @@ pub struct FullL2NodeConfig { pub type FullSequencerConfig = FullL2NodeConfig; pub type FullBatchProverConfig = FullL2NodeConfig; pub type FullLightClientProverConfig = FullL2NodeConfig; -pub type FullVerifierConfig = FullL2NodeConfig; pub type FullFullNodeConfig = FullL2NodeConfig<()>; pub trait NodeKindMarker { @@ -64,10 +63,6 @@ impl NodeKindMarker for FullFullNodeConfig { const KIND: NodeKind = NodeKind::FullNode; } -impl NodeKindMarker for FullVerifierConfig { - const KIND: NodeKind = NodeKind::Verifier; -} - impl Config for FullL2NodeConfig where FullL2NodeConfig: NodeKindMarker, @@ -105,6 +100,10 @@ where fn rollup_config(&self) -> &RollupConfig { &self.rollup } + + fn clementine_config(&self) -> &ClementineConfig { + unimplemented!() + } } impl LogPathProvider for FullL2NodeConfig @@ -123,3 +122,78 @@ where self.dir().join("stderr.log") } } + +#[derive(Clone, Debug)] +pub struct ClementineNodeConfig { + pub node: T, + pub config: ClementineConfig, + pub docker_image: Option, + pub dir: PathBuf, + pub env: Vec<(&'static str, &'static str)>, +} + +pub type FullVerifierConfig = ClementineNodeConfig; + +impl NodeKindMarker for FullVerifierConfig { + const KIND: NodeKind = NodeKind::Verifier; +} + +impl Config for ClementineNodeConfig +where + ClementineNodeConfig: NodeKindMarker, +{ + type NodeConfig = T; + + fn dir(&self) -> &PathBuf { + &self.dir + } + + fn rpc_bind_host(&self) -> &str { + &self.config.client.host + } + + fn rpc_bind_port(&self) -> u16 { + self.config.client.port + } + + fn env(&self) -> Vec<(&'static str, &'static str)> { + self.env.clone() + } + + fn node_config(&self) -> Option<&Self::NodeConfig> { + if std::mem::size_of::() == 0 { + None + } else { + Some(&self.node) + } + } + + fn node_kind() -> NodeKind { + ::KIND + } + + fn rollup_config(&self) -> &RollupConfig { + unimplemented!() + } + + fn clementine_config(&self) -> &ClementineConfig { + &self.config + } +} + +impl LogPathProvider for ClementineNodeConfig +where + ClementineNodeConfig: Config, +{ + fn kind() -> NodeKind { + Self::node_kind() + } + + fn log_path(&self) -> PathBuf { + self.dir().join("stdout.log") + } + + fn stderr_path(&self) -> PathBuf { + self.dir().join("stderr.log") + } +} diff --git a/src/node.rs b/src/node.rs index 3c9bdf5..7c0dc35 100644 --- a/src/node.rs +++ b/src/node.rs @@ -17,7 +17,7 @@ use tracing::{info, trace}; use crate::{ client::Client, - config::{config_to_file, RollupConfig}, + config::{config_to_file, ClementineConfig, RollupConfig}, log_provider::LogPathProvider, traits::{NodeT, Restart, SpawnOutput}, utils::{get_citrea_path, get_genesis_path}, @@ -57,6 +57,7 @@ pub trait Config: Clone { fn node_config(&self) -> Option<&Self::NodeConfig>; fn node_kind() -> NodeKind; fn rollup_config(&self) -> &RollupConfig; + fn clementine_config(&self) -> &ClementineConfig; } pub struct Node { From 6bf6bf4523137edac386d316b01f4a84225ad359 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ceyhun=20=C5=9Een?= Date: Fri, 11 Oct 2024 11:19:18 +0300 Subject: [PATCH 4/8] utils: Add get_clementine_path. --- src/utils.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/utils.rs b/src/utils.rs index e913c9d..66e4825 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -33,6 +33,14 @@ pub fn get_citrea_path() -> Result { .map(PathBuf::from) .map_err(|_| anyhow!("CITREA_E2E_TEST_BINARY is not set. Cannot resolve citrea path")) } +/// Get clementine path from `CLEMENTINE_E2E_TEST_BINARY` env +pub fn get_clementine_path() -> Result { + std::env::var("CLEMENTINE_E2E_TEST_BINARY") + .map(PathBuf::from) + .map_err(|_| { + anyhow!("CLEMENTINE_E2E_TEST_BINARY is not set. Cannot resolve clementine path") + }) +} /// Get genesis path from resources /// TODO: assess need for customable genesis path in e2e tests From 8111d7c9999523f1516c1adab50b3ed9ea61f1d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ceyhun=20=C5=9Een?= Date: Fri, 11 Oct 2024 11:19:35 +0300 Subject: [PATCH 5/8] node: Add clementine node. --- src/clementine/mod.rs | 1 + src/clementine/node.rs | 149 +++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + 3 files changed, 151 insertions(+) create mode 100644 src/clementine/mod.rs create mode 100644 src/clementine/node.rs diff --git a/src/clementine/mod.rs b/src/clementine/mod.rs new file mode 100644 index 0000000..492bc84 --- /dev/null +++ b/src/clementine/mod.rs @@ -0,0 +1 @@ +pub mod node; diff --git a/src/clementine/node.rs b/src/clementine/node.rs new file mode 100644 index 0000000..fa31b9f --- /dev/null +++ b/src/clementine/node.rs @@ -0,0 +1,149 @@ +//! # Clementine Node +use std::{fs::File, process::Stdio, time::Duration}; + +use anyhow::Context; +use async_trait::async_trait; +use tokio::{process::Command, time::Instant}; +use tracing::info; + +use crate::{ + client::Client, + config::config_to_file, + log_provider::LogPathProvider, + node::Config, + traits::{NodeT, Restart, SpawnOutput}, + utils::get_clementine_path, + Result, +}; + +pub struct ClementineNode { + spawn_output: SpawnOutput, + config: C, + pub client: Client, +} + +impl ClementineNode { + pub async fn new(config: &C) -> Result { + let spawn_output = Self::spawn(config)?; + + let client = Client::new(config.rpc_bind_host(), config.rpc_bind_port())?; + Ok(Self { + spawn_output, + config: config.clone(), + client, + }) + } + + fn spawn(config: &C) -> Result { + let clementine = get_clementine_path()?; + let dir = config.dir(); + + let kind = C::node_kind(); + + let stdout_path = config.log_path(); + let stdout_file = File::create(&stdout_path).context("Failed to create stdout file")?; + info!( + "{} stdout logs available at : {}", + kind, + stdout_path.display() + ); + + let stderr_path = config.stderr_path(); + let stderr_file = File::create(stderr_path).context("Failed to create stderr file")?; + + let server_arg = match kind { + crate::node::NodeKind::Verifier => "--verifier-server", + _ => panic!("Wrong kind {}", kind), + }; + + let config_path = dir.join(format!("{kind}_config.toml")); + config_to_file(&config.clementine_config(), &config_path)?; + + Command::new(clementine) + .arg(server_arg) + .envs(config.env()) + .stdout(Stdio::from(stdout_file)) + .stderr(Stdio::from(stderr_file)) + .kill_on_drop(true) + .spawn() + .context(format!("Failed to spawn {kind} process")) + .map(SpawnOutput::Child) + } +} + +#[async_trait] +impl NodeT for ClementineNode +where + C: Config + LogPathProvider + Send + Sync, +{ + type Config = C; + type Client = Client; + + fn spawn(config: &Self::Config) -> Result { + Self::spawn(config) + } + + fn spawn_output(&mut self) -> &mut SpawnOutput { + &mut self.spawn_output + } + + async fn wait_for_ready(&self, timeout: Option) -> Result<()> { + let start = Instant::now(); + let timeout = timeout.unwrap_or(Duration::from_secs(30)); + while start.elapsed() < timeout { + // if self + // .client + // .ledger_get_head_soft_confirmation() + // .await + // .is_ok() + // { + return Ok(()); + // } + // sleep(Duration::from_millis(500)).await; + } + anyhow::bail!( + "{} failed to become ready within the specified timeout", + C::node_kind() + ) + } + + fn client(&self) -> &Self::Client { + &self.client + } + + fn env(&self) -> Vec<(&'static str, &'static str)> { + self.config.env() + } + + fn config_mut(&mut self) -> &mut Self::Config { + &mut self.config + } + + fn config(&self) -> &Self::Config { + &self.config + } +} + +#[async_trait] +impl Restart for ClementineNode +where + C: Config + LogPathProvider + Send + Sync, +{ + async fn wait_until_stopped(&mut self) -> Result<()> { + self.stop().await?; + match &mut self.spawn_output { + SpawnOutput::Child(pid) => pid.wait().await?, + SpawnOutput::Container(_) => unimplemented!("L2 nodes don't run in docker yet"), + }; + Ok(()) + } + + async fn start(&mut self, new_config: Option) -> Result<()> { + let config = self.config_mut(); + if let Some(new_config) = new_config { + *config = new_config; + } + *self.spawn_output() = Self::spawn(config)?; + self.wait_for_ready(None).await + } +} diff --git a/src/lib.rs b/src/lib.rs index ffe9d63..8321ddc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ pub mod batch_prover; pub mod bitcoin; mod citrea_config; +pub mod clementine; mod client; pub mod config; mod docker; From 095533d906407ff2b88f8bf05436b4f6bf57cf5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ceyhun=20=C5=9Een?= Date: Fri, 11 Oct 2024 15:00:46 +0300 Subject: [PATCH 6/8] tests: Add clementine spawner. --- tests/clementine.rs | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 tests/clementine.rs diff --git a/tests/clementine.rs b/tests/clementine.rs new file mode 100644 index 0000000..f04711a --- /dev/null +++ b/tests/clementine.rs @@ -0,0 +1,37 @@ +use anyhow::bail; +use async_trait::async_trait; + +use citrea_e2e::{ + config::TestCaseConfig, + framework::TestFramework, + test_case::{TestCase, TestCaseRunner}, +}; + +struct BasicClementineTest; + +#[async_trait] +impl TestCase for BasicClementineTest { + fn test_config() -> TestCaseConfig { + TestCaseConfig { + with_verifier: true, + with_sequencer: false, + ..Default::default() + } + } + + async fn run_test(&mut self, f: &mut TestFramework) -> citrea_e2e::Result<()> { + let Some(_da) = f.bitcoin_nodes.get(0) else { + bail!("bitcoind not running!") + }; + let Some(_verifier) = &f.verifier else { + bail!("Verifier is not running!") + }; + + Ok(()) + } +} + +#[tokio::test] +async fn basic_clementine_test() -> citrea_e2e::Result<()> { + TestCaseRunner::new(BasicClementineTest).run().await +} From e25e730fae801610c4fb7de6dc3a5a29812c0245 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ceyhun=20=C5=9Een?= Date: Fri, 11 Oct 2024 15:03:25 +0300 Subject: [PATCH 7/8] framework: Add verifier. --- src/framework.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/framework.rs b/src/framework.rs index d667b6d..19b10dd 100644 --- a/src/framework.rs +++ b/src/framework.rs @@ -12,6 +12,7 @@ use crate::{ light_client_prover::LightClientProver, log_provider::{LogPathProvider, LogPathProviderErased}, utils::tail_file, + verifier::Verifier, }; pub struct TestContext { @@ -40,6 +41,7 @@ pub struct TestFramework { pub batch_prover: Option, pub light_client_prover: Option, pub full_node: Option, + pub verifier: Option, pub initial_da_height: u64, } @@ -68,6 +70,7 @@ impl TestFramework { batch_prover: None, light_client_prover: None, full_node: None, + verifier: None, ctx, initial_da_height: 0, }) @@ -81,7 +84,12 @@ impl TestFramework { ) .await?; - (self.batch_prover, self.light_client_prover, self.full_node) = tokio::try_join!( + ( + self.batch_prover, + self.light_client_prover, + self.full_node, + self.verifier, + ) = tokio::try_join!( create_optional( self.ctx.config.test_case.with_batch_prover, BatchProver::new(&self.ctx.config.batch_prover) @@ -94,6 +102,10 @@ impl TestFramework { self.ctx.config.test_case.with_full_node, FullNode::new(&self.ctx.config.full_node) ), + create_optional( + self.ctx.config.test_case.with_verifier, + Verifier::new(&self.ctx.config.verifier) + ), )?; Ok(()) From 7162243d73e47aea9189b5f7eb5c53d946c2b67f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ceyhun=20=C5=9Een?= Date: Fri, 11 Oct 2024 15:05:34 +0300 Subject: [PATCH 8/8] verifier: Small details to let it work. --- src/clementine/node.rs | 38 +++++++++++++++++++------------------- src/config/test.rs | 3 ++- src/config/test_case.rs | 7 +++++++ src/test_case.rs | 24 ++++++++++++++++++++---- src/verifier.rs | 4 ++-- 5 files changed, 50 insertions(+), 26 deletions(-) diff --git a/src/clementine/node.rs b/src/clementine/node.rs index fa31b9f..7cba182 100644 --- a/src/clementine/node.rs +++ b/src/clementine/node.rs @@ -3,7 +3,7 @@ use std::{fs::File, process::Stdio, time::Duration}; use anyhow::Context; use async_trait::async_trait; -use tokio::{process::Command, time::Instant}; +use tokio::process::Command; use tracing::info; use crate::{ @@ -87,24 +87,24 @@ where &mut self.spawn_output } - async fn wait_for_ready(&self, timeout: Option) -> Result<()> { - let start = Instant::now(); - let timeout = timeout.unwrap_or(Duration::from_secs(30)); - while start.elapsed() < timeout { - // if self - // .client - // .ledger_get_head_soft_confirmation() - // .await - // .is_ok() - // { - return Ok(()); - // } - // sleep(Duration::from_millis(500)).await; - } - anyhow::bail!( - "{} failed to become ready within the specified timeout", - C::node_kind() - ) + async fn wait_for_ready(&self, _timeout: Option) -> Result<()> { + // let start = Instant::now(); + // let timeout = timeout.unwrap_or(Duration::from_secs(30)); + // while start.elapsed() < timeout { + // if self + // .client + // .ledger_get_head_soft_confirmation() + // .await + // .is_ok() + // { + return Ok(()); + // } + // sleep(Duration::from_millis(500)).await; + // } + // anyhow::bail!( + // "{} failed to become ready within the specified timeout", + // C::node_kind() + // ) } fn client(&self) -> &Self::Client { diff --git a/src/config/test.rs b/src/config/test.rs index f67755d..8d0027d 100644 --- a/src/config/test.rs +++ b/src/config/test.rs @@ -1,6 +1,6 @@ use super::{ bitcoin::BitcoinConfig, test_case::TestCaseConfig, FullBatchProverConfig, FullFullNodeConfig, - FullLightClientProverConfig, FullSequencerConfig, + FullLightClientProverConfig, FullSequencerConfig, FullVerifierConfig, }; #[derive(Clone)] @@ -10,5 +10,6 @@ pub struct TestConfig { pub sequencer: FullSequencerConfig, pub batch_prover: FullBatchProverConfig, pub light_client_prover: FullLightClientProverConfig, + pub verifier: FullVerifierConfig, pub full_node: FullFullNodeConfig, } diff --git a/src/config/test_case.rs b/src/config/test_case.rs index bc081a8..3325c94 100644 --- a/src/config/test_case.rs +++ b/src/config/test_case.rs @@ -10,6 +10,7 @@ pub struct TestCaseEnv { pub batch_prover: Vec<(&'static str, &'static str)>, pub light_client_prover: Vec<(&'static str, &'static str)>, pub bitcoin: Vec<(&'static str, &'static str)>, + pub verifier: Vec<(&'static str, &'static str)>, } impl TestCaseEnv { @@ -41,6 +42,10 @@ impl TestCaseEnv { pub fn bitcoin(&self) -> Vec<(&'static str, &'static str)> { [self.test_env(), self.bitcoin.clone()].concat() } + + pub fn verifier(&self) -> Vec<(&'static str, &'static str)> { + [self.test_env(), self.verifier.clone()].concat() + } } #[derive(Clone)] @@ -50,6 +55,7 @@ pub struct TestCaseConfig { pub with_full_node: bool, pub with_batch_prover: bool, pub with_light_client_prover: bool, + pub with_verifier: bool, pub timeout: Duration, pub dir: PathBuf, pub docker: bool, @@ -67,6 +73,7 @@ impl Default for TestCaseConfig { with_batch_prover: false, with_light_client_prover: false, with_full_node: false, + with_verifier: false, timeout: Duration::from_secs(60), dir: TempDir::new() .expect("Failed to create temporary directory") diff --git a/src/test_case.rs b/src/test_case.rs index dc04c67..9686852 100644 --- a/src/test_case.rs +++ b/src/test_case.rs @@ -23,8 +23,9 @@ use super::{ }; use crate::{ config::{ - BatchProverConfig, BitcoinServiceConfig, FullLightClientProverConfig, - LightClientProverConfig, RpcConfig, RunnerConfig, SequencerConfig, StorageConfig, + BatchProverConfig, BitcoinServiceConfig, ClementineConfig, FullLightClientProverConfig, + FullVerifierConfig, LightClientProverConfig, RpcConfig, RunnerConfig, SequencerConfig, + StorageConfig, }, traits::NodeT, utils::{get_default_genesis_path, get_workspace_root}, @@ -129,8 +130,9 @@ impl TestCaseRunner { let batch_prover_rollup = default_rollup_config(); let light_client_prover_rollup = default_rollup_config(); let full_node_rollup = default_rollup_config(); + let clementine_config = T::clementine_config(); - let [bitcoin_dir, dbs_dir, batch_prover_dir, light_client_prover_dir, sequencer_dir, full_node_dir, genesis_dir, tx_backup_dir] = + let [bitcoin_dir, dbs_dir, batch_prover_dir, light_client_prover_dir, sequencer_dir, full_node_dir, genesis_dir, tx_backup_dir, verifier_dir] = create_dirs(&test_case.dir)?; copy_genesis_dir(&test_case.genesis_dir, &genesis_dir)?; @@ -298,6 +300,13 @@ impl TestCaseRunner { node: (), env: env.full_node(), }, + verifier: FullVerifierConfig { + config: ClementineConfig::default(), + docker_image: None, + env: env.verifier(), + node: clementine_config, + dir: verifier_dir, + }, test_case, }) } @@ -346,6 +355,12 @@ pub trait TestCase: Send + Sync + 'static { LightClientProverConfig::default() } + /// Returns the light clementine configuration for the test. + /// Override this method to provide a custom clementine configuration. + fn clementine_config() -> ClementineConfig { + ClementineConfig::default() + } + /// Returns the test setup /// Override this method to add custom initialization logic async fn setup(&self, _framework: &mut TestFramework) -> Result<()> { @@ -366,7 +381,7 @@ pub trait TestCase: Send + Sync + 'static { } } -fn create_dirs(base_dir: &Path) -> Result<[PathBuf; 8]> { +fn create_dirs(base_dir: &Path) -> Result<[PathBuf; 9]> { let paths = [ NodeKind::Bitcoin.to_string(), "dbs".to_string(), @@ -374,6 +389,7 @@ fn create_dirs(base_dir: &Path) -> Result<[PathBuf; 8]> { NodeKind::LightClientProver.to_string(), NodeKind::Sequencer.to_string(), NodeKind::FullNode.to_string(), + NodeKind::Verifier.to_string(), "genesis".to_string(), "inscription_txs".to_string(), ] diff --git a/src/verifier.rs b/src/verifier.rs index 6c4de8a..e0b1522 100644 --- a/src/verifier.rs +++ b/src/verifier.rs @@ -1,8 +1,8 @@ use std::path::PathBuf; -use crate::{config::FullVerifierConfig, node::Node, traits::NodeT}; +use crate::{clementine, config::FullVerifierConfig, traits::NodeT}; -pub type Verifier = Node; +pub type Verifier = clementine::node::ClementineNode; impl Verifier { pub fn dir(&self) -> &PathBuf {