diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ecf99698..4ecbe576 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,9 +29,9 @@ jobs: uses: actions/checkout@v3 - name: Node.js setup - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: - node-version: 16 + node-version: "20" cache: 'yarn' - name: Rust setup (native) diff --git a/.github/workflows/reusable-ci-jobs.yml b/.github/workflows/reusable-ci-jobs.yml index c16df769..d318f948 100644 --- a/.github/workflows/reusable-ci-jobs.yml +++ b/.github/workflows/reusable-ci-jobs.yml @@ -109,9 +109,9 @@ - name: Cache rust dependencies uses: Swatinem/rust-cache@v1 - name: setup node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: - node-version: "16" + node-version: "20" - name: ubuntu dependencies if: ${{ inputs.build-tari }} run: | @@ -152,9 +152,9 @@ - name: checkout uses: actions/checkout@v2 - name: setup node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: - node-version: "16" + node-version: "20" - name: Install Yarn run: npm install -g yarn - name: log javascript environment diff --git a/.github/workflows/reusable-tests.yml b/.github/workflows/reusable-tests.yml index 15913311..4003cd57 100644 --- a/.github/workflows/reusable-tests.yml +++ b/.github/workflows/reusable-tests.yml @@ -24,9 +24,9 @@ jobs: - name: toolchain uses: actions-rs/toolchain@v1 - name: setup node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: - node-version: "16" + node-version: "20" - uses: Swatinem/rust-cache@v1 - name: ubuntu dependencies run: | diff --git a/backend/src/docker/models.rs b/backend/src/docker/models.rs index f20b3657..50d93e47 100644 --- a/backend/src/docker/models.rs +++ b/backend/src/docker/models.rs @@ -142,36 +142,36 @@ impl From for LogMessage { /// Supported networks for the launchpad #[derive(Serialize, Debug, Deserialize, Clone, Copy)] pub enum TariNetwork { - Dibbler, - Esmeralda, Igor, + Nextnet, + Stagenet, Mainnet, } impl TariNetwork { pub fn lower_case(self) -> &'static str { match self { - Self::Dibbler => "dibbler", - Self::Esmeralda => "esmeralda", Self::Igor => "igor", + Self::Nextnet => "nextnet", + Self::Stagenet => "stagenet", Self::Mainnet => "mainnet", } } pub fn upper_case(self) -> &'static str { match self { - Self::Dibbler => "DIBBLER", - Self::Esmeralda => "ESMERALDA", Self::Igor => "IGOR", + Self::Nextnet => "NEXTNET", + Self::Stagenet => "STAGENET", Self::Mainnet => "MAINNET", } } } -/// Default network is Esme. This will change after mainnet launch +/// Default network is Stagenet. This will change after mainnet launch impl Default for TariNetwork { fn default() -> Self { - Self::Esmeralda + Self::Stagenet } } @@ -180,9 +180,9 @@ impl TryFrom<&str> for TariNetwork { fn try_from(value: &str) -> Result { match value { - "dibbler" => Ok(TariNetwork::Dibbler), - "esmeralda" => Ok(TariNetwork::Esmeralda), "igor" => Ok(TariNetwork::Igor), + "nextnet" => Ok(TariNetwork::Nextnet), + "stagenet" => Ok(TariNetwork::Stagenet), "mainnet" => Ok(TariNetwork::Mainnet), _ => Err(DockerWrapperError::UnsupportedNetwork), } diff --git a/cli/src/main.rs b/cli/src/main.rs index 00e38e64..91cfcd5b 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -31,8 +31,7 @@ use tari_sdm_assets::configurator::Configurator; #[tokio::main] async fn main() -> Result<(), Error> { let mut configurator = Configurator::init()?; - configurator.clean_configuration().await?; - configurator.init_configuration().await?; + configurator.init_configuration(false).await?; let workdir = configurator.base_path(); env::set_current_dir(workdir)?; diff --git a/libs/protocol/src/container.rs b/libs/protocol/src/container.rs index 16fc3467..419c314b 100644 --- a/libs/protocol/src/container.rs +++ b/libs/protocol/src/container.rs @@ -130,7 +130,7 @@ pub enum TaskStatus { Pending, Progress(TaskProgress), Active, - // TODO: Add failed with a reason? + Failed(String), } impl TaskStatus { @@ -145,6 +145,10 @@ impl TaskStatus { pub fn is_inactive(&self) -> bool { matches!(self, Self::Inactive) } + + pub fn is_failed(&self) -> bool { + matches!(self, Self::Failed(_)) + } } impl fmt::Display for TaskStatus { @@ -154,6 +158,7 @@ impl fmt::Display for TaskStatus { Self::Pending => write!(f, "Pending"), Self::Progress(value) => write!(f, "Progress({} - {}%)", value.stage, value.pct), Self::Active => write!(f, "Active"), + Self::Failed(reason) => write!(f, "Failed. Reason: {}", reason), } } } diff --git a/libs/protocol/src/settings.rs b/libs/protocol/src/settings.rs index f25d00c7..8fd65ac1 100644 --- a/libs/protocol/src/settings.rs +++ b/libs/protocol/src/settings.rs @@ -95,7 +95,7 @@ impl MmProxyConfig { pub struct LaunchpadSettings { /// The directory to use for config, id files and logs pub data_directory: PathBuf, - /// The Tari network to use. Default = esmeralda + /// The Tari network to use. Default = stagenet pub tari_network: TariNetwork, /// The tor control password to share among containers. pub tor_control_password: String, @@ -155,9 +155,8 @@ pub struct UnsupportedNetwork(String); /// Supported networks for the launchpad #[derive(Serialize, Debug, Deserialize, Clone, Copy)] pub enum TariNetwork { - Dibbler, - Esmeralda, Igor, + Nextnet, Stagenet, Mainnet, } @@ -165,9 +164,8 @@ pub enum TariNetwork { impl TariNetwork { pub fn lower_case(self) -> &'static str { match self { - Self::Dibbler => "dibbler", - Self::Esmeralda => "esmeralda", Self::Igor => "igor", + Self::Nextnet => "nextnet", Self::Stagenet => "stagenet", Self::Mainnet => "mainnet", } @@ -175,9 +173,8 @@ impl TariNetwork { pub fn upper_case(self) -> &'static str { match self { - Self::Dibbler => "DIBBLER", - Self::Esmeralda => "ESMERALDA", Self::Igor => "IGOR", + Self::Nextnet => "NEXTNET", Self::Stagenet => "STAGENET", Self::Mainnet => "MAINNET", } @@ -196,9 +193,8 @@ impl TryFrom<&str> for TariNetwork { fn try_from(value: &str) -> Result { match value { - "dibbler" => Ok(TariNetwork::Dibbler), - "esmeralda" => Ok(TariNetwork::Esmeralda), "igor" => Ok(TariNetwork::Igor), + "nextnet" => Ok(TariNetwork::Nextnet), "mainnet" => Ok(TariNetwork::Mainnet), other => Err(UnsupportedNetwork(other.to_owned())), } diff --git a/libs/sdm-assets/src/configurator.rs b/libs/sdm-assets/src/configurator.rs index d2c3c55d..b8171c7f 100644 --- a/libs/sdm-assets/src/configurator.rs +++ b/libs/sdm-assets/src/configurator.rs @@ -78,53 +78,58 @@ impl Configurator { // Ok(config) // } - async fn create_dir(&mut self, folder: &Path) -> Result<(), Error> { - if !folder.exists() { - fs::create_dir_all(&folder).await?; + /// Create directory if it doesn't exist. Returns `true` if the directory was created. + async fn create_dir>(&mut self, folder: P) -> Result { + if folder.as_ref().exists() { + Ok(false) + } else { + fs::create_dir_all(folder).await?; + Ok(true) } - Ok(()) } - async fn create_sub_dir(&mut self, folder: &Path, sub_path: &str) -> Result { + async fn create_sub_dir(&mut self, folder: &Path, sub_path: &str) -> Result { let mut path = folder.to_path_buf(); path.push(sub_path); - if !path.exists() { - fs::create_dir_all(&path).await?; - } - Ok(path) + self.create_dir(sub_path).await } - async fn store_file(&mut self, folder: &Path, file: &ConfigFile) -> Result<(), Error> { - let mut path = folder.to_path_buf(); + async fn store_file>(&mut self, folder: P, file: &ConfigFile, overwrite: bool) -> Result<(), Error> { + let mut path = folder.as_ref().to_path_buf(); path.push(file.filename); - if !path.exists() { + if overwrite || !path.exists() { fs::write(path, file.data).await?; } Ok(()) } - pub async fn clean_configuration(&mut self) -> Result<(), Error> { - let base_dir = self.base_dir.clone(); - let config_dir = self.create_sub_dir(&base_dir, "config").await?; - tokio::fs::remove_dir_all(config_dir).await?; - Ok(()) - } - - pub async fn init_configuration(&mut self) -> Result<(), Error> { + /// Initialize configuration files + /// + /// If `overwrite` is `true`, then existing files will be overwritten. + pub async fn init_configuration(&mut self, overwrite: bool) -> Result<(), Error> { // base path let base_dir = self.base_dir.clone(); - self.create_dir(&base_dir).await?; - let config_dir = self.create_sub_dir(&base_dir, "config").await?; + let _ = self.create_dir(&base_dir).await?; + let mut config_dir = base_dir.clone(); + config_dir.push("config"); + let new_config_dir = self.create_dir(&config_dir).await?; // config files - self.store_file(&config_dir, &CONFIG_TOML).await?; - self.store_file(&config_dir, &DEFAULTS_INI).await?; - self.store_file(&config_dir, &LOGS4RS_YML).await?; - self.store_file(&config_dir, &LOKI_YML).await?; - self.store_file(&config_dir, &PROMTAIL_YML).await?; - self.store_file(&config_dir, &PROVISION_YML).await?; - - self.create_sub_dir(&base_dir, "log").await?; - self.store_file(&config_dir, &LOG4RS_CLI_YML).await?; + self.store_file(&config_dir, &CONFIG_TOML, new_config_dir || overwrite) + .await?; + self.store_file(&config_dir, &DEFAULTS_INI, new_config_dir || overwrite) + .await?; + self.store_file(&config_dir, &LOGS4RS_YML, new_config_dir || overwrite) + .await?; + self.store_file(&config_dir, &LOKI_YML, new_config_dir || overwrite) + .await?; + self.store_file(&config_dir, &PROMTAIL_YML, new_config_dir || overwrite) + .await?; + self.store_file(&config_dir, &PROVISION_YML, new_config_dir || overwrite) + .await?; + + let new_log_dir = self.create_sub_dir(&base_dir, "log").await?; + self.store_file(&config_dir, &LOG4RS_CLI_YML, new_log_dir || overwrite) + .await?; // TODO: Use `enum` here... // images diff --git a/libs/sdm-launchpad/src/bus.rs b/libs/sdm-launchpad/src/bus.rs index 5be352c4..028cf99f 100644 --- a/libs/sdm-launchpad/src/bus.rs +++ b/libs/sdm-launchpad/src/bus.rs @@ -78,7 +78,7 @@ impl LaunchpadWorker { in_rx: mpsc::UnboundedReceiver, out_tx: mpsc::UnboundedSender, ) -> Result<(), Error> { - let mut scope = SdmScope::connect("esmeralda")?; + let mut scope = SdmScope::connect("stagenet")?; scope.add_network(networks::LocalNet::default())?; scope.add_volume(volumes::SharedVolume::default())?; scope.add_volume(volumes::SharedGrafanaVolume::default())?; @@ -125,7 +125,7 @@ impl LaunchpadWorker { async fn load_configuration(&mut self) -> Result<(), Error> { let mut configurator = Configurator::init()?; let data_directory = configurator.base_path().clone(); - configurator.init_configuration().await?; + configurator.init_configuration(false).await?; let wallet_config = WalletConfig { password: "123".to_string(), }; diff --git a/libs/sdm-launchpad/src/resources/images/l2_base_node.rs b/libs/sdm-launchpad/src/resources/images/l2_base_node.rs index 5230a644..90cf0b40 100644 --- a/libs/sdm-launchpad/src/resources/images/l2_base_node.rs +++ b/libs/sdm-launchpad/src/resources/images/l2_base_node.rs @@ -77,11 +77,11 @@ impl ManagedContainer for TariBaseNode { } fn image_name(&self) -> &str { - "tari_base_node" + "minotari_node" } fn tag(&self) -> &str { - "v0.49.2_20230628_e0e4ebc" + "0.52" } fn reconfigure(&mut self, config: Option<&LaunchpadConfig>) -> Option { @@ -136,6 +136,7 @@ impl ManagedContainer for TariBaseNode { } } +/// A helper struct to track the progress of the initial block download. struct Checker { progress: SyncProgress, identity_sent: bool, @@ -155,6 +156,10 @@ impl Checker { #[async_trait] impl ContainerChecker for Checker { + /// The interval hook in the base node checker is used to query the base node via gRPC for the current sync + /// progress. The progress is then reported to the SDM via the `CheckerEvent::Progress` event. + /// The task is reported as complete (`READY`) once the `sync_state` value from the `get_sync_progress` RPC call + /// is `Done`. async fn on_interval(&mut self, ctx: &mut CheckerContext) -> Result<(), Error> { if self.ready { return Ok(()); diff --git a/libs/sdm-launchpad/src/resources/images/l2_wallet.rs b/libs/sdm-launchpad/src/resources/images/l2_wallet.rs index 80b44f0c..4d447677 100644 --- a/libs/sdm-launchpad/src/resources/images/l2_wallet.rs +++ b/libs/sdm-launchpad/src/resources/images/l2_wallet.rs @@ -85,11 +85,11 @@ impl ManagedContainer for TariWallet { } fn image_name(&self) -> &str { - "tari_wallet" + "minotari_console_wallet" } fn tag(&self) -> &str { - "v0.49.2_20230628_e0e4ebc" + "0.52" } fn reconfigure(&mut self, config: Option<&LaunchpadConfig>) -> Option { diff --git a/libs/sdm-launchpad/src/resources/images/l3_miner.rs b/libs/sdm-launchpad/src/resources/images/l3_miner.rs index 69a37236..72281282 100644 --- a/libs/sdm-launchpad/src/resources/images/l3_miner.rs +++ b/libs/sdm-launchpad/src/resources/images/l3_miner.rs @@ -57,11 +57,11 @@ impl ManagedContainer for TariSha3Miner { } fn image_name(&self) -> &str { - "tari_sha3_miner" + "minotari_sha3_miner" } fn tag(&self) -> &str { - "v0.49.2_20230628_e0e4ebc" + "0.52" } fn reconfigure(&mut self, config: Option<&LaunchpadConfig>) -> Option { diff --git a/libs/sdm-launchpad/src/resources/images/l5_mmproxy.rs b/libs/sdm-launchpad/src/resources/images/l5_mmproxy.rs index 0a51a019..cc170a9e 100644 --- a/libs/sdm-launchpad/src/resources/images/l5_mmproxy.rs +++ b/libs/sdm-launchpad/src/resources/images/l5_mmproxy.rs @@ -59,11 +59,11 @@ impl ManagedContainer for MmProxy { } fn image_name(&self) -> &str { - "tari_mm_proxy" + "minotari_merge_mining_proxy" } fn tag(&self) -> &str { - "v0.49.2_20230628_e0e4ebc" + "0.52" } fn reconfigure(&mut self, config: Option<&LaunchpadConfig>) -> Option { diff --git a/libs/sdm-launchpad/src/resources/images/mod.rs b/libs/sdm-launchpad/src/resources/images/mod.rs index f37e49cc..676a3549 100644 --- a/libs/sdm-launchpad/src/resources/images/mod.rs +++ b/libs/sdm-launchpad/src/resources/images/mod.rs @@ -44,7 +44,7 @@ pub use l8_grafana::Grafana; pub use l8_loki::Loki; pub use l8_promtail::Promtail; -static DEFAULT_REGISTRY: &str = "quay.io/tarilabs"; +static DEFAULT_REGISTRY: &str = "ghcr.io/tari-project"; static GRAFANA_REGISTRY: &str = "grafana"; static GENERAL_VOLUME: &str = "/var/tari"; diff --git a/libs/sdm/src/image/checker.rs b/libs/sdm/src/image/checker.rs index d906a212..2f2e3a77 100644 --- a/libs/sdm/src/image/checker.rs +++ b/libs/sdm/src/image/checker.rs @@ -68,6 +68,11 @@ impl CheckerContext

{ } } +/// Polls a container for the logs and stats, and executes the related hooks for the event. If no events are +/// received for 1 second, the `on_interval` hook is called. The default implementation of all of the hooks do nothing. +/// +/// In each of the hooks, a mutable reference to a `CheckerContext` is provided, which can be used to access / update +/// the log and stats history, and update the progress of a task. #[async_trait] pub trait ContainerChecker: Send { async fn entrypoint(mut self: Box, mut ctx: CheckerContext

) { diff --git a/libs/sdm/src/image/mod.rs b/libs/sdm/src/image/mod.rs index 6af5a53f..731fe828 100644 --- a/libs/sdm/src/image/mod.rs +++ b/libs/sdm/src/image/mod.rs @@ -32,6 +32,7 @@ pub(crate) use task::ImageTask; use crate::config::ManagedProtocol; +/// A container that can be managed by SDM. pub trait ManagedContainer: fmt::Debug + Send + 'static { type Protocol: ManagedProtocol; diff --git a/libs/sdm/src/image/task/docker.rs b/libs/sdm/src/image/task/docker.rs index f8127f59..144050ea 100644 --- a/libs/sdm/src/image/task/docker.rs +++ b/libs/sdm/src/image/task/docker.rs @@ -363,8 +363,12 @@ struct ProgressConv; impl Converter for ProgressConv { fn convert(&self, res: Result) -> Option { - log::debug!("Create Image Info: {:?}", res); - let info = res.ok()?; + if let Err(err) = res { + log::error!("Error while pulling image: {}", err); + return Some(Event::PullingFailed(err.to_string())); + } + let info = res.unwrap(); + log::debug!("Created Image Info: {:?}", info); let details = info.progress_detail?; let current = details.current? * 100; let total = details.total?; diff --git a/libs/sdm/src/image/task/events.rs b/libs/sdm/src/image/task/events.rs index b3f44b93..55129da2 100644 --- a/libs/sdm/src/image/task/events.rs +++ b/libs/sdm/src/image/task/events.rs @@ -33,10 +33,11 @@ use crate::{ impl TaskContext> { pub fn process_event_impl(&mut self, event: Event) -> Result<(), Error> { - log::warn!("EVENT: {event:?}"); + log::debug!("Image event triggered. Image: {} Event: {event:?}", self.image_name); match event { Event::Created => self.on_created(), Event::PullingProgress(value) => self.on_pulling_progress(value), + Event::PullingFailed(reason) => self.on_pulling_failed(reason), Event::Destroyed => self.on_destroyed(), Event::Started => self.on_started(), Event::Killed => self.on_killed(), @@ -59,6 +60,14 @@ impl TaskContext> { Ok(()) } + fn on_pulling_failed(&mut self, reason: String) -> Result<(), Error> { + if let Status::PullingImage { .. } = self.status.get() { + self.status.set(Status::CannotStart); + self.update_task_status(TaskStatus::Failed(reason))?; + } + Ok(()) + } + fn on_destroyed(&mut self) -> Result<(), Error> { if let Status::WaitContainerRemoved = self.status.get() { self.status.set(Status::CleanDangling); diff --git a/libs/sdm/src/image/task/mod.rs b/libs/sdm/src/image/task/mod.rs index 21a795db..5086fb23 100644 --- a/libs/sdm/src/image/task/mod.rs +++ b/libs/sdm/src/image/task/mod.rs @@ -120,6 +120,7 @@ pub enum Status { CleanDangling, WaitContainerKilled, WaitContainerRemoved, + CannotStart, CreateContainer, WaitContainerCreated, @@ -161,6 +162,7 @@ pub enum ContainerState { pub enum Event { Destroyed, PullingProgress(TaskProgress), + PullingFailed(String), Created, Started, Killed, diff --git a/libs/sdm/src/image/task/update.rs b/libs/sdm/src/image/task/update.rs index 8b3b4d98..71f2ed00 100644 --- a/libs/sdm/src/image/task/update.rs +++ b/libs/sdm/src/image/task/update.rs @@ -35,6 +35,7 @@ impl TaskContext> { Status::CleanDangling => self.do_clean_dangling().await, Status::WaitContainerKilled => self.do_wait_container_killed().await, Status::WaitContainerRemoved => self.do_wait_container_removed().await, + Status::CannotStart => self.abort().await, Status::Idle => self.do_idle().await, Status::CreateContainer => self.do_create_container().await, Status::WaitContainerCreated => self.do_wait_container_created().await, @@ -48,8 +49,10 @@ impl TaskContext> { async fn do_initial_state(&mut self) -> Result<(), Error> { self.update_task_status(TaskStatus::Inactive)?; - log::debug!("Cheking image {} ...", self.inner.image_name); + log::debug!("Checking image {} ...", self.inner.image_name); if self.image_exists().await { + // The image exists, so check if there's a dangling container + log::debug!("Image {} exists. Skip pulling.", self.inner.image_name); self.clean_dangling()?; } else { self.start_pulling()?; @@ -58,8 +61,7 @@ impl TaskContext> { } fn clean_dangling(&mut self) -> Result<(), Error> { - log::debug!("Image {} exists. Skip pulling.", self.inner.image_name); - let progress = TaskProgress::new("Cleaning..."); + let progress = TaskProgress::new("Checking for old containers..."); self.update_task_status(TaskStatus::Progress(progress))?; self.status.set(Status::CleanDangling); Ok(()) @@ -83,8 +85,11 @@ impl TaskContext> { Ok(()) } + /// Removes containers that shouldn't be there, for example after a crash, or if the user started a container + /// manually in docker. If the container is still running, we'll try and kill it first, otherwise we'll just + /// remove it. async fn do_clean_dangling(&mut self) -> Result<(), Error> { - log::debug!("Cheking container {} ...", self.inner.container_name); + log::debug!("Checking container {} ...", self.inner.container_name); let state = self.container_state().await; match state { ContainerState::Running => { @@ -116,6 +121,10 @@ impl TaskContext> { Ok(()) } + async fn abort(&mut self) -> Result<(), Error> { + Ok(()) + } + async fn do_idle(&mut self) -> Result<(), Error> { if self.force_pull { self.force_pull = false; diff --git a/libs/sdm/src/status.rs b/libs/sdm/src/status.rs index 1e732416..7ca63772 100644 --- a/libs/sdm/src/status.rs +++ b/libs/sdm/src/status.rs @@ -77,7 +77,7 @@ impl SdmStatus { } pub fn set(&mut self, status: S) { - log::debug!("Set the new status !{}::status={:?}", self.name, self.status); + log::debug!("Set the new status for [{}]={:?}", self.name, self.status); self.status = status; self.has_work = true; self.fallback = None; diff --git a/libs/sdm/src/task.rs b/libs/sdm/src/task.rs index a4c157d7..590ecb9c 100644 --- a/libs/sdm/src/task.rs +++ b/libs/sdm/src/task.rs @@ -319,7 +319,7 @@ where TaskContext: RunnableContext fn broadcast(&mut self, event: ControlEvent) { if let Err(err) = self.requests_sender.send(event) { - log::error!("Can't brodcast event: {:?}", err); + log::error!("Can't broadcast event: {:?}", err); } } @@ -352,7 +352,7 @@ where TaskContext: RunnableContext } fn check_dependencies(&mut self) { - // If the set is empty `all` returs `true`. + // If the set is empty `all` returns `true`. self.context.dependencies_ready = self.dependencies.values().all(|ready| *ready); } diff --git a/libs/sim-launchpad/src/simulator.rs b/libs/sim-launchpad/src/simulator.rs index 27b2b1fd..08a39c18 100644 --- a/libs/sim-launchpad/src/simulator.rs +++ b/libs/sim-launchpad/src/simulator.rs @@ -228,6 +228,7 @@ impl Simulator { (_, false) => { new_status = Some(TaskStatus::Inactive); }, + (TaskStatus::Failed(_), _) => {}, } if let Some(status) = new_status { let delta = TaskDelta::UpdateStatus(status);