Merge branch 'staging' into lmdb_update

This commit is contained in:
ardocrat
2026-06-01 14:48:24 +03:00
17 changed files with 360 additions and 100 deletions
+33 -6
View File
@@ -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"]
+2 -1
View File
@@ -16,5 +16,6 @@
"github.copilot-chat"
]
}
}
},
"runArgs": ["--privileged", "-v", "/sys/fs/cgroup:/sys/fs/cgroup:ro", "-v", "/tmp:/tmp"]
}
+45
View File
@@ -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
+40
View File
@@ -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]
+41 -9
View File
@@ -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 <your-fork-url>
```
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
+1 -2
View File
@@ -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
+1 -1
View File
@@ -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'.
+1 -1
View File
@@ -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'.
+5 -2
View File
@@ -262,8 +262,11 @@ fn comments() -> HashMap<String, String> {
"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(),
);
+3 -1
View File
@@ -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
+2 -2
View File
@@ -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).
+1 -1
View File
@@ -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,
+15 -1
View File
@@ -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),
}
+11 -9
View File
@@ -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(
+19 -12
View File
@@ -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<RwLock<pool::TransactionPool<PoolToChainAdapter, PoolToNetAdapter>>>;
@@ -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<F>(
pub fn start(
config: ServerConfig,
logs_rx: Option<mpsc::Receiver<LogEntry>>,
mut info_callback: F,
stop_state: Option<Arc<StopState>>,
server_tx: Option<mpsc::Sender<ServerInitStatus>>,
api_chan: &'static mut (oneshot::Sender<()>, oneshot::Receiver<()>),
) -> Result<(), Error>
where
F: FnMut(Server, Option<mpsc::Receiver<LogEntry>>),
{
) -> Result<Server, Error> {
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<Arc<StopState>>,
server_tx: Option<mpsc::Sender<ServerInitStatus>>,
api_chan: &'static mut (oneshot::Sender<()>, oneshot::Receiver<()>),
) -> Result<Server, Error> {
// 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());
+45 -28
View File
@@ -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<mpsc::Receiver<LogEntry>>,
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<mpsc::Receiver<LogEntry>>,
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<mpsc::Receiver<LogEntry>>| {
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::<ServerInitStatus>();
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<mpsc::Receiver<LogEntry>>| {
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
}
}
}
}
+95 -24
View File
@@ -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<CursiveRunnable>,
ui_rx: mpsc::Receiver<UIMessage>,
ui_tx: mpsc::Sender<UIMessage>,
controller_tx: mpsc::Sender<ControllerMessage>,
logs_rx: mpsc::Receiver<LogEntry>,
logs_rx: Option<mpsc::Receiver<LogEntry>>,
show_dialog: Arc<AtomicBool>,
}
fn modify_theme(theme: &mut Theme) {
@@ -65,7 +67,7 @@ impl UI {
/// Create a new UI
pub fn new(
controller_tx: mpsc::Sender<ControllerMessage>,
logs_rx: mpsc::Receiver<LogEntry>,
logs_rx: Option<mpsc::Receiver<LogEntry>>,
) -> UI {
let (ui_tx, ui_rx) = mpsc::channel::<UIMessage>();
@@ -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<ControllerMessage>,
ui: UI,
serv_rx: mpsc::Receiver<ServerInitStatus>,
server: Option<Server>,
}
pub enum ControllerMessage {
@@ -182,39 +193,99 @@ pub enum ControllerMessage {
impl Controller {
/// Create a new controller
pub fn new(logs_rx: mpsc::Receiver<LogEntry>) -> Result<Controller, String> {
pub fn new(
logs_rx: Option<mpsc::Receiver<LogEntry>>,
serv_rx: mpsc::Receiver<ServerInitStatus>,
) -> Result<Controller, String> {
let (tx, rx) = mpsc::channel::<ControllerMessage>();
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
}
}