diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 8067909b..1d9c0a67 100755 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,6 +1,33 @@ -# Rust latest -FROM mcr.microsoft.com/devcontainers/rust:latest - -# Install Required Dependencies -RUN apt-get -qq update -RUN apt-get install -y build-essential cmake git libgit2-dev clang libncurses5-dev libncursesw5-dev zlib1g-dev pkg-config libssl-dev llvm +# Rust latest +FROM mcr.microsoft.com/devcontainers/rust:latest + +# Install Required Dependencies +RUN apt-get -qq update +RUN apt-get install -y \ + build-essential \ + clang \ + cmake \ + fuse \ + gh \ + git \ + libgit2-dev \ + libncurses5-dev \ + libncursesw5-dev \ + libssl-dev \ + llvm \ + pkg-config \ + snapd \ + squashfuse \ + sudo \ + zlib1g-dev + +RUN echo '#!/bin/bash\n\ +/usr/lib/snapd/snapd &\n\ +sleep 5\n\ +exec "$@"' > /start-snapd.sh && \ + chmod +x /start-snapd.sh + +VOLUME ["/sys/fs/cgroup"] + +ENTRYPOINT ["/start-snapd.sh"] +CMD ["bash"] diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index ba8003bd..fe05b995 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -16,5 +16,6 @@ "github.copilot-chat" ] } - } + }, + "runArgs": ["--privileged", "-v", "/sys/fs/cgroup:/sys/fs/cgroup:ro", "-v", "/tmp:/tmp"] } \ No newline at end of file diff --git a/.github/workflows/snap.yaml b/.github/workflows/snap.yaml new file mode 100644 index 00000000..273c12a7 --- /dev/null +++ b/.github/workflows/snap.yaml @@ -0,0 +1,45 @@ +name: Snap Package + +on: + workflow_dispatch: + push: + branches: + - master + - staging + +jobs: + build-snap: + name: Build snap + strategy: + matrix: + runs-on: [ubuntu-latest, ubuntu-24.04-arm] + runs-on: ${{ matrix.runs-on }} + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Prepare snapcraft project + run: | + grade=devel + if [ "${{ github.ref_name }}" = "master" ]; then + grade=stable + fi + + mkdir -p snap + sed "s/SNAP_GRADE/$grade/" .packaging/snaps/snapcraft.yaml > snap/snapcraft.yaml + + - name: Build snap package + id: build + uses: snapcore/action-build@v1 + with: + path: . + + - name: Show snap package + run: find . -maxdepth 1 -name "*.snap" -ls + + - name: Upload snap artifact + uses: actions/upload-artifact@v6 + with: + name: grin-snap + path: "*.snap" + if-no-files-found: error diff --git a/.packaging/snaps/snapcraft.yaml b/.packaging/snaps/snapcraft.yaml new file mode 100644 index 00000000..ec1bfec4 --- /dev/null +++ b/.packaging/snaps/snapcraft.yaml @@ -0,0 +1,40 @@ +name: grin +adopt-info: grin +summary: Minimal implementation of the Mimblewimble protocol +description: | + https://grin.mw/ + +confinement: strict +grade: SNAP_GRADE +base: core22 +architectures: + - build-on: [arm64] + build-for: [arm64] + - build-on: [amd64] + build-for: [amd64] + +parts: + grin: + plugin: rust + source: . + override-pull: | + craftctl default + craftctl set version="$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -n 1)" + build-packages: + - build-essential + - clang + - cmake + - libncurses5-dev + - libncursesw5-dev + - libssl-dev + - llvm + - pkg-config + - zlib1g-dev + +apps: + grin: + environment: + LC_ALL: C.UTF-8 + LANG: C.UTF-8 + command: bin/grin + plugs: [network, network-bind] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 732efac1..c310ff37 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,7 +12,36 @@ More documentation or updates/fixes to existing documentation are also very welc We generally prefer you to PR your work earlier rather than later. This ensures everyone else has a better idea of what's being worked on, and can help reduce wasted effort. If work on your PR has just begun, please feel free to create the PR with [WIP] (work in progress) in the PR title, and let us know when it's ready for review in the comments. -Since mainnet has been released, the bar for having PRs accepted has been raised. Before submitting your PR for approval, please be ensure it: +All changes should first be submitted against the `staging` branch and follow the PR rules below. The `staging` branch is tested by the community before changes are merged into `master` by an admin when appropriate. + +```text +Contributor branch ++-- Pull request + +-- staging branch + +-- testing (community) + +-- Admin merge + +-- master +``` + +## Creating a Pull Request + +Create your work on a dedicated branch based on the latest `staging` branch. + +```sh +git checkout staging +git pull +git checkout -b my-change +``` + +Fork the Grin repository and add your fork as a git remote. + +```sh +git remote add my-fork +``` + +Push your branch to your fork and open a GitHub pull request from your fork's branch into the Grin repository's `staging` branch. You can do this through the GitHub web interface. + +Since mainnet has been released, the bar for having PRs accepted has been raised. Before submitting your PR for approval, please ensure it: * Includes a proper description of what problems the PR addresses, as well as a detailed explanation as to what it changes * Explains whether/how the change is consensus breaking or breaks existing client functionality * Contains unit tests exercising new/changed functionality @@ -26,23 +55,26 @@ The development team will be happy to help and guide you with any of these point # Find Us -When you are starting to contribute to grin, we really would appreciate if you come by the gitter chat channels. +When you are starting to contribute to grin, we really would appreciate if you come by one of the community channels. -In case of problems with trying out grin, before starting to contribute, there's the [grincoin#support](https://keybase.io/team/grincoin) on Keybase. Write there about what you've done, what you want to do, and maybe paste logs through a text paste webservice. +If you run into problems while trying out Grin before contributing, ask in [grincoin#support](https://keybase.io/team/grincoin) on Keybase. Describe what you've done, what you want to do, and include logs using a paste service if needed. -* Please [join the grincoin#general on Keybase](https://keybase.io/team/grincoin) to get a feeling for the community. -* And see the developers chat channel [grincoin#dev on Keybase](https://keybase.io/team/grincoin) if you have questions about source code files. +* Join the development discussion on [Telegram](https://t.me/grindevelopment). +* [Grin Governance Council Public](https://t.me/Grin_Governance) channel where biweekly meetings are held for governance and development issues. +* [Grin General Telegram channel](https://t.me/grinprivacy/1) for general community discussion. +* Please [join grincoin#general on Keybase](https://keybase.io/team/grincoin) to get a feeling for the community. +* See the developers' chat channel [grincoin#dev on Keybase](https://keybase.io/team/grincoin) if you have questions about source code files. If you explain what you're looking at and what you want to do, we'll try to help you along the way. * See `docs/*.md` and the folder structure explanations, [the wiki](https://github.com/mimblewimble/docs/wiki) and the official [Grin documentation](https://docs.grin.mw/). -* Further information and discussions are in the [Forum](https://forum.grin.mw), the [website](https://grin.mw), the [mailing list](https://lists.launchpad.net/mimblewimble/) and news channels like the [Reddit/grincoin](https://www.reddit.com/r/grincoin/) and a (mostly unfiltered!) Twitter bot that collects headlines, mailing list posts, and reddit posts related to Mimblewimble/Grin: [@grinmw](https://twitter.com/grinmw) +* Further information and discussions are in the [Forum](https://forum.grin.mw), the [website](https://grin.mw), the [mailing list](https://lists.launchpad.net/mimblewimble/), and news channels like [Reddit/grincoin](https://www.reddit.com/r/grincoin/). ## Testing -Run all tests with `cargo test --all` and please remember to test locally before creating a PR on github. +Run all tests with `cargo test --all` and please remember to test locally before creating a PR against `staging`. -### Check Travis output +### Check CI output -After creating a PR on github, the code will be tested automatically by Travis CI, and from the results you'll get a red or green light. The test can take a while, and you'll have a "yellow traffic light" on your PR until Travis CI is done testing. +After creating a PR, the code will be tested automatically by CI. The test can take a while, and your PR may remain pending until CI is done testing. ### Building quality diff --git a/README.md b/README.md index ab3de1d9..948916f3 100644 --- a/README.md +++ b/README.md @@ -30,9 +30,8 @@ To get involved, read our [contributing docs](CONTRIBUTING.md). Find us: +* Telegram: [Grin Development](https://t.me/grindevelopment) * Chat: [Keybase](https://keybase.io/team/grincoin), more instructions on how to join [here](https://grin.mw/community). -* Mailing list: join the [~Mimblewimble team](https://launchpad.net/~mimblewimble) and subscribe on Launchpad. -* Twitter for the Grin council: [@grincouncil](https://twitter.com/grincouncil) ## Getting Started diff --git a/api/src/foreign.rs b/api/src/foreign.rs index cddfe783..f1f83c00 100644 --- a/api/src/foreign.rs +++ b/api/src/foreign.rs @@ -32,7 +32,7 @@ use crate::{rest::*, BlockListing}; use std::sync::Weak; /// Main interface into all node API functions. -/// Node APIs are split into two seperate blocks of functionality +/// Node APIs are split into two separate blocks of functionality /// called the ['Owner'](struct.Owner.html) and ['Foreign'](struct.Foreign.html) APIs /// /// Methods in this API are intended to be 'single use'. diff --git a/api/src/owner.rs b/api/src/owner.rs index b63609c2..2ec02c12 100644 --- a/api/src/owner.rs +++ b/api/src/owner.rs @@ -27,7 +27,7 @@ use std::net::SocketAddr; use std::sync::Weak; /// Main interface into all node API functions. -/// Node APIs are split into two seperate blocks of functionality +/// Node APIs are split into two separate blocks of functionality /// called the ['Owner'](struct.Owner.html) and ['Foreign'](struct.Foreign.html) APIs /// /// Methods in this API are intended to be 'single use'. diff --git a/config/src/comments.rs b/config/src/comments.rs index be3a5863..3b93ac6e 100644 --- a/config/src/comments.rs +++ b/config/src/comments.rs @@ -262,8 +262,11 @@ fn comments() -> HashMap { "host".to_string(), " #The interface on which to listen. -#0.0.0.0 will listen on all interfaces, allowing others to interact -#127.0.0.1 will listen on the local machine only +#:: will listen on all IPv6 interfaces and may also accept IPv4 depending on OS socket settings +#0.0.0.0 will listen on all IPv4 interfaces, allowing others to interact +#Set host to 0.0.0.0 if only IPv4 listening is desired +#127.0.0.1 will listen on the local machine only over IPv4 +#::1 will listen on the local machine only over IPv6 " .to_string(), ); diff --git a/doc/intro.md b/doc/intro.md index 9a05f9cb..a57763a7 100644 --- a/doc/intro.md +++ b/doc/intro.md @@ -252,7 +252,9 @@ which can be signed by the attacker because Carol's blinding factor cancels out This output (`(113 + 99)*G + 2*H`) requires that both the numbers 113 and 99 are known in order to be spent; the attacker would thus have successfully locked Carol's UTXO. The requirement for a range proof for the blinding factor prevents this -because the attacker doesn't know the number 113 and thus neither (113 + 99). A more detailed description of range proofs is further detailed in the [range proof paper](https://eprint.iacr.org/2017/1066.pdf). +because the attacker doesn't know the number 113 and thus neither (113 + 99). In other words, without knowing the private +key (blinding factor), the attacker would not know the value in the output and would not be able to produce a valid range proof for it. +A more detailed description of range proofs is further detailed in the [range proof paper](https://eprint.iacr.org/2017/1066.pdf). #### Putting It All Together diff --git a/doc/pow/pow.md b/doc/pow/pow.md index 52ef3607..3de4728d 100644 --- a/doc/pow/pow.md +++ b/doc/pow/pow.md @@ -113,10 +113,10 @@ Now, (hopefully) armed with a basic understanding of what the Cuckoo Cycle algor ## Mining in Grin -The Cuckoo Cycle outlined above forms the basis of Grin's mining process, however Grin uses two variantion of Cuckoo Cycle in tandem with several other systems to create a Proof-of-Work. +The Cuckoo Cycle outlined above forms the basis of Grin's mining process, however Grin uses two variations of Cuckoo Cycle in tandem with several other systems to create a Proof-of-Work. 1. for GPUs: Cuckaroo on 2^29 edges - * Tweaked every 6 months to maitain ASIC resistance. + * Tweaked every 6 months to maintain ASIC resistance. * 90% of rewards at launch, linearly decreasing to 0% in 2 years. * Variant of Cuckoo that enforces so-called ``mean'' mining. * Takes 5.5GB of memory (perhaps 4GB with slowdown). diff --git a/p2p/src/types.rs b/p2p/src/types.rs index 76cc3fe7..8d0bdd1a 100644 --- a/p2p/src/types.rs +++ b/p2p/src/types.rs @@ -293,7 +293,7 @@ pub struct P2PConfig { /// Default address for peer-to-peer connections. impl Default for P2PConfig { fn default() -> P2PConfig { - let ipaddr = "0.0.0.0".parse().unwrap(); + let ipaddr = "::".parse().unwrap(); P2PConfig { host: ipaddr, port: 3414, diff --git a/servers/src/common/types.rs b/servers/src/common/types.rs index ea83fecf..a428a5eb 100644 --- a/servers/src/common/types.rs +++ b/servers/src/common/types.rs @@ -19,7 +19,6 @@ use std::sync::Arc; use chrono::prelude::Utc; use rand::prelude::*; -use crate::api; use crate::chain; use crate::core::global::{ChainTypes, DEFAULT_FUTURE_TIME_LIMIT}; use crate::core::{core, libtx, pow}; @@ -28,6 +27,7 @@ use crate::p2p; use crate::pool; use crate::pool::types::DandelionConfig; use crate::store; +use crate::{api, Server}; /// Error type wrapping underlying module errors. #[derive(Debug)] @@ -405,3 +405,17 @@ impl DandelionEpoch { self.relay_peer.clone() } } + +/// Server initialization status. +pub enum ServerInitStatus { + /// Database loading. + LoadDatabase, + /// P2P server initialization. + StartSync, + /// API server initialization. + StartAPI, + /// Server instance after successful initialization. + FinishedLoading(Server), + /// Error on initialization. + ErrorLoading(Error), +} diff --git a/servers/src/grin/seed.rs b/servers/src/grin/seed.rs index 3d5fe8d8..aa1e8d44 100644 --- a/servers/src/grin/seed.rs +++ b/servers/src/grin/seed.rs @@ -35,18 +35,20 @@ use crate::util::StopState; /// DNS Seeds with contacts associated - Mainnet pub const MAINNET_DNS_SEEDS: &[&str] = &[ - "mainnet-seed.grinnode.live", // info@grinnode.live - "grincoin.org", // xmpp:aglkm@conversations.im - "main.gri.mw", // admin@gri.mw - "mainnet.grinffindor.org", // support@grinffindor.org - "main-seed.grin.money", // support@grinily.com + "mainnet-seed.grinnode.live", // info@grinnode.live + "grincoin.org", // xmpp:aglkm@conversations.im + "main.gri.mw", // admin@gri.mw + "mainnet.grinffindor.org", // support@grinffindor.org + "main-seed.grin.money", // support@grinily.com + "mainnet.fountainoffairfortune.it", // support@fountainoffairfortune.it ]; /// DNS Seeds with contacts associated - Testnet pub const TESTNET_DNS_SEEDS: &[&str] = &[ - "testnet.grincoin.org", // xmpp:aglkm@conversations.im - "test.gri.mw", // admin@gri.mw - "testnet.grinffindor.org", // support@grinffindor.org - "test-seed.grin.money", // support@grinily.com + "testnet.grincoin.org", // xmpp:aglkm@conversations.im + "test.gri.mw", // admin@gri.mw + "testnet.grinffindor.org", // support@grinffindor.org + "test-seed.grin.money", // support@grinily.com + "testnet.fountainoffairfortune.it", // support@fountainoffairfortune.it ]; pub fn connect_and_monitor( diff --git a/servers/src/grin/server.rs b/servers/src/grin/server.rs index d3510fe2..f492ed17 100644 --- a/servers/src/grin/server.rs +++ b/servers/src/grin/server.rs @@ -39,7 +39,7 @@ use crate::common::hooks::{init_chain_hooks, init_net_hooks}; use crate::common::stats::{ ChainStats, DiffBlock, DiffStats, PeerStats, ServerStateInfo, ServerStats, TxStats, }; -use crate::common::types::{Error, ServerConfig, StratumServerConfig}; +use crate::common::types::{Error, ServerConfig, ServerInitStatus, StratumServerConfig}; use crate::core::core::hash::{Hashed, ZERO_HASH}; use crate::core::ser::ProtocolVersion; use crate::core::{consensus, genesis, global, pow}; @@ -52,7 +52,6 @@ use crate::pool; use crate::util::file::get_first_line; use crate::util::{RwLock, StopState}; use futures::channel::oneshot; -use grin_util::logger::LogEntry; /// Arcified thread-safe TransactionPool with type parameters used by server components pub type ServerTxPool = Arc>>; @@ -84,20 +83,16 @@ impl Server { /// Instantiates and starts a new server. Optionally takes a callback /// for the server to send an ARC copy of itself, to allow another process /// to poll info about the server status - pub fn start( + pub fn start( config: ServerConfig, - logs_rx: Option>, - mut info_callback: F, stop_state: Option>, + server_tx: Option>, api_chan: &'static mut (oneshot::Sender<()>, oneshot::Receiver<()>), - ) -> Result<(), Error> - where - F: FnMut(Server, Option>), - { + ) -> Result { let mining_config = config.stratum_mining_config.clone(); let enable_test_miner = config.run_test_miner; let test_miner_wallet_url = config.test_miner_wallet_url.clone(); - let serv = Server::new(config, stop_state, api_chan)?; + let serv = Server::new(config, stop_state, server_tx, api_chan)?; if let Some(c) = mining_config { let enable_stratum_server = c.enable_stratum_server; @@ -118,8 +113,7 @@ impl Server { } } - info_callback(serv, logs_rx); - Ok(()) + Ok(serv) } // Exclusive (advisory) lock_file to ensure we do not run multiple @@ -151,6 +145,7 @@ impl Server { pub fn new( config: ServerConfig, stop_state: Option>, + server_tx: Option>, api_chan: &'static mut (oneshot::Sender<()>, oneshot::Receiver<()>), ) -> Result { // Obtain our lock_file or fail immediately with an error. @@ -193,6 +188,10 @@ impl Server { info!("Starting server, genesis block: {}", genesis.hash()); + if let Some(ref server_tx) = server_tx { + let _ = server_tx.send(ServerInitStatus::LoadDatabase); + } + let shared_chain = Arc::new(chain::Chain::init( config.db_root.clone(), chain_adapter.clone(), @@ -220,6 +219,10 @@ impl Server { }; debug!("Capabilities: {:?}", capabilities); + if let Some(ref server_tx) = server_tx { + let _ = server_tx.send(ServerInitStatus::StartSync); + } + let p2p_server = Arc::new(p2p::Server::new( &config.db_root, capabilities, @@ -265,6 +268,10 @@ impl Server { } })?; + if let Some(ref server_tx) = server_tx { + let _ = server_tx.send(ServerInitStatus::StartAPI); + } + info!("Starting rest apis at: {}", &config.api_http_addr); let api_secret = get_first_line(config.api_secret_path.clone()); let foreign_api_secret = get_first_line(config.foreign_api_secret_path.clone()); diff --git a/src/bin/cmd/server.rs b/src/bin/cmd/server.rs index 6eb60f9f..fbca7d47 100644 --- a/src/bin/cmd/server.rs +++ b/src/bin/cmd/server.rs @@ -28,7 +28,10 @@ use crate::tui::ui; use futures::channel::oneshot; use grin_p2p::msg::PeerAddrs; use grin_p2p::PeerAddr; +use grin_servers::common::types::ServerInitStatus; +use grin_servers::Server; use grin_util::logger::LogEntry; +use grin_util::StopState; use std::sync::mpsc; /// wrap below to allow UI to clean up on stop @@ -37,38 +40,50 @@ pub fn start_server( logs_rx: Option>, api_chan: &'static mut (oneshot::Sender<()>, oneshot::Receiver<()>), ) { - start_server_tui(config, logs_rx, api_chan); - exit(0); + exit(start_server_tui(config, logs_rx, api_chan)); } fn start_server_tui( config: servers::ServerConfig, logs_rx: Option>, api_chan: &'static mut (oneshot::Sender<()>, oneshot::Receiver<()>), -) { - // Run the UI controller.. here for now for simplicity to access - // everything it might need +) -> i32 { if config.run_tui.unwrap_or(false) { warn!("Starting GRIN in UI mode..."); - servers::Server::start( - config, - logs_rx, - |serv: servers::Server, logs_rx: Option>| { - let mut controller = ui::Controller::new(logs_rx.unwrap()).unwrap_or_else(|e| { - panic!("Error loading UI controller: {}", e); - }); - controller.run(serv); - }, - None, - api_chan, - ) - .unwrap(); + // Run the UI controller. + let (serv_tx, serv_rx) = mpsc::channel::(); + let mut controller = ui::Controller::new(logs_rx, serv_rx).unwrap_or_else(|e| { + panic!("Error loading UI controller: {}", e); + }); + let serv_tx_clone = serv_tx.clone(); + let stop_state = Arc::new(StopState::new()); + let stop_state_clone = stop_state.clone(); + thread::spawn(move || { + match Server::start( + config, + Some(stop_state_clone.clone()), + Some(serv_tx_clone.clone()), + api_chan, + ) { + Ok(s) => { + if stop_state_clone.is_stopped() { + s.stop(); + return; + } + let _ = serv_tx_clone.send(ServerInitStatus::FinishedLoading(s)); + } + Err(e) => { + let _ = serv_tx_clone.send(ServerInitStatus::ErrorLoading(e)); + } + } + }); + let exit_code = controller.run(); + stop_state.stop(); + exit_code } else { warn!("Starting GRIN w/o UI..."); - servers::Server::start( - config, - logs_rx, - |serv: servers::Server, _: Option>| { + match Server::start(config, None, None, api_chan) { + Ok(s) => { let running = Arc::new(AtomicBool::new(true)); let r = running.clone(); ctrlc::set_handler(move || { @@ -79,12 +94,14 @@ fn start_server_tui( thread::sleep(Duration::from_secs(1)); } warn!("Received SIGINT (Ctrl+C) or SIGTERM (kill)."); - serv.stop(); - }, - None, - api_chan, - ) - .unwrap(); + s.stop(); + 0 + } + Err(e) => { + error!("Error starting GRIN: {:?}", e); + 1 + } + } } } diff --git a/src/bin/tui/ui.rs b/src/bin/tui/ui.rs index 3021ebdf..e28876dc 100644 --- a/src/bin/tui/ui.rs +++ b/src/bin/tui/ui.rs @@ -15,6 +15,12 @@ //! Basic TUI to better output the overall system status and status //! of various subsystems +use super::constants::MAIN_MENU; +use crate::built_info; +use crate::servers::Server; +use crate::tui::constants::{ROOT_STACK, VIEW_BASIC_STATUS, VIEW_MINING, VIEW_PEER_SYNC}; +use crate::tui::types::{TUIStatusListener, UIMessage}; +use crate::tui::{logs, menu, mining, peers, status, version}; use chrono::prelude::Utc; use cursive::direction::Orientation; use cursive::theme::BaseColor::{Black, Blue, Cyan, White}; @@ -29,24 +35,20 @@ use cursive::views::{ CircularFocus, Dialog, LinearLayout, Panel, SelectView, StackView, TextView, ViewRef, }; use cursive::{CursiveRunnable, CursiveRunner}; -use std::sync::mpsc; -use std::{thread, time}; - -use super::constants::MAIN_MENU; -use crate::built_info; -use crate::servers::Server; -use crate::tui::constants::{ROOT_STACK, VIEW_BASIC_STATUS, VIEW_MINING, VIEW_PEER_SYNC}; -use crate::tui::types::{TUIStatusListener, UIMessage}; -use crate::tui::{logs, menu, mining, peers, status, version}; use grin_core::global; +use grin_servers::common::types::{Error, ServerInitStatus}; use grin_util::logger::LogEntry; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{mpsc, Arc}; +use std::{thread, time}; pub struct UI { cursive: CursiveRunner, ui_rx: mpsc::Receiver, ui_tx: mpsc::Sender, controller_tx: mpsc::Sender, - logs_rx: mpsc::Receiver, + logs_rx: Option>, + show_dialog: Arc, } fn modify_theme(theme: &mut Theme) { @@ -65,7 +67,7 @@ impl UI { /// Create a new UI pub fn new( controller_tx: mpsc::Sender, - logs_rx: mpsc::Receiver, + logs_rx: Option>, ) -> UI { let (ui_tx, ui_rx) = mpsc::channel::(); @@ -75,6 +77,7 @@ impl UI { ui_rx, controller_tx, logs_rx, + show_dialog: Arc::new(AtomicBool::new(false)), }; // Create UI objects, etc @@ -102,7 +105,7 @@ impl UI { built_info::PKG_VERSION, global::get_chain_type() ), - Color::Dark(BaseColor::Green), + Dark(BaseColor::Green), )); let main_layer = LinearLayout::new(Orientation::Vertical) @@ -117,17 +120,21 @@ impl UI { let mut theme = grin_ui.cursive.current_theme().clone(); modify_theme(&mut theme); grin_ui.cursive.set_theme(theme); + grin_ui.cursive.add_fullscreen_layer(main_layer); // Configure a callback (shutdown, for the first test) let controller_tx_clone = grin_ui.controller_tx.clone(); + let show_dialog_clone = grin_ui.show_dialog.clone(); grin_ui.cursive.add_global_callback('q', move |c| { + if show_dialog_clone.load(Ordering::Relaxed) { + c.pop_layer(); + } let content = StyledString::styled("Shutting down...", Color::Light(BaseColor::Yellow)); c.add_layer(CircularFocus::new(Dialog::around(TextView::new(content))).wrap_tab()); - controller_tx_clone - .send(ControllerMessage::Shutdown) - .unwrap(); + let _ = controller_tx_clone.send(ControllerMessage::Shutdown); }); + grin_ui.cursive.set_fps(3); grin_ui } @@ -139,8 +146,10 @@ impl UI { return false; } - while let Some(message) = self.logs_rx.try_iter().next() { - logs::TUILogsView::update(&mut self.cursive, message); + if let Some(logs_rx) = &self.logs_rx { + while let Some(message) = logs_rx.try_iter().next() { + logs::TUILogsView::update(&mut self.cursive, message); + } } // Process any pending UI messages @@ -174,6 +183,8 @@ impl UI { pub struct Controller { rx: mpsc::Receiver, ui: UI, + serv_rx: mpsc::Receiver, + server: Option, } pub enum ControllerMessage { @@ -182,39 +193,99 @@ pub enum ControllerMessage { impl Controller { /// Create a new controller - pub fn new(logs_rx: mpsc::Receiver) -> Result { + pub fn new( + logs_rx: Option>, + serv_rx: mpsc::Receiver, + ) -> Result { let (tx, rx) = mpsc::channel::(); Ok(Controller { rx, ui: UI::new(tx, logs_rx), + serv_rx, + server: None, }) } + /// Server initialization status. + pub fn init_status(&mut self, text: &str, pop: bool) { + if pop { + self.ui.cursive.pop_layer(); + } + let content = StyledString::styled(text, Color::Light(BaseColor::Green)); + self.ui + .cursive + .add_layer(CircularFocus::new(Dialog::around(TextView::new(content))).wrap_tab()); + self.ui.show_dialog.store(true, Ordering::Relaxed); + } + + /// Server initialization error. + pub fn init_error(&mut self, e: Error) { + let content = StyledString::styled(format!("{:?}", e), Color::Light(BaseColor::Red)); + self.ui.cursive.add_layer( + CircularFocus::new(Dialog::around(TextView::new(content)).button("Exit", |s| { + s.quit(); + })) + .wrap_tab(), + ); + self.ui.show_dialog.store(true, Ordering::Relaxed); + } + + /// Server UI after initialization. + pub fn server(&mut self, server: &Server) { + if let Ok(stats) = server.get_server_stats() { + self.ui.ui_tx.send(UIMessage::UpdateStatus(stats)).unwrap(); + } + } + /// Run the controller - pub fn run(&mut self, server: Server) { + pub fn run(&mut self) -> i32 { + self.init_status("Starting server...", false); + let stat_update_interval = 1; let mut next_stat_update = Utc::now().timestamp() + stat_update_interval; let delay = time::Duration::from_millis(50); + let mut exit_code = 0; while self.ui.step() { if let Some(message) = self.rx.try_iter().next() { match message { ControllerMessage::Shutdown => { warn!("Shutdown in progress, please wait"); self.ui.stop(); - server.stop(); - return; + if let Some(s) = self.server.take() { + s.stop(); + } + return exit_code; + } + } + } + + if let Some(m) = self.serv_rx.try_iter().next() { + match m { + ServerInitStatus::LoadDatabase => self.init_status("Loading database...", true), + ServerInitStatus::StartSync => self.init_status("Start syncing...", true), + ServerInitStatus::StartAPI => self.init_status("Starting API...", true), + ServerInitStatus::FinishedLoading(s) => { + self.ui.cursive.pop_layer(); + self.ui.show_dialog.store(false, Ordering::Relaxed); + self.server = Some(s) + } + ServerInitStatus::ErrorLoading(e) => { + exit_code = 1; + self.init_error(e); } } } if Utc::now().timestamp() > next_stat_update { next_stat_update = Utc::now().timestamp() + stat_update_interval; - if let Ok(stats) = server.get_server_stats() { - self.ui.ui_tx.send(UIMessage::UpdateStatus(stats)).unwrap(); + if let Some(server) = &self.server { + if let Ok(stats) = server.get_server_stats() { + self.ui.ui_tx.send(UIMessage::UpdateStatus(stats)).unwrap(); + } } } thread::sleep(delay); } - server.stop(); + exit_code } }