From 82ca86f03d76cf8681cfac796cbbcae08676985e Mon Sep 17 00:00:00 2001 From: victor Date: Sat, 30 Aug 2025 02:31:26 +0200 Subject: [PATCH] bless --- TODO | 6 + hell0 | 0 node/Cargo.lock | 310 +++----------------------------- node/Cargo.toml | 2 +- node/database/tx.db | 32 ++-- node/src/args.rs | 7 +- node/src/cli.rs | 6 +- node/src/core/block.rs | 10 +- node/src/core/blockchain.rs | 43 +++-- node/src/core/data.rs | 6 + node/src/core/tx.rs | 5 - node/src/event_bus.rs | 5 +- node/src/lib.rs | 4 + node/src/main.rs | 27 ++- node/src/native_node/node.rs | 30 ++-- node/src/watcher/executor.rs | 12 +- node/src/watcher/renderer.rs | 335 +++++++++++++++++++++++------------ node/src/watcher/watcher.rs | 90 ++++++++-- 18 files changed, 446 insertions(+), 484 deletions(-) create mode 100644 TODO delete mode 100644 hell0 create mode 100644 node/src/core/data.rs diff --git a/TODO b/TODO new file mode 100644 index 0000000..f96b6ea --- /dev/null +++ b/TODO @@ -0,0 +1,6 @@ +Non Exaustive list of ideas: + +- Scripting with rhai [] +- Mouse Input [] +- Http server [] +- RCI [] diff --git a/hell0 b/hell0 deleted file mode 100644 index e69de29..0000000 diff --git a/node/Cargo.lock b/node/Cargo.lock index bd6fbca..60c9cb1 100644 --- a/node/Cargo.lock +++ b/node/Cargo.lock @@ -105,12 +105,6 @@ dependencies = [ "syn", ] -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - [[package]] name = "autocfg" version = "1.5.0" @@ -132,12 +126,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - [[package]] name = "bitflags" version = "2.9.3" @@ -171,12 +159,12 @@ dependencies = [ "serde", "serde_json", "sha2", + "textwrap", "thiserror", "tokio", "tokio-tungstenite", "uuid", "vlogger", - "warp", "wasm-bindgen", "web-sys", ] @@ -498,24 +486,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" -[[package]] -name = "form_urlencoded" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", -] - [[package]] name = "futures-core" version = "0.3.31" @@ -576,25 +546,6 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" -[[package]] -name = "h2" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - [[package]] name = "hashbrown" version = "0.15.5" @@ -606,30 +557,6 @@ dependencies = [ "foldhash", ] -[[package]] -name = "headers" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb" -dependencies = [ - "base64", - "bytes", - "headers-core", - "http", - "httpdate", - "mime", - "sha1", -] - -[[package]] -name = "headers-core" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" -dependencies = [ - "http", -] - [[package]] name = "heck" version = "0.5.0" @@ -653,79 +580,12 @@ dependencies = [ "itoa", ] -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] - [[package]] name = "httparse" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "hyper" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" -dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "pin-utils", - "smallvec", - "tokio", -] - -[[package]] -name = "hyper-util" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "hyper", - "pin-project-lite", - "tokio", - "tower-service", -] - [[package]] name = "iana-time-zone" version = "0.1.63" @@ -756,16 +616,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" -[[package]] -name = "indexmap" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" -dependencies = [ - "equivalent", - "hashbrown", -] - [[package]] name = "indoc" version = "2.0.6" @@ -930,22 +780,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "mime_guess" -version = "2.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" -dependencies = [ - "mime", - "unicase", -] - [[package]] name = "miniz_oxide" version = "0.8.9" @@ -1026,32 +860,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "pin-project" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "pin-project-lite" version = "0.2.16" @@ -1206,12 +1014,6 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - [[package]] name = "scopeguard" version = "1.2.0" @@ -1250,18 +1052,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - [[package]] name = "sha1" version = "0.10.6" @@ -1332,6 +1122,12 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + [[package]] name = "socket2" version = "0.6.0" @@ -1387,6 +1183,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width 0.2.0", +] + [[package]] name = "thiserror" version = "2.0.16" @@ -1450,45 +1257,6 @@ dependencies = [ "tungstenite", ] -[[package]] -name = "tokio-util" -version = "0.7.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" -dependencies = [ - "log", - "pin-project-lite", - "tracing-core", -] - -[[package]] -name = "tracing-core" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" -dependencies = [ - "once_cell", -] - [[package]] name = "tungstenite" version = "0.27.0" @@ -1512,18 +1280,18 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" -[[package]] -name = "unicase" -version = "2.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" - [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + [[package]] name = "unicode-segmentation" version = "1.12.0" @@ -1590,36 +1358,6 @@ dependencies = [ "colored", ] -[[package]] -name = "warp" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d06d9202adc1f15d709c4f4a2069be5428aa912cc025d6f268ac441ab066b0" -dependencies = [ - "bytes", - "futures-util", - "headers", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-util", - "log", - "mime", - "mime_guess", - "percent-encoding", - "pin-project", - "scoped-tls", - "serde", - "serde_json", - "serde_urlencoded", - "tokio", - "tokio-tungstenite", - "tokio-util", - "tower-service", - "tracing", -] - [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" diff --git a/node/Cargo.toml b/node/Cargo.toml index fffa66a..511cec9 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -14,7 +14,6 @@ thiserror = "2.0.16" tokio = { version = "1.47.1", features = ["full"] } tokio-tungstenite = "0.27.0" uuid = { version = "1.18.0", features = ["v4", "serde"] } -warp = { version = "0.4.2", features = ["server", "websocket"] } wasm-bindgen = "0.2.100" web-sys = { version = "0.3.77", features = ["WebSocket"] } vlogger = { path = "./lib/logger-rs" } @@ -26,3 +25,4 @@ anyhow = "1.0.99" memory-stats = "1.2.0" jemalloc = "0.3.0" jemallocator = "0.5.4" +textwrap = "0.16.2" diff --git a/node/database/tx.db b/node/database/tx.db index c943850..34eb4c5 100644 --- a/node/database/tx.db +++ b/node/database/tx.db @@ -2,18 +2,30 @@ { "head": { "previous_hash": "", - "timestamp": 1756157257, - "merkle_root": "3f99805580f3b4bbd1a903180d2057cfd7dace97d820d7170ffd83c95ce3852c", - "block_hash": "06af1aabb570a702dfd2ee7654200890c26e1fdc380589ae76825976524d5d09", - "nonce": 9 + "timestamp": 1756502951, + "merkle_root": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "block_hash": "04e5cdf592f6ab35dccdc41add80568c0de8be60609f5f9053cbe0d156fb8ea0", + "nonce": 6 }, - "tx": [ + "data": [] + }, + { + "head": { + "previous_hash": "04e5cdf592f6ab35dccdc41add80568c0de8be60609f5f9053cbe0d156fb8ea0", + "timestamp": 1756503577, + "merkle_root": "3f99805580f3b4bbd1a903180d2057cfd7dace97d820d7170ffd83c95ce3852c", + "block_hash": "0655c8ab80aeda9ed1db6e72ec48cdf9d9c7799356b11519979f3d7dbce57677", + "nonce": 7 + }, + "data": [ { - "from": "victor", - "to": "pia", - "value": 500, - "data": "almosen" + "Transaction": { + "from": "victor", + "to": "pia", + "value": 500, + "data": "almosen" + } } ] } -] +] \ No newline at end of file diff --git a/node/src/args.rs b/node/src/args.rs index 171b8a0..a60a860 100644 --- a/node/src/args.rs +++ b/node/src/args.rs @@ -2,7 +2,7 @@ use std::net::SocketAddr; use clap::{Parser, Subcommand}; use crate::core; -use crate::watcher::RenderPane; +use crate::watcher::{RenderLayoutKind, RenderPane}; use clap::*; @@ -59,6 +59,11 @@ pub enum CliCommand{ #[command(name = "clear", aliases = ["c"])] Clear{ pane: RenderPane + }, + + #[command(name = "layout", aliases = ["lay"])] + Layout { + mode: RenderLayoutKind } } diff --git a/node/src/cli.rs b/node/src/cli.rs index 3a94a00..7bb5606 100644 --- a/node/src/cli.rs +++ b/node/src/cli.rs @@ -1,8 +1,9 @@ use crate::args::*; use crate::core::NetworkData; +use crate::event_bus::publish_render_event; use crate::native_node::node::*; use clap::Parser; -use crate::watcher::ExecutorCommand; +use crate::watcher::{ExecutorCommand, RenderCommand}; pub fn handle_peer_command(cmd: CliPeerCommand) -> NodeCommand { match cmd { @@ -36,7 +37,8 @@ fn handle_ping(cmd: CliPingCommand) -> NodeCommand { pub fn cli(input: &[&str]) -> ExecutorCommand { match Cli::try_parse_from(input) { Ok(cmd) => match cmd.command { - CliCommand::Clear { pane } => ExecutorCommand::Clear(pane), + CliCommand::Layout { mode } => ExecutorCommand::Render(RenderCommand::ChangeLayout(mode)), + CliCommand::Clear { pane } => ExecutorCommand::Render(RenderCommand::ClearPane(pane)), CliCommand::Peer { peer_cmd } => ExecutorCommand::Node(handle_peer_command(peer_cmd)), CliCommand::Block { block_cmd } => ExecutorCommand::Node(handle_block_command(block_cmd)), CliCommand::Transaction(tx)=> ExecutorCommand::Node(NodeCommand::ProcessNetworkData(NetworkData::Transaction(tx))), diff --git a/node/src/core/block.rs b/node/src/core/block.rs index dd1ddbb..bbe0feb 100644 --- a/node/src/core/block.rs +++ b/node/src/core/block.rs @@ -12,7 +12,7 @@ pub struct BlockHeader { #[derive(Clone, Debug, serde::Deserialize, serde::Serialize, Default)] pub struct Block { pub head: BlockHeader, - pub tx: Vec + pub data: Vec } impl BlockHeader { @@ -34,13 +34,13 @@ impl BlockHeader { } impl Block { - pub fn new(head: BlockHeader, tx: Vec) -> Self { - Self { head, tx } + pub fn new(head: BlockHeader, data: Vec) -> Self { + Self { head, data } } pub fn head(&self) -> &BlockHeader { &self.head } - pub fn tx(&self) -> &[NetworkData] { - &self.tx + pub fn data(&self) -> &[NetworkData] { + &self.data } } diff --git a/node/src/core/blockchain.rs b/node/src/core/blockchain.rs index 39edf69..340b238 100644 --- a/node/src/core/blockchain.rs +++ b/node/src/core/blockchain.rs @@ -3,6 +3,7 @@ use sha2::Sha256; use vlogger::*; use crate::core::NetworkData; +use crate::error::print_error_chain; use crate::log; use crate::core; @@ -15,11 +16,14 @@ const BLOCKCHAIN_ID: &str = "watch-chain"; pub type Account = String; -#[derive(Debug)] +#[derive(Debug, thiserror::Error)] pub enum ValidationError { + #[error("Invalid Block Hash Detected")] InvalidBlockHash, + #[error("Previous Block Hash doesn't match")] InvalidPreviousBlockHash, - InvalidBlockJson + #[error("Invalid Block JSON: {0}")] + InvalidBlockJson(#[from] serde_json::Error) } #[allow(dead_code)] @@ -34,7 +38,7 @@ pub struct Blockchain { #[allow(dead_code)] impl Blockchain { pub fn add(&mut self, data: NetworkData) -> Result<(), BlockchainError> { - self.apply(&data)?; + self.apply(data.clone())?; self.mempool.push(data); Ok(()) } @@ -147,6 +151,7 @@ impl Blockchain { fn apply_transaction(&mut self, tx: &core::Tx) -> Result<(), BlockchainError> { tx.validate()?; + return Ok(()); if let Some(from_balance) = self.balances.get_mut(tx.from()) { if *from_balance > tx.value() { *from_balance -= tx.value(); @@ -169,10 +174,11 @@ impl Blockchain { Ok(()) } - pub fn apply(&mut self, data: &NetworkData) -> Result<(), BlockchainError> { + pub fn apply(&mut self, data: NetworkData) -> Result<(), BlockchainError> { match &data { NetworkData::Transaction(tx) => { self.apply_transaction(tx)?; + self.mempool.push(data); } } Ok(()) @@ -203,7 +209,7 @@ pub fn calculate_block_hash(head: &core::BlockHeader) -> String { impl Blockchain { pub fn list_blocks(&self) -> String { - let mut ret = String::from("Blocks List\n-------------------"); + let mut ret = String::from("Blocks List\n-------------------\n"); for (i, b) in self.blocks.iter().enumerate() { ret.push_str(format!("Block Hash #{i}: {}\n", b.head.block_hash()).as_str()) } @@ -224,7 +230,7 @@ impl Blockchain { Err(e) => match e { ValidationError::InvalidBlockHash => log(msg!(ERROR, "Invalid Block Hash")), ValidationError::InvalidPreviousBlockHash => log(msg!(ERROR, "Invalid Previos Block Hash")), - ValidationError::InvalidBlockJson => log(msg!(ERROR, "Invalid Block JSON")) + ValidationError::InvalidBlockJson(e) => print_error_chain(e.into()), } } } @@ -257,17 +263,20 @@ impl Blockchain { } pub fn build(blocks: &str) -> Result { - if let Ok(blocks) = serde_json::from_str::>(&blocks) { - let chain = Blockchain { - blocks, - balances: HashMap::new(), - mempool: vec![], - id: BLOCKCHAIN_ID.to_string(), - }; - - chain.validate_chain()?; - return Ok(chain) + match serde_json::from_str::>(&blocks) { + Ok(blocks) => { + let chain = Blockchain { + blocks, + balances: HashMap::new(), + mempool: vec![], + id: BLOCKCHAIN_ID.to_string(), + }; + chain.validate_chain()?; + Ok(chain) + } + Err(e) => { + Err(ValidationError::InvalidBlockJson(e)) + } } - Err(ValidationError::InvalidBlockJson) } } diff --git a/node/src/core/data.rs b/node/src/core/data.rs new file mode 100644 index 0000000..b1fc0ae --- /dev/null +++ b/node/src/core/data.rs @@ -0,0 +1,6 @@ +use super::Tx; + +#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] +pub enum NetworkData { + Transaction(Tx) +} diff --git a/node/src/core/tx.rs b/node/src/core/tx.rs index 5b7390d..56a8f7e 100644 --- a/node/src/core/tx.rs +++ b/node/src/core/tx.rs @@ -1,11 +1,6 @@ use crate::core::Account; use crate::error::TxError; -#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] -pub enum NetworkData { - Transaction(Tx) -} - #[derive(serde::Deserialize, serde::Serialize, Debug, clap::Args, Clone)] pub struct Tx { from: Account, diff --git a/node/src/event_bus.rs b/node/src/event_bus.rs index 7ece230..5e56f38 100644 --- a/node/src/event_bus.rs +++ b/node/src/event_bus.rs @@ -62,7 +62,10 @@ impl EventBus { #[derive(Clone, Debug)] pub enum SystemEvent { - Exit + ExecutorStarted, + RendererStarted, + NodeStarted, + Exit, } #[derive(Clone, Debug)] diff --git a/node/src/lib.rs b/node/src/lib.rs index 624d947..ba7d484 100644 --- a/node/src/lib.rs +++ b/node/src/lib.rs @@ -47,6 +47,10 @@ pub mod core { pub mod tx; pub use tx::*; + + pub mod data; + pub use data::*; + } pub mod seeds_constants; diff --git a/node/src/main.rs b/node/src/main.rs index 7c772b3..d00159f 100644 --- a/node/src/main.rs +++ b/node/src/main.rs @@ -2,14 +2,14 @@ use blockchain::watcher::Watcher; use blockchain::args; use clap::Parser; -// src/main.rs -use jemallocator::Jemalloc; - -#[global_allocator] -static GLOBAL: Jemalloc = Jemalloc; - +// // src/main.rs +// use jemallocator::Jemalloc; +// +// #[global_allocator] +// static GLOBAL: Jemalloc = Jemalloc; +// #[tokio::main] -async fn main() { +async fn main() -> Result<(), std::io::Error> { let args = args::CliArgs::parse(); @@ -21,6 +21,12 @@ async fn main() { .bootstrap(args.bootstrap) .start().await; + crossterm::execute!( + std::io::stdout(), + crossterm::event::EnableBracketedPaste, + crossterm::event::EnableFocusChange, + crossterm::event::EnableMouseCapture, + )?; loop { if !watcher.poll().await.is_ok_and(|b| b) { @@ -28,6 +34,13 @@ async fn main() { } } + crossterm::execute!( + std::io::stdout(), + crossterm::event::DisableBracketedPaste, + crossterm::event::DisableFocusChange, + crossterm::event::DisableMouseCapture + )?; ratatui::restore(); println!("Hello, world!"); + Ok(()) } diff --git a/node/src/native_node/node.rs b/node/src/native_node/node.rs index 1488ac6..67f150b 100644 --- a/node/src/native_node/node.rs +++ b/node/src/native_node/node.rs @@ -1,5 +1,6 @@ use crate::core::{self, Blockchain, NetworkData, ValidationError}; use crate::error::print_error_chain; +use crate::event_bus::{publish_system_event, SystemEvent}; use crate::protocol::ProtocolMessage; use crate::seeds_constants::SEED_NODES; @@ -118,17 +119,22 @@ impl NativeNode { rx, } } - + pub async fn new( addr: Option, blocks_json: &str, exec_tx: mpsc::Sender, ) -> Self { let (tx, rx) = mpsc::channel::(100); + let chain = Blockchain::build(blocks_json) + .unwrap_or_else(|e| { + print_error_chain(e.into()); + Default::default() + }); Self { id: Uuid::new_v4(), tcp_peers: HashMap::new(), - chain: Blockchain::build(blocks_json).unwrap_or(Default::default()), + chain, addr, exec_tx, listner_handle: None, @@ -138,7 +144,7 @@ impl NativeNode { } } - pub async fn process_message(&mut self, peer_id: uuid::Uuid, message: &ProtocolMessage) { + pub async fn process_message(&mut self, peer_id: uuid::Uuid, message: ProtocolMessage) { match message { ProtocolMessage::BootstrapRequest { .. } => { @@ -155,19 +161,19 @@ impl NativeNode { }, ProtocolMessage::BootstrapResponse { blocks } => { log(msg!(DEBUG, "Received BootstrapResponse from seed")); - self.chain = core::Blockchain::build(blocks).unwrap(); + self.chain = core::Blockchain::build(&blocks).unwrap(); }, ProtocolMessage::Ping {peer_id} => { log(msg!(DEBUG, "Received Ping from {peer_id}")); let resp = ProtocolMessage::Pong { peer_id: self.id.clone() }; - let peer = &self.tcp_peers[peer_id]; + let peer = &self.tcp_peers[&peer_id]; peer.sender.send(resp).await.unwrap(); }, ProtocolMessage::GetPeersRequest { peer_id } => { log(msg!(DEBUG, "Received GetPeersRequest from {peer_id}")); let peers = self.peer_addresses(); let resp = ProtocolMessage::GetPeersResponse { peer_addresses: peers }; - let peer = &self.tcp_peers[peer_id]; + let peer = &self.tcp_peers[&peer_id]; peer.sender.send(resp).await.unwrap(); } ProtocolMessage::Block { block, ..} => { @@ -225,7 +231,7 @@ impl NativeNode { Ok(()) } - async fn broadcast_network_data(&self, data: &NetworkData) { + async fn broadcast_network_data(&self, data: NetworkData) { for (id, peer) in &self.tcp_peers { let message = ProtocolMessage::NetworkData{peer_id: self.id, data: data.clone()}; peer.sender.send(message).await.unwrap(); @@ -253,7 +259,7 @@ impl NativeNode { return self.exec_tx.clone() } - async fn network_data(&mut self, data: &NetworkData) { + async fn network_data(&mut self, data: NetworkData) { match self.chain.apply(data) { Ok(_) => log(msg!(DEBUG, "NetworkData Applied")), Err(e) => print_error_chain(e.into()) @@ -301,7 +307,7 @@ impl NativeNode { self.start_connection_listner(SocketAddr::new(std::net::IpAddr::V4(std::net::Ipv4Addr::new(0,0,0,0)), 8080)).await; }; - log(msg!(INFO, "Started Node")); + publish_system_event(SystemEvent::NodeStarted); while let Some(command) = self.rx.recv().await { match command { @@ -347,11 +353,11 @@ impl NativeNode { self.remove_tcp_peer(peer_id).await; } NodeCommand::ProcessMessage { peer_id, message } => { - self.process_message(peer_id, &message).await; + self.process_message(peer_id, message).await; }, NodeCommand::ProcessNetworkData(data) => { - self.network_data(&data).await; - self.broadcast_network_data(&data).await; + self.network_data(data.clone()).await; + self.broadcast_network_data(data).await; }, NodeCommand::CreateBlock => { log(msg!(DEBUG, "Received CreateBlock Command")); diff --git a/node/src/watcher/executor.rs b/node/src/watcher/executor.rs index 9e58d82..e83540a 100644 --- a/node/src/watcher/executor.rs +++ b/node/src/watcher/executor.rs @@ -1,4 +1,4 @@ -use crate::{event_bus::publish_render_event, log, native_node::node::NodeCommand, watcher::renderer::*}; +use crate::{event_bus::{publish_render_event, publish_system_event, SystemEvent}, log, native_node::node::NodeCommand, watcher::renderer::*}; use thiserror::Error; use tokio::sync::mpsc; use vlogger::*; @@ -10,7 +10,7 @@ pub enum ExecutorCommand { Print(String), InvalidCommand(String), Node(NodeCommand), - Clear(RenderPane), + Render(RenderCommand), Exit } @@ -36,6 +36,7 @@ impl Executor { } pub async fn run(&mut self) { + publish_system_event(SystemEvent::ExecutorStarted); while !self.exit { self.listen().await; } @@ -70,11 +71,6 @@ impl Executor { publish_render_event(rd_cmd); } - async fn clear(&self, p: RenderPane) { - let rd_cmd = RenderCommand::ClearPane(p); - publish_render_event(rd_cmd); - } - async fn invalid_command(&self, str: String) { let rd_cmd = RenderCommand::RenderStringToPane{ str, @@ -87,7 +83,7 @@ impl Executor { match cmd { ExecutorCommand::NodeResponse(resp) => log(resp), ExecutorCommand::Node(n) => self.handle_node_cmd(n).await, - ExecutorCommand::Clear(p) => self.clear(p).await, + ExecutorCommand::Render(p) => publish_render_event(p), ExecutorCommand::Echo(s) => self.echo(s).await, ExecutorCommand::Print(s) => log(s), ExecutorCommand::InvalidCommand(str) => self.invalid_command(str).await, diff --git a/node/src/watcher/renderer.rs b/node/src/watcher/renderer.rs index 7d661a6..ba9f675 100644 --- a/node/src/watcher/renderer.rs +++ b/node/src/watcher/renderer.rs @@ -11,8 +11,8 @@ use ratatui::{ use vlogger::*; -use crate::event_bus::subscribe_render_event; -use tokio::time::{timeout, Duration}; +use crate::event_bus::{publish_system_event, subscribe_render_event, SystemEvent}; +use tokio::time::{interval, timeout, Duration}; use std::io; #[derive(Debug)] @@ -27,6 +27,9 @@ pub struct Pane { title: Option, target: RenderPane, buffer: RenderBuffer, + focused: bool, + scroll: i16, + max_scroll: i16, } #[derive(Debug, PartialEq, Clone, clap::ValueEnum)] @@ -46,7 +49,7 @@ enum RenderBuffer { } impl Pane { - fn render(&self, area: Rect, buf: &mut Buffer) { + fn render(&mut self, area: Rect, buf: &mut Buffer) { let block = Block::bordered() .title({ if let Some(t) = &self.title { @@ -55,27 +58,38 @@ impl Pane { Default::default() } }) - .border_set(border::PLAIN); + .border_set(border::PLAIN) + .border_style({ + if self.focused { + Style::new().green() + } else { + Style::new().white() + } + }); + let inner_area = block.inner(area); + let content_width = inner_area.width as usize; + let content_height = inner_area.height as usize; match &self.buffer { RenderBuffer::String(s) => { - let inner_area = block.inner(area); - let content_width = inner_area.width as usize; - let content_height = inner_area.height as usize; let wrapped_lines = s - .lines() - .map(|line| { - if line.is_empty() { - 1 - } else { - (line.len() + content_width - 1) / content_width - } - }) - .sum::(); - let scroll_offset = if wrapped_lines > content_height { - wrapped_lines - content_height + .lines() + .map(|line| { + if line.is_empty() { + 1 + } else { + (line.len() + content_width - 1) / { content_width + (content_width == 0) as usize } + } + }) + .sum::(); + + self.max_scroll = if wrapped_lines > content_height { + (wrapped_lines - content_height) as i16 } else { - 0 + 0 }; + + let scroll_offset = self.max_scroll.saturating_sub(self.scroll) as u16; + Paragraph::new(s.clone()) .wrap(Wrap::default()) .left_aligned() @@ -84,13 +98,12 @@ impl Pane { .render(area, buf); } RenderBuffer::List { list, .. } => { - let items: Vec = list + let list_w = List::new(list .iter() .map(|s| { - format!("> {s}") - }) - .collect(); - Widget::render(List::new(items).block(block), area, buf); + format!("> {}", textwrap::fill(s, content_width.saturating_sub(2))) + })).block(block); + Widget::render(list_w, area, buf); } } } @@ -109,12 +122,21 @@ pub enum RenderCommand { }, ChangeLayout(RenderLayoutKind), ClearPane(RenderPane), + + + /// Mouse Events + MouseClickLeft(u16, u16), + MouseScrollUp, + MouseScrollDown, Exit, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, clap::ValueEnum)] pub enum RenderLayoutKind { - Cli, + #[value(name = "horizontal", aliases = ["h"])] + CliHorizontal, + #[value(name = "vertical", aliases = ["v"])] + CliVertical } #[derive(Debug)] @@ -126,12 +148,21 @@ pub struct RenderLayout { impl RenderLayoutKind { pub fn rects(&self, area: Rect) -> std::rc::Rc<[Rect]> { match self { - Self::Cli => { + Self::CliHorizontal => { + Layout::default() + .direction(Direction::Vertical) + .constraints(vec![ + Constraint::Percentage(70), + Constraint::Percentage(30), + ]) + .split(area) + } + Self::CliVertical => { Layout::default() .direction(Direction::Horizontal) .constraints(vec![ Constraint::Percentage(30), - Constraint::Percentage(70) + Constraint::Percentage(70), ]) .split(area) } @@ -139,23 +170,59 @@ impl RenderLayoutKind { } pub fn generate(&self) -> RenderLayout { - RenderLayout { - kind: self.clone(), - panes: vec![ - Pane { - title: Some(" Input Pane ".to_string()), - target: RenderPane::CliInput, - buffer: RenderBuffer::List{ - list: vec![String::new()], - index: 0 - } - }, - Pane { - title: Some(" Output Pane ".to_string()), - target: RenderPane::CliOutput, - buffer: RenderBuffer::String(String::new()), + match self { + RenderLayoutKind::CliVertical => { + RenderLayout { + kind: self.clone(), + panes: vec![ + Pane { + title: Some(" Input Pane ".to_string()), + target: RenderPane::CliInput, + buffer: RenderBuffer::List{ + list: vec![String::new()], + index: 0 + }, + focused: true, + scroll: 0, + max_scroll: 0, + }, + Pane { + title: Some(" Output Pane ".to_string()), + target: RenderPane::CliOutput, + buffer: RenderBuffer::String(String::new()), + focused: false, + scroll: 0, + max_scroll: 0, + }, + ] } - ] + } + RenderLayoutKind::CliHorizontal => { + RenderLayout { + kind: self.clone(), + panes: vec![ + Pane { + title: Some(" Output Pane ".to_string()), + target: RenderPane::CliOutput, + buffer: RenderBuffer::String(String::new()), + focused: false, + scroll: 0, + max_scroll: 0, + }, + Pane { + title: Some(" Input Pane ".to_string()), + target: RenderPane::CliInput, + buffer: RenderBuffer::List{ + list: vec![String::new()], + index: 0 + }, + focused: true, + scroll: 0, + max_scroll: 0, + }, + ] + } + } } } } @@ -174,11 +241,23 @@ impl Renderer { self.log(msg!(INFO, "Started Renderer")); let mut rx = subscribe_render_event(); let mut terminal = ratatui::init(); + publish_system_event(SystemEvent::RendererStarted); + + let mut render_interval = interval(Duration::from_millis(32)); // 60 FPS + while !self.exit { - terminal.draw(|frame| self.draw(frame))?; - if let Ok(mes) = self.listen(&mut rx).await { - self.apply(mes); - } + tokio::select! { + _ = render_interval.tick() => { + terminal.draw(|frame| self.draw(frame))?; + } + mes = rx.recv() => { + if let Ok(mes) = mes { + let frame = terminal.get_frame(); + let rects = self.layout.kind.rects(frame.area()); + self.apply(mes, rects); + } + } + } } ratatui::restore(); Ok(()) @@ -188,7 +267,7 @@ impl Renderer { self.buffer.push_str(&msg) } - pub fn draw(&self, frame: &mut Frame) { + pub fn draw(&mut self, frame: &mut Frame) { frame.render_widget(self, frame.area()); } @@ -205,78 +284,95 @@ impl Renderer { self.layout.panes.iter_mut().find(|p| p.target == RenderPane::CliInput) } - fn apply(&mut self, mes: RenderCommand) { - match mes { - RenderCommand::RenderInput(k) => { - if let Some(p) = self.input_pane() { - match k { - KeyCode::Char(c) => { - if let RenderBuffer::List{list, index} = &mut p.buffer { - list[*index].push(c); - } - } - KeyCode::Backspace => { - if let RenderBuffer::List{list, index} = &mut p.buffer { - list[*index].pop(); - } - } - KeyCode::Enter => { - if let RenderBuffer::List{list, index} = &mut p.buffer { - list.push(String::new()); - *index += 1; - } - } - _ => {} - } - } - }, + fn focused(&mut self) -> Option<&mut Pane> { + self.layout.panes.iter_mut().find(|p| p.focused == true) + } - RenderCommand::ListMove{ pane, index } => { - if let Some(p) = self.layout.panes.iter_mut().find(|p| p.target == pane) { - if let RenderBuffer::List { list, index: idx } = &mut p.buffer { - if index > 0 && index < list.len() { - list[*idx] = list[index].clone(); - } - } + fn handle_mouse_click_left(&mut self, x: u16, y: u16, rects: std::rc::Rc<[Rect]>) { + for (i, r) in rects.iter().enumerate() { + if r.contains(layout::Position{x, y}) { + self.layout.panes[i].focused = true; + } else { + self.layout.panes[i].focused = false; + } + } + } + + fn apply(&mut self, mes: RenderCommand, rects: std::rc::Rc<[Rect]>) { + match mes { + RenderCommand::MouseClickLeft(x, y) => { + self.handle_mouse_click_left(x, y, rects); + } + RenderCommand::MouseScrollUp => { + if let Some(p) = self.focused() { + if p.scroll < p.max_scroll { + p.scroll += 1; } } - - RenderCommand::RenderStringToPane{ str, pane } => { - if let Some(p) = self.layout.panes.iter_mut().find(|p| p.target == pane) { - match &mut p.buffer { - RenderBuffer::List { list, index } => { - list.push(str); + } + RenderCommand::MouseScrollDown => { + if let Some(p) = self.focused() { + if p.scroll > i16::MIN { + p.scroll -= 1; + } + } + } + RenderCommand::RenderInput(k) => { + if let Some(p) = self.layout.panes.iter_mut().find(|p| p.focused) { + match k { + KeyCode::Char(c) => { + if let RenderBuffer::List{list, index} = &mut p.buffer { + list[*index].push(c); + } + } + KeyCode::Backspace => { + if let RenderBuffer::List{list, index} = &mut p.buffer { + list[*index].pop(); + } + } + KeyCode::Enter => { + if let RenderBuffer::List{list, index} = &mut p.buffer { + list.push(String::new()); *index += 1; } - RenderBuffer::String(s) => { - s.push_str(&str) - } + } + _ => {} + } + } + }, + + RenderCommand::ListMove{ pane, index } => { + if let Some(p) = self.layout.panes.iter_mut().find(|p| p.target == pane) { + if let RenderBuffer::List { list, index: idx } = &mut p.buffer { + if index > 0 && index < list.len() { + list[*idx] = list[index].clone(); } } } - RenderCommand::Exit => { - self.exit(); - } - RenderCommand::ChangeLayout(l) => { - match l { - RenderLayoutKind::Cli => { - self.layout = l.generate(); + } + + RenderCommand::RenderStringToPane{ str, pane } => { + if let Some(p) = self.layout.panes.iter_mut().find(|p| p.target == pane) { + match &mut p.buffer { + RenderBuffer::List { list, index } => { + list.push(str); + *index += 1; + } + RenderBuffer::String(s) => { + s.push_str(&str) } } } - RenderCommand::ClearPane(pane) => { - if matches!(pane, RenderPane::All) { - for p in self.layout.panes.iter_mut() { - match &mut p.buffer { - RenderBuffer::List { list, index } => { - list.clear(); - *index = 0; - list.push(String::new()); - } - RenderBuffer::String(s) => s.clear() - } - } - } else if let Some(p) = self.layout.panes.iter_mut().find(|p| p.target == pane) { + } + RenderCommand::Exit => { + self.exit(); + } + RenderCommand::ChangeLayout(l) => { + self.layout = l.generate(); + } + RenderCommand::ClearPane(pane) => { + if matches!(pane, RenderPane::All) { + for p in self.layout.panes.iter_mut() { match &mut p.buffer { RenderBuffer::List { list, index } => { list.clear(); @@ -286,8 +382,18 @@ impl Renderer { RenderBuffer::String(s) => s.clear() } } + } else if let Some(p) = self.layout.panes.iter_mut().find(|p| p.target == pane) { + match &mut p.buffer { + RenderBuffer::List { list, index } => { + list.clear(); + *index = 0; + list.push(String::new()); + } + RenderBuffer::String(s) => s.clear() + } } } + } } async fn listen(&mut self, rx: &mut tokio::sync::broadcast::Receiver) -> Result { @@ -298,13 +404,12 @@ impl Renderer { } } -impl Widget for &Renderer { +impl Widget for &mut Renderer { fn render(self, area: Rect, buf: &mut Buffer) { - let layout = self.layout.kind.rects(area); - - for (i, p) in self.layout.panes.iter().enumerate() { - p.render(layout[i], buf) + let rects = self.layout.kind.rects(area); + for (i, p) in self.layout.panes.iter_mut().enumerate() { + p.render(rects[i], buf) } } } diff --git a/node/src/watcher/watcher.rs b/node/src/watcher/watcher.rs index cab54e5..44b5b4f 100644 --- a/node/src/watcher/watcher.rs +++ b/node/src/watcher/watcher.rs @@ -1,9 +1,9 @@ -use crossterm::event::{self, Event, KeyCode, KeyEventKind}; +use crossterm::event::{self, Event, KeyCode, KeyEventKind, MouseButton, MouseEventKind}; use memory_stats::memory_stats; use tokio::sync::mpsc; use std::{io::{self, Write}, net::SocketAddr, time::Duration}; -use crate::{event_bus::NetworkEvent, native_node::node::{NativeNode, NodeCommand}}; +use crate::{error::print_error_chain, event_bus::{subscribe_system_event, NetworkEvent, SystemEvent}, native_node::node::{NativeNode, NodeCommand}}; use vlogger::*; use super::*; @@ -67,6 +67,21 @@ impl Watcher { pub async fn poll(&mut self) -> io::Result { match event::read()? { + Event::Mouse(event) => { + match event.kind { + MouseEventKind::ScrollUp => { publish_render_event(RenderCommand::MouseScrollUp); } + MouseEventKind::ScrollDown => { publish_render_event(RenderCommand::MouseScrollDown); } + MouseEventKind::Down(b) => { + match b { + MouseButton::Left => { + publish_render_event(RenderCommand::MouseClickLeft(event.column, event.row)); + } + _ => {} + } + } + _ => {} + } + } Event::Key(k) if k.kind == KeyEventKind::Press => { match k.code { KeyCode::Char(c) => { @@ -167,31 +182,34 @@ impl WatcherBuilder { pub async fn start(mut self) -> Watcher { let (parser_tx, parser_rx) = mpsc::channel::(100); let (exec_tx, exec_rx) = mpsc::channel::(100); - + let mut sys_event = subscribe_system_event(); if self.debug { Watcher::log_memory().await; } + let render_handle = if self.render { Some(tokio::spawn({ async move { - let _ = Renderer::new(RenderLayoutKind::Cli).run().await; + let _ = Renderer::new(RenderLayoutKind::CliHorizontal).run().await; } })) } else { None }; - let blocks = self.seed_file - .as_ref() - .and_then(|path| std::fs::read_to_string(path).ok()) - .unwrap_or_default(); - if self.seed { - self.addr = Some(crate::seeds_constants::SEED_NODES[0]); + for i in 0..3 { + if let Ok(ev) = sys_event.recv().await { + match ev { + SystemEvent::RendererStarted => { + log(msg!(INFO, "Renderer Started")); + break; + }, + _ => { log(msg!(WARNING, "Wrong Event: {ev:?}! Retrying... (try {i})")) } + } + } } - let mut node = NativeNode::new(self.addr.clone(), &blocks, exec_tx.clone()).await; - log(msg!(INFO, "Build Node")); let parser_handle = tokio::spawn({ let exec_tx = exec_tx.clone(); @@ -200,6 +218,28 @@ impl WatcherBuilder { } }); + log(msg!(DEBUG, "Seed File: {:?}", self.seed_file)); + let blocks = self.seed_file + .as_ref() + .and_then(|path| { + log(msg!(INFO, "Reading chain data from {path}")); + match std::fs::read_to_string(path) { + Ok(s) => Some(s), + Err(e) => { + print_error_chain(e.into()); + None + }, + } + }) + .unwrap_or_default(); + + if self.seed { + self.addr = Some(crate::seeds_constants::SEED_NODES[0]); + } + + let mut node = NativeNode::new(self.addr.clone(), &blocks, exec_tx.clone()).await; + log(msg!(INFO, "Built Node")); + let executor_handle = tokio::spawn({ let node_tx = node.tx(); async move { @@ -207,14 +247,37 @@ impl WatcherBuilder { } }); - let node_tx = node.tx(); + for i in 0..3 { + if let Ok(ev) = sys_event.recv().await { + match ev { + SystemEvent::ExecutorStarted => { + log(msg!(INFO, "Executor Started")); + break; + }, + _ => { log(msg!(WARNING, "Wrong Event: {ev:?}! Retrying... (try {i})")) } + } + } + } + let node_tx = node.tx(); let node_handle = tokio::spawn({ async move { node.run().await; } }); + for i in 0..3 { + if let Ok(ev) = sys_event.recv().await { + match ev { + SystemEvent::NodeStarted => { + log(msg!(INFO, "Executor Started")); + break; + }, + _ => { log(msg!(WARNING, "Wrong Event: {ev:?}! Retrying... (try {i})")) } + } + } + } + if self.bootstrap { let exec_tx = exec_tx.clone(); @@ -234,7 +297,6 @@ impl WatcherBuilder { } }); } - Watcher { node_tx, cmd_history: Vec::new(),