initial Abscissa bin (#18)

* abscissa init

* remove package in cargo.toml

* cargo fmt + remove more stuff fro toml

* bump rust edition
This commit is contained in:
Boog900 2023-04-26 18:22:06 +00:00 committed by GitHub
parent a187d9a357
commit 5f20342736
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 490 additions and 103 deletions

View file

@ -1,19 +1,3 @@
[package]
name = "cuprate"
version = "0.0.1"
edition = "2021"
rust-version = "1.68.0"
description = "An upcoming experimental, modern & secure monero node"
readme = "readme.md"
license = "AGPL-3.0-only"
repository = "https://github.com/SyntheticBird45/cuprate"
# All Contributors on github
authors=[
"SyntheticBird45",
"Boog900"
]
[workspace]
@ -36,20 +20,3 @@ tracing-subscriber = "*"
# As suggested by /u/danda :
thiserror = "*"
[profile.release]
opt-level = 3
debug = 0
strip = "symbols"
lto = "thin"
panic = "abort"
[build]
linker="clang"
rustflags=[
"-Clink-arg=-fuse-ld=mold",
"-Zcf-protection=full",
"-Zsanitizer=cfi",
"-Crelocation-model=pie",
"-Cstack-protector=all",
]

View file

@ -1,12 +1,21 @@
[package]
name = "cuprate-bin"
name = "cuprate"
authors = []
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
clap = { version = "*", features = [] }
clap_complete = "*"
tracing = { workspace = true }
tracing-subscriber = { workspace = true }
clap = "4"
serde = { version = "1", features = ["serde_derive"] }
thiserror = "1"
[dependencies.abscissa_core]
version = "0.7.0"
# optional: use `gimli` to capture backtraces
# see https://github.com/rust-lang/backtrace-rs/issues/189
# features = ["gimli-backtrace"]
[dev-dependencies]
abscissa_core = { version = "0.7.0", features = ["testing"] }
once_cell = "1.2"

9
cuprate/README.md Normal file
View file

@ -0,0 +1,9 @@
This application is authored using [Abscissa], a Rust application framework.
For more information, see:
[Documentation]
[Abscissa]: https://github.com/iqlusioninc/abscissa
[Documentation]: https://docs.rs/abscissa_core/

View file

@ -0,0 +1,88 @@
//! Cuprate Abscissa Application
use crate::{commands::EntryPoint, config::CuprateConfig};
use abscissa_core::{
application::{self, AppCell},
config::{self, CfgCell},
trace, Application, FrameworkError, StandardPaths,
};
/// Application state
pub static APP: AppCell<CuprateApp> = AppCell::new();
/// Cuprate Application
#[derive(Debug)]
pub struct CuprateApp {
/// Application configuration.
config: CfgCell<CuprateConfig>,
/// Application state.
state: application::State<Self>,
}
/// Initialize a new application instance.
///
/// By default no configuration is loaded, and the framework state is
/// initialized to a default, empty state (no components, threads, etc).
impl Default for CuprateApp {
fn default() -> Self {
Self {
config: CfgCell::default(),
state: application::State::default(),
}
}
}
impl Application for CuprateApp {
/// Entrypoint command for this application.
type Cmd = EntryPoint;
/// Application configuration.
type Cfg = CuprateConfig;
/// Paths to resources within the application.
type Paths = StandardPaths;
/// Accessor for application configuration.
fn config(&self) -> config::Reader<CuprateConfig> {
self.config.read()
}
/// Borrow the application state immutably.
fn state(&self) -> &application::State<Self> {
&self.state
}
/// Register all components used by this application.
///
/// If you would like to add additional components to your application
/// beyond the default ones provided by the framework, this is the place
/// to do so.
fn register_components(&mut self, command: &Self::Cmd) -> Result<(), FrameworkError> {
let framework_components = self.framework_components(command)?;
let mut app_components = self.state.components_mut();
app_components.register(framework_components)
}
/// Post-configuration lifecycle callback.
///
/// Called regardless of whether config is loaded to indicate this is the
/// time in app lifecycle when configuration would be loaded if
/// possible.
fn after_config(&mut self, config: Self::Cfg) -> Result<(), FrameworkError> {
// Configure components
let mut components = self.state.components_mut();
components.after_config(&config)?;
self.config.set_once(config);
Ok(())
}
/// Get tracing configuration from command-line options
fn tracing_config(&self, command: &EntryPoint) -> trace::Config {
if command.verbose {
trace::Config::verbose()
} else {
trace::Config::default()
}
}
}

View file

@ -0,0 +1,11 @@
//! Main entry point for Cuprate
#![deny(warnings, missing_docs, trivial_casts, unused_qualifications)]
#![forbid(unsafe_code)]
use cuprate::application::APP;
/// Boot Cuprate
fn main() {
abscissa_core::boot(&APP);
}

View file

@ -1,50 +0,0 @@
use crate::CUPRATE_VERSION;
use clap::{value_parser, Arg, ArgAction, ArgGroup, ArgMatches, Command};
use tracing::{event, span, Level, Span};
/// This function simply contains clap arguments
pub fn args() -> ArgMatches {
Command::new("Cuprate")
.version(CUPRATE_VERSION)
.author("Cuprate's contributors")
.about("An upcoming experimental, modern, and secure monero node")
// Generic Arguments
.arg(
Arg::new("log")
.long("log-level")
.value_name("Level")
.help("Set the log level")
.value_parser(value_parser!(u8))
.default_value("1")
.long_help("Set the log level. There is 3 log level: <1~INFO, 2~DEBUG >3~TRACE.")
.required(false)
.action(ArgAction::Set),
)
.get_matches()
}
/// This function initialize the FmtSubscriber used by tracing to display event in the console. It send back a span used during runtime.
pub fn init(matches: &ArgMatches) -> Span {
// Getting the log level from args
let log_level = matches.get_one::<u8>("log").unwrap();
let level_filter = match log_level {
2 => Level::DEBUG,
x if x > &2 => Level::TRACE,
_ => Level::INFO,
};
// Initializing tracing subscriber and runtime span
let subscriber = tracing_subscriber::FmtSubscriber::builder()
.with_max_level(level_filter)
.with_target(false)
.finish();
tracing::subscriber::set_global_default(subscriber).expect("Failed to set global subscriber for tracing. We prefer to abort the node since without it you have no output in the console");
let runtime_span = span!(Level::INFO, "Runtime");
let _guard = runtime_span.enter();
// Notifying log level
event!(Level::INFO, "Log level set to {}", level_filter);
drop(_guard);
runtime_span
}

87
cuprate/src/commands.rs Normal file
View file

@ -0,0 +1,87 @@
//! Cuprate Subcommands
//!
//! This is where you specify the subcommands of your application.
//!
//! The default application comes with two subcommands:
//!
//! - `start`: launches the application
//! - `--version`: print application version
//!
//! See the `impl Configurable` below for how to specify the path to the
//! application's configuration file.
mod start;
use self::start::StartCmd;
use crate::config::CuprateConfig;
use abscissa_core::{config::Override, Command, Configurable, FrameworkError, Runnable};
use std::path::PathBuf;
/// Cuprate Configuration Filename
pub const CONFIG_FILE: &str = "cuprate.toml";
/// Cuprate Subcommands
/// Subcommands need to be listed in an enum.
#[derive(clap::Parser, Command, Debug, Runnable)]
pub enum CuprateCmd {
/// The `start` subcommand
Start(StartCmd),
}
/// Entry point for the application. It needs to be a struct to allow using subcommands!
#[derive(clap::Parser, Command, Debug)]
#[command(author, about, version)]
pub struct EntryPoint {
#[command(subcommand)]
cmd: CuprateCmd,
/// Enable verbose logging
#[arg(short, long)]
pub verbose: bool,
/// Use the specified config file
#[arg(short, long)]
pub config: Option<String>,
}
impl Runnable for EntryPoint {
fn run(&self) {
self.cmd.run()
}
}
/// This trait allows you to define how application configuration is loaded.
impl Configurable<CuprateConfig> for EntryPoint {
/// Location of the configuration file
fn config_path(&self) -> Option<PathBuf> {
// Check if the config file exists, and if it does not, ignore it.
// If you'd like for a missing configuration file to be a hard error
// instead, always return `Some(CONFIG_FILE)` here.
let filename = self
.config
.as_ref()
.map(PathBuf::from)
.unwrap_or_else(|| CONFIG_FILE.into());
if filename.exists() {
Some(filename)
} else {
None
}
}
/// Apply changes to the config after it's been loaded, e.g. overriding
/// values in a config file using command-line options.
///
/// This can be safely deleted if you don't want to override config
/// settings from command-line options.
fn process_config(&self, config: CuprateConfig) -> Result<CuprateConfig, FrameworkError> {
match &self.cmd {
CuprateCmd::Start(cmd) => cmd.override_config(config),
//
// If you don't need special overrides for some
// subcommands, you can just use a catch all
// _ => Ok(config),
}
}
}

View file

@ -0,0 +1,42 @@
//! `start` subcommand - example of how to write a subcommand
/// App-local prelude includes `app_reader()`/`app_writer()`/`app_config()`
/// accessors along with logging macros. Customize as you see fit.
use crate::prelude::*;
use crate::config::CuprateConfig;
use abscissa_core::{config, Command, FrameworkError, Runnable};
/// `start` subcommand
///
/// The `Parser` proc macro generates an option parser based on the struct
/// definition, and is defined in the `clap` crate. See their documentation
/// for a more comprehensive example:
///
/// <https://docs.rs/clap/>
#[derive(clap::Parser, Command, Debug)]
pub struct StartCmd {
/// To whom are we saying hello?
recipient: Vec<String>,
}
impl Runnable for StartCmd {
/// Start the application.
fn run(&self) {
let config = APP.config();
println!("Hello, {}!", &config.hello.recipient);
}
}
impl config::Override<CuprateConfig> for StartCmd {
// Process the given command line options, overriding settings from
// a configuration file using explicit flags taken from command-line
// arguments.
fn override_config(&self, mut config: CuprateConfig) -> Result<CuprateConfig, FrameworkError> {
if !self.recipient.is_empty() {
config.hello.recipient = self.recipient.join(" ");
}
Ok(config)
}
}

45
cuprate/src/config.rs Normal file
View file

@ -0,0 +1,45 @@
//! Cuprate Config
//!
//! See instructions in `commands.rs` to specify the path to your
//! application's configuration file and/or command-line options
//! for specifying it.
use serde::{Deserialize, Serialize};
/// Cuprate Configuration
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct CuprateConfig {
/// An example configuration section
pub hello: ExampleSection,
}
/// Default configuration settings.
///
/// Note: if your needs are as simple as below, you can
/// use `#[derive(Default)]` on CuprateConfig instead.
impl Default for CuprateConfig {
fn default() -> Self {
Self {
hello: ExampleSection::default(),
}
}
}
/// Example configuration section.
///
/// Delete this and replace it with your actual configuration structs.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ExampleSection {
/// Example configuration value
pub recipient: String,
}
impl Default for ExampleSection {
fn default() -> Self {
Self {
recipient: "world".to_owned(),
}
}
}

70
cuprate/src/error.rs Normal file
View file

@ -0,0 +1,70 @@
//! Error types
use abscissa_core::error::{BoxError, Context};
use std::{
fmt::{self, Display},
io,
ops::Deref,
};
use thiserror::Error;
/// Kinds of errors
#[derive(Copy, Clone, Debug, Eq, Error, PartialEq)]
pub enum ErrorKind {
/// Error in configuration file
#[error("config error")]
Config,
/// Input/output error
#[error("I/O error")]
Io,
}
impl ErrorKind {
/// Create an error context from this error
pub fn context(self, source: impl Into<BoxError>) -> Context<ErrorKind> {
Context::new(self, Some(source.into()))
}
}
/// Error type
#[derive(Debug)]
pub struct Error(Box<Context<ErrorKind>>);
impl Deref for Error {
type Target = Context<ErrorKind>;
fn deref(&self) -> &Context<ErrorKind> {
&self.0
}
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.0.source()
}
}
impl From<ErrorKind> for Error {
fn from(kind: ErrorKind) -> Self {
Context::new(kind, None).into()
}
}
impl From<Context<ErrorKind>> for Error {
fn from(context: Context<ErrorKind>) -> Self {
Error(Box::new(context))
}
}
impl From<io::Error> for Error {
fn from(err: io::Error) -> Self {
ErrorKind::Io.context(err).into()
}
}

22
cuprate/src/lib.rs Normal file
View file

@ -0,0 +1,22 @@
//! Cuprate
//!
//! Application based on the [Abscissa] framework.
//!
//! [Abscissa]: https://github.com/iqlusioninc/abscissa
// Tip: Deny warnings with `RUSTFLAGS="-D warnings"` environment variable in CI
#![forbid(unsafe_code)]
#![warn(
missing_docs,
rust_2018_idioms,
trivial_casts,
unused_lifetimes,
unused_qualifications
)]
pub mod application;
pub mod commands;
pub mod config;
pub mod error;
pub mod prelude;

View file

@ -1,13 +0,0 @@
use tracing::{event, info, span, Level};
pub mod cli;
const CUPRATE_VERSION: &str = "0.1.0";
fn main() {
// Collecting options
let matches = cli::args();
// Initializing tracing subscriber and runtime span
let _runtime_span = cli::init(&matches);
}

9
cuprate/src/prelude.rs Normal file
View file

@ -0,0 +1,9 @@
//! Application-local prelude: conveniently import types/functions/macros
//! which are generally useful and should be available in every module with
//! `use crate::prelude::*;
/// Abscissa core prelude
pub use abscissa_core::prelude::*;
/// Application state
pub use crate::application::APP;

View file

@ -0,0 +1,91 @@
//! Acceptance test: runs the application as a subprocess and asserts its
//! output for given argument combinations matches what is expected.
//!
//! Modify and/or delete these as you see fit to test the specific needs of
//! your application.
//!
//! For more information, see:
//! <https://docs.rs/abscissa_core/latest/abscissa_core/testing/index.html>
// Tip: Deny warnings with `RUSTFLAGS="-D warnings"` environment variable in CI
#![forbid(unsafe_code)]
#![warn(
missing_docs,
rust_2018_idioms,
trivial_casts,
unused_lifetimes,
unused_qualifications
)]
use abscissa_core::testing::prelude::*;
use cuprate::config::CuprateConfig;
use once_cell::sync::Lazy;
/// Executes your application binary via `cargo run`.
///
/// Storing this value as a [`Lazy`] static ensures that all instances of
/// the runner acquire a mutex when executing commands and inspecting
/// exit statuses, serializing what would otherwise be multithreaded
/// invocations as `cargo test` executes tests in parallel by default.
pub static RUNNER: Lazy<CmdRunner> = Lazy::new(|| CmdRunner::default());
/// Use `CuprateConfig::default()` value if no config or args
#[test]
fn start_no_args() {
let mut runner = RUNNER.clone();
let mut cmd = runner.arg("start").capture_stdout().run();
cmd.stdout().expect_line("Hello, world!");
cmd.wait().unwrap().expect_success();
}
/// Use command-line argument value
#[test]
fn start_with_args() {
let mut runner = RUNNER.clone();
let mut cmd = runner
.args(&["start", "acceptance", "test"])
.capture_stdout()
.run();
cmd.stdout().expect_line("Hello, acceptance test!");
cmd.wait().unwrap().expect_success();
}
/// Use configured value
#[test]
fn start_with_config_no_args() {
let mut config = CuprateConfig::default();
config.hello.recipient = "configured recipient".to_owned();
let expected_line = format!("Hello, {}!", &config.hello.recipient);
let mut runner = RUNNER.clone();
let mut cmd = runner.config(&config).arg("start").capture_stdout().run();
cmd.stdout().expect_line(&expected_line);
cmd.wait().unwrap().expect_success();
}
/// Override configured value with command-line argument
#[test]
fn start_with_config_and_args() {
let mut config = CuprateConfig::default();
config.hello.recipient = "configured recipient".to_owned();
let mut runner = RUNNER.clone();
let mut cmd = runner
.config(&config)
.args(&["start", "acceptance", "test"])
.capture_stdout()
.run();
cmd.stdout().expect_line("Hello, acceptance test!");
cmd.wait().unwrap().expect_success();
}
/// Example of a test which matches a regular expression
#[test]
fn version_no_args() {
let mut runner = RUNNER.clone();
let mut cmd = runner.arg("--version").capture_stdout().run();
cmd.stdout().expect_regex(r"\A\w+ [\d\.\-]+\z");
}