diff --git a/Cargo.toml b/Cargo.toml index e176710..0f5a095 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ bitcoin = { version = "0.32.2", features = ["serde", "rand"] } bitcoincore-rpc = { version = "0.18.0" } bollard = { version = "0.17.1" } futures = "0.3" +jsonrpsee = { version = "0.24.2", features = ["http-client", "ws-client"] } log = "0.4" rand = "0.8" serde = { version = "1.0.192", default-features = false, features = ["alloc", "derive"] } @@ -19,7 +20,6 @@ tempfile = "3.8" tokio = { version = "1.39", features = ["full"] } toml = "0.8.0" which = "6.0.1" -jsonrpsee = { version = "0.24.2", features = ["http-client", "ws-client"] } # Citrea dependencies bitcoin-da = { git = "https://github.com/chainwayxyz/citrea", rev = "82bf52d", features = ["native"] } diff --git a/src/batch_prover.rs b/src/batch_prover.rs index 2c318da..c7f33df 100644 --- a/src/batch_prover.rs +++ b/src/batch_prover.rs @@ -1,11 +1,12 @@ use std::time::SystemTime; -use super::{config::FullBatchProverConfig, Result}; -use crate::node::Node; use anyhow::bail; use log::debug; use tokio::time::{sleep, Duration}; +use super::{config::FullBatchProverConfig, Result}; +use crate::node::Node; + pub type BatchProver = Node; impl BatchProver { diff --git a/src/client.rs b/src/client.rs index fe87110..a8d3633 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,8 +1,8 @@ use std::time::{Duration, SystemTime}; use anyhow::{bail, Result}; -use jsonrpsee::core::client::ClientT; use jsonrpsee::{ + core::client::ClientT, http_client::{HttpClient, HttpClientBuilder}, rpc_params, }; diff --git a/src/config/mod.rs b/src/config/mod.rs index 7bc9ae2..0f6de61 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -33,6 +33,8 @@ pub struct FullL2NodeConfig { pub type FullSequencerConfig = FullL2NodeConfig; pub type FullBatchProverConfig = FullL2NodeConfig; +// TODO: use LightClientProverConfig +pub type FullLightClientProverConfig = FullL2NodeConfig; pub type FullFullNodeConfig = FullL2NodeConfig<()>; pub trait NodeKindMarker { diff --git a/src/config/test.rs b/src/config/test.rs index 7c0d9f1..f67755d 100644 --- a/src/config/test.rs +++ b/src/config/test.rs @@ -1,6 +1,6 @@ use super::{ - bitcoin::BitcoinConfig, test_case::TestCaseConfig, FullFullNodeConfig, FullBatchProverConfig, - FullSequencerConfig, + bitcoin::BitcoinConfig, test_case::TestCaseConfig, FullBatchProverConfig, FullFullNodeConfig, + FullLightClientProverConfig, FullSequencerConfig, }; #[derive(Clone)] @@ -9,5 +9,6 @@ pub struct TestConfig { pub bitcoin: Vec, pub sequencer: FullSequencerConfig, pub batch_prover: FullBatchProverConfig, + pub light_client_prover: FullLightClientProverConfig, pub full_node: FullFullNodeConfig, } diff --git a/src/config/test_case.rs b/src/config/test_case.rs index 9dfda7a..bc081a8 100644 --- a/src/config/test_case.rs +++ b/src/config/test_case.rs @@ -8,6 +8,7 @@ pub struct TestCaseEnv { pub full_node: Vec<(&'static str, &'static str)>, pub sequencer: Vec<(&'static str, &'static str)>, pub batch_prover: Vec<(&'static str, &'static str)>, + pub light_client_prover: Vec<(&'static str, &'static str)>, pub bitcoin: Vec<(&'static str, &'static str)>, } @@ -29,6 +30,10 @@ impl TestCaseEnv { [self.test_env(), self.batch_prover.clone()].concat() } + pub fn light_client_prover(&self) -> Vec<(&'static str, &'static str)> { + [self.test_env(), self.light_client_prover.clone()].concat() + } + pub fn full_node(&self) -> Vec<(&'static str, &'static str)> { [self.test_env(), self.full_node.clone()].concat() } @@ -44,7 +49,7 @@ pub struct TestCaseConfig { pub with_sequencer: bool, pub with_full_node: bool, pub with_batch_prover: bool, - #[allow(unused)] + pub with_light_client_prover: bool, pub timeout: Duration, pub dir: PathBuf, pub docker: bool, @@ -60,6 +65,7 @@ impl Default for TestCaseConfig { n_nodes: 1, with_sequencer: true, with_batch_prover: false, + with_light_client_prover: false, with_full_node: false, timeout: Duration::from_secs(60), dir: TempDir::new() diff --git a/src/framework.rs b/src/framework.rs index a90191b..a56a480 100644 --- a/src/framework.rs +++ b/src/framework.rs @@ -12,7 +12,7 @@ use super::{ traits::{LogProvider, LogProviderErased, NodeT}, Result, }; -use crate::{batch_prover::BatchProver, utils::tail_file}; +use crate::{batch_prover::BatchProver, light_client_prover::LightClientProver, utils::tail_file}; pub struct TestContext { pub config: TestConfig, @@ -38,6 +38,7 @@ pub struct TestFramework { pub bitcoin_nodes: BitcoinNodeCluster, pub sequencer: Option, pub batch_prover: Option, + pub light_client_prover: Option, pub full_node: Option, show_logs: bool, pub initial_da_height: u64, @@ -67,6 +68,7 @@ impl TestFramework { bitcoin_nodes, sequencer: None, batch_prover: None, + light_client_prover: None, full_node: None, ctx, show_logs: true, @@ -82,11 +84,15 @@ impl TestFramework { ) .await?; - (self.batch_prover, self.full_node) = tokio::try_join!( + (self.batch_prover, self.light_client_prover, self.full_node) = tokio::try_join!( create_optional( self.ctx.config.test_case.with_batch_prover, BatchProver::new(&self.ctx.config.batch_prover) ), + create_optional( + self.ctx.config.test_case.with_light_client_prover, + LightClientProver::new(&self.ctx.config.light_client_prover) + ), create_optional( self.ctx.config.test_case.with_full_node, FullNode::new(&self.ctx.config.full_node) @@ -102,6 +108,9 @@ impl TestFramework { self.sequencer.as_ref().map(LogProvider::as_erased), self.full_node.as_ref().map(LogProvider::as_erased), self.batch_prover.as_ref().map(LogProvider::as_erased), + self.light_client_prover + .as_ref() + .map(LogProvider::as_erased), ] .into_iter() .flatten() @@ -154,6 +163,11 @@ impl TestFramework { println!("Successfully stopped batch_prover"); } + if let Some(light_client_prover) = &mut self.light_client_prover { + let _ = light_client_prover.stop().await; + println!("Successfully stopped light_client_prover"); + } + if let Some(full_node) = &mut self.full_node { let _ = full_node.stop().await; println!("Successfully stopped full_node"); @@ -177,6 +191,14 @@ impl TestFramework { .await?; da.create_wallet(&NodeKind::BatchProver.to_string(), None, None, None, None) .await?; + da.create_wallet( + &NodeKind::LightClientProver.to_string(), + None, + None, + None, + None, + ) + .await?; da.create_wallet(&NodeKind::Bitcoin.to_string(), None, None, None, None) .await?; @@ -191,6 +213,12 @@ impl TestFramework { da.fund_wallet(NodeKind::BatchProver.to_string(), blocks_to_fund) .await?; } + + if self.ctx.config.test_case.with_light_client_prover { + da.fund_wallet(NodeKind::LightClientProver.to_string(), blocks_to_fund) + .await?; + } + da.fund_wallet(NodeKind::Bitcoin.to_string(), blocks_to_fund) .await?; diff --git a/src/lib.rs b/src/lib.rs index 9e5b6e7..43832db 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,12 @@ +pub mod batch_prover; pub mod bitcoin; mod client; pub mod config; mod docker; pub mod framework; pub mod full_node; +pub mod light_client_prover; pub mod node; -pub mod batch_prover; pub mod sequencer; pub mod test_case; pub mod traits; diff --git a/src/light_client_prover.rs b/src/light_client_prover.rs new file mode 100644 index 0000000..df4f48a --- /dev/null +++ b/src/light_client_prover.rs @@ -0,0 +1,37 @@ +use std::time::SystemTime; + +use anyhow::bail; +use log::debug; +use tokio::time::{sleep, Duration}; + +use super::{config::FullLightClientProverConfig, Result}; +use crate::node::Node; + +pub type LightClientProver = Node; + +impl LightClientProver { + // TODO: remove _l at the end + pub async fn wait_for_l1_height_l(&self, height: u64, timeout: Option) -> Result<()> { + let start = SystemTime::now(); + let timeout = timeout.unwrap_or(Duration::from_secs(600)); + loop { + debug!("Waiting for light client prover height {}", height); + let latest_block = self.client.ledger_get_last_scanned_l1_height().await?; + + if latest_block >= height { + break; + } + + let now = SystemTime::now(); + if start + timeout <= now { + bail!( + "Timeout. Latest light client prover L1 height is {}", + latest_block + ); + } + + sleep(Duration::from_secs(1)).await; + } + Ok(()) + } +} diff --git a/src/node.rs b/src/node.rs index 1e91d9d..b6f5d55 100644 --- a/src/node.rs +++ b/src/node.rs @@ -27,6 +27,7 @@ use crate::{ pub enum NodeKind { Bitcoin, BatchProver, + LightClientProver, Sequencer, FullNode, } @@ -36,6 +37,7 @@ impl fmt::Display for NodeKind { match self { NodeKind::Bitcoin => write!(f, "bitcoin"), NodeKind::BatchProver => write!(f, "batch-prover"), + NodeKind::LightClientProver => write!(f, "light-client-prover"), NodeKind::Sequencer => write!(f, "sequencer"), NodeKind::FullNode => write!(f, "full-node"), } diff --git a/src/sequencer.rs b/src/sequencer.rs index 73bb2e0..50a352f 100644 --- a/src/sequencer.rs +++ b/src/sequencer.rs @@ -1,8 +1,7 @@ use std::path::PathBuf; use super::config::FullSequencerConfig; -use crate::node::Node; -use crate::traits::NodeT; +use crate::{node::Node, traits::NodeT}; pub type Sequencer = Node; diff --git a/src/test_case.rs b/src/test_case.rs index da3633f..ff70f81 100644 --- a/src/test_case.rs +++ b/src/test_case.rs @@ -13,7 +13,7 @@ use futures::FutureExt; use super::{ config::{ - default_rollup_config, BitcoinConfig, FullFullNodeConfig, FullBatchProverConfig, + default_rollup_config, BitcoinConfig, FullBatchProverConfig, FullFullNodeConfig, FullSequencerConfig, RollupConfig, TestCaseConfig, TestCaseEnv, TestConfig, }, framework::TestFramework, @@ -23,7 +23,8 @@ use super::{ }; use crate::{ config::{ - BitcoinServiceConfig, ProverConfig, RpcConfig, RunnerConfig, SequencerConfig, StorageConfig, + BitcoinServiceConfig, FullLightClientProverConfig, ProverConfig, RpcConfig, RunnerConfig, + SequencerConfig, StorageConfig, }, traits::NodeT, utils::{get_default_genesis_path, get_workspace_root}, @@ -54,7 +55,19 @@ impl TestCaseRunner { .await?; } if let Some(batch_prover) = &f.batch_prover { - batch_prover.wait_for_ready(Some(Duration::from_secs(5))).await?; + batch_prover + .wait_for_ready(Some(Duration::from_secs(5))) + .await?; + } + if let Some(light_client_prover) = &f.light_client_prover { + light_client_prover + .wait_for_ready(Some(Duration::from_secs(5))) + .await?; + } + if let Some(full_node) = &f.full_node { + full_node + .wait_for_ready(Some(Duration::from_secs(5))) + .await?; } Ok(()) @@ -111,12 +124,14 @@ impl TestCaseRunner { let env = T::test_env(); let bitcoin = T::bitcoin_config(); let batch_prover = T::batch_prover_config(); + let light_client_prover = T::light_client_prover_config(); let sequencer = T::sequencer_config(); let sequencer_rollup = default_rollup_config(); let batch_prover_rollup = default_rollup_config(); + let light_client_prover_rollup = default_rollup_config(); let full_node_rollup = default_rollup_config(); - let [bitcoin_dir, dbs_dir, batch_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] = create_dirs(&test_case.dir)?; copy_genesis_dir(&test_case.genesis_dir, &genesis_dir)?; @@ -204,6 +219,29 @@ impl TestCaseRunner { } }; + let light_client_prover_rollup = { + let bind_port = get_available_port()?; + let node_kind = NodeKind::LightClientProver.to_string(); + RollupConfig { + da: BitcoinServiceConfig { + da_private_key: None, + node_url: format!("http://{}/wallet/{}", da_config.node_url, node_kind), + tx_backup_dir: tx_backup_dir.display().to_string(), + ..da_config.clone() + }, + storage: StorageConfig { + path: dbs_dir.join(format!("{node_kind}-db")), + db_max_open_files: None, + }, + rpc: RpcConfig { + bind_port, + ..light_client_prover_rollup.rpc + }, + runner: runner_config.clone(), + ..light_client_prover_rollup + } + }; + let full_node_rollup = { let bind_port = get_available_port()?; let node_kind = NodeKind::FullNode.to_string(); @@ -246,6 +284,13 @@ impl TestCaseRunner { node: batch_prover, env: env.batch_prover(), }, + light_client_prover: FullLightClientProverConfig { + rollup: light_client_prover_rollup, + dir: light_client_prover_dir, + docker_image: None, + node: light_client_prover, + env: env.light_client_prover(), + }, full_node: FullFullNodeConfig { rollup: full_node_rollup, dir: full_node_dir, @@ -290,11 +335,17 @@ pub trait TestCase: Send + Sync + 'static { } /// Returns the batch prover configuration for the test. - /// Override this method to provide a custom prover configuration. + /// Override this method to provide a custom batch prover configuration. fn batch_prover_config() -> ProverConfig { ProverConfig::default() } + /// Returns the light client prover configuration for the test. + /// Override this method to provide a custom light client prover configuration. + fn light_client_prover_config() -> ProverConfig { + ProverConfig::default() + } + /// Returns the test setup /// Override this method to add custom initialization logic async fn setup(&self, _framework: &mut TestFramework) -> Result<()> { @@ -315,11 +366,12 @@ pub trait TestCase: Send + Sync + 'static { } } -fn create_dirs(base_dir: &Path) -> Result<[PathBuf; 7]> { +fn create_dirs(base_dir: &Path) -> Result<[PathBuf; 8]> { let paths = [ NodeKind::Bitcoin.to_string(), "dbs".to_string(), NodeKind::BatchProver.to_string(), + NodeKind::LightClientProver.to_string(), NodeKind::Sequencer.to_string(), NodeKind::FullNode.to_string(), "genesis".to_string(),