From 3fe31e655bbf0a9366ca9f8b37e60e82cdb92cea Mon Sep 17 00:00:00 2001 From: dantee-e Date: Tue, 25 Mar 2025 23:27:49 +0100 Subject: [PATCH 01/40] mining --- src/chain/block.rs | 26 ++++++++++++++++++++++---- src/chain/utils.rs | 28 +++------------------------- src/chain/wallet.rs | 2 +- src/main.rs | 5 ----- 4 files changed, 26 insertions(+), 35 deletions(-) diff --git a/src/chain/block.rs b/src/chain/block.rs index 373a5ae..b8a9804 100644 --- a/src/chain/block.rs +++ b/src/chain/block.rs @@ -7,6 +7,7 @@ use sha2::{Digest, Sha256}; use super::transaction::Transaction; use super::Chain; +use super::utils::PROOF_OF_WORK_DIFFICULTY; #[derive(Clone)] @@ -16,6 +17,7 @@ pub struct Block { index: i64, timestamp: DateTime, hash: String, + nonce: u64 } impl Block { @@ -46,7 +48,9 @@ impl Block { &self.index, "::END_INDEX::BEGIN_TIMESTAMP::", &self.timestamp.to_string(), - "::END_TIMESTAMP::END::", + "::END_TIMESTAMP::BEGIN_NONCE::", + &self.nonce, + "::END_NONCE::END", )).expect("Coudn't serialize the block to create the hash"); println!("behold the serialized block:\n{serialized}"); @@ -54,7 +58,8 @@ impl Block { let mut hasher = Sha256::new(); hasher.update(serialized.as_bytes()); let result = hasher.finalize(); - + let encoded_result = hex::encode(result); + println!("Hash = {encoded_result}"); hex::encode(result) // Converts bytes to a hex string } @@ -69,7 +74,8 @@ impl Block { transactions, index, timestamp, - hash: String::new() // temporary so that we can calculate hash + hash: String::new(), + nonce: 0 // temporary so that we can calculate hash }; block.hash = block.calculate_hash(); @@ -84,8 +90,20 @@ impl Block { transactions: Vec::new(), index: 0, timestamp: Utc::now(), - hash: String::from("The Times 03/Jan/2009 Chancellor on brink of second bailout for banks") + hash: String::from("The Times 03/Jan/2009 Chancellor on brink of second bailout for banks"), + nonce: 0 + } + } + + pub fn mine_block(mut self) -> Self { + let prefix = "0".repeat(PROOF_OF_WORK_DIFFICULTY.into()); + + while !self.hash.starts_with(&prefix) { + self.nonce+=1; + self.hash = self.calculate_hash(); } + + self } } diff --git a/src/chain/utils.rs b/src/chain/utils.rs index 5ebe58e..ff879a3 100644 --- a/src/chain/utils.rs +++ b/src/chain/utils.rs @@ -1,6 +1,9 @@ use sha2::{Sha256, Digest}; + +pub const PROOF_OF_WORK_DIFFICULTY: u8 = 4; + pub struct HashedData{ hash: [u8;32], } @@ -34,28 +37,3 @@ impl HashedData { } } -/* pub struct Date{ - year: u32, - month: u32, - day: u32, - hour: u32, - minute: u32, - second: u32, -} - -impl Date { - pub fn now() -> Self { - let now = Local::now(); - Self { - year: now.year_ce().1, - month: now.month(), - day: now.day(), - hour: now.hour(), - minute: now.minute(), - second: now.second(), - } - } - fn to_string(&self) -> String { - format!("{}.{}.{}.{}.{}.{}", self.year, self.month, self.day, self.hour, self.minute, self.second) - } -} */ \ No newline at end of file diff --git a/src/chain/wallet.rs b/src/chain/wallet.rs index a7013ec..343cdcd 100644 --- a/src/chain/wallet.rs +++ b/src/chain/wallet.rs @@ -38,7 +38,7 @@ impl Wallet { ( Wallet{ - public_key, + public_key, verifying_key }, WalletPK{ diff --git a/src/main.rs b/src/main.rs index 2d6a730..1a474c5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,8 +7,6 @@ fn main() { let (wallet1, mut wallet1_pk) = Wallet::new(); let (wallet2, _) = Wallet::new(); - - let transaction_info = TransactionInfo::new(10.5, Utc::now()); let signature = match wallet1_pk.sign_transaction(&transaction_info) { @@ -18,12 +16,9 @@ fn main() { let new_transaction = Transaction::new(wallet1, wallet2, transaction_info, signature); - let mut chain = chain::Chain::new(); let block = block::Block::new(&mut chain, vec![new_transaction]); chain.add_block(block); - - } From 7849ce6b2ca696b1cefa51354ee681218dda3cdf Mon Sep 17 00:00:00 2001 From: dantee-e Date: Tue, 25 Mar 2025 23:52:36 +0100 Subject: [PATCH 02/40] fds --- src/chain/block.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/chain/block.rs b/src/chain/block.rs index 1ecf0c2..ef9857e 100644 --- a/src/chain/block.rs +++ b/src/chain/block.rs @@ -10,7 +10,7 @@ use super::utils::PROOF_OF_WORK_DIFFICULTY; pub struct Block { previous_hash: String, transactions: Vec, - index: i64, + index: u64, timestamp: DateTime, hash: String, nonce: u64 @@ -102,5 +102,3 @@ impl Block { self } } - - From 0d69de35f744158772a3bf5ed666ec0c86f2ceff Mon Sep 17 00:00:00 2001 From: dantee-e Date: Mon, 31 Mar 2025 16:33:49 +0200 Subject: [PATCH 03/40] readd TODO --- src/TODO.txt | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/TODO.txt diff --git a/src/TODO.txt b/src/TODO.txt new file mode 100644 index 0000000..bdbf309 --- /dev/null +++ b/src/TODO.txt @@ -0,0 +1,8 @@ + +- Criar um node com uma port no localhost para receber transacoes, e de x em x tempos fechar o bloco + +- Encontrar nodes + +- Quando fechar o bloco, emitir essa info pra outros nodes + +- Minerar o bloco \ No newline at end of file From a2a81173c81c53164bd415ac0d8651d0a22111de Mon Sep 17 00:00:00 2001 From: dantee-e Date: Wed, 2 Apr 2025 19:26:20 +0200 Subject: [PATCH 04/40] Created node.rs file --- Cargo.toml | 2 +- src/lib.rs | 1 - src/node.rs | 26 ++++++++++++++++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) delete mode 100644 src/lib.rs create mode 100644 src/node.rs diff --git a/Cargo.toml b/Cargo.toml index cd3e288..d85b205 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "CleytoCoin" version = "0.1.0" -edition = "2024" +edition = "2021" [dependencies] sha2 = "0.10" diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 28ac57d..0000000 --- a/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod chain; diff --git a/src/node.rs b/src/node.rs new file mode 100644 index 0000000..1c32429 --- /dev/null +++ b/src/node.rs @@ -0,0 +1,26 @@ +use std::net::TcpListener; +use std::io; + +struct Node { + let default_port: str = "9473"; +} + +impl Node { + pub fn run() { + let mut port = String::new(); + + + println!("Please input the port you want to use (Press enter for default {default_port}): "); + io::stdin() + .read_line(&mut port) + .expect("Failed to read port"); + + let listener = TcpListener::bind("127.0.0.1:" + default_port).unwrap(); + + for stream in listener.incoming() { + let stream = stream.unwrap(); + + println!("Connection established!"); + } + } +} \ No newline at end of file From 7075c1410269f0906123b57a8fc463dab04af885 Mon Sep 17 00:00:00 2001 From: dantee-e Date: Thu, 3 Apr 2025 11:25:03 +0200 Subject: [PATCH 05/40] changed project name to rust snake case convention, started implementation of node connection --- Cargo.lock | 28 ++++++------- Cargo.toml | 2 +- src/chain.rs | 2 +- src/chain/block.rs | 2 +- src/lib.rs | 2 + src/main.rs | 5 +++ src/node.rs | 52 ++++++++++++++++++++---- tests/create_block_and_add_chain.rs | 8 +--- tests/create_transaction.rs | 4 +- tests/sign_and_verify_transactioninfo.rs | 2 +- 10 files changed, 72 insertions(+), 35 deletions(-) create mode 100644 src/lib.rs create mode 100644 src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 0560b30..94fa6fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,20 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "CleytoCoin" -version = "0.1.0" -dependencies = [ - "chrono", - "hex", - "rand", - "rsa", - "serde", - "serde_json", - "sha2", - "tiny-bip39", -] - [[package]] name = "android-tzdata" version = "0.1.1" @@ -93,6 +79,20 @@ dependencies = [ "windows-link", ] +[[package]] +name = "cleyto_coin" +version = "0.1.0" +dependencies = [ + "chrono", + "hex", + "rand", + "rsa", + "serde", + "serde_json", + "sha2", + "tiny-bip39", +] + [[package]] name = "const-oid" version = "0.9.6" diff --git a/Cargo.toml b/Cargo.toml index d85b205..5bf477a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "CleytoCoin" +name = "cleyto_coin" version = "0.1.0" edition = "2021" diff --git a/src/chain.rs b/src/chain.rs index ecaf706..f4a073b 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -39,7 +39,7 @@ impl Chain { } } - fn get_last_index(&mut self) -> i64 { + fn get_last_index(&mut self) -> u64 { match self.blocks.last() { Some(block) => block.get_index(), None =>{ diff --git a/src/chain/block.rs b/src/chain/block.rs index ef9857e..72be148 100644 --- a/src/chain/block.rs +++ b/src/chain/block.rs @@ -21,7 +21,7 @@ impl Block { self.hash.clone() } - pub fn get_index(&self) -> i64 { + pub fn get_index(&self) -> u64 { self.index.clone() } diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..2ff6c45 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,2 @@ +pub mod chain; +pub mod node; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..c384e4f --- /dev/null +++ b/src/main.rs @@ -0,0 +1,5 @@ +pub mod chain; +pub mod node; +fn main(){ + node::Node::run(); +} \ No newline at end of file diff --git a/src/node.rs b/src/node.rs index 1c32429..9b10396 100644 --- a/src/node.rs +++ b/src/node.rs @@ -1,24 +1,60 @@ -use std::net::TcpListener; +use std::{ + io::{prelude::*, BufReader}, + net::{TcpListener, TcpStream}, +}; use std::io; -struct Node { - let default_port: str = "9473"; +pub struct Node { + default_port: u16 } impl Node { + + const DEFAULT_PORT: u16 = 9473; + + pub fn new(port: u16) -> Self { + Node { + default_port: port + } + } + + fn handle_connection(stream: TcpStream){ + let buf_reader = BufReader::new(&stream); + let http_request: Vec<_> = buf_reader + .lines() + .map(|result| result.unwrap()) + .take_while(|line| !line.is_empty()) + .collect(); + + println!("Request: {http_request:#?}"); + } + pub fn run() { - let mut port = String::new(); - + let port: u16; + let mut input = String::new(); - println!("Please input the port you want to use (Press enter for default {default_port}): "); + println!("Please input the port you want to use (Press enter for default {}): ", Self::DEFAULT_PORT); io::stdin() - .read_line(&mut port) + .read_line(&mut input) .expect("Failed to read port"); - let listener = TcpListener::bind("127.0.0.1:" + default_port).unwrap(); + port = if input.trim().is_empty() { + Self::DEFAULT_PORT + } else { + match input.trim().parse::() { + Ok(port) if (1..=65535).contains(&port) => port, + _ => { + println!("Invalid port! Using default: {}", Self::DEFAULT_PORT); + Self::DEFAULT_PORT + } + } + }; + + let listener = TcpListener::bind(format!("127.0.0.1:{port}")).unwrap(); for stream in listener.incoming() { let stream = stream.unwrap(); + Self::handle_connection(stream); println!("Connection established!"); } diff --git a/tests/create_block_and_add_chain.rs b/tests/create_block_and_add_chain.rs index 30094e4..9cebab5 100644 --- a/tests/create_block_and_add_chain.rs +++ b/tests/create_block_and_add_chain.rs @@ -1,4 +1,4 @@ -use CleytoCoin::chain::{Chain, wallet::Wallet, block::Block, transaction::{Transaction, TransactionInfo}}; +use cleyto_coin::chain::{Chain, wallet::Wallet, block::Block, transaction::{Transaction, TransactionInfo}}; use chrono::Utc; #[test] @@ -15,15 +15,9 @@ fn create_block_and_add_chain() { let new_transaction = Transaction::new(wallet1, wallet2, transaction_info, signature); -<<<<<<< HEAD:src/main.rs - let mut chain = chain::Chain::new(); - - let block = block::Block::new(&mut chain, vec![new_transaction]); -======= let mut chain = Chain::new(); let block = Block::new(&mut chain, vec![new_transaction]); ->>>>>>> 68fcfb662ba503f732442252f2097e1882a40c57:tests/create_block_and_add_chain.rs chain.add_block(block); } diff --git a/tests/create_transaction.rs b/tests/create_transaction.rs index 172a4a1..51d03a6 100644 --- a/tests/create_transaction.rs +++ b/tests/create_transaction.rs @@ -1,5 +1,5 @@ -use CleytoCoin::chain::transaction::{TransactionInfo, Transaction}; -use CleytoCoin::chain::wallet::Wallet; +use cleyto_coin::chain::transaction::{TransactionInfo, Transaction}; +use cleyto_coin::chain::wallet::Wallet; use chrono::Utc; #[test] diff --git a/tests/sign_and_verify_transactioninfo.rs b/tests/sign_and_verify_transactioninfo.rs index 73184f6..016f08f 100644 --- a/tests/sign_and_verify_transactioninfo.rs +++ b/tests/sign_and_verify_transactioninfo.rs @@ -1,4 +1,4 @@ -use CleytoCoin::chain::{transaction::TransactionInfo, wallet::Wallet}; +use cleyto_coin::chain::{transaction::TransactionInfo, wallet::Wallet}; use chrono::Utc; #[test] From dc1194f3f8a8d21c375124af6a08d855f512beb3 Mon Sep 17 00:00:00 2001 From: dantee-e Date: Thu, 3 Apr 2025 17:20:31 +0200 Subject: [PATCH 06/40] The basis of the http protocol implemented, all that's left to do is everything --- Cargo.toml | 2 +- README.md | 10 +++---- src/TODO.txt | 1 - src/chain.rs | 3 -- src/node.rs | 55 +++++++++++++++++++++++++++++++---- src/node/example_http_get.yml | 18 ++++++++++++ src/node/resolve_requests.rs | 46 +++++++++++++++++++++++++++++ src/node/utils.rs | 20 +++++++++++++ 8 files changed, 140 insertions(+), 15 deletions(-) create mode 100644 src/node/example_http_get.yml create mode 100644 src/node/resolve_requests.rs create mode 100644 src/node/utils.rs diff --git a/Cargo.toml b/Cargo.toml index 5bf477a..1a59eae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,5 +10,5 @@ rand = "0.8.0" hex = "0.4.3" tiny-bip39 = "2.0.0" chrono = "0.4.40" -serde = "1.0.219" +serde = { version="1.0.219", features = ["derive"] } serde_json = "1.0.140" diff --git a/README.md b/README.md index 97e1eab..4748da6 100644 --- a/README.md +++ b/README.md @@ -57,33 +57,33 @@ This will compile the project and generate an optimized binary in the `target/re ### Starting the node To start the cryptocurrency node, use the following command: ``` bash -cargo run --bin CleytoCoin +cargo run --bin cleyto_coin ``` The node will start and connect to the network. You can start mining or send/receive transactions. ### Creating a wallet To generate a new wallet, run the following: ``` bash -cargo run --bin CleytoCoin-wallet generate +cargo run --bin cleyto_coin-wallet generate ``` This will generate a private key and address for your wallet. ### Sending a transaction To send a transaction, use the following command: ``` bash -cargo run --bin CleytoCoin-wallet send --to --amount --private-key +cargo run --bin cleyto_coin-wallet send --to --amount --private-key ``` ### Mining Start mining by running: ``` bash -cargo run --bin CleytoCoin-miner start --mining-key +cargo run --bin cleyto_coin-miner start --mining-key ``` ### Stopping the node To stop the node, press `CTRL+C` or run the following: ``` bash -cargo run --bin CleytoCoin stop +cargo run --bin cleyto_coin stop ``` ## Testing diff --git a/src/TODO.txt b/src/TODO.txt index bdbf309..f77fc3f 100644 --- a/src/TODO.txt +++ b/src/TODO.txt @@ -1,4 +1,3 @@ - - Criar um node com uma port no localhost para receber transacoes, e de x em x tempos fechar o bloco - Encontrar nodes diff --git a/src/chain.rs b/src/chain.rs index f4a073b..201b7f3 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -3,9 +3,6 @@ pub mod transaction; pub mod block; pub mod wallet; use block::Block; -use chrono::{DateTime, Utc}; -use sha2::Sha256; -use transaction::Transaction as Transaction; pub struct Chain { blocks: Vec diff --git a/src/node.rs b/src/node.rs index 9b10396..5481090 100644 --- a/src/node.rs +++ b/src/node.rs @@ -1,23 +1,39 @@ +mod utils; +mod resolve_requests; + +use resolve_requests::methods; +use crate::chain::transaction::Transaction; + + + use std::{ - io::{prelude::*, BufReader}, - net::{TcpListener, TcpStream}, + io::{prelude::*, BufReader}, net::{TcpListener, TcpStream} }; use std::io; + + + + pub struct Node { - default_port: u16 + port: u16, + transactions_list: Vec } impl Node { + const DEFAULT_PORT: u16 = 9473; pub fn new(port: u16) -> Self { Node { - default_port: port + port: port, + transactions_list: Vec::new() } } + + fn handle_connection(stream: TcpStream){ let buf_reader = BufReader::new(&stream); let http_request: Vec<_> = buf_reader @@ -26,7 +42,30 @@ impl Node { .take_while(|line| !line.is_empty()) .collect(); - println!("Request: {http_request:#?}"); + let mut request_tokens = http_request[0].split(' '); // iterator + + let method = if let Some(token) = request_tokens.next() { + token + } else { + methods::return_json(&stream, methods::HTTPResponse::BadRequest); + "shit happened" + }; + + let endpoint = if let Some(token) = request_tokens.next() { + token + } else { + methods::return_json(&stream, methods::HTTPResponse::BadRequest); + "shit happened" + }; + + match method { + "GET" => methods::get(&stream, endpoint), + "POST" => methods::post(&stream, endpoint), + _ => { + methods::return_json(&stream, methods::HTTPResponse::InvalidMethod); + } + } + } pub fn run() { @@ -38,6 +77,7 @@ impl Node { .read_line(&mut input) .expect("Failed to read port"); + port = if input.trim().is_empty() { Self::DEFAULT_PORT } else { @@ -50,8 +90,13 @@ impl Node { } }; + println!("Activating listener..."); + let listener = TcpListener::bind(format!("127.0.0.1:{port}")).unwrap(); + println!("Listening to new connections on port {port}!"); + + for stream in listener.incoming() { let stream = stream.unwrap(); Self::handle_connection(stream); diff --git a/src/node/example_http_get.yml b/src/node/example_http_get.yml new file mode 100644 index 0000000..63d5990 --- /dev/null +++ b/src/node/example_http_get.yml @@ -0,0 +1,18 @@ +GET / HTTP/1.1 +Host: localhost:9473 +Connection: keep-alive +sec-ch-ua: "Not(A:Brand";v="99", "Brave";v="133", "Chromium";v="133" +sec-ch-ua-mobile: ?0 +sec-ch-ua-platform: "Linux" +DNT: 1 +Upgrade-Insecure-Requests: 1 +User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36 +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8 +Sec-GPC: 1 +Accept-Language: en-US,en;q=0.6 +Sec-Fetch-Site: none +Sec-Fetch-Mode: navigate +Sec-Fetch-User: ?1 +Sec-Fetch-Dest: document +Accept-Encoding: gzip, deflate, br, zstd +Cookie: session=eyJpZCI6ICIxZjZjNDBiNS02YTBkLTRiOWYtOGY4Yy1kYzQ4N2NjYmQ5OT \ No newline at end of file diff --git a/src/node/resolve_requests.rs b/src/node/resolve_requests.rs new file mode 100644 index 0000000..a5d2b1c --- /dev/null +++ b/src/node/resolve_requests.rs @@ -0,0 +1,46 @@ +pub mod methods { + use std::net::TcpStream; + use std::io::prelude::*; + use serde_json::json; + + pub enum HTTPResponse { + OK, + InvalidMethod, + BadRequest + } + + pub fn return_json(mut stream: &TcpStream, status: HTTPResponse){ + + let (msg, status_code) = match status { + HTTPResponse::OK => ("The request was successful".to_owned(), 200), + HTTPResponse::InvalidMethod => ("Invalid HTTP method".to_owned(), 405), + HTTPResponse::BadRequest => ("Bad Request".to_owned(), 400) + }; + + let response = json!({ + "msg": msg, + "status_code": status_code + }); + + let success_json = serde_json::to_string(&response) + .expect("Couldn't convert the object to json"); + + let response = format!( + "HTTP/1.1 200 OK\r\n\ + Content-Type: application/json\r\n\ + Content-Length: {}\r\n +{}", + success_json.len(), + success_json + ); + + stream.write_all(response.as_bytes()).unwrap(); + } + + pub fn get(stream: &TcpStream, endpoint: &str) { + return_json(stream, HTTPResponse::OK); + } + pub fn post(stream: &TcpStream, endpoint: &str) { + return_json(&stream, HTTPResponse::OK); + } +} \ No newline at end of file diff --git a/src/node/utils.rs b/src/node/utils.rs new file mode 100644 index 0000000..2ac9e8f --- /dev/null +++ b/src/node/utils.rs @@ -0,0 +1,20 @@ +// use serde::{Deserialize, Serialize}; + + +/* +pub const RESPONSES_FOLDER: &str = "src/node/responses/"; +pub const SUCCESS_FILE_JSON: &str = "sucess_response.json"; +pub const ERROR_FILE_JSON: &str = "error_response.json"; + +#[derive(Serialize, Deserialize)] +pub struct SuccessJson { + msg: String, + status_code: u8 +} + + +#[derive(Serialize, Deserialize)] +pub struct ErrorJson { + msg: String, + status_code: u8 +} */ \ No newline at end of file From de327b00e3013b61ed309a55d3fd801a64a36399 Mon Sep 17 00:00:00 2001 From: dantee-e Date: Thu, 3 Apr 2025 18:00:12 +0200 Subject: [PATCH 07/40] remaking the http handling --- src/node.rs | 46 ++++++++++++++++-------------------- src/node/resolve_requests.rs | 42 +++++++++++++++++++++++++++++++- 2 files changed, 61 insertions(+), 27 deletions(-) diff --git a/src/node.rs b/src/node.rs index 5481090..13d3b91 100644 --- a/src/node.rs +++ b/src/node.rs @@ -1,13 +1,13 @@ mod utils; mod resolve_requests; -use resolve_requests::methods; +use resolve_requests::methods::{self, HTTPParseError}; use crate::chain::transaction::Transaction; use std::{ - io::{prelude::*, BufReader}, net::{TcpListener, TcpStream} + collections::HashMap, io::{prelude::*, BufReader}, net::{TcpListener, TcpStream} }; use std::io; @@ -21,8 +21,6 @@ pub struct Node { } impl Node { - - const DEFAULT_PORT: u16 = 9473; pub fn new(port: u16) -> Self { @@ -32,7 +30,22 @@ impl Node { } } - + fn parse_http_request(http_request: Vec) -> Result { + let mut tokens = http_request[0].split(' '); + let (method, path, http_version) = ( + tokens.next().ok_or(HTTPParseError::InvalidRequestLine)?.to_string(), + tokens.next().ok_or(HTTPParseError::InvalidRequestLine)?.to_string(), + tokens.next().ok_or(HTTPParseError::InvalidRequestLine)?.to_string() + ); + + let headers = HashMap::new(); + let body = Some(String::new()); + + + + + Ok(methods::HTTPRequest::new(method, path, http_version, headers, body)) + } fn handle_connection(stream: TcpStream){ let buf_reader = BufReader::new(&stream); @@ -44,27 +57,8 @@ impl Node { let mut request_tokens = http_request[0].split(' '); // iterator - let method = if let Some(token) = request_tokens.next() { - token - } else { - methods::return_json(&stream, methods::HTTPResponse::BadRequest); - "shit happened" - }; - - let endpoint = if let Some(token) = request_tokens.next() { - token - } else { - methods::return_json(&stream, methods::HTTPResponse::BadRequest); - "shit happened" - }; - - match method { - "GET" => methods::get(&stream, endpoint), - "POST" => methods::post(&stream, endpoint), - _ => { - methods::return_json(&stream, methods::HTTPResponse::InvalidMethod); - } - } + let request_object = Self::parse_http_request(http_request); + } diff --git a/src/node/resolve_requests.rs b/src/node/resolve_requests.rs index a5d2b1c..fc43c1b 100644 --- a/src/node/resolve_requests.rs +++ b/src/node/resolve_requests.rs @@ -1,4 +1,6 @@ pub mod methods { + use std::collections::HashMap; + use std::fmt; use std::net::TcpStream; use std::io::prelude::*; use serde_json::json; @@ -9,8 +11,46 @@ pub mod methods { BadRequest } - pub fn return_json(mut stream: &TcpStream, status: HTTPResponse){ + pub struct HTTPRequest { + method: String, + path: String, + http_version: String, + headers: HashMap, + body: Option + } + impl HTTPRequest { + pub fn new(method:String, path: String, http_version: String, headers: HashMap, body: Option) -> HTTPRequest { + HTTPRequest { + method, + path, + http_version, + headers, + body, + } + } + } + + #[derive(Debug)] + pub enum HTTPParseError { + InvalidRequestLine, + MissingFields, + } + impl fmt::Display for HTTPParseError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + HTTPParseError::InvalidRequestLine => write!(f, "Invalid request line"), + HTTPParseError::MissingFields => write!(f, "Missing required fields"), + } + } + } + impl std::error::Error for HTTPParseError {} + + + type Result = std::result::Result; + + pub fn return_json(mut stream: &TcpStream, status: HTTPResponse){ + let (msg, status_code) = match status { HTTPResponse::OK => ("The request was successful".to_owned(), 200), HTTPResponse::InvalidMethod => ("Invalid HTTP method".to_owned(), 405), From 4d82b9f4fcae98c4e3efeb20fbea276579ea3db9 Mon Sep 17 00:00:00 2001 From: dantee-e Date: Fri, 4 Apr 2025 18:26:05 +0200 Subject: [PATCH 08/40] thread_pool not working, we should just switch to one already implemented --- Cargo.lock | 1137 ++++++++++++++++++++++++++++++++- Cargo.toml | 1 + src/chain/transaction.rs | 2 +- src/lib.rs | 2 +- src/main.rs | 39 +- src/node.rs | 196 ++++-- src/node/example_http_get.yml | 11 +- src/node/resolve_requests.rs | 19 +- src/node/thread_pool.rs | 105 +++ tests/create_transaction.rs | 2 +- tests/requests.rs | 81 +++ 11 files changed, 1532 insertions(+), 63 deletions(-) create mode 100644 src/node/thread_pool.rs create mode 100644 tests/requests.rs diff --git a/Cargo.lock b/Cargo.lock index 94fa6fc..e0c1f0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -23,12 +38,45 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64ct" version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" + [[package]] name = "block-buffer" version = "0.10.4" @@ -50,6 +98,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + [[package]] name = "cc" version = "1.2.16" @@ -86,6 +140,7 @@ dependencies = [ "chrono", "hex", "rand", + "reqwest", "rsa", "serde", "serde_json", @@ -99,6 +154,16 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -147,6 +212,126 @@ dependencies = [ "subtle", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-io", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -165,9 +350,52 @@ checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", ] +[[package]] +name = "getrandom" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + [[package]] name = "hex" version = "0.4.3" @@ -183,6 +411,77 @@ dependencies = [ "digest", ] +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http", + "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 = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "iana-time-zone" version = "0.1.61" @@ -206,6 +505,161 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + [[package]] name = "itoa" version = "1.0.15" @@ -243,6 +697,18 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +[[package]] +name = "linux-raw-sys" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" + +[[package]] +name = "litemap" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" + [[package]] name = "log" version = "0.4.26" @@ -255,6 +721,49 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff70ce3e48ae43fa075863cef62e8b43b71a4f2382229920e0df362592919430" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", +] + +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "num-bigint-dig" version = "0.8.4" @@ -302,12 +811,65 @@ dependencies = [ "libm", ] +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc" +[[package]] +name = "openssl" +version = "0.10.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd" +dependencies = [ + "bitflags 2.9.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "pbkdf2" version = "0.12.2" @@ -327,6 +889,24 @@ dependencies = [ "base64ct", ] +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pkcs1" version = "0.7.5" @@ -348,6 +928,12 @@ dependencies = [ "spki", ] +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -375,6 +961,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + [[package]] name = "rand" version = "0.8.5" @@ -402,7 +994,47 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.15", +] + +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", ] [[package]] @@ -426,12 +1058,40 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + [[package]] name = "rustc-hash" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustix" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" +dependencies = [ + "bitflags 2.9.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64", +] + [[package]] name = "rustversion" version = "1.0.20" @@ -439,10 +1099,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" [[package]] -name = "ryu" -version = "1.0.20" +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.9.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] [[package]] name = "serde" @@ -476,6 +1168,18 @@ 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 = "sha2" version = "0.10.8" @@ -503,12 +1207,31 @@ dependencies = [ "rand_core", ] +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + [[package]] name = "smallvec" version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" +[[package]] +name = "socket2" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "spin" version = "0.9.8" @@ -525,6 +1248,12 @@ dependencies = [ "der", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "subtle" version = "2.6.1" @@ -542,6 +1271,57 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" +dependencies = [ + "fastrand", + "getrandom 0.3.2", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -579,6 +1359,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinyvec" version = "1.9.0" @@ -594,6 +1384,75 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "tokio" +version = "1.44.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" +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 = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "typenum" version = "1.18.0" @@ -615,18 +1474,65 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -653,6 +1559,19 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.100" @@ -685,13 +1604,23 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "windows-core" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -700,34 +1629,94 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -740,30 +1729,109 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.9.0", +] + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + [[package]] name = "zerocopy" version = "0.8.23" @@ -784,6 +1852,27 @@ dependencies = [ "syn", ] +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + [[package]] name = "zeroize" version = "1.8.1" @@ -803,3 +1892,25 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 1a59eae..0c72a21 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,3 +12,4 @@ tiny-bip39 = "2.0.0" chrono = "0.4.40" serde = { version="1.0.219", features = ["derive"] } serde_json = "1.0.140" +reqwest = { version = "0.11", features = ["blocking", "json"] } \ No newline at end of file diff --git a/src/chain/transaction.rs b/src/chain/transaction.rs index 28de67e..c910433 100644 --- a/src/chain/transaction.rs +++ b/src/chain/transaction.rs @@ -70,7 +70,7 @@ impl Transaction { // ---------------------------------------------- UNIT TESTS ------------------------------------------------------- #[cfg(test)] mod tests { - use crate::chain::{transaction::TransactionInfo, wallet::Wallet}; + use crate::chain::transaction::TransactionInfo; use chrono::Utc; #[test] //mark a function as a test. diff --git a/src/lib.rs b/src/lib.rs index 2ff6c45..2ad62a0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,2 @@ pub mod chain; -pub mod node; \ No newline at end of file +pub mod node; diff --git a/src/main.rs b/src/main.rs index c384e4f..9da1cb4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,42 @@ pub mod chain; pub mod node; + +use std::{sync::{mpsc, Arc, Mutex}, thread}; +use std::io::Read; fn main(){ - node::Node::run(); + let (_, rx) = mpsc::channel::<()>(); + + // Channel to kill thread + let rx = Arc::new(Mutex::new(rx)); + + // Run server thread + let server = thread::spawn(move || { + let rx = Arc::clone(&rx); + node::Node::run(true, rx, 0); + + }); + + + let mut input = String::new(); + + loop { + print!("command: "); + + std::io::stdin() + .read_line(&mut input) + .expect("Failed to read line"); + + input = input.trim().to_string(); + println!("{:?}", input); + + match input.as_str() { + "quit" => {println!("quit program"); break;}, + _ => println!("no command") + + } + + + } + + } \ No newline at end of file diff --git a/src/node.rs b/src/node.rs index 13d3b91..6ac1c5e 100644 --- a/src/node.rs +++ b/src/node.rs @@ -1,15 +1,18 @@ mod utils; mod resolve_requests; +mod thread_pool; -use resolve_requests::methods::{self, HTTPParseError}; +use core::panic; +use std::time::Duration; +use resolve_requests::methods::{self, return_json, HTTPParseError, HTTPRequest, HTTPResponse}; use crate::chain::transaction::Transaction; +use thread_pool::ThreadPool; use std::{ - collections::HashMap, io::{prelude::*, BufReader}, net::{TcpListener, TcpStream} + collections::HashMap, io::{prelude::*, BufReader}, net::{TcpListener, TcpStream}, sync::{mpsc::Receiver, Arc, Mutex}, thread }; -use std::io; @@ -21,81 +24,192 @@ pub struct Node { } impl Node { - const DEFAULT_PORT: u16 = 9473; + // these configurations should be moved to a file + pub const DEFAULT_PORT: u16 = 9473; + pub const REFRESH_RATE_SERVER_IN_MS: u64 = 50; + pub const NUMBER_OF_THREADS_IN_THREAD_POOL: usize = 10; - pub fn new(port: u16) -> Self { + pub fn new(port: u16) -> Node { + Node { port: port, transactions_list: Vec::new() } } - fn parse_http_request(http_request: Vec) -> Result { - let mut tokens = http_request[0].split(' '); + fn parse_http_request(stream: &TcpStream,mut buf_reader: BufReader) -> Result { + + let mut http_headers: HashMap = HashMap::new(); + let http_body: Option; + + + println!("Beginning of request"); + + let mut line = String::new(); + + + // reading status_line + let status_line: String; + + match buf_reader.read_line(&mut line) { + Ok(n) if (n > 0) => n, + Ok(_) => return Err(HTTPParseError::InvalidStatusLine), + Err(_) => return Err(HTTPParseError::InvalidStatusLine), + }; + + status_line = line.trim().to_string(); + + let mut tokens = status_line.split(' '); let (method, path, http_version) = ( tokens.next().ok_or(HTTPParseError::InvalidRequestLine)?.to_string(), tokens.next().ok_or(HTTPParseError::InvalidRequestLine)?.to_string(), tokens.next().ok_or(HTTPParseError::InvalidRequestLine)?.to_string() ); - let headers = HashMap::new(); - let body = Some(String::new()); + + + + // reading headers + loop { + line.clear(); + + if let Err(e) = buf_reader.read_line(&mut line) { + return Err(HTTPParseError::InvalidRequestLine); + } + + let line = line.trim_end().to_string(); + + + if line.is_empty() { + break; + } + + if let Some((key, value)) = line.split_once(":") { + http_headers.insert(key.trim().to_string(), value.trim().to_string()); + } else { + return_json(stream, HTTPResponse::BadRequest); + }; + + } + + // If method is GET, return before trying to read the body + if method == "GET" { + return Ok(HTTPRequest::new(method, path, http_version, http_headers, None)) + } + + + + // getting content_lenght from headers + let content_length = match http_headers.get("content-length") { + Some(value) => match value.parse::() { + Ok(length) => length, + Err(_) => { + return_json(stream, HTTPResponse::BadRequest); + return Err(HTTPParseError::MissingContentLength); + } + }, + None => { + return_json(stream, HTTPResponse::BadRequest); + return Err(HTTPParseError::MissingContentLength); + }, + }; + + // reading body + let mut body = vec![0; content_length]; + if let Err(e) = buf_reader.read_exact(&mut body) { + eprintln!("Error reading body: {}", e); + return_json(&stream, HTTPResponse::BadRequest); + } + + http_body = Some(String::from_utf8_lossy(&body).to_string()); + + + + + - Ok(methods::HTTPRequest::new(method, path, http_version, headers, body)) + Ok(HTTPRequest::new(method, path, http_version, http_headers, http_body)) } fn handle_connection(stream: TcpStream){ let buf_reader = BufReader::new(&stream); - let http_request: Vec<_> = buf_reader - .lines() - .map(|result| result.unwrap()) - .take_while(|line| !line.is_empty()) - .collect(); - - let mut request_tokens = http_request[0].split(' '); // iterator - - let request_object = Self::parse_http_request(http_request); - } + println!("End of request"); + + let request_object: HTTPRequest; + match Self::parse_http_request(&stream, buf_reader) { + Ok(value) => request_object = value, + Err(e) => { + println!("Error processing HTTP request: {e}"); + return_json(&stream, HTTPResponse::BadRequest); + return; + }, + }; - pub fn run() { - let port: u16; - let mut input = String::new(); + match request_object.get_method().as_str() { + "GET" => methods::get(&stream, request_object), + "POST" => methods::post(&stream, request_object), + _ => return_json(&stream, HTTPResponse::InvalidMethod), + } - println!("Please input the port you want to use (Press enter for default {}): ", Self::DEFAULT_PORT); - io::stdin() - .read_line(&mut input) - .expect("Failed to read port"); + return; + + } - port = if input.trim().is_empty() { - Self::DEFAULT_PORT - } else { - match input.trim().parse::() { - Ok(port) if (1..=65535).contains(&port) => port, + pub fn run(default: bool, rx: Arc>>, selected_port: u16) { + let port: u16; + if default == true { + port = Self::DEFAULT_PORT; + } + + else { + port = match selected_port { + port if (1..=65535).contains(&port) => port, _ => { println!("Invalid port! Using default: {}", Self::DEFAULT_PORT); Self::DEFAULT_PORT } } - }; - - println!("Activating listener..."); + } + let listener = TcpListener::bind(format!("127.0.0.1:{port}")).unwrap(); - println!("Listening to new connections on port {port}!"); - + listener.set_nonblocking(true).expect("Cannot set non-blocking"); - for stream in listener.incoming() { - let stream = stream.unwrap(); - Self::handle_connection(stream); + let thread_pool = match ThreadPool::new(Self::NUMBER_OF_THREADS_IN_THREAD_POOL){ + Ok(value) => value, + Err(e) => panic!("{e}"), + }; - println!("Connection established!"); + loop { + // Check for termination signal + if let Ok(lock) = rx.try_lock() { + if let Ok(_) = lock.try_recv() { + break; + } + }; + + // Try accepting a connection + match listener.accept() { + Ok((stream, addr)) => { + thread_pool.execute(move ||{ + Self::handle_connection(stream); + }); + }, + Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { + thread::sleep(Duration::from_millis(Self::REFRESH_RATE_SERVER_IN_MS)); + }, + Err(e) => { + eprintln!("Error accepting connection: {}", e); + break; + } + } } } } \ No newline at end of file diff --git a/src/node/example_http_get.yml b/src/node/example_http_get.yml index 63d5990..14a7a3e 100644 --- a/src/node/example_http_get.yml +++ b/src/node/example_http_get.yml @@ -1,4 +1,4 @@ -GET / HTTP/1.1 +POST / HTTP/1.1 Host: localhost:9473 Connection: keep-alive sec-ch-ua: "Not(A:Brand";v="99", "Brave";v="133", "Chromium";v="133" @@ -15,4 +15,11 @@ Sec-Fetch-Mode: navigate Sec-Fetch-User: ?1 Sec-Fetch-Dest: document Accept-Encoding: gzip, deflate, br, zstd -Cookie: session=eyJpZCI6ICIxZjZjNDBiNS02YTBkLTRiOWYtOGY4Yy1kYzQ4N2NjYmQ5OT \ No newline at end of file +Cookie: session=eyJpZCI6ICIxZjZjNDBiNS02YTBkLTRiOWYtOGY4Yy1kYzQ4N2NjYmQ5OT + + +POST / HTTP/1.1 +content-type: application/json +content-length: 36 +accept: */* +host: localhost:9473 \ No newline at end of file diff --git a/src/node/resolve_requests.rs b/src/node/resolve_requests.rs index fc43c1b..d163fec 100644 --- a/src/node/resolve_requests.rs +++ b/src/node/resolve_requests.rs @@ -11,6 +11,7 @@ pub mod methods { BadRequest } + #[derive(Debug)] pub struct HTTPRequest { method: String, path: String, @@ -29,25 +30,35 @@ pub mod methods { body, } } + + pub fn get_method(&self) -> String { + self.method.clone() + } + pub fn get_path(&self) -> String { + self.path.clone() + } } #[derive(Debug)] pub enum HTTPParseError { + InvalidStatusLine, InvalidRequestLine, MissingFields, + MissingContentLength } impl fmt::Display for HTTPParseError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { HTTPParseError::InvalidRequestLine => write!(f, "Invalid request line"), HTTPParseError::MissingFields => write!(f, "Missing required fields"), + HTTPParseError::InvalidStatusLine => write!(f, "Invalid status line"), + HTTPParseError::MissingContentLength => write!(f, "Missing content-length field in headers"), } } } impl std::error::Error for HTTPParseError {} - type Result = std::result::Result; pub fn return_json(mut stream: &TcpStream, status: HTTPResponse){ @@ -77,10 +88,12 @@ pub mod methods { stream.write_all(response.as_bytes()).unwrap(); } - pub fn get(stream: &TcpStream, endpoint: &str) { + pub fn get(stream: &TcpStream, request: HTTPRequest) { + println!("{:#?}", request); return_json(stream, HTTPResponse::OK); } - pub fn post(stream: &TcpStream, endpoint: &str) { + pub fn post(stream: &TcpStream, request: HTTPRequest) { + println!("{:#?}", request); return_json(&stream, HTTPResponse::OK); } } \ No newline at end of file diff --git a/src/node/thread_pool.rs b/src/node/thread_pool.rs new file mode 100644 index 0000000..e25eb01 --- /dev/null +++ b/src/node/thread_pool.rs @@ -0,0 +1,105 @@ +use std::{fmt, sync::{mpsc, Arc, Mutex}, thread}; + +#[derive(Debug)] +pub enum PoolCreationError { + TooFewThreads, + TooManyThreads +} + +impl fmt::Display for PoolCreationError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + PoolCreationError::TooFewThreads => write!(f, "You must use at least one thread on the pool"), + PoolCreationError::TooManyThreads => write!(f, "You must use less than {} threads on the pool", u32::MAX), + } + } +} +impl std::error::Error for PoolCreationError {} + + + + +struct Worker { + id: usize, + handle: thread::JoinHandle<()> +} + +impl Worker { + pub fn new(id: usize, receiver: Arc>>) -> Worker { + let thread = thread::spawn(move || loop { + let message = receiver.lock().unwrap().recv(); + + match message { + Ok(job) => { + println!("Worker {id} got a job; executing."); + job(); + } + Err(_) => { + println!("Worker {id} disconnected; shutting down."); + break; + } + } + }); + Worker { + id, + handle: thread + } + } +} + + +type Job = Box; + +pub struct ThreadPool { + workers: Vec, + sender: Option> +} + +impl ThreadPool { + /// Create a new ThreadPool. + /// + /// The size is the number of threads in the pool. + /// + /// # Panics + /// + /// The `new` function will panic if the size is zero. + pub fn new(size: usize) -> Result { + if size < 1 { + return Err(PoolCreationError::TooFewThreads) + } else if size > usize::MAX { // I think this is unreachable + return Err(PoolCreationError::TooManyThreads) + } + + let (sender, receiver) = mpsc::channel(); + + let receiver = Arc::new(Mutex::new(receiver)); + + let mut workers = Vec::with_capacity(size); + + for id in 0..size { + workers.push(Worker::new(id, Arc::clone(&receiver))); + } + + Ok(ThreadPool { workers, sender: Some(sender) }) + } + + /// Receives a closure compatible with the thread::spawn() function. + pub fn execute(&self, f: F) + where + F: FnOnce() + Send + 'static, // to receive a closure compatible with thread.spawn() + { + let job = Box::new(f); + self.sender.as_ref().unwrap().send(job).unwrap(); + } +} + +impl Drop for ThreadPool { + fn drop(&mut self) { + drop(self.sender.take()); + for worker in self.workers.drain(..) { + println!("Shutting down worker {}", worker.id); + + worker.handle.join().unwrap(); + } + } +} \ No newline at end of file diff --git a/tests/create_transaction.rs b/tests/create_transaction.rs index 51d03a6..dd88814 100644 --- a/tests/create_transaction.rs +++ b/tests/create_transaction.rs @@ -5,7 +5,7 @@ use chrono::Utc; #[test] fn create_transaction() { let (wallet_sender, mut walletpk_sender) = Wallet::new(); - let (wallet_receiver, walletpk_receiver) = Wallet::new(); + let (wallet_receiver, _) = Wallet::new(); let transactioninfo: TransactionInfo = TransactionInfo::new(12345 as f32, Utc::now()); let signature = match walletpk_sender.sign_transaction(&transactioninfo) { diff --git a/tests/requests.rs b/tests/requests.rs new file mode 100644 index 0000000..8ccbac0 --- /dev/null +++ b/tests/requests.rs @@ -0,0 +1,81 @@ +use cleyto_coin::node; +use std::{sync::{mpsc, Arc, Mutex}, thread}; + +use reqwest::blocking::Client; +use serde::Serialize; +use rand::Rng; + + +#[derive(Serialize)] +struct RandomData { + message: String, + value: i32, +} + +#[test] +fn post_request() -> Result<(), Box> { + let mut rng = rand::thread_rng(); + let data = RandomData { + message: (0..10).map(|_| rng.sample(rand::distributions::Alphanumeric) as char).collect(), + value: rng.gen_range(1..=1000), + }; + + let client = Client::new(); + let res = client + .post(format!("http://localhost:{}", node::Node::DEFAULT_PORT)) + .json(&data) + .send()?; + + assert_eq!(res.status(), 200); + + Ok(()) +} + +#[test] +fn get_request() -> Result<(), Box> { + + let client = Client::new(); + let res = client + .get(format!("http://localhost:{}", node::Node::DEFAULT_PORT)) + .send()?; + + assert_eq!(res.status(), 200); + let body = res.text()?; + + Ok(()) +} + +#[test] +fn post_and_get_request(){ + /* let (tx, rx) = mpsc::channel::<()>(); + + // Channel to kill thread + let rx = Arc::new(Mutex::new(rx)); + + // Run server thread + let server = thread::spawn(move || { + let rx = Arc::clone(&rx); + node::Node::run(true, rx, 0); + }); */ + + for _ in 0..30 { + match post_request() { + Ok(_) => println!("Post successful"), + Err(_) => println!("Post failed"), + }; + match get_request() { + Ok(_) => println!("Get successful"), + Err(_) => println!("Get failed"), + }; + } + + + /* tx.send(()).expect("Failed to send termination signal."); + + // Wait for the server thread to finish (this will block until the server thread terminates) + server.join().expect("Server thread panicked."); */ +} + + + + From d88f258c655edbf1e5eb635d3e91ac17433ac6ec Mon Sep 17 00:00:00 2001 From: dantee-e Date: Sat, 5 Apr 2025 12:49:20 +0200 Subject: [PATCH 09/40] thread_pool working with shutdown --- Cargo.lock | 52 +++++++++++ Cargo.toml | 3 +- src/main.rs | 15 ++-- src/node.rs | 17 +++- src/node/thread_pool.rs | 190 ++++++++++++++++++++++------------------ 5 files changed, 182 insertions(+), 95 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e0c1f0f..293b80f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -140,6 +140,7 @@ dependencies = [ "chrono", "hex", "rand", + "rayon", "reqwest", "rsa", "serde", @@ -179,6 +180,31 @@ dependencies = [ "libc", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + [[package]] name = "crypto-common" version = "0.1.6" @@ -223,6 +249,12 @@ dependencies = [ "syn", ] +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "encoding_rs" version = "0.8.35" @@ -997,6 +1029,26 @@ dependencies = [ "getrandom 0.2.15", ] +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "reqwest" version = "0.11.27" diff --git a/Cargo.toml b/Cargo.toml index 0c72a21..f98e5ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,4 +12,5 @@ tiny-bip39 = "2.0.0" chrono = "0.4.40" serde = { version="1.0.219", features = ["derive"] } serde_json = "1.0.140" -reqwest = { version = "0.11", features = ["blocking", "json"] } \ No newline at end of file +reqwest = { version = "0.11", features = ["blocking", "json"] } +rayon = "1.10.0" diff --git a/src/main.rs b/src/main.rs index 9da1cb4..5996ff1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,9 +2,8 @@ pub mod chain; pub mod node; use std::{sync::{mpsc, Arc, Mutex}, thread}; -use std::io::Read; fn main(){ - let (_, rx) = mpsc::channel::<()>(); + let (tx, rx) = mpsc::channel::<()>(); // Channel to kill thread let rx = Arc::new(Mutex::new(rx)); @@ -30,13 +29,15 @@ fn main(){ println!("{:?}", input); match input.as_str() { - "quit" => {println!("quit program"); break;}, + "quit" => { + println!("quitting server"); + tx.send(()).unwrap(); + break; + }, _ => println!("no command") } - - } - - + // Waits for the node to stop running + server.join().unwrap(); } \ No newline at end of file diff --git a/src/node.rs b/src/node.rs index 6ac1c5e..616a5c3 100644 --- a/src/node.rs +++ b/src/node.rs @@ -6,7 +6,9 @@ use core::panic; use std::time::Duration; use resolve_requests::methods::{self, return_json, HTTPParseError, HTTPRequest, HTTPResponse}; use crate::chain::transaction::Transaction; -use thread_pool::ThreadPool; +use thread_pool::custom_thread_pool::ThreadPool; + +use rayon::prelude::*; @@ -187,6 +189,8 @@ impl Node { Err(e) => panic!("{e}"), }; + + loop { // Check for termination signal if let Ok(lock) = rx.try_lock() { @@ -198,9 +202,10 @@ impl Node { // Try accepting a connection match listener.accept() { Ok((stream, addr)) => { - thread_pool.execute(move ||{ + // rayon + thread_pool.execute(|| { Self::handle_connection(stream); - }); + }) }, Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { thread::sleep(Duration::from_millis(Self::REFRESH_RATE_SERVER_IN_MS)); @@ -210,6 +215,10 @@ impl Node { break; } } - } + } + + println!("Dropping thread pool"); + + drop(thread_pool); } } \ No newline at end of file diff --git a/src/node/thread_pool.rs b/src/node/thread_pool.rs index e25eb01..3aa9a05 100644 --- a/src/node/thread_pool.rs +++ b/src/node/thread_pool.rs @@ -1,105 +1,129 @@ -use std::{fmt, sync::{mpsc, Arc, Mutex}, thread}; - -#[derive(Debug)] -pub enum PoolCreationError { - TooFewThreads, - TooManyThreads -} - -impl fmt::Display for PoolCreationError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - PoolCreationError::TooFewThreads => write!(f, "You must use at least one thread on the pool"), - PoolCreationError::TooManyThreads => write!(f, "You must use less than {} threads on the pool", u32::MAX), - } - } -} -impl std::error::Error for PoolCreationError {} -struct Worker { - id: usize, - handle: thread::JoinHandle<()> -} -impl Worker { - pub fn new(id: usize, receiver: Arc>>) -> Worker { - let thread = thread::spawn(move || loop { - let message = receiver.lock().unwrap().recv(); - match message { - Ok(job) => { - println!("Worker {id} got a job; executing."); - job(); - } - Err(_) => { - println!("Worker {id} disconnected; shutting down."); - break; - } + + + + + + + + + + + + + +pub mod custom_thread_pool { + use std::{fmt, sync::{mpsc, Arc, Mutex}, thread}; + + #[derive(Debug)] + pub enum PoolCreationError { + TooFewThreads, + TooManyThreads + } + + impl fmt::Display for PoolCreationError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + PoolCreationError::TooFewThreads => write!(f, "You must use at least one thread on the pool"), + PoolCreationError::TooManyThreads => write!(f, "You must use less than {} threads on the pool", u32::MAX), } - }); - Worker { - id, - handle: thread } } -} - - -type Job = Box; - -pub struct ThreadPool { - workers: Vec, - sender: Option> -} - -impl ThreadPool { - /// Create a new ThreadPool. - /// - /// The size is the number of threads in the pool. - /// - /// # Panics - /// - /// The `new` function will panic if the size is zero. - pub fn new(size: usize) -> Result { - if size < 1 { - return Err(PoolCreationError::TooFewThreads) - } else if size > usize::MAX { // I think this is unreachable - return Err(PoolCreationError::TooManyThreads) - } + impl std::error::Error for PoolCreationError {} - let (sender, receiver) = mpsc::channel(); - let receiver = Arc::new(Mutex::new(receiver)); - let mut workers = Vec::with_capacity(size); - for id in 0..size { - workers.push(Worker::new(id, Arc::clone(&receiver))); - } + struct Worker { + id: usize, + handle: thread::JoinHandle<()> + } - Ok(ThreadPool { workers, sender: Some(sender) }) + impl Worker { + pub fn new(id: usize, receiver: Arc>>) -> Worker { + let thread = thread::spawn(move || loop { + let message = receiver.lock().unwrap().recv(); + + match message { + Ok(job) => { + println!("Worker {id} got a job; executing."); + job(); + } + Err(_) => { + println!("Worker {id} disconnected; shutting down."); + break; + } + } + }); + Worker { + id, + handle: thread + } + } } - /// Receives a closure compatible with the thread::spawn() function. - pub fn execute(&self, f: F) - where - F: FnOnce() + Send + 'static, // to receive a closure compatible with thread.spawn() - { - let job = Box::new(f); - self.sender.as_ref().unwrap().send(job).unwrap(); + + type Job = Box; + + pub struct ThreadPool { + workers: Vec, + sender: Option> } -} -impl Drop for ThreadPool { - fn drop(&mut self) { - drop(self.sender.take()); - for worker in self.workers.drain(..) { - println!("Shutting down worker {}", worker.id); + impl ThreadPool { + /// Create a new ThreadPool. + /// + /// The size is the number of threads in the pool. + /// + /// # Panics + /// + /// The `new` function will panic if the size is zero. + pub fn new(size: usize) -> Result { + if size < 1 { + return Err(PoolCreationError::TooFewThreads) + } else if size > usize::MAX { // I think this is unreachable + return Err(PoolCreationError::TooManyThreads) + } + + let (sender, receiver) = mpsc::channel(); - worker.handle.join().unwrap(); + let receiver = Arc::new(Mutex::new(receiver)); + + let mut workers = Vec::with_capacity(size); + + for id in 0..size { + workers.push(Worker::new(id, Arc::clone(&receiver))); + } + + Ok(ThreadPool { workers, sender: Some(sender) }) + } + + /// Receives a closure compatible with the thread::spawn() function. + pub fn execute(&self, f: F) + where + F: FnOnce() + Send + 'static, // to receive a closure compatible with thread.spawn() + { + let job = Box::new(f); + self.sender.as_ref().unwrap().send(job).unwrap(); + } + } + + impl Drop for ThreadPool { + fn drop(&mut self) { + println!("Dropping threadPool"); + + drop(self.sender.take()); + + for worker in self.workers.drain(..) { + println!("Shutting down worker {}", worker.id); + worker.handle.join().unwrap(); + } } } + } \ No newline at end of file From a61efc313062b071eb14b542dae067aaedec6819 Mon Sep 17 00:00:00 2001 From: dantee-e Date: Sat, 5 Apr 2025 13:03:27 +0200 Subject: [PATCH 10/40] changing again how the routing is handled --- src/node.rs | 12 +++++------- src/node/resolve_requests.rs | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/src/node.rs b/src/node.rs index 616a5c3..c1cfe18 100644 --- a/src/node.rs +++ b/src/node.rs @@ -4,11 +4,13 @@ mod thread_pool; use core::panic; use std::time::Duration; -use resolve_requests::methods::{self, return_json, HTTPParseError, HTTPRequest, HTTPResponse}; +use resolve_requests::{ + methods::{self, return_json, HTTPParseError, HTTPRequest, HTTPResponse}, + endpoints::resolve_endpoint +}; use crate::chain::transaction::Transaction; use thread_pool::custom_thread_pool::ThreadPool; -use rayon::prelude::*; @@ -152,11 +154,7 @@ impl Node { }, }; - match request_object.get_method().as_str() { - "GET" => methods::get(&stream, request_object), - "POST" => methods::post(&stream, request_object), - _ => return_json(&stream, HTTPResponse::InvalidMethod), - } + resolve_endpoint(request_object); return; diff --git a/src/node/resolve_requests.rs b/src/node/resolve_requests.rs index d163fec..1c91790 100644 --- a/src/node/resolve_requests.rs +++ b/src/node/resolve_requests.rs @@ -1,3 +1,37 @@ + +pub mod endpoints { + use super::methods::HTTPRequest; + + + fn path_not_found(){ + + } + + fn index(){ + + } + + + pub fn resolve_endpoint(request: HTTPRequest){ + + if request.get_method() == "GET" { + match request.get_path().as_str() { + "/" => index(), + _ => path_not_found(), + } + } + + else if request.get_method() == "POST" { + match request.get_path().as_str() { + "/" => index(), + _ => path_not_found(), + } + } + + + } +} + pub mod methods { use std::collections::HashMap; use std::fmt; From b5d8d50f764c92630d48f2443ffd3983be01be69 Mon Sep 17 00:00:00 2001 From: dantee-e Date: Sat, 5 Apr 2025 14:54:39 +0200 Subject: [PATCH 11/40] test for lots of threads --- Cargo.lock | 22 +++++++++++++++-- Cargo.toml | 2 ++ src/node.rs | 39 +++++++++++++----------------- src/node/resolve_requests.rs | 46 +++++++++++++++++++++++++++++++----- tests/requests.rs | 33 +++++++++++++++++++++++++- 5 files changed, 111 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 293b80f..ba72b15 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -139,6 +139,8 @@ version = "0.1.0" dependencies = [ "chrono", "hex", + "num_cpus", + "once_cell", "rand", "rayon", "reqwest", @@ -428,6 +430,12 @@ version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + [[package]] name = "hex" version = "0.4.3" @@ -843,6 +851,16 @@ dependencies = [ "libm", ] +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "object" version = "0.36.7" @@ -854,9 +872,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.1" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "openssl" diff --git a/Cargo.toml b/Cargo.toml index f98e5ae..dbb2ee6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,3 +14,5 @@ serde = { version="1.0.219", features = ["derive"] } serde_json = "1.0.140" reqwest = { version = "0.11", features = ["blocking", "json"] } rayon = "1.10.0" +num_cpus = "1.16.0" +once_cell = "1.21.3" diff --git a/src/node.rs b/src/node.rs index c1cfe18..3bab11d 100644 --- a/src/node.rs +++ b/src/node.rs @@ -2,7 +2,7 @@ mod utils; mod resolve_requests; mod thread_pool; -use core::panic; +use core::{num, panic}; use std::time::Duration; use resolve_requests::{ methods::{self, return_json, HTTPParseError, HTTPRequest, HTTPResponse}, @@ -13,7 +13,6 @@ use thread_pool::custom_thread_pool::ThreadPool; - use std::{ collections::HashMap, io::{prelude::*, BufReader}, net::{TcpListener, TcpStream}, sync::{mpsc::Receiver, Arc, Mutex}, thread }; @@ -27,13 +26,17 @@ pub struct Node { transactions_list: Vec } +use once_cell::sync::Lazy; +static NUMBER_OF_THREADS_IN_THREAD_POOL: Lazy = Lazy::new(num_cpus::get); + impl Node { + // these configurations should be moved to a file pub const DEFAULT_PORT: u16 = 9473; pub const REFRESH_RATE_SERVER_IN_MS: u64 = 50; - pub const NUMBER_OF_THREADS_IN_THREAD_POOL: usize = 10; pub fn new(port: u16) -> Node { + num_cpus::get(); Node { port: port, @@ -41,14 +44,12 @@ impl Node { } } - fn parse_http_request(stream: &TcpStream,mut buf_reader: BufReader) -> Result { + fn parse_http_request(mut buf_reader: BufReader) -> Result { let mut http_headers: HashMap = HashMap::new(); let http_body: Option; - println!("Beginning of request"); - let mut line = String::new(); @@ -70,10 +71,6 @@ impl Node { tokens.next().ok_or(HTTPParseError::InvalidRequestLine)?.to_string() ); - - - - // reading headers loop { line.clear(); @@ -92,14 +89,14 @@ impl Node { if let Some((key, value)) = line.split_once(":") { http_headers.insert(key.trim().to_string(), value.trim().to_string()); } else { - return_json(stream, HTTPResponse::BadRequest); + return Err(HTTPParseError::InvalidRequestLine); }; } // If method is GET, return before trying to read the body if method == "GET" { - return Ok(HTTPRequest::new(method, path, http_version, http_headers, None)) + return Ok(HTTPRequest::new(None, method, path, http_version, http_headers, None)) } @@ -109,12 +106,10 @@ impl Node { Some(value) => match value.parse::() { Ok(length) => length, Err(_) => { - return_json(stream, HTTPResponse::BadRequest); return Err(HTTPParseError::MissingContentLength); } }, None => { - return_json(stream, HTTPResponse::BadRequest); return Err(HTTPParseError::MissingContentLength); }, }; @@ -123,7 +118,7 @@ impl Node { let mut body = vec![0; content_length]; if let Err(e) = buf_reader.read_exact(&mut body) { eprintln!("Error reading body: {}", e); - return_json(&stream, HTTPResponse::BadRequest); + return Err(HTTPParseError::InvalidRequestLine); } http_body = Some(String::from_utf8_lossy(&body).to_string()); @@ -135,18 +130,15 @@ impl Node { - Ok(HTTPRequest::new(method, path, http_version, http_headers, http_body)) + Ok(HTTPRequest::new(None, method, path, http_version, http_headers, http_body)) } fn handle_connection(stream: TcpStream){ let buf_reader = BufReader::new(&stream); - - println!("End of request"); - let request_object: HTTPRequest; - match Self::parse_http_request(&stream, buf_reader) { - Ok(value) => request_object = value, + let mut request_object: HTTPRequest = match Self::parse_http_request(buf_reader) { + Ok(value) => value, Err(e) => { println!("Error processing HTTP request: {e}"); return_json(&stream, HTTPResponse::BadRequest); @@ -154,6 +146,9 @@ impl Node { }, }; + request_object.set_stream(stream); + + resolve_endpoint(request_object); return; @@ -182,7 +177,7 @@ impl Node { listener.set_nonblocking(true).expect("Cannot set non-blocking"); - let thread_pool = match ThreadPool::new(Self::NUMBER_OF_THREADS_IN_THREAD_POOL){ + let thread_pool = match ThreadPool::new(*NUMBER_OF_THREADS_IN_THREAD_POOL){ Ok(value) => value, Err(e) => panic!("{e}"), }; diff --git a/src/node/resolve_requests.rs b/src/node/resolve_requests.rs index 1c91790..9b679b5 100644 --- a/src/node/resolve_requests.rs +++ b/src/node/resolve_requests.rs @@ -1,14 +1,14 @@ pub mod endpoints { - use super::methods::HTTPRequest; + use super::methods::{self, HTTPRequest, HTTPResponse}; fn path_not_found(){ } - fn index(){ - + fn index(mut request: HTTPRequest){ + request.response_json(HTTPResponse::OK); } @@ -16,14 +16,14 @@ pub mod endpoints { if request.get_method() == "GET" { match request.get_path().as_str() { - "/" => index(), + "/" => index(request), _ => path_not_found(), } } else if request.get_method() == "POST" { match request.get_path().as_str() { - "/" => index(), + "/" => index(request), _ => path_not_found(), } } @@ -47,6 +47,7 @@ pub mod methods { #[derive(Debug)] pub struct HTTPRequest { + stream: Option, method: String, path: String, http_version: String, @@ -55,8 +56,9 @@ pub mod methods { } impl HTTPRequest { - pub fn new(method:String, path: String, http_version: String, headers: HashMap, body: Option) -> HTTPRequest { + pub fn new(stream: Option, method:String, path: String, http_version: String, headers: HashMap, body: Option) -> HTTPRequest { HTTPRequest { + stream, method, path, http_version, @@ -65,12 +67,44 @@ pub mod methods { } } + pub fn set_stream(&mut self, stream: TcpStream) { + self.stream = Some(stream); + } + pub fn get_method(&self) -> String { self.method.clone() } pub fn get_path(&self) -> String { self.path.clone() } + + pub fn response_json(&mut self, status: HTTPResponse) { + let mut stream = self.stream.as_ref().unwrap(); + let (msg, status_code) = match status { + HTTPResponse::OK => ("The request was successful".to_owned(), 200), + HTTPResponse::InvalidMethod => ("Invalid HTTP method".to_owned(), 405), + HTTPResponse::BadRequest => ("Bad Request".to_owned(), 400) + }; + + let response = json!({ + "msg": msg, + "status_code": status_code + }); + + let success_json = serde_json::to_string(&response) + .expect("Couldn't convert the object to json"); + + let response = format!( + "HTTP/1.1 200 OK\r\n\ + Content-Type: application/json\r\n\ + Content-Length: {}\r\n + {}", + success_json.len(), + success_json + ); + + stream.write_all(response.as_bytes()).unwrap(); + } } #[derive(Debug)] diff --git a/tests/requests.rs b/tests/requests.rs index 8ccbac0..aa6de3d 100644 --- a/tests/requests.rs +++ b/tests/requests.rs @@ -58,7 +58,7 @@ fn post_and_get_request(){ node::Node::run(true, rx, 0); }); */ - for _ in 0..30 { + for _ in 0..200 { match post_request() { Ok(_) => println!("Post successful"), Err(_) => println!("Post failed"), @@ -76,6 +76,37 @@ fn post_and_get_request(){ server.join().expect("Server thread panicked."); */ } +#[test] +fn thread_posts(){ + let url = "http://localhost:9473/"; // Replace with your server URL + let client = Client::new(); + + let mut handles = vec![]; + + for i in 0..10000 { + let client = client.clone(); + let url = url.to_string(); + let handle = thread::spawn(move || { + let body = format!(r#"{{"message": "Hello #{}"}}"#, i); + match client.post(&url) + .header("Content-Type", "application/json") + .body(body) + .send() + { + + Ok(resp) => {assert_eq!(resp.status(), 200);println!("Thread #{i}: {}", resp.status())}, + Err(err) => eprintln!("Thread #{i} failed: {err}"), + } + }); + + handles.push(handle); + } + + for handle in handles { + let _ = handle.join(); + } + println!("All 200 threads finished."); +} \ No newline at end of file From 21412cd7a3b7e7a7174b6bcf02251fe349139d8f Mon Sep 17 00:00:00 2001 From: dantee-e Date: Sat, 5 Apr 2025 16:39:50 +0200 Subject: [PATCH 12/40] Changed AGAIN the HTTPRequest object (Method is now enum which holds info based on what method). This makes invalid states unrepresentable --- src/node.rs | 14 +++---- src/node/resolve_requests.rs | 73 ++++++++++++++++++++++-------------- 2 files changed, 50 insertions(+), 37 deletions(-) diff --git a/src/node.rs b/src/node.rs index 3bab11d..0e3d611 100644 --- a/src/node.rs +++ b/src/node.rs @@ -2,10 +2,10 @@ mod utils; mod resolve_requests; mod thread_pool; -use core::{num, panic}; +use core::panic; use std::time::Duration; use resolve_requests::{ - methods::{self, return_json, HTTPParseError, HTTPRequest, HTTPResponse}, + methods::{return_json, HTTPParseError, HTTPRequest, HTTPResponse}, endpoints::resolve_endpoint }; use crate::chain::transaction::Transaction; @@ -22,7 +22,6 @@ use std::{ pub struct Node { - port: u16, transactions_list: Vec } @@ -39,7 +38,6 @@ impl Node { num_cpus::get(); Node { - port: port, transactions_list: Vec::new() } } @@ -126,11 +124,11 @@ impl Node { - - - + if method == "POST"{ + return Ok(HTTPRequest::new(None, method, path, http_version, http_headers, http_body)) + } - Ok(HTTPRequest::new(None, method, path, http_version, http_headers, http_body)) + Err(HTTPParseError::InvalidStatusLine) } fn handle_connection(stream: TcpStream){ diff --git a/src/node/resolve_requests.rs b/src/node/resolve_requests.rs index 9b679b5..d130dc6 100644 --- a/src/node/resolve_requests.rs +++ b/src/node/resolve_requests.rs @@ -1,6 +1,7 @@ pub mod endpoints { - use super::methods::{self, HTTPRequest, HTTPResponse}; + use super::methods::{HTTPRequest, HTTPResponse, Method}; + fn path_not_found(){ @@ -13,26 +14,26 @@ pub mod endpoints { pub fn resolve_endpoint(request: HTTPRequest){ - - if request.get_method() == "GET" { - match request.get_path().as_str() { - "/" => index(request), - _ => path_not_found(), - } - } - else if request.get_method() == "POST" { - match request.get_path().as_str() { - "/" => index(request), - _ => path_not_found(), - } + match request.get_method() { + Method::GET(data) => { + match data.path.as_str() { + "/" => index(request), + _ => path_not_found(), + } + }, + Method::POST(data) => { + match data.path.as_str() { + "/" => index(request), + _ => path_not_found(), + } + }, } - - } } pub mod methods { + use core::panic; use std::collections::HashMap; use std::fmt; use std::net::TcpStream; @@ -45,25 +46,42 @@ pub mod methods { BadRequest } + + #[derive(Debug, Clone)] + pub struct GETData { + pub path: String, + pub headers: HashMap + } + #[derive(Debug, Clone)] + pub struct POSTData { + pub path: String, + pub headers: HashMap, + pub body: Option + } + + #[derive(Debug, Clone)] + pub enum Method { + GET(GETData), + POST(POSTData) + } + #[derive(Debug)] pub struct HTTPRequest { stream: Option, - method: String, - path: String, + method: Method, http_version: String, - headers: HashMap, - body: Option } impl HTTPRequest { - pub fn new(stream: Option, method:String, path: String, http_version: String, headers: HashMap, body: Option) -> HTTPRequest { + pub fn new(stream: Option, method: String, path: String, http_version: String, headers: HashMap, body: Option) -> HTTPRequest { HTTPRequest { stream, - method, - path, - http_version, - headers, - body, + method: match method.as_str() { + "GET" => Method::GET(GETData {headers, path}), + "POST" => Method::POST(POSTData {headers, path, body}), + _ => panic!("Unavailable method") + }, + http_version } } @@ -71,12 +89,9 @@ pub mod methods { self.stream = Some(stream); } - pub fn get_method(&self) -> String { + pub fn get_method(&self) -> Method { self.method.clone() } - pub fn get_path(&self) -> String { - self.path.clone() - } pub fn response_json(&mut self, status: HTTPResponse) { let mut stream = self.stream.as_ref().unwrap(); From bbab872e752786eab0fafe735d63b23d3363eb19 Mon Sep 17 00:00:00 2001 From: dantee-e Date: Sat, 5 Apr 2025 17:07:40 +0200 Subject: [PATCH 13/40] html responses implemented --- src/node/resolve_requests.rs | 67 ++++++++++++++++++++++++++---------- src/node/static/error.html | 44 +++++++++++++++++++++++ src/node/static/success.html | 44 +++++++++++++++++++++++ 3 files changed, 136 insertions(+), 19 deletions(-) create mode 100644 src/node/static/error.html create mode 100644 src/node/static/success.html diff --git a/src/node/resolve_requests.rs b/src/node/resolve_requests.rs index d130dc6..be38e93 100644 --- a/src/node/resolve_requests.rs +++ b/src/node/resolve_requests.rs @@ -9,7 +9,8 @@ pub mod endpoints { } fn index(mut request: HTTPRequest){ - request.response_json(HTTPResponse::OK); + + request.response(HTTPResponse::OK); } @@ -35,7 +36,7 @@ pub mod endpoints { pub mod methods { use core::panic; use std::collections::HashMap; - use std::fmt; + use std::{fmt, fs}; use std::net::TcpStream; use std::io::prelude::*; use serde_json::json; @@ -49,13 +50,11 @@ pub mod methods { #[derive(Debug, Clone)] pub struct GETData { - pub path: String, - pub headers: HashMap + pub path: String } #[derive(Debug, Clone)] pub struct POSTData { pub path: String, - pub headers: HashMap, pub body: Option } @@ -65,9 +64,12 @@ pub mod methods { POST(POSTData) } + + #[derive(Debug)] pub struct HTTPRequest { stream: Option, + pub headers: HashMap, method: Method, http_version: String, } @@ -77,11 +79,13 @@ pub mod methods { HTTPRequest { stream, method: match method.as_str() { - "GET" => Method::GET(GETData {headers, path}), - "POST" => Method::POST(POSTData {headers, path, body}), + "GET" => Method::GET(GETData {path}), + "POST" => Method::POST(POSTData {path, body}), _ => panic!("Unavailable method") }, - http_version + http_version, + headers + } } @@ -93,29 +97,54 @@ pub mod methods { self.method.clone() } - pub fn response_json(&mut self, status: HTTPResponse) { + + + pub fn response(&mut self, status: HTTPResponse) { + enum ResponseType { + JSON, + HTML, + PlainText + } + let mut stream = self.stream.as_ref().unwrap(); let (msg, status_code) = match status { HTTPResponse::OK => ("The request was successful".to_owned(), 200), HTTPResponse::InvalidMethod => ("Invalid HTTP method".to_owned(), 405), HTTPResponse::BadRequest => ("Bad Request".to_owned(), 400) }; + + let response: String; + + let (response_type, response_type_str) = if let Some(value) = self.headers.get("Accept") { + if value.contains("text/html") {(ResponseType::HTML, "text/html")} + else if value.contains("pat") {(ResponseType::JSON, "application/json")} + else {(ResponseType::PlainText, "text")} + } else {(ResponseType::PlainText, "text")}; + + response = match response_type { + ResponseType::JSON => { + serde_json::to_string(&json!({"msg": msg,"status_code": status_code})) + .expect("Couldn't convert the object to json") + }, + ResponseType::HTML => { + fs::read_to_string("src/node/static/success.html") + .expect("Couldn't read the file") + }, + ResponseType::PlainText => { + msg + }, + }; + - let response = json!({ - "msg": msg, - "status_code": status_code - }); - - let success_json = serde_json::to_string(&response) - .expect("Couldn't convert the object to json"); let response = format!( "HTTP/1.1 200 OK\r\n\ - Content-Type: application/json\r\n\ + Content-Type: {}\r\n\ Content-Length: {}\r\n {}", - success_json.len(), - success_json + response_type_str, + response.len(), + response ); stream.write_all(response.as_bytes()).unwrap(); diff --git a/src/node/static/error.html b/src/node/static/error.html new file mode 100644 index 0000000..06349d2 --- /dev/null +++ b/src/node/static/error.html @@ -0,0 +1,44 @@ + + + + + Error + + + +
+

Oops! Something went wrong.

+

An unexpected error has occurred. Please try again later.

+ Go back to homepage +
+ + \ No newline at end of file diff --git a/src/node/static/success.html b/src/node/static/success.html new file mode 100644 index 0000000..130f226 --- /dev/null +++ b/src/node/static/success.html @@ -0,0 +1,44 @@ + + + + + Success + + + +
+

You are successfully connected.

+

The node is operating correctly

+ Go back to homepage +
+ + \ No newline at end of file From 36595a0ec304211117accb6310b7c6adbbb54747 Mon Sep 17 00:00:00 2001 From: dantee-e Date: Tue, 8 Apr 2025 20:27:51 +0200 Subject: [PATCH 14/40] changed rsa to openssl crate, tests are working --- .idea/.gitignore | 8 + .idea/CleytoCoin.iml | 12 + .idea/inspectionProfiles/Project_Default.xml | 6 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + Cargo.lock | 446 ++++++++++++------- Cargo.toml | 13 +- src/chain/transaction.rs | 120 ++++- src/chain/wallet.rs | 102 +++-- src/node/resolve_requests.rs | 22 +- src/node/static/200.html | 26 ++ src/node/static/400.html | 26 ++ src/node/static/404.html | 26 ++ src/node/static/405.html | 26 ++ src/node/static/error.html | 44 -- src/node/static/success.html | 44 -- src/node/thread_pool.rs | 18 - tests/create_block_and_add_chain.rs | 2 +- tests/create_transaction.rs | 4 +- tests/requests.rs | 67 ++- tests/sign_and_verify_transactioninfo.rs | 2 +- 21 files changed, 696 insertions(+), 332 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/CleytoCoin.iml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 src/node/static/200.html create mode 100644 src/node/static/400.html create mode 100644 src/node/static/404.html create mode 100644 src/node/static/405.html delete mode 100644 src/node/static/error.html delete mode 100644 src/node/static/success.html diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/CleytoCoin.iml b/.idea/CleytoCoin.iml new file mode 100644 index 0000000..bbe0a70 --- /dev/null +++ b/.idea/CleytoCoin.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..03d9549 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..f0100af --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index ba72b15..8f26d28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -32,6 +32,12 @@ dependencies = [ "libc", ] +[[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.4.0" @@ -55,9 +61,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.21.7" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" @@ -65,12 +71,6 @@ version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.9.0" @@ -129,6 +129,7 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", "windows-link", ] @@ -141,13 +142,15 @@ dependencies = [ "hex", "num_cpus", "once_cell", - "rand", - "rayon", + "openssl", + "rand 0.9.0", "reqwest", "rsa", "serde", + "serde_bytes", "serde_json", "sha2", + "smallvec", "tiny-bip39", ] @@ -182,31 +185,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crossbeam-deque" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - [[package]] name = "crypto-common" version = "0.1.6" @@ -251,12 +229,6 @@ dependencies = [ "syn", ] -[[package]] -name = "either" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" - [[package]] name = "encoding_rs" version = "0.8.35" @@ -325,6 +297,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -359,6 +332,7 @@ checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", "futures-io", + "futures-sink", "futures-task", "memchr", "pin-project-lite", @@ -407,15 +381,15 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "h2" -version = "0.3.26" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2" dependencies = [ + "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "futures-util", "http", "indexmap", "slab", @@ -453,9 +427,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.12" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", "fnv", @@ -464,12 +438,24 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.6" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +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", ] @@ -479,47 +465,77 @@ 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 = "0.14.32" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" dependencies = [ "bytes", "futures-channel", - "futures-core", "futures-util", "h2", "http", "http-body", "httparse", - "httpdate", "itoa", "pin-project-lite", - "socket2", + "smallvec", "tokio", - "tower-service", - "tracing", "want", ] +[[package]] +name = "hyper-rustls" +version = "0.27.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + [[package]] name = "hyper-tls" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", + "http-body-util", "hyper", + "hyper-util", "native-tls", "tokio", "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "libc", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", ] [[package]] @@ -816,7 +832,8 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand", + "rand 0.8.5", + "serde", "smallvec", "zeroize", ] @@ -882,7 +899,7 @@ version = "0.10.71" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd" dependencies = [ - "bitflags 2.9.0", + "bitflags", "cfg-if", "foreign-types", "libc", @@ -1024,8 +1041,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", + "zerocopy", ] [[package]] @@ -1035,54 +1063,57 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", ] [[package]] -name = "rand_core" -version = "0.6.4" +name = "rand_chacha" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ - "getrandom 0.2.15", + "ppv-lite86", + "rand_core 0.9.3", ] [[package]] -name = "rayon" -version = "1.10.0" +name = "rand_core" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "either", - "rayon-core", + "getrandom 0.2.15", ] [[package]] -name = "rayon-core" -version = "1.12.1" +name = "rand_core" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "crossbeam-deque", - "crossbeam-utils", + "getrandom 0.3.2", ] [[package]] name = "reqwest" -version = "0.11.27" +version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" dependencies = [ "base64", "bytes", "encoding_rs", + "futures-channel", "futures-core", "futures-util", "h2", "http", "http-body", + "http-body-util", "hyper", + "hyper-rustls", "hyper-tls", + "hyper-util", "ipnet", "js-sys", "log", @@ -1099,12 +1130,27 @@ dependencies = [ "system-configuration", "tokio", "tokio-native-tls", + "tower", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "winreg", + "windows-registry", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.15", + "libc", + "untrusted", + "windows-sys 0.52.0", ] [[package]] @@ -1120,7 +1166,8 @@ dependencies = [ "num-traits", "pkcs1", "pkcs8", - "rand_core", + "rand_core 0.6.4", + "serde", "sha2", "signature", "spki", @@ -1146,20 +1193,50 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" dependencies = [ - "bitflags 2.9.0", + "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys 0.59.0", ] +[[package]] +name = "rustls" +version = "0.23.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + [[package]] name = "rustls-pemfile" -version = "1.0.4" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "base64", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" + +[[package]] +name = "rustls-webpki" +version = "0.103.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", ] [[package]] @@ -1189,7 +1266,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.9.0", + "bitflags", "core-foundation", "core-foundation-sys", "libc", @@ -1215,6 +1292,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_bytes" +version = "0.11.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8437fd221bde2d4ca316d61b90e337e9e702b3820b87d63caa9ba6c02bd06d96" +dependencies = [ + "serde", +] + [[package]] name = "serde_derive" version = "1.0.219" @@ -1274,7 +1360,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -1291,6 +1377,9 @@ name = "smallvec" version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" +dependencies = [ + "serde", +] [[package]] name = "socket2" @@ -1343,9 +1432,12 @@ dependencies = [ [[package]] name = "sync_wrapper" -version = "0.1.2" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] [[package]] name = "synstructure" @@ -1360,20 +1452,20 @@ dependencies = [ [[package]] name = "system-configuration" -version = "0.5.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 1.3.2", + "bitflags", "core-foundation", "system-configuration-sys", ] [[package]] name = "system-configuration-sys" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" dependencies = [ "core-foundation-sys", "libc", @@ -1420,7 +1512,7 @@ checksum = "a30fd743a02bf35236f6faf99adb03089bb77e91c998dac2c2ad76bb424f668c" dependencies = [ "once_cell", "pbkdf2", - "rand", + "rand 0.8.5", "rustc-hash", "sha2", "thiserror", @@ -1479,6 +1571,16 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +dependencies = [ + "rustls", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.14" @@ -1492,6 +1594,27 @@ dependencies = [ "tokio", ] +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + [[package]] name = "tower-service" version = "0.3.3" @@ -1544,6 +1667,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.4" @@ -1700,12 +1829,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" [[package]] -name = "windows-sys" -version = "0.48.0" +name = "windows-registry" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.53.0", +] + +[[package]] +name = "windows-result" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "06374efe858fab7e4f881500e6e86ec8bc28f9462c47e5a9941a0142ad86b189" dependencies = [ - "windows-targets 0.48.5", + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +dependencies = [ + "windows-link", ] [[package]] @@ -1726,21 +1875,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -1750,7 +1884,7 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", @@ -1758,10 +1892,20 @@ dependencies = [ ] [[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" +name = "windows-targets" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] [[package]] name = "windows_aarch64_gnullvm" @@ -1770,10 +1914,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" +name = "windows_aarch64_gnullvm" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" [[package]] name = "windows_aarch64_msvc" @@ -1782,10 +1926,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] -name = "windows_i686_gnu" -version = "0.48.5" +name = "windows_aarch64_msvc" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" [[package]] name = "windows_i686_gnu" @@ -1793,6 +1937,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" @@ -1800,10 +1950,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] -name = "windows_i686_msvc" -version = "0.48.5" +name = "windows_i686_gnullvm" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" [[package]] name = "windows_i686_msvc" @@ -1812,10 +1962,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" +name = "windows_i686_msvc" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" [[package]] name = "windows_x86_64_gnu" @@ -1824,10 +1974,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" +name = "windows_x86_64_gnu" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" [[package]] name = "windows_x86_64_gnullvm" @@ -1836,10 +1986,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" +name = "windows_x86_64_gnullvm" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" [[package]] name = "windows_x86_64_msvc" @@ -1848,14 +1998,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "winreg" -version = "0.50.0" +name = "windows_x86_64_msvc" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "wit-bindgen-rt" @@ -1863,7 +2009,7 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.0", + "bitflags", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index dbb2ee6..8447224 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,14 +5,17 @@ edition = "2021" [dependencies] sha2 = "0.10" -rsa = {version = "0.9.8", features = ["sha2"]} -rand = "0.8.0" +rsa = {version = "0.9.8", features = ["sha2", "serde"]} +rand = "0.9.0" hex = "0.4.3" tiny-bip39 = "2.0.0" -chrono = "0.4.40" +chrono = { version="0.4.40", features = ["serde"] } serde = { version="1.0.219", features = ["derive"] } serde_json = "1.0.140" -reqwest = { version = "0.11", features = ["blocking", "json"] } -rayon = "1.10.0" +reqwest = { version = "0.12.15", features = ["blocking", "json"] } num_cpus = "1.16.0" once_cell = "1.21.3" +serde_bytes = "0.11.17" +smallvec = { version="1.14.0", features = ["serde"] } +openssl = "0.10.71" + diff --git a/src/chain/transaction.rs b/src/chain/transaction.rs index c910433..066d7b5 100644 --- a/src/chain/transaction.rs +++ b/src/chain/transaction.rs @@ -1,15 +1,20 @@ -//use std::fmt; - +use core::panic; +use std::fmt; +use std::fmt::Debug; use super::wallet::Wallet; -use rsa::pkcs1v15::Signature; +use openssl::error::{Error, ErrorStack}; +use openssl::sign::{Signer, Verifier}; +use openssl::rsa::Rsa; +use openssl::pkey::PKey; +use openssl::hash::MessageDigest; use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; -#[derive(Clone)] -#[derive(Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] // ---------------------------------------------- TransactionInfo definition ---------------------------------------- pub struct TransactionInfo { - value: f32, - date: DateTime + pub value: f32, + pub date: DateTime } impl TransactionInfo { @@ -30,27 +35,83 @@ impl TransactionInfo { } // ----------------------------------------------------------------------------------------------------------------- +// --------------------------------------- Transaction Serialization Utils ----------------------------------------- + +// TODO move this to a errors file +#[derive(Debug)] +pub enum TransactionDeserializeError { + InvalidSignature, + InsufficientFunds, + MalformedTransaction, + OpenSSLError(ErrorStack), + SerdeError(serde_json::Error), +} +impl fmt::Display for TransactionDeserializeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + TransactionDeserializeError::InvalidSignature => write!(f, "Invalid signature"), + TransactionDeserializeError::InsufficientFunds => write!(f, "Insufficient funds"), + TransactionDeserializeError::MalformedTransaction => write!(f, "Malformed transaction"), + TransactionDeserializeError::OpenSSLError(e) => write!(f, "OpenSSL Error"), + TransactionDeserializeError::SerdeError(value) => write!(f, "{}", value), + } + } +} +impl std::error::Error for TransactionDeserializeError {} + +/* +mod signature_def { + use rsa::BigUint; + use serde::{Deserialize, Serialize}; + use serde_bytes; + use smallvec::SmallVec; + + type BigDigit = u64; + const VEC_SIZE: usize = 4; + + #[derive(Serialize, Deserialize)] + #[serde(remote = "BigUint")] + struct BigUintDef { + data: SmallVec<[BigDigit; VEC_SIZE]>, + } + + #[derive(Serialize, Deserialize)] + #[serde(remote = "rsa::pkcs1v15::Signature")] + pub struct SignatureDef { + inner: BigUint, + len: usize, + } +} */ + + +// ----------------------------------------------------------------------------------------------------------------- + // ---------------------------------------------- Transaction definition ------------------------------------------- -#[derive(Clone)] + +#[derive(Clone, Serialize, Deserialize)] pub struct Transaction { pub sender: Wallet, pub receiver: Wallet, - pub signature: Signature, + // #[serde(with = "signature_def::SignatureDef")] + pub signature: Vec, pub transaction_info: TransactionInfo, } impl Transaction { - pub fn new(sender: Wallet, receiver: Wallet, transaction_info: TransactionInfo, signature: Signature) -> Self { + pub fn new(sender: Wallet, receiver: Wallet, transaction_info: TransactionInfo, signature: Vec) -> Result { - let verify_signature = sender.verify_transaction_info(&transaction_info, &signature); + let verify_signature = match sender.verify_transaction_info(&transaction_info, &signature) { + Ok(value) => value, + Err(e) => return Err(e), + }; if verify_signature { - Self { + Ok(Self { sender, receiver, signature, transaction_info - } + }) } else { panic!("Signature couldn't be verified"); } @@ -58,13 +119,38 @@ impl Transaction { pub fn to_string(&self) -> String { format!( - "SENDER::{}::RECEIVER::{}::{}::SIGNATURE::{}", - self.sender.to_string(), - self.receiver.to_string(), + "SENDER::{:?}::RECEIVER::{:?}::{}::SIGNATURE::{:?}", + self.sender, + self.receiver.to_vec(), self.transaction_info.to_string(), - self.signature.to_string() + self.signature ) } + + pub fn serialize(&self) -> String { + let value = serde_json::to_string(self).unwrap(); + println!("serialized transaction: {value}"); + value + } + pub fn deserialize(json: String) -> Result { + let tx: Transaction = match serde_json::from_str(&json) { + Ok(tx) => tx, + Err(e) => return Err(TransactionDeserializeError::SerdeError(e)) + }; + + if tx.transaction_info.value <= 0.0 { + return Err(TransactionDeserializeError::InsufficientFunds); + } + let verified = match tx.sender.verify_transaction_info(&tx.transaction_info, &tx.signature) { + Ok(value) => value, + Err(e) => return Err(TransactionDeserializeError::OpenSSLError(e)), + }; + if !verified { + return Err(TransactionDeserializeError::InvalidSignature); + } + // TODO check if wallets exist + Ok(tx) + } } // ---------------------------------------------- UNIT TESTS ------------------------------------------------------- diff --git a/src/chain/wallet.rs b/src/chain/wallet.rs index 824ad0b..e35225e 100644 --- a/src/chain/wallet.rs +++ b/src/chain/wallet.rs @@ -1,69 +1,97 @@ -use rsa::{RsaPrivateKey, RsaPublicKey}; -use rsa::pkcs1v15::{SigningKey, VerifyingKey}; -use rsa::signature::{Keypair, RandomizedSigner, Verifier}; -use rsa::pkcs1::EncodeRsaPublicKey; -use rsa::sha2::Sha256; +use openssl::error::ErrorStack; +use serde::{Deserialize, Serialize}; use super::transaction::TransactionInfo; +use openssl::sign::{Signer, Verifier}; +use openssl::rsa::Rsa; +use openssl::pkey::{PKey, Private, Public}; +use openssl::hash::MessageDigest; + +fn test_sign() { + let rsa = Rsa::generate(2048).unwrap(); + let pkey = PKey::from_rsa(rsa).unwrap(); + + // Data to be signed (this would normally be provided by the signer) + let data = b"hello, world!"; + + // Step 2: Sign the data with the private key (this would be done by the sender) + let mut signer = Signer::new(MessageDigest::sha256(), &pkey).unwrap(); + signer.update(data).unwrap(); + let signature = signer.sign_to_vec().unwrap(); + + // --- Now, we are at the verification step --- + // Step 3: Extract the public key from the PKey and use it for verification + let public_key = pkey.public_key_to_pem().unwrap(); // Extract public key in PEM format + let rsa_public = Rsa::public_key_from_pem(&public_key).unwrap(); // Convert back to Rsa + let pkey_public = PKey::from_rsa(rsa_public).unwrap(); // Create a PKey for public key + + // Step 4: Verify the signature using the public key + let mut verifier = Verifier::new(MessageDigest::sha256(), &pkey_public).unwrap(); + verifier.update(data).unwrap(); + let is_valid = verifier.verify(&signature).unwrap(); + + // Step 5: Check if the signature is valid + if is_valid { + println!("Signature is valid!"); + } else { + println!("Signature is invalid."); + } +} // ---------------------------------------------- WalletPK definition ---------------------------------------------- #[derive(Debug)] pub struct WalletPK{ - #[allow(unused)] - private_key: RsaPrivateKey, - signing_key: SigningKey + private_key: PKey } impl WalletPK { - pub fn sign_transaction(&mut self, transaction_info: &TransactionInfo) -> Result{ - let signed_hashed_message = self.signing_key.sign_with_rng(&mut rand::thread_rng(), transaction_info.to_string().as_bytes()); - - Ok(signed_hashed_message) + pub fn sign_transaction(&mut self, transaction_info: &TransactionInfo) -> Result, ErrorStack>{ + let mut signer = Signer::new(MessageDigest::sha256(), &self.private_key)?; + signer.update(transaction_info.to_string().as_bytes())?; + Ok(signer.sign_to_vec()?) } } // ----------------------------------------------------------------------------------------------------------------- // ---------------------------------------------- Wallet definition ------------------------------------------------ -#[derive(Clone)] -#[derive(Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct Wallet{ - public_key: RsaPublicKey, - verifying_key: VerifyingKey + public_key: Vec } impl Wallet { pub fn new() -> (Self, WalletPK) { - let bits: usize = 2048; - let private_key: RsaPrivateKey = RsaPrivateKey::new(&mut rand::thread_rng(), bits).expect("failed to generate a key"); - let public_key: RsaPublicKey = RsaPublicKey::from(&private_key); - let signing_key: SigningKey = SigningKey::::new(private_key.clone()); - let verifying_key = signing_key.verifying_key(); - + let bits: u32 = 2048; + let rsa = Rsa::generate(bits).unwrap(); + let private_key = PKey::from_rsa(rsa).unwrap(); + + let public_key = private_key.public_key_to_pem().expect("Error extracting public key from private key"); + ( Wallet{ - public_key, - verifying_key + public_key }, WalletPK{ - private_key, - signing_key, } + private_key + } ) } - pub fn verify_transaction_info(&self, data: &TransactionInfo, signature: &rsa::pkcs1v15::Signature) -> bool { - let verified = self.verifying_key.verify(data.to_string().as_bytes(), &signature); - match verified { - Ok(()) => true, - Err(_) => false, - } - } + pub fn verify_transaction_info(&self, transaction_info: &TransactionInfo, signature: &[u8]) -> Result { - pub fn to_string(&self) -> String { - self.public_key.to_pkcs1_pem(rsa::pkcs1::LineEnding::LF).unwrap().to_string() + let public_key = self.to_pkey(); + let mut verifier = Verifier::new(MessageDigest::sha256(), &public_key)?; + verifier.update(transaction_info.to_string().as_bytes())?; + Ok(verifier.verify(&signature)?) } #[allow(unused)] - pub fn get_public_key(&self) -> RsaPublicKey { + pub fn to_pkey(&self) -> PKey { + let rsa_public = Rsa::public_key_from_pem(&self.public_key).expect("Error extracting Rsa object from public key"); + PKey::from_rsa(rsa_public).expect("Error extracting PKey object from Rsa object") + } + + pub fn to_vec(&self) -> Vec { self.public_key.clone() } } @@ -77,7 +105,7 @@ mod tests { #[test] //mark a function as a test. fn test_wallet_creation() { let (wallet, wallet_pk) = Wallet::new(); - println!("wallet.to_string: {}", wallet.to_string()); + println!("wallet.to_string: {:?}", wallet.to_vec()); println!("{:#?}", wallet); println!("{:#?}", wallet_pk); } diff --git a/src/node/resolve_requests.rs b/src/node/resolve_requests.rs index be38e93..e72eb81 100644 --- a/src/node/resolve_requests.rs +++ b/src/node/resolve_requests.rs @@ -1,8 +1,8 @@ pub mod endpoints { - use super::methods::{HTTPRequest, HTTPResponse, Method}; - + use crate::chain::transaction::Transaction; + use super::methods::{HTTPRequest, HTTPResponse, Method}; fn path_not_found(){ @@ -13,6 +13,16 @@ pub mod endpoints { request.response(HTTPResponse::OK); } + fn submit_transaction(mut request: HTTPRequest){ + match request.get_method() { + Method::GET(_) => request.response(HTTPResponse::InvalidMethod), + Method::POST(data) => { + let json: Transaction = serde_json::from_str(&data.body.unwrap()).unwrap(); + println!("{}", json.to_string()); + } + } + } + pub fn resolve_endpoint(request: HTTPRequest){ @@ -127,8 +137,12 @@ pub mod methods { .expect("Couldn't convert the object to json") }, ResponseType::HTML => { - fs::read_to_string("src/node/static/success.html") - .expect("Couldn't read the file") + match status { + HTTPResponse::OK => fs::read_to_string("src/node/static/200.html").expect("Couldn't read the file"), + HTTPResponse::InvalidMethod => fs::read_to_string("src/node/static/405.html").expect("Couldn't read the file"), + HTTPResponse::BadRequest => fs::read_to_string("src/node/static/400.html").expect("Couldn't read the file"), + } + }, ResponseType::PlainText => { msg diff --git a/src/node/static/200.html b/src/node/static/200.html new file mode 100644 index 0000000..3382bd5 --- /dev/null +++ b/src/node/static/200.html @@ -0,0 +1,26 @@ + + + + + Success + + + +

Success!

+

Your request was completed successfully.

+ + diff --git a/src/node/static/400.html b/src/node/static/400.html new file mode 100644 index 0000000..cc7e866 --- /dev/null +++ b/src/node/static/400.html @@ -0,0 +1,26 @@ + + + + + 400 Bad Request + + + +

400 - Bad Request

+

The server couldn't understand your request. Please check the syntax and try again.

+ + diff --git a/src/node/static/404.html b/src/node/static/404.html new file mode 100644 index 0000000..956761d --- /dev/null +++ b/src/node/static/404.html @@ -0,0 +1,26 @@ + + + + + 404 Not Found + + + +

404 - Page Not Found

+

The page you're looking for doesn't exist or has been moved.

+ + diff --git a/src/node/static/405.html b/src/node/static/405.html new file mode 100644 index 0000000..e4348b5 --- /dev/null +++ b/src/node/static/405.html @@ -0,0 +1,26 @@ + + + + + 405 Method Not Allowed + + + +

405 - Method Not Allowed

+

The method used in the request is not supported for this resource.

+ + diff --git a/src/node/static/error.html b/src/node/static/error.html deleted file mode 100644 index 06349d2..0000000 --- a/src/node/static/error.html +++ /dev/null @@ -1,44 +0,0 @@ - - - - - Error - - - -
-

Oops! Something went wrong.

-

An unexpected error has occurred. Please try again later.

- Go back to homepage -
- - \ No newline at end of file diff --git a/src/node/static/success.html b/src/node/static/success.html deleted file mode 100644 index 130f226..0000000 --- a/src/node/static/success.html +++ /dev/null @@ -1,44 +0,0 @@ - - - - - Success - - - -
-

You are successfully connected.

-

The node is operating correctly

- Go back to homepage -
- - \ No newline at end of file diff --git a/src/node/thread_pool.rs b/src/node/thread_pool.rs index 3aa9a05..d7590de 100644 --- a/src/node/thread_pool.rs +++ b/src/node/thread_pool.rs @@ -1,22 +1,4 @@ - - - - - - - - - - - - - - - - - - pub mod custom_thread_pool { use std::{fmt, sync::{mpsc, Arc, Mutex}, thread}; diff --git a/tests/create_block_and_add_chain.rs b/tests/create_block_and_add_chain.rs index 9cebab5..ecfcc92 100644 --- a/tests/create_block_and_add_chain.rs +++ b/tests/create_block_and_add_chain.rs @@ -13,7 +13,7 @@ fn create_block_and_add_chain() { Err(e) => panic!("Error creating signed message: {e}"), }; - let new_transaction = Transaction::new(wallet1, wallet2, transaction_info, signature); + let new_transaction = Transaction::new(wallet1, wallet2, transaction_info, signature).unwrap(); let mut chain = Chain::new(); diff --git a/tests/create_transaction.rs b/tests/create_transaction.rs index dd88814..6933924 100644 --- a/tests/create_transaction.rs +++ b/tests/create_transaction.rs @@ -15,13 +15,13 @@ fn create_transaction() { println!("Transaction signature (signed using the wallet_pk):\n{:?}", signature); // this will also be verified by the Transaction::new(); - if wallet_sender.verify_transaction_info(&transactioninfo, &signature) == true { + if wallet_sender.verify_transaction_info(&transactioninfo, &signature).unwrap() == true { println!("transaction verified (by the wallet)"); } else { println!("transaction not verified"); } - let transaction: Transaction = Transaction::new(wallet_sender, wallet_receiver, transactioninfo, signature); + let transaction: Transaction = Transaction::new(wallet_sender, wallet_receiver, transactioninfo, signature).unwrap(); println!("transaction.to_string(): {}", transaction.to_string()); } diff --git a/tests/requests.rs b/tests/requests.rs index aa6de3d..8e445d7 100644 --- a/tests/requests.rs +++ b/tests/requests.rs @@ -12,12 +12,11 @@ struct RandomData { value: i32, } -#[test] fn post_request() -> Result<(), Box> { - let mut rng = rand::thread_rng(); + let mut rng = rand::rng(); let data = RandomData { - message: (0..10).map(|_| rng.sample(rand::distributions::Alphanumeric) as char).collect(), - value: rng.gen_range(1..=1000), + message: (0..10).map(|_| rng.sample(rand::distr::Alphanumeric) as char).collect(), + value: rng.random_range(1..=1000), }; let client = Client::new(); @@ -31,7 +30,6 @@ fn post_request() -> Result<(), Box> { Ok(()) } -#[test] fn get_request() -> Result<(), Box> { let client = Client::new(); @@ -45,7 +43,6 @@ fn get_request() -> Result<(), Box> { Ok(()) } -#[test] fn post_and_get_request(){ /* let (tx, rx) = mpsc::channel::<()>(); @@ -76,8 +73,7 @@ fn post_and_get_request(){ server.join().expect("Server thread panicked."); */ } -#[test] -fn thread_posts(){ +fn thread_post(){ let url = "http://localhost:9473/"; // Replace with your server URL let client = Client::new(); @@ -107,6 +103,59 @@ fn thread_posts(){ for handle in handles { let _ = handle.join(); } +} - println!("All 200 threads finished."); +fn thread_get(){ + let url = "http://localhost:9473/"; // Replace with your server URL + let client = Client::new(); + + let mut handles = vec![]; + + for i in 0..10000 { + let client = client.clone(); + let url = url.to_string(); + + let handle = thread::spawn(move || { + let body = format!(r#"{{"message": "Hello #{}"}}"#, i); + + match client.get(&url) + .header("Content-Type", "application/json") + .send() + { + + Ok(resp) => {assert_eq!(resp.status(), 200);println!("Thread #{i}: {}", resp.status())}, + Err(err) => eprintln!("Thread #{i} failed: {err}"), + } + }); + + handles.push(handle); + } + + for handle in handles { + let _ = handle.join(); + } +} + +#[test] +fn main(){ + let (tx, rx) = mpsc::channel::<()>(); + + // Channel to kill thread + let rx = Arc::new(Mutex::new(rx)); + + // Run server thread + let server = thread::spawn(move || { + let rx = Arc::clone(&rx); + node::Node::run(true, rx, 0); + }); + + + thread_post(); + thread_get(); + + + tx.send(()).expect("Failed to send termination signal."); + + // Wait for the server thread to finish (this will block until the server thread terminates) + server.join().expect("Server thread panicked."); } \ No newline at end of file diff --git a/tests/sign_and_verify_transactioninfo.rs b/tests/sign_and_verify_transactioninfo.rs index 016f08f..13410f6 100644 --- a/tests/sign_and_verify_transactioninfo.rs +++ b/tests/sign_and_verify_transactioninfo.rs @@ -12,7 +12,7 @@ fn sign_and_verify_transactioninfo() { }; println!("Transaction signature (signed using the wallet_pk):\n{:?}", signature); - if wallet.verify_transaction_info(&transactioninfo, &signature) == true { + if wallet.verify_transaction_info(&transactioninfo, &signature).unwrap() == true { println!("transaction verified (by the wallet)"); } else { println!("transaction not verified"); From 1955bc0e945404627225877371cb9f46834e3478 Mon Sep 17 00:00:00 2001 From: dantee-e Date: Tue, 8 Apr 2025 20:41:50 +0200 Subject: [PATCH 15/40] added test to check serialization of transactions --- tests/sign_and_verify_transactioninfo.rs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/tests/sign_and_verify_transactioninfo.rs b/tests/sign_and_verify_transactioninfo.rs index 13410f6..bae4307 100644 --- a/tests/sign_and_verify_transactioninfo.rs +++ b/tests/sign_and_verify_transactioninfo.rs @@ -1,4 +1,4 @@ -use cleyto_coin::chain::{transaction::TransactionInfo, wallet::Wallet}; +use cleyto_coin::chain::{transaction::{Transaction, TransactionInfo}, wallet::Wallet}; use chrono::Utc; #[test] @@ -18,3 +18,25 @@ fn sign_and_verify_transactioninfo() { println!("transaction not verified"); } } + +#[test] +fn serialize_and_deserialize_transaction() { + let (wallet, mut wallet_pk) = Wallet::new(); + let (mallet, _) = Wallet::new(); + let transactioninfo: TransactionInfo = TransactionInfo::new(12345 as f32, Utc::now()); + + let signature = match wallet_pk.sign_transaction(&transactioninfo) { + Ok(signed_hashed_message) => signed_hashed_message, + _ => panic!("error while signing transaction"), + }; + + let transaction = Transaction::new(wallet, mallet, transactioninfo, signature).unwrap(); + + let serialized_transaction = transaction.serialize(); + println!("serialized_transaction: \n{serialized_transaction}"); + + let deserialized_transaction = match Transaction::deserialize(serialized_transaction) { + Ok(value) => {println!("Success deserializing transaction"); value}, + Err(_) => panic!("Error deserializing transaction"), + }; +} \ No newline at end of file From fef2d8f87d4487822d1a65ae31caf2b3500babd9 Mon Sep 17 00:00:00 2001 From: dantee-e Date: Thu, 10 Apr 2025 11:11:02 +0200 Subject: [PATCH 16/40] last commit in this pc --- src/chain/wallet.rs | 6 ++---- src/node.rs | 11 +++++++---- src/node/config | 2 ++ 3 files changed, 11 insertions(+), 8 deletions(-) create mode 100644 src/node/config diff --git a/src/chain/wallet.rs b/src/chain/wallet.rs index e35225e..5bbef9a 100644 --- a/src/chain/wallet.rs +++ b/src/chain/wallet.rs @@ -78,7 +78,6 @@ impl Wallet { } pub fn verify_transaction_info(&self, transaction_info: &TransactionInfo, signature: &[u8]) -> Result { - let public_key = self.to_pkey(); let mut verifier = Verifier::new(MessageDigest::sha256(), &public_key)?; verifier.update(transaction_info.to_string().as_bytes())?; @@ -91,9 +90,8 @@ impl Wallet { PKey::from_rsa(rsa_public).expect("Error extracting PKey object from Rsa object") } - pub fn to_vec(&self) -> Vec { - self.public_key.clone() - } + pub fn to_vec(&self) -> Vec { self.public_key.clone() } + } // ----------------------------------------------------------------------------------------------------------------- diff --git a/src/node.rs b/src/node.rs index 0e3d611..8f2e8c2 100644 --- a/src/node.rs +++ b/src/node.rs @@ -2,17 +2,16 @@ mod utils; mod resolve_requests; mod thread_pool; + use core::panic; use std::time::Duration; use resolve_requests::{ methods::{return_json, HTTPParseError, HTTPRequest, HTTPResponse}, endpoints::resolve_endpoint }; -use crate::chain::transaction::Transaction; +use crate::chain::{transaction::Transaction, Chain}; use thread_pool::custom_thread_pool::ThreadPool; - - use std::{ collections::HashMap, io::{prelude::*, BufReader}, net::{TcpListener, TcpStream}, sync::{mpsc::Receiver, Arc, Mutex}, thread }; @@ -22,10 +21,13 @@ use std::{ pub struct Node { + chain: Chain, transactions_list: Vec } use once_cell::sync::Lazy; + + static NUMBER_OF_THREADS_IN_THREAD_POOL: Lazy = Lazy::new(num_cpus::get); impl Node { @@ -34,10 +36,11 @@ impl Node { pub const DEFAULT_PORT: u16 = 9473; pub const REFRESH_RATE_SERVER_IN_MS: u64 = 50; - pub fn new(port: u16) -> Node { + pub fn new(chain: Chain) -> Node { num_cpus::get(); Node { + chain, transactions_list: Vec::new() } } diff --git a/src/node/config b/src/node/config new file mode 100644 index 0000000..f602c2c --- /dev/null +++ b/src/node/config @@ -0,0 +1,2 @@ +RESOURCES_FOLDER="./resources" +CHAIN_FILE="chain.bin" \ No newline at end of file From 6da4234e6ef14132dc82ec8cc2d2424e11525656 Mon Sep 17 00:00:00 2001 From: dantee-e Date: Mon, 28 Apr 2025 13:03:51 +0200 Subject: [PATCH 17/40] gitignore add .idea --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ea8c4bf..6b39d31 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +.idea/ \ No newline at end of file From 1f14340c33e1adebb0dcc1ec3e58e8802f02dc86 Mon Sep 17 00:00:00 2001 From: dantee-e Date: Mon, 28 Apr 2025 15:43:50 +0200 Subject: [PATCH 18/40] restructuring and adding UI for node in terminal --- Cargo.lock | 780 +++++++++++++++++++++------------ Cargo.toml | 14 +- src/bin/node.rs | 31 ++ src/{chain.rs => chain/mod.rs} | 0 src/lib.rs | 1 + src/main.rs | 44 +- src/{node.rs => node/mod.rs} | 46 +- src/node/ui.rs | 88 ++++ 8 files changed, 660 insertions(+), 344 deletions(-) create mode 100644 src/bin/node.rs rename src/{chain.rs => chain/mod.rs} (100%) rename src/{node.rs => node/mod.rs} (94%) create mode 100644 src/node/ui.rs diff --git a/Cargo.lock b/Cargo.lock index 8f26d28..410c8fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,18 +4,24 @@ version = 4 [[package]] name = "addr2line" -version = "0.24.2" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ "gimli", ] [[package]] -name = "adler2" -version = "2.0.0" +name = "adler" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "android-tzdata" @@ -46,17 +52,17 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", + "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", - "windows-targets 0.52.6", ] [[package]] @@ -65,12 +71,6 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "base64ct" -version = "1.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" - [[package]] name = "bitflags" version = "2.9.0" @@ -92,18 +92,27 @@ version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - [[package]] name = "bytes" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + +[[package]] +name = "castaway" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" +dependencies = [ + "rustversion", +] + [[package]] name = "cc" version = "1.2.16" @@ -139,26 +148,69 @@ name = "cleyto_coin" version = "0.1.0" dependencies = [ "chrono", + "color-eyre", + "crossterm 0.29.0", "hex", "num_cpus", "once_cell", "openssl", - "rand 0.9.0", + "rand", + "ratatui", "reqwest", - "rsa", "serde", - "serde_bytes", "serde_json", "sha2", - "smallvec", - "tiny-bip39", ] [[package]] -name = "const-oid" -version = "0.9.6" +name = "color-eyre" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" +dependencies = [ + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", +] + +[[package]] +name = "compact_str" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "static_assertions", +] + +[[package]] +name = "convert_case" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7" +dependencies = [ + "unicode-segmentation", +] [[package]] name = "core-foundation" @@ -185,6 +237,49 @@ dependencies = [ "libc", ] +[[package]] +name = "crossterm" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +dependencies = [ + "bitflags", + "crossterm_winapi", + "mio", + "parking_lot", + "rustix 0.38.44", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" +dependencies = [ + "bitflags", + "crossterm_winapi", + "derive_more", + "document-features", + "mio", + "parking_lot", + "rustix 1.0.5", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -196,14 +291,59 @@ dependencies = [ ] [[package]] -name = "der" -version = "0.7.9" +name = "darling" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ - "const-oid", - "pem-rfc7468", - "zeroize", + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -213,9 +353,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", - "const-oid", "crypto-common", - "subtle", ] [[package]] @@ -229,6 +367,21 @@ dependencies = [ "syn", ] +[[package]] +name = "document-features" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" +dependencies = [ + "litrs", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "encoding_rs" version = "0.8.35" @@ -254,6 +407,16 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -266,6 +429,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "foreign-types" version = "0.3.2" @@ -375,9 +544,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.31.1" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "h2" @@ -403,6 +572,17 @@ name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" @@ -416,15 +596,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - [[package]] name = "http" version = "1.3.1" @@ -679,6 +850,12 @@ dependencies = [ "syn", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.0.3" @@ -700,6 +877,12 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + [[package]] name = "indexmap" version = "2.8.0" @@ -710,12 +893,40 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "indoc" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" + +[[package]] +name = "instability" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf9fed6d91cfb734e7476a06bde8300a1b94e217e1b523b6f0cd1a01998c71d" +dependencies = [ + "darling", + "indoc", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "ipnet" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.15" @@ -737,9 +948,6 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -dependencies = [ - "spin", -] [[package]] name = "libc" @@ -748,10 +956,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" [[package]] -name = "libm" -version = "0.2.11" +name = "linux-raw-sys" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" @@ -765,12 +973,37 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" +[[package]] +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown", +] + [[package]] name = "memchr" version = "2.7.4" @@ -785,11 +1018,11 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "miniz_oxide" -version = "0.8.7" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff70ce3e48ae43fa075863cef62e8b43b71a4f2382229920e0df362592919430" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ - "adler2", + "adler", ] [[package]] @@ -799,6 +1032,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", + "log", "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] @@ -820,44 +1054,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "num-bigint-dig" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" -dependencies = [ - "byteorder", - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand 0.8.5", - "serde", - "smallvec", - "zeroize", -] - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -865,7 +1061,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", - "libm", ] [[package]] @@ -880,9 +1075,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.7" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] @@ -938,24 +1133,40 @@ dependencies = [ ] [[package]] -name = "pbkdf2" -version = "0.12.2" +name = "owo-colors" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ - "digest", - "hmac", + "lock_api", + "parking_lot_core", ] [[package]] -name = "pem-rfc7468" -version = "0.7.0" +name = "parking_lot_core" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ - "base64ct", + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -974,27 +1185,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkcs1" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" -dependencies = [ - "der", - "pkcs8", - "spki", -] - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - [[package]] name = "pkg-config" version = "0.3.32" @@ -1034,64 +1224,64 @@ version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - [[package]] name = "rand" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.3", + "rand_chacha", + "rand_core", "zerocopy", ] [[package]] name = "rand_chacha" -version = "0.3.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.6.4", + "rand_core", ] [[package]] -name = "rand_chacha" -version = "0.9.0" +name = "rand_core" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "ppv-lite86", - "rand_core 0.9.3", + "getrandom 0.3.2", ] [[package]] -name = "rand_core" -version = "0.6.4" +name = "ratatui" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" dependencies = [ - "getrandom 0.2.15", + "bitflags", + "cassowary", + "compact_str", + "crossterm 0.28.1", + "indoc", + "instability", + "itertools", + "lru", + "paste", + "strum", + "unicode-segmentation", + "unicode-truncate", + "unicode-width 0.2.0", ] [[package]] -name = "rand_core" -version = "0.9.3" +name = "redox_syscall" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" dependencies = [ - "getrandom 0.3.2", + "bitflags", ] [[package]] @@ -1153,28 +1343,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rsa" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" -dependencies = [ - "const-oid", - "digest", - "num-bigint-dig", - "num-integer", - "num-traits", - "pkcs1", - "pkcs8", - "rand_core 0.6.4", - "serde", - "sha2", - "signature", - "spki", - "subtle", - "zeroize", -] - [[package]] name = "rustc-demangle" version = "0.1.24" @@ -1182,10 +1350,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] -name = "rustc-hash" -version = "1.1.0" +name = "rustix" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] [[package]] name = "rustix" @@ -1196,7 +1371,7 @@ dependencies = [ "bitflags", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.9.3", "windows-sys 0.59.0", ] @@ -1260,6 +1435,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "security-framework" version = "2.11.1" @@ -1292,15 +1473,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde_bytes" -version = "0.11.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8437fd221bde2d4ca316d61b90e337e9e702b3820b87d63caa9ba6c02bd06d96" -dependencies = [ - "serde", -] - [[package]] name = "serde_derive" version = "1.0.219" @@ -1347,6 +1519,15 @@ dependencies = [ "digest", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" @@ -1354,13 +1535,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] -name = "signature" -version = "2.2.0" +name = "signal-hook" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" dependencies = [ - "digest", - "rand_core 0.6.4", + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +dependencies = [ + "libc", ] [[package]] @@ -1377,9 +1578,6 @@ name = "smallvec" version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" -dependencies = [ - "serde", -] [[package]] name = "socket2" @@ -1392,26 +1590,44 @@ dependencies = [ ] [[package]] -name = "spin" -version = "0.9.8" +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] -name = "spki" -version = "0.7.3" +name = "strum" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" dependencies = [ - "base64ct", - "der", + "strum_macros", ] [[package]] -name = "stable_deref_trait" -version = "1.2.0" +name = "strum_macros" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] [[package]] name = "subtle" @@ -1480,45 +1696,18 @@ dependencies = [ "fastrand", "getrandom 0.3.2", "once_cell", - "rustix", + "rustix 1.0.5", "windows-sys 0.59.0", ] [[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" +name = "thread_local" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tiny-bip39" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a30fd743a02bf35236f6faf99adb03089bb77e91c998dac2c2ad76bb424f668c" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ + "cfg-if", "once_cell", - "pbkdf2", - "rand 0.8.5", - "rustc-hash", - "sha2", - "thiserror", - "unicode-normalization", - "wasm-bindgen", - "zeroize", ] [[package]] @@ -1531,21 +1720,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "tinyvec" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - [[package]] name = "tokio" version = "1.44.1" @@ -1638,6 +1812,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-error" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" +dependencies = [ + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "sharded-slab", + "thread_local", + "tracing-core", ] [[package]] @@ -1659,14 +1855,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] -name = "unicode-normalization" -version = "0.1.24" +name = "unicode-segmentation" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-truncate" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" dependencies = [ - "tinyvec", + "itertools", + "unicode-segmentation", + "unicode-width 0.1.14", ] +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + [[package]] name = "untrusted" version = "0.9.0" @@ -1696,6 +1912,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "vcpkg" version = "0.2.15" @@ -1813,6 +2035,28 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-core" version = "0.52.0" @@ -2094,20 +2338,6 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] [[package]] name = "zerovec" diff --git a/Cargo.toml b/Cargo.toml index 8447224..0d45dc9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,19 +3,25 @@ name = "cleyto_coin" version = "0.1.0" edition = "2021" +[[bin]] +name = "node" +test = false +bench = false + + + [dependencies] sha2 = "0.10" -rsa = {version = "0.9.8", features = ["sha2", "serde"]} rand = "0.9.0" hex = "0.4.3" -tiny-bip39 = "2.0.0" chrono = { version="0.4.40", features = ["serde"] } serde = { version="1.0.219", features = ["derive"] } serde_json = "1.0.140" reqwest = { version = "0.12.15", features = ["blocking", "json"] } num_cpus = "1.16.0" once_cell = "1.21.3" -serde_bytes = "0.11.17" -smallvec = { version="1.14.0", features = ["serde"] } openssl = "0.10.71" +crossterm = "0.29" +ratatui = "0.29.0" +color-eyre = "0.6.3" diff --git a/src/bin/node.rs b/src/bin/node.rs new file mode 100644 index 0000000..48479c6 --- /dev/null +++ b/src/bin/node.rs @@ -0,0 +1,31 @@ +use std::{sync::{mpsc, Arc, Mutex}, thread}; +use cleyto_coin::node::{self, ui::App}; + + + + +fn main() -> color_eyre::Result<()> { + let (tx, rx) = mpsc::channel::<()>(); + + // Channel to kill thread + let rx = Arc::new(Mutex::new(rx)); + + // Run server thread + let server = thread::spawn(move || { + let rx = Arc::clone(&rx); + node::Node::run(true, rx, 0); + + }); + + + color_eyre::install()?; + let terminal = ratatui::init(); + let result = App::new().run(terminal, node::Node::DEFAULT_PORT); + ratatui::restore(); + + // Quits server + tx.send(())?; + + server.join().unwrap(); + result +} \ No newline at end of file diff --git a/src/chain.rs b/src/chain/mod.rs similarity index 100% rename from src/chain.rs rename to src/chain/mod.rs diff --git a/src/lib.rs b/src/lib.rs index 2ad62a0..d0bbc29 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,3 @@ pub mod chain; pub mod node; + diff --git a/src/main.rs b/src/main.rs index 5996ff1..95106f5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,43 +1 @@ -pub mod chain; -pub mod node; - -use std::{sync::{mpsc, Arc, Mutex}, thread}; -fn main(){ - let (tx, rx) = mpsc::channel::<()>(); - - // Channel to kill thread - let rx = Arc::new(Mutex::new(rx)); - - // Run server thread - let server = thread::spawn(move || { - let rx = Arc::clone(&rx); - node::Node::run(true, rx, 0); - - }); - - - let mut input = String::new(); - - loop { - print!("command: "); - - std::io::stdin() - .read_line(&mut input) - .expect("Failed to read line"); - - input = input.trim().to_string(); - println!("{:?}", input); - - match input.as_str() { - "quit" => { - println!("quitting server"); - tx.send(()).unwrap(); - break; - }, - _ => println!("no command") - - } - } - // Waits for the node to stop running - server.join().unwrap(); -} \ No newline at end of file +fn main(){} \ No newline at end of file diff --git a/src/node.rs b/src/node/mod.rs similarity index 94% rename from src/node.rs rename to src/node/mod.rs index 8f2e8c2..395fff0 100644 --- a/src/node.rs +++ b/src/node/mod.rs @@ -1,7 +1,7 @@ mod utils; mod resolve_requests; mod thread_pool; - +pub mod ui; use core::panic; use std::time::Duration; @@ -31,14 +31,14 @@ use once_cell::sync::Lazy; static NUMBER_OF_THREADS_IN_THREAD_POOL: Lazy = Lazy::new(num_cpus::get); impl Node { - + // these configurations should be moved to a file pub const DEFAULT_PORT: u16 = 9473; pub const REFRESH_RATE_SERVER_IN_MS: u64 = 50; pub fn new(chain: Chain) -> Node { num_cpus::get(); - + Node { chain, transactions_list: Vec::new() @@ -56,7 +56,7 @@ impl Node { // reading status_line let status_line: String; - + match buf_reader.read_line(&mut line) { Ok(n) if (n > 0) => n, Ok(_) => return Err(HTTPParseError::InvalidStatusLine), @@ -68,7 +68,7 @@ impl Node { let mut tokens = status_line.split(' '); let (method, path, http_version) = ( tokens.next().ok_or(HTTPParseError::InvalidRequestLine)?.to_string(), - tokens.next().ok_or(HTTPParseError::InvalidRequestLine)?.to_string(), + tokens.next().ok_or(HTTPParseError::InvalidRequestLine)?.to_string(), tokens.next().ok_or(HTTPParseError::InvalidRequestLine)?.to_string() ); @@ -76,25 +76,25 @@ impl Node { loop { line.clear(); - if let Err(e) = buf_reader.read_line(&mut line) { + if let Err(_) = buf_reader.read_line(&mut line) { return Err(HTTPParseError::InvalidRequestLine); } let line = line.trim_end().to_string(); - - + + if line.is_empty() { break; } - + if let Some((key, value)) = line.split_once(":") { http_headers.insert(key.trim().to_string(), value.trim().to_string()); } else { return Err(HTTPParseError::InvalidRequestLine); }; - + } - + // If method is GET, return before trying to read the body if method == "GET" { return Ok(HTTPRequest::new(None, method, path, http_version, http_headers, None)) @@ -131,13 +131,13 @@ impl Node { return Ok(HTTPRequest::new(None, method, path, http_version, http_headers, http_body)) } - Err(HTTPParseError::InvalidStatusLine) + Err(HTTPParseError::InvalidStatusLine) } fn handle_connection(stream: TcpStream){ let buf_reader = BufReader::new(&stream); - - + + let mut request_object: HTTPRequest = match Self::parse_http_request(buf_reader) { Ok(value) => value, Err(e) => { @@ -153,16 +153,16 @@ impl Node { resolve_endpoint(request_object); return; - + } pub fn run(default: bool, rx: Arc>>, selected_port: u16) { let port: u16; if default == true { - port = Self::DEFAULT_PORT; + port = Self::DEFAULT_PORT; } - + else { port = match selected_port { port if (1..=65535).contains(&port) => port, @@ -172,7 +172,7 @@ impl Node { } } } - + let listener = TcpListener::bind(format!("127.0.0.1:{port}")).unwrap(); @@ -183,7 +183,7 @@ impl Node { Err(e) => panic!("{e}"), }; - + loop { // Check for termination signal @@ -195,7 +195,7 @@ impl Node { // Try accepting a connection match listener.accept() { - Ok((stream, addr)) => { + Ok((stream, _)) => { // rayon thread_pool.execute(|| { Self::handle_connection(stream); @@ -209,10 +209,12 @@ impl Node { break; } } - } + } println!("Dropping thread pool"); drop(thread_pool); } -} \ No newline at end of file +} + + diff --git a/src/node/ui.rs b/src/node/ui.rs new file mode 100644 index 0000000..1fc2ccf --- /dev/null +++ b/src/node/ui.rs @@ -0,0 +1,88 @@ +use color_eyre::Result; +use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers}; +use ratatui::{ + DefaultTerminal, Frame, + style::Stylize, + text::Line, + widgets::{Block, Paragraph}, +}; + + + +/// The main application which holds the state and logic of the application. +#[derive(Debug, Default)] +pub struct App { + /// Is the application running? + running: bool, + port: u16 +} + +impl App { + /// Construct a new instance of [`App`]. + pub fn new() -> Self { + Self::default() + } + + /// Run the application's main loop. + pub fn run(mut self, mut terminal: DefaultTerminal, port: u16) -> Result<()> { + self.port = port; + self.running = true; + while self.running { + terminal.draw(|frame| self.render(frame))?; + self.handle_crossterm_events()?; + } + Ok(()) + } + + /// Renders the user interface. + /// + /// This is where you add new widgets. See the following resources for more information: + /// + /// - + /// - + fn render(&mut self, frame: &mut Frame) { + let title = Line::from("CleytoCoin node is running!") + .bold() + .blue() + .centered(); + let port = self.port; + let text = format!("Node running in port {port} + \n\nPress `Esc`, `Ctrl-C` or `q` to stop running."); + frame.render_widget( + Paragraph::new(text) + .block(Block::bordered().title(title)) + .centered(), + frame.area(), + ) + } + + /// Reads the crossterm events and updates the state of [`App`]. + /// + /// If your application needs to perform work in between handling events, you can use the + /// [`event::poll`] function to check if there are any events available with a timeout. + fn handle_crossterm_events(&mut self) -> Result<()> { + match event::read()? { + // it's important to check KeyEventKind::Press to avoid handling key release events + Event::Key(key) if key.kind == KeyEventKind::Press => self.on_key_event(key), + Event::Mouse(_) => {} + Event::Resize(_, _) => {} + _ => {} + } + Ok(()) + } + + /// Handles the key events and updates the state of [`App`]. + fn on_key_event(&mut self, key: KeyEvent) { + match (key.modifiers, key.code) { + (_, KeyCode::Esc | KeyCode::Char('q')) + | (KeyModifiers::CONTROL, KeyCode::Char('c') | KeyCode::Char('C')) => self.quit(), + // Add other key handlers here. + _ => {} + } + } + + /// Set running to false to quit the application. + fn quit(&mut self) { + self.running = false; + } +} \ No newline at end of file From cb9171d00e7d9a66893ae81288fd73dbc886633c Mon Sep 17 00:00:00 2001 From: dantee-e Date: Mon, 28 Apr 2025 19:08:15 +0200 Subject: [PATCH 19/40] Logger added, have to restructure the calls to fn response --- src/bin/node.rs | 25 ++-- src/node/logger.rs | 52 ++++++++ src/node/mod.rs | 33 +++-- src/node/resolve_requests.rs | 230 ++++++++++++++++++++++++----------- src/node/static/500.html | 26 ++++ src/node/thread_pool.rs | 3 +- src/node/ui.rs | 62 ++++++---- tests/requests.rs | 6 +- 8 files changed, 316 insertions(+), 121 deletions(-) create mode 100644 src/node/logger.rs create mode 100644 src/node/static/500.html diff --git a/src/bin/node.rs b/src/bin/node.rs index 48479c6..e0bd3b7 100644 --- a/src/bin/node.rs +++ b/src/bin/node.rs @@ -1,31 +1,40 @@ use std::{sync::{mpsc, Arc, Mutex}, thread}; -use cleyto_coin::node::{self, ui::App}; - +use cleyto_coin::chain::Chain; +use cleyto_coin::node::{ + self, + ui::App, + logger::Logger +}; fn main() -> color_eyre::Result<()> { + + let logger = Arc::new(Logger::new()); + let (tx, rx) = mpsc::channel::<()>(); // Channel to kill thread let rx = Arc::new(Mutex::new(rx)); - + + // Run server thread + let logger_clone_for_node = Arc::clone(&logger); let server = thread::spawn(move || { let rx = Arc::clone(&rx); - node::Node::run(true, rx, 0); - + let mut node = node::Node::new(Chain::new(), logger_clone_for_node); + node.run(true, rx, 0); }); color_eyre::install()?; let terminal = ratatui::init(); - let result = App::new().run(terminal, node::Node::DEFAULT_PORT); + let result = App::new(Arc::clone(&logger), node::Node::DEFAULT_PORT).run(terminal); ratatui::restore(); - + // Quits server tx.send(())?; - + server.join().unwrap(); result } \ No newline at end of file diff --git a/src/node/logger.rs b/src/node/logger.rs new file mode 100644 index 0000000..3d25148 --- /dev/null +++ b/src/node/logger.rs @@ -0,0 +1,52 @@ +use std::io::{self, Write}; +use std::sync::Mutex; + +pub struct Logger { + logs: Mutex>, + temp_logs: Mutex>, +} + +impl Logger { + pub fn new() -> Self { + Self { + logs: Mutex::new(Vec::new()), + temp_logs: Mutex::new(Vec::new()), + } + } + + fn temp_log(&self, log: String) { + let mut temp_logs = self.temp_logs.lock().unwrap(); // Lock the Mutex to modify the temp_logs + temp_logs.push(log); + if temp_logs.len() > 50 { + temp_logs.remove(0); // Remove the oldest log if there are more than 50 + } + } + + fn log_internal(&self, log: String) { + println!("Logging {log}"); + let mut logs = self.logs.lock().unwrap(); // Lock the Mutex to modify the logs + logs.push(log); + + if let Err(e) = std::fs::write("/tmp/foo", logs.join("\n").as_bytes()) { + eprintln!("Unable to write to log file: {e}"); + } + } + pub fn log_error(&self, log: String) { + self.log_internal(format!("[ERROR] {}", log)); + self.temp_log(format!("[ERROR] {}", log)); + } + pub fn log(&self, log: String) { + self.log_internal(format!("[LOG] {}", log)); + self.temp_log(format!("[LOG] {}", log)); + } + + + pub fn read_logs(&self) -> io::Result> { + let logs = self.logs.lock().unwrap(); + Ok(logs.clone()) + } + pub fn read_temp_logs(&self) -> io::Result> { + let logs = self.temp_logs.lock().unwrap(); + Ok(logs.clone()) + } +} \ No newline at end of file diff --git a/src/node/mod.rs b/src/node/mod.rs index 395fff0..dc1cfcc 100644 --- a/src/node/mod.rs +++ b/src/node/mod.rs @@ -2,7 +2,9 @@ mod utils; mod resolve_requests; mod thread_pool; pub mod ui; +pub mod logger; +use std::fmt::Error; use core::panic; use std::time::Duration; use resolve_requests::{ @@ -22,11 +24,12 @@ use std::{ pub struct Node { chain: Chain, - transactions_list: Vec + transactions_list: Vec, + logger: Arc, } use once_cell::sync::Lazy; - +use crate::node::logger::Logger; static NUMBER_OF_THREADS_IN_THREAD_POOL: Lazy = Lazy::new(num_cpus::get); @@ -36,12 +39,13 @@ impl Node { pub const DEFAULT_PORT: u16 = 9473; pub const REFRESH_RATE_SERVER_IN_MS: u64 = 50; - pub fn new(chain: Chain) -> Node { + pub fn new(chain: Chain, logger: Arc) -> Node { num_cpus::get(); Node { chain, - transactions_list: Vec::new() + transactions_list: Vec::new(), + logger } } @@ -134,30 +138,30 @@ impl Node { Err(HTTPParseError::InvalidStatusLine) } - fn handle_connection(stream: TcpStream){ + fn handle_connection(stream: TcpStream) -> Result{ + let buf_reader = BufReader::new(&stream); + let mut request_object: HTTPRequest = match Self::parse_http_request(buf_reader) { Ok(value) => value, Err(e) => { println!("Error processing HTTP request: {e}"); return_json(&stream, HTTPResponse::BadRequest); - return; + return Err("Error processing HTTP request: {e}".parse().unwrap()); }, }; request_object.set_stream(stream); - resolve_endpoint(request_object); - - return; + resolve_endpoint(request_object) } - pub fn run(default: bool, rx: Arc>>, selected_port: u16) { + pub fn run(&mut self, default: bool, rx: Arc>>, selected_port: u16) { let port: u16; if default == true { port = Self::DEFAULT_PORT; @@ -196,9 +200,12 @@ impl Node { // Try accepting a connection match listener.accept() { Ok((stream, _)) => { - // rayon - thread_pool.execute(|| { - Self::handle_connection(stream); + let logger = Arc::clone(&self.logger); + thread_pool.execute(move || { + match Self::handle_connection(stream) { + Ok(value) => logger.log(format!("{value}")), + Err(value) => logger.log_error(format!("{value}")) + }; }) }, Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { diff --git a/src/node/resolve_requests.rs b/src/node/resolve_requests.rs index e72eb81..6dda963 100644 --- a/src/node/resolve_requests.rs +++ b/src/node/resolve_requests.rs @@ -1,36 +1,59 @@ + + pub mod endpoints { use crate::chain::transaction::Transaction; use super::methods::{HTTPRequest, HTTPResponse, Method}; - fn path_not_found(){ - + fn path_not_found() -> Result{ + Err(String::from("Someone tried to access a nonexistent path")) } - fn index(mut request: HTTPRequest){ + fn index(mut request: HTTPRequest) -> Result { request.response(HTTPResponse::OK); + Ok(String::from("Index.html returned to client")) } - fn submit_transaction(mut request: HTTPRequest){ - match request.get_method() { - Method::GET(_) => request.response(HTTPResponse::InvalidMethod), - Method::POST(data) => { - let json: Transaction = serde_json::from_str(&data.body.unwrap()).unwrap(); - println!("{}", json.to_string()); - } + fn submit_transaction(mut request: HTTPRequest) -> Result { + if let Method::GET(_) = request.get_method() { + request.response(HTTPResponse::InvalidMethod); + return Err(String::from("Method not supported on function submit_transaction")); } - } + if let Method::POST(data) = request.get_method() { + let json: Transaction = match serde_json::from_str(&data.body.unwrap()){ + Ok(json) => json, + Err(_) => { + request.response(HTTPResponse::BadRequest); + return Err(String::from("Error extracting transaction from POST").to_string()); + } - pub fn resolve_endpoint(request: HTTPRequest){ + }; + request.response(HTTPResponse::OK); + return Ok(format!("Received transaction: {}", json.to_string())) + } + Err(String::from("Something went wrong on the submit_transaction function")) + + } + + fn favicon(mut request: HTTPRequest) -> Result { + if let Method::GET(_) = request.get_method() { + // TODO implement the happy path + return Err(String::from("Favicon")); + } + } + + pub fn resolve_endpoint(request: HTTPRequest) -> Result { match request.get_method() { Method::GET(data) => { match data.path.as_str() { "/" => index(request), + "/favicon.ico" => favicon(request), _ => path_not_found(), + } }, Method::POST(data) => { @@ -40,6 +63,7 @@ pub mod endpoints { } }, } + } } @@ -51,10 +75,18 @@ pub mod methods { use std::io::prelude::*; use serde_json::json; + + pub enum Content { + HTML(String), // path to HTML file + JSON(serde_json::Value), + Image(String), // path to image file + PlainText(String), + } pub enum HTTPResponse { - OK, + OK(Option), InvalidMethod, - BadRequest + BadRequest, + Favicon } @@ -84,6 +116,42 @@ pub mod methods { http_version: String, } + struct Response { + status: u16, + content_type: &'static str, + body: Vec, + } + + impl Response { + fn to_bytes(&self) -> Vec { + let mut v = Vec::new(); + let header = format!( + "HTTP/1.1 {} {}\r\n\ + Content-Type: {}\r\n\ + Content-Length: {}\r\n\r\n", + self.status, + match self.status { + 200 => "OK", + 400 => "Bad Request", + 405 => "Method Not Allowed", + _ => "Unknown", + }, + self.content_type, + self.body.len(), + ); + v.extend_from_slice(header.as_bytes()); + v.extend_from_slice(&self.body); + v + } + fn new(status: u16, content_type: &'static str, body: Vec) -> Self { + Self { + status, + content_type, + body + } + } + } + impl HTTPRequest { pub fn new(stream: Option, method: String, path: String, http_version: String, headers: HashMap, body: Option) -> HTTPRequest { HTTPRequest { @@ -99,69 +167,85 @@ pub mod methods { } } - pub fn set_stream(&mut self, stream: TcpStream) { - self.stream = Some(stream); - } - - pub fn get_method(&self) -> Method { - self.method.clone() - } - - - - pub fn response(&mut self, status: HTTPResponse) { - enum ResponseType { - JSON, - HTML, - PlainText + pub fn set_stream(&mut self, stream: TcpStream) { self.stream = Some(stream) } + + pub fn get_method(&self) -> Method { self.method.clone() } + + + + fn make_response(status: HTTPResponse, accept: Option<&str>) -> std::io::Result { + + fn response_ok_content(content: Content) -> std::io::Result { + match content { + Content::HTML(path) => Ok(Response { + status: 200, + content_type: "text/html", + body: fs::read(path)?, + }), + Content::JSON(value) => Ok(Response { + status: 200, + content_type: "application/json", + body: serde_json::to_vec(&value)?, + }), + Content::Image(path) => Ok(Response { + status: 200, + content_type: "image/png", + body: fs::read(path)?, + }), + Content::PlainText(text) => Ok(Response { + status: 200, + content_type: "text/plain", + body: text.into_bytes(), + }), + } + } + fn response_ok_no_content(a: &str) -> std::io::Result { + if a.contains("text/html") { + return Ok(Response::new(200, "text/html", fs::read("static/200.html").unwrap_or("NOT FOUND".into()))); + } + + else if a.contains("application/json") { + let j = json!({ "msg": "OK", "status": 200 }); + return Ok(Response::new(200, "application/json", serde_json::to_vec(&j)?)) + } + + Ok(Response::new(200, "text", "Success".into())) } - let mut stream = self.stream.as_ref().unwrap(); - let (msg, status_code) = match status { - HTTPResponse::OK => ("The request was successful".to_owned(), 200), - HTTPResponse::InvalidMethod => ("Invalid HTTP method".to_owned(), 405), - HTTPResponse::BadRequest => ("Bad Request".to_owned(), 400) - }; - - let response: String; - - let (response_type, response_type_str) = if let Some(value) = self.headers.get("Accept") { - if value.contains("text/html") {(ResponseType::HTML, "text/html")} - else if value.contains("pat") {(ResponseType::JSON, "application/json")} - else {(ResponseType::PlainText, "text")} - } else {(ResponseType::PlainText, "text")}; - - response = match response_type { - ResponseType::JSON => { - serde_json::to_string(&json!({"msg": msg,"status_code": status_code})) - .expect("Couldn't convert the object to json") - }, - ResponseType::HTML => { - match status { - HTTPResponse::OK => fs::read_to_string("src/node/static/200.html").expect("Couldn't read the file"), - HTTPResponse::InvalidMethod => fs::read_to_string("src/node/static/405.html").expect("Couldn't read the file"), - HTTPResponse::BadRequest => fs::read_to_string("src/node/static/400.html").expect("Couldn't read the file"), + match status { + HTTPResponse::OK(content_opt) => { + if let Some(content) = content_opt { + response_ok_content(content) + } else { + response_ok_no_content(accept.unwrap_or("text")) } - - }, - ResponseType::PlainText => { - msg - }, - }; + } + HTTPResponse::InvalidMethod => { + if accept.unwrap_or("").contains("text/html") { + Ok(Response::new(405, "text/html", fs::read("static/405.html").unwrap_or_else(|_| b"405 Invalid Method".to_vec()))) + } else { + Ok(Response::new(405, "text/plain", b"405 Invalid Method".to_vec())) + } + } + HTTPResponse::BadRequest => { + if accept.unwrap_or("").contains("text/html") { + Ok(Response::new(400, "text/html", fs::read("static/400.html").unwrap_or_else(|_| b"400 Bad Request".to_vec()))) + } else { + Ok(Response::new(400, "text/plain", b"400 Bad Request".to_vec())) + } + } + HTTPResponse::Favicon => { + Ok(Response::new(200, "image/x-icon", fs::read("static/favicon.ico").unwrap_or_else(|_| b"".to_vec()))) + } + }.expect("TODO: panic message"); - - - let response = format!( - "HTTP/1.1 200 OK\r\n\ - Content-Type: {}\r\n\ - Content-Length: {}\r\n - {}", - response_type_str, - response.len(), - response - ); - - stream.write_all(response.as_bytes()).unwrap(); + Ok(Response::new(500, "text/html", fs::read("static/500.html").unwrap_or_else(|_| b"500 Internal Server Error".to_vec()))) + } + + pub fn response(&mut self, status: HTTPResponse) -> std::io::Result<()> { + let accept = self.headers.get("Accept").map(|s| s.as_str()); + let resp = Self::make_response(status, accept)?; + self.stream.as_mut().unwrap().write_all(&resp.to_bytes()) } } diff --git a/src/node/static/500.html b/src/node/static/500.html new file mode 100644 index 0000000..a043f66 --- /dev/null +++ b/src/node/static/500.html @@ -0,0 +1,26 @@ + + + + + 500 Internal Server Error + + + +

500 - Internal Server Error

+

Oops! Something went wrong on the server.

+ + diff --git a/src/node/thread_pool.rs b/src/node/thread_pool.rs index d7590de..3cf49a6 100644 --- a/src/node/thread_pool.rs +++ b/src/node/thread_pool.rs @@ -1,4 +1,4 @@ - +use super::logger; pub mod custom_thread_pool { use std::{fmt, sync::{mpsc, Arc, Mutex}, thread}; @@ -33,7 +33,6 @@ pub mod custom_thread_pool { match message { Ok(job) => { - println!("Worker {id} got a job; executing."); job(); } Err(_) => { diff --git a/src/node/ui.rs b/src/node/ui.rs index 1fc2ccf..1b3adf4 100644 --- a/src/node/ui.rs +++ b/src/node/ui.rs @@ -1,3 +1,4 @@ +use std::sync::Arc; use color_eyre::Result; use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers}; use ratatui::{ @@ -5,27 +6,31 @@ use ratatui::{ style::Stylize, text::Line, widgets::{Block, Paragraph}, + prelude::* }; - - +use crate::node::logger::Logger; /// The main application which holds the state and logic of the application. -#[derive(Debug, Default)] + pub struct App { /// Is the application running? running: bool, - port: u16 + port: u16, + logger: Arc } impl App { /// Construct a new instance of [`App`]. - pub fn new() -> Self { - Self::default() + pub fn new(logger: Arc, port: u16) -> Self { + Self { + running: true, + port, + logger + } } /// Run the application's main loop. - pub fn run(mut self, mut terminal: DefaultTerminal, port: u16) -> Result<()> { - self.port = port; + pub fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> { self.running = true; while self.running { terminal.draw(|frame| self.render(frame))?; @@ -34,32 +39,43 @@ impl App { Ok(()) } - /// Renders the user interface. - /// - /// This is where you add new widgets. See the following resources for more information: - /// - /// - - /// - fn render(&mut self, frame: &mut Frame) { - let title = Line::from("CleytoCoin node is running!") + let layout = Layout::default() + .direction(Direction::Horizontal) + .constraints(vec![ + Constraint::Percentage(50), + Constraint::Percentage(50), + ]) + .split(frame.area()); + + let main_title = Line::from(" CleytoCoin node is running! ") + .bold() + .blue() + .centered(); + let logs_title = Line::from(" LOGS ") .bold() .blue() .centered(); let port = self.port; let text = format!("Node running in port {port} \n\nPress `Esc`, `Ctrl-C` or `q` to stop running."); + + frame.render_widget( Paragraph::new(text) - .block(Block::bordered().title(title)) + .block(Block::bordered().title(main_title)) .centered(), - frame.area(), + layout[0], + ); + frame.render_widget( + Paragraph::new(self.logger.read_temp_logs().unwrap().join("\n")) + .block(Block::bordered().title(logs_title)) + .left_aligned(), + layout[1], ) } - /// Reads the crossterm events and updates the state of [`App`]. - /// - /// If your application needs to perform work in between handling events, you can use the - /// [`event::poll`] function to check if there are any events available with a timeout. + fn handle_crossterm_events(&mut self) -> Result<()> { match event::read()? { // it's important to check KeyEventKind::Press to avoid handling key release events @@ -71,7 +87,7 @@ impl App { Ok(()) } - /// Handles the key events and updates the state of [`App`]. + fn on_key_event(&mut self, key: KeyEvent) { match (key.modifiers, key.code) { (_, KeyCode::Esc | KeyCode::Char('q')) @@ -81,7 +97,7 @@ impl App { } } - /// Set running to false to quit the application. + fn quit(&mut self) { self.running = false; } diff --git a/tests/requests.rs b/tests/requests.rs index 8e445d7..59923ee 100644 --- a/tests/requests.rs +++ b/tests/requests.rs @@ -4,7 +4,8 @@ use std::{sync::{mpsc, Arc, Mutex}, thread}; use reqwest::blocking::Client; use serde::Serialize; use rand::Rng; - +use cleyto_coin::chain::Chain; +use cleyto_coin::node::logger::Logger; #[derive(Serialize)] struct RandomData { @@ -146,7 +147,8 @@ fn main(){ // Run server thread let server = thread::spawn(move || { let rx = Arc::clone(&rx); - node::Node::run(true, rx, 0); + let mut node = node::Node::new(Chain::new(), Arc::new(Logger::new())); + node.run(true, rx, 0); }); From 9c285c77d7a6de19f70e4b777fa6f75c918fc429 Mon Sep 17 00:00:00 2001 From: dantee-e Date: Wed, 30 Apr 2025 11:50:48 +0200 Subject: [PATCH 20/40] Added index.html, now everything seems to be working --- src/node/logger.rs | 1 - src/node/mod.rs | 31 ++-- src/node/resolve_requests.rs | 324 ++++++++++++++++++++++++----------- src/node/static/index.html | 93 ++++++++++ 4 files changed, 326 insertions(+), 123 deletions(-) create mode 100644 src/node/static/index.html diff --git a/src/node/logger.rs b/src/node/logger.rs index 3d25148..c823c7c 100644 --- a/src/node/logger.rs +++ b/src/node/logger.rs @@ -23,7 +23,6 @@ impl Logger { } fn log_internal(&self, log: String) { - println!("Logging {log}"); let mut logs = self.logs.lock().unwrap(); // Lock the Mutex to modify the logs logs.push(log); diff --git a/src/node/mod.rs b/src/node/mod.rs index dc1cfcc..6e6e06a 100644 --- a/src/node/mod.rs +++ b/src/node/mod.rs @@ -8,7 +8,7 @@ use std::fmt::Error; use core::panic; use std::time::Duration; use resolve_requests::{ - methods::{return_json, HTTPParseError, HTTPRequest, HTTPResponse}, + methods::{HTTPParseError, HTTPRequest, HTTPResponse}, endpoints::resolve_endpoint }; use crate::chain::{transaction::Transaction, Chain}; @@ -17,10 +17,7 @@ use thread_pool::custom_thread_pool::ThreadPool; use std::{ collections::HashMap, io::{prelude::*, BufReader}, net::{TcpListener, TcpStream}, sync::{mpsc::Receiver, Arc, Mutex}, thread }; - - - - +use std::path::PathBuf; pub struct Node { chain: Chain, @@ -101,12 +98,12 @@ impl Node { // If method is GET, return before trying to read the body if method == "GET" { - return Ok(HTTPRequest::new(None, method, path, http_version, http_headers, None)) + return Ok(HTTPRequest::new(None, method, PathBuf::from(path), http_version, http_headers, None)) } - // getting content_lenght from headers + // getting content_length from headers let content_length = match http_headers.get("content-length") { Some(value) => match value.parse::() { Ok(length) => length, @@ -132,33 +129,26 @@ impl Node { if method == "POST"{ - return Ok(HTTPRequest::new(None, method, path, http_version, http_headers, http_body)) + return Ok(HTTPRequest::new(None, method, PathBuf::from(path), http_version, http_headers, http_body)) } Err(HTTPParseError::InvalidStatusLine) } - fn handle_connection(stream: TcpStream) -> Result{ - + fn handle_connection(stream: TcpStream) -> Result, Option>{ let buf_reader = BufReader::new(&stream); - - let mut request_object: HTTPRequest = match Self::parse_http_request(buf_reader) { Ok(value) => value, Err(e) => { - println!("Error processing HTTP request: {e}"); - return_json(&stream, HTTPResponse::BadRequest); - return Err("Error processing HTTP request: {e}".parse().unwrap()); + return Err(Some("Error processing HTTP request: {e}".parse().unwrap())); }, }; request_object.set_stream(stream); - + // TODO add logging resolve_endpoint(request_object) - - } pub fn run(&mut self, default: bool, rx: Arc>>, selected_port: u16) { @@ -203,8 +193,9 @@ impl Node { let logger = Arc::clone(&self.logger); thread_pool.execute(move || { match Self::handle_connection(stream) { - Ok(value) => logger.log(format!("{value}")), - Err(value) => logger.log_error(format!("{value}")) + Ok(Some(value)) => logger.log(format!("{value}")), + Err(Some(value)) => logger.log_error(format!("{value}")), + _ => {} }; }) }, diff --git a/src/node/resolve_requests.rs b/src/node/resolve_requests.rs index 6dda963..e03235f 100644 --- a/src/node/resolve_requests.rs +++ b/src/node/resolve_requests.rs @@ -1,69 +1,214 @@ +pub mod endpoints { + mod errors { + use std::fmt; + + #[derive(Debug)] + pub enum HTTPResponseError { + InvalidMethod(Option), + InvalidPath(Option), + InvalidBody(Option), + InternalServerError(Option), + BadRequest(Option), + } + impl fmt::Display for HTTPResponseError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + _ => todo!(), + } + } + } + impl std::error::Error for HTTPResponseError {} + } + mod helpers { + use std::path::PathBuf; + use crate::node::resolve_requests::endpoints::errors::HTTPResponseError; + use crate::node::resolve_requests::methods::{Content, GETData, HTTPRequest, HTTPResponse, Method, POSTData}; + const STATIC_FOLDER: &str = "src/node/static/"; + pub type HTTPResult = Result; -pub mod endpoints { - use crate::chain::transaction::Transaction; + pub type POSTFunc = fn(&POSTData) -> HTTPResult; + pub type GETFunc = fn(&GETData) -> HTTPResult; - use super::methods::{HTTPRequest, HTTPResponse, Method}; + pub fn path_not_found() -> HTTPResult{ + Err(HTTPResponseError::InvalidPath(Some("Path not found".to_string()))) + } + pub fn method_not_allowed() -> HTTPResult{ + Err(HTTPResponseError::InvalidMethod(None)) + } - fn path_not_found() -> Result{ - Err(String::from("Someone tried to access a nonexistent path")) - } + pub trait Handler { fn call(&self, request: &HTTPRequest) -> HTTPResult; } - fn index(mut request: HTTPRequest) -> Result { + // Implement the trait for GETFunc + impl Handler for GETFunc { + fn call(&self, request: &HTTPRequest) -> HTTPResult { + match request.get_method() { + Method::GET(data) => self(&data), + _ => method_not_allowed() + } + } + } + + // Implement the trait for POSTFunc + impl Handler for POSTFunc { + fn call(&self, request: &HTTPRequest) -> HTTPResult { + match request.get_method() { + Method::POST(data) => self(&data), + _ => method_not_allowed() + } + } + } - request.response(HTTPResponse::OK); - Ok(String::from("Index.html returned to client")) + pub fn return_image(path: &str) -> HTTPResult { + Ok(HTTPResponse::OK(Some(Content::Image(PathBuf::from(STATIC_FOLDER.to_owned() + path))))) + } + pub fn return_html(path: &str) -> HTTPResult { + Ok(HTTPResponse::OK(Some(Content::HTML(PathBuf::from(STATIC_FOLDER.to_owned() + path))))) + } + + pub fn post(request: HTTPRequest, f: POSTFunc) -> HTTPResult { + let method = request.get_method(); + if let Method::POST(data) = method { + f(data) + } + else { + Err(HTTPResponseError::InvalidMethod(None)) + } + } + pub fn get(request: HTTPRequest, f: GETFunc) -> HTTPResult { + if let Method::GET(data) = request.get_method() { + f(data) + } + else { + Err(HTTPResponseError::InvalidMethod(None)) + } + } + pub fn get_post( + request: HTTPRequest, + get: GETFunc, + post: POSTFunc + ) -> HTTPResult { + match request.get_method() { + Method::POST(data) => post(data), + Method::GET(data) => get(data) + } + } } - fn submit_transaction(mut request: HTTPRequest) -> Result { - if let Method::GET(_) = request.get_method() { - request.response(HTTPResponse::InvalidMethod); - return Err(String::from("Method not supported on function submit_transaction")); + use std::collections::HashMap; + use super::methods::{HTTPRequest, HTTPResponse, Method}; + use helpers::*; + + mod endpoints { + use crate::chain::transaction::Transaction; + use crate::node::resolve_requests::endpoints::errors::HTTPResponseError; + use crate::node::resolve_requests::endpoints::helpers::{return_html, return_image, HTTPResult}; + use crate::node::resolve_requests::methods::{GETData, POSTData}; + + pub fn index(data: &GETData) -> HTTPResult { + return_html("index.html") } - if let Method::POST(data) = request.get_method() { - let json: Transaction = match serde_json::from_str(&data.body.unwrap()){ + pub fn submit_transaction(data: &POSTData) -> HTTPResult { + let body = data.body.clone().unwrap(); + let json: Transaction = match serde_json::from_str(body.as_str()) { Ok(json) => json, Err(_) => { - request.response(HTTPResponse::BadRequest); - return Err(String::from("Error extracting transaction from POST").to_string()); + return Err(HTTPResponseError::InvalidBody(None)); } - }; - request.response(HTTPResponse::OK); - return Ok(format!("Received transaction: {}", json.to_string())) - } + Err(HTTPResponseError::InternalServerError(Some("Something went wrong on the submit_transaction function".parse().unwrap()))) - Err(String::from("Something went wrong on the submit_transaction function")) + } + pub fn favicon(data: &GETData) -> HTTPResult { + return_image("favicon.ico") + } } + use endpoints::*; + use crate::node::resolve_requests::endpoints::errors::HTTPResponseError; + + pub fn resolve_endpoint(mut request: HTTPRequest) -> Result, Option> { + /* + TODO: This creates the endpoints var every time the resolve_endpoints function runs, + which is very inefficient. We should move the creation of the endpoints var to the + initialization of the program, and pass it around as a parameter to the functions that + need it + */ + + fn handle_error(request: &mut HTTPRequest, response: HTTPResponse, log: Option<&str>) -> Result<(), String> { + request.response(response); + Err(log.map(String::from).unwrap_or_default()) + } + + fn add_endpoint<'a>( + path: &'a str, + endpoints: &mut HashMap<&'a str, HashMap<&str, Box>>, + get: Option, + post: Option + ) { + let mut methods: HashMap<&str, Box> = HashMap::new(); + + if let Some(get) = get { + methods.insert("GET", Box::new(get) as Box); + } + if let Some(post) = post { + methods.insert("POST", Box::new(post) as Box); + } + + endpoints.insert(path, methods); - fn favicon(mut request: HTTPRequest) -> Result { - if let Method::GET(_) = request.get_method() { - // TODO implement the happy path - return Err(String::from("Favicon")); } - } + - pub fn resolve_endpoint(request: HTTPRequest) -> Result { - match request.get_method() { - Method::GET(data) => { - match data.path.as_str() { - "/" => index(request), - "/favicon.ico" => favicon(request), - _ => path_not_found(), + let mut endpoints: HashMap<&str, HashMap<&str, Box>> = HashMap::new(); - } + add_endpoint("/", &mut endpoints, Some(index), None); + add_endpoint("/favicon.ico", &mut endpoints, Some(favicon), None); + + + let (path, method) = match request.get_method() { + Method::GET(data) => (data.path.clone(), "GET"), + Method::POST(data) => (data.path.clone(), "POST"), + }; + + let r = match endpoints.get(path.to_str().unwrap()) { + Some(methods) => match methods.get(method) { + Some(handler) => handler.call(&request), + None => method_not_allowed(), }, - Method::POST(data) => { - match data.path.as_str() { - "/" => index(request), - _ => path_not_found(), + None => path_not_found(), + }; + + let log = String::new(); + match r { + Ok(value) => {request.response(value); Ok(Some("Success".to_string()))} + Err(e) => { + match e { + HTTPResponseError::InvalidMethod(log) => { + request.response(HTTPResponse::InvalidMethod); + Err(log) + }, + HTTPResponseError::InvalidPath(log) => { + request.response(HTTPResponse::BadRequest); + Err(log) + }, + HTTPResponseError::InvalidBody(log) => { + request.response(HTTPResponse::BadRequest); + Err(log) + }, + HTTPResponseError::InternalServerError(log) => { + request.response(HTTPResponse::InternalServerError); + Err(log) + }, + HTTPResponseError::BadRequest(log) => { + request.response(HTTPResponse::BadRequest); + Err(log) + }, } - }, + } } - } } @@ -73,30 +218,31 @@ pub mod methods { use std::{fmt, fs}; use std::net::TcpStream; use std::io::prelude::*; + use std::path::{Path, PathBuf}; use serde_json::json; pub enum Content { - HTML(String), // path to HTML file + HTML(PathBuf), // path to HTML file JSON(serde_json::Value), - Image(String), // path to image file + Image(PathBuf), // path to image file PlainText(String), } pub enum HTTPResponse { OK(Option), InvalidMethod, BadRequest, - Favicon + InternalServerError, } #[derive(Debug, Clone)] pub struct GETData { - pub path: String + pub path: PathBuf } #[derive(Debug, Clone)] pub struct POSTData { - pub path: String, + pub path: PathBuf, pub body: Option } @@ -106,15 +252,6 @@ pub mod methods { POST(POSTData) } - - - #[derive(Debug)] - pub struct HTTPRequest { - stream: Option, - pub headers: HashMap, - method: Method, - http_version: String, - } struct Response { status: u16, @@ -152,8 +289,22 @@ pub mod methods { } } + #[derive(Debug)] + pub struct HTTPRequest { + stream: Option, + pub headers: HashMap, + method: Method, + http_version: String, + } + impl HTTPRequest { - pub fn new(stream: Option, method: String, path: String, http_version: String, headers: HashMap, body: Option) -> HTTPRequest { + pub fn new( + stream: Option, + method: String, path: PathBuf, + http_version: String, headers: + HashMap, + body: Option + ) -> HTTPRequest { HTTPRequest { stream, method: match method.as_str() { @@ -169,9 +320,7 @@ pub mod methods { pub fn set_stream(&mut self, stream: TcpStream) { self.stream = Some(stream) } - pub fn get_method(&self) -> Method { self.method.clone() } - - + pub fn get_method(&self) -> &Method { &self.method } fn make_response(status: HTTPResponse, accept: Option<&str>) -> std::io::Result { @@ -234,21 +383,27 @@ pub mod methods { Ok(Response::new(400, "text/plain", b"400 Bad Request".to_vec())) } } - HTTPResponse::Favicon => { - Ok(Response::new(200, "image/x-icon", fs::read("static/favicon.ico").unwrap_or_else(|_| b"".to_vec()))) + HTTPResponse::InternalServerError => { + if accept.unwrap_or("").contains("text/html") { + Ok(Response::new(500, "text/html", fs::read("static/500.html").unwrap_or_else(|_| b"500 Internal Server Error".to_vec()))) + } else { + Ok(Response::new(500, "text/plain", b"500 Internal Server Error".to_vec())) + } } - }.expect("TODO: panic message"); - - Ok(Response::new(500, "text/html", fs::read("static/500.html").unwrap_or_else(|_| b"500 Internal Server Error".to_vec()))) + } } - pub fn response(&mut self, status: HTTPResponse) -> std::io::Result<()> { + pub fn response(&mut self, status: HTTPResponse) { let accept = self.headers.get("Accept").map(|s| s.as_str()); - let resp = Self::make_response(status, accept)?; - self.stream.as_mut().unwrap().write_all(&resp.to_bytes()) + let resp = Self::make_response(status, accept).unwrap(); + if let Err(e) = self.stream.as_mut().unwrap().write_all(&resp.to_bytes()){ + eprintln!("Error writing response to stream: {}", e); + } + } } + #[derive(Debug)] pub enum HTTPParseError { InvalidStatusLine, @@ -268,42 +423,7 @@ pub mod methods { } impl std::error::Error for HTTPParseError {} - - - pub fn return_json(mut stream: &TcpStream, status: HTTPResponse){ - - let (msg, status_code) = match status { - HTTPResponse::OK => ("The request was successful".to_owned(), 200), - HTTPResponse::InvalidMethod => ("Invalid HTTP method".to_owned(), 405), - HTTPResponse::BadRequest => ("Bad Request".to_owned(), 400) - }; - let response = json!({ - "msg": msg, - "status_code": status_code - }); - let success_json = serde_json::to_string(&response) - .expect("Couldn't convert the object to json"); - let response = format!( - "HTTP/1.1 200 OK\r\n\ - Content-Type: application/json\r\n\ - Content-Length: {}\r\n -{}", - success_json.len(), - success_json - ); - - stream.write_all(response.as_bytes()).unwrap(); - } - - pub fn get(stream: &TcpStream, request: HTTPRequest) { - println!("{:#?}", request); - return_json(stream, HTTPResponse::OK); - } - pub fn post(stream: &TcpStream, request: HTTPRequest) { - println!("{:#?}", request); - return_json(&stream, HTTPResponse::OK); - } } \ No newline at end of file diff --git a/src/node/static/index.html b/src/node/static/index.html new file mode 100644 index 0000000..39368c0 --- /dev/null +++ b/src/node/static/index.html @@ -0,0 +1,93 @@ + + + + + + Cryptocoin Node Dashboard + + + +
+

Cryptocoin Node Dashboard

+
+

Node Status: Checking...

+

Block Height: 0

+

Connected Peers: 0

+

Last Updated: -

+
+ +
+ + + + \ No newline at end of file From a2b7362dc63c86cfaf85d769f8723573ea5f3d4d Mon Sep 17 00:00:00 2001 From: dantee-e Date: Wed, 30 Apr 2025 13:24:04 +0200 Subject: [PATCH 21/40] Added index.html and favicon.ico --- src/bin/node.rs | 18 +- src/chain/block.rs | 30 +-- src/chain/mod.rs | 19 +- src/chain/transaction.rs | 41 ++-- src/chain/utils.rs | 27 ++- src/chain/wallet.rs | 55 ++--- src/lib.rs | 1 - src/main.rs | 2 +- src/node/logger.rs | 11 +- src/node/mod.rs | 102 +++++---- src/node/resolve_requests.rs | 270 +++++++++++++---------- src/node/static/fav.ico | Bin 0 -> 4286 bytes src/node/static/fav.png | Bin 0 -> 2252 bytes src/node/thread_pool.rs | 49 ++-- src/node/ui.rs | 34 ++- src/node/utils.rs | 5 +- tests/create_block_and_add_chain.rs | 7 +- tests/create_transaction.rs | 18 +- tests/requests.rs | 49 ++-- tests/sign_and_verify_transactioninfo.rs | 25 ++- 20 files changed, 419 insertions(+), 344 deletions(-) create mode 100644 src/node/static/fav.ico create mode 100644 src/node/static/fav.png diff --git a/src/bin/node.rs b/src/bin/node.rs index e0bd3b7..60324f1 100644 --- a/src/bin/node.rs +++ b/src/bin/node.rs @@ -1,23 +1,18 @@ -use std::{sync::{mpsc, Arc, Mutex}, thread}; use cleyto_coin::chain::Chain; -use cleyto_coin::node::{ - self, - ui::App, - logger::Logger +use cleyto_coin::node::{self, logger::Logger, ui::App}; +use std::{ + sync::{mpsc, Arc, Mutex}, + thread, }; - - fn main() -> color_eyre::Result<()> { - let logger = Arc::new(Logger::new()); let (tx, rx) = mpsc::channel::<()>(); // Channel to kill thread let rx = Arc::new(Mutex::new(rx)); - - + // Run server thread let logger_clone_for_node = Arc::clone(&logger); let server = thread::spawn(move || { @@ -26,7 +21,6 @@ fn main() -> color_eyre::Result<()> { node.run(true, rx, 0); }); - color_eyre::install()?; let terminal = ratatui::init(); let result = App::new(Arc::clone(&logger), node::Node::DEFAULT_PORT).run(terminal); @@ -37,4 +31,4 @@ fn main() -> color_eyre::Result<()> { server.join().unwrap(); result -} \ No newline at end of file +} diff --git a/src/chain/block.rs b/src/chain/block.rs index 72be148..12224d0 100644 --- a/src/chain/block.rs +++ b/src/chain/block.rs @@ -2,9 +2,8 @@ use chrono::{DateTime, Utc}; use sha2::{Digest, Sha256}; use super::transaction::Transaction; -use super::Chain; use super::utils::PROOF_OF_WORK_DIFFICULTY; - +use super::Chain; #[derive(Clone)] pub struct Block { @@ -13,7 +12,7 @@ pub struct Block { index: u64, timestamp: DateTime, hash: String, - nonce: u64 + nonce: u64, } impl Block { @@ -26,14 +25,14 @@ impl Block { } pub fn calculate_hash(&self) -> String { - let transactions_string = self.transactions + let transactions_string = self + .transactions .iter() .map(|t| t.to_string()) // Calls the `to_string()` method of `Transaction` .collect::>() // Collects into a Vec .join("::END_OF_TRANSACTION::BEGIN_OF_TRANSACTION::"); // Joins all elements with "; " as separator - - let serialized = serde_json::to_string - (&( + + let serialized = serde_json::to_string(&( "BEGIN::BEGIN_PREVIOUS_HASH::", &self.previous_hash, "::END_PREVIOUS_HASH::BEGIN_TRANSACTIONS::", @@ -47,7 +46,8 @@ impl Block { "::END_TIMESTAMP::BEGIN_NONCE::", &self.nonce, "::END_NONCE::END", - )).expect("Coudn't serialize the block to create the hash"); + )) + .expect("Coudn't serialize the block to create the hash"); println!("behold the serialized block:\n{serialized}"); @@ -64,30 +64,30 @@ impl Block { let index = chain.get_last_index() + 1; let timestamp = Utc::now(); - let mut block = Self { previous_hash, transactions, index, timestamp, hash: String::new(), - nonce: 0 // temporary so that we can calculate hash + nonce: 0, // temporary so that we can calculate hash }; block.hash = block.calculate_hash(); block - } - pub fn genesis_block() -> Self{ + pub fn genesis_block() -> Self { Self { previous_hash: String::from("Foguete nao da re"), transactions: Vec::new(), index: 0, timestamp: Utc::now(), - hash: String::from("The Times 03/Jan/2009 Chancellor on brink of second bailout for banks"), - nonce: 0 + hash: String::from( + "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks", + ), + nonce: 0, } } @@ -95,7 +95,7 @@ impl Block { let prefix = "0".repeat(PROOF_OF_WORK_DIFFICULTY.into()); while !self.hash.starts_with(&prefix) { - self.nonce+=1; + self.nonce += 1; self.hash = self.calculate_hash(); } diff --git a/src/chain/mod.rs b/src/chain/mod.rs index 201b7f3..7c7deb0 100644 --- a/src/chain/mod.rs +++ b/src/chain/mod.rs @@ -1,22 +1,19 @@ -pub mod utils; -pub mod transaction; pub mod block; +pub mod transaction; +pub mod utils; pub mod wallet; use block::Block; pub struct Chain { - blocks: Vec + blocks: Vec, } impl Chain { - pub fn new() -> Self { - Self{ - blocks: Vec::new() - } + Self { blocks: Vec::new() } } - pub fn add_block(&mut self, block: Block){ + pub fn add_block(&mut self, block: Block) { self.blocks.push(block); } @@ -32,17 +29,17 @@ impl Chain { None => { let genesis_block = self.create_genesis_block(); genesis_block.get_hash() - }, + } } } fn get_last_index(&mut self) -> u64 { match self.blocks.last() { Some(block) => block.get_index(), - None =>{ + None => { let genesis_block = self.create_genesis_block(); genesis_block.get_index() - }, + } } } } diff --git a/src/chain/transaction.rs b/src/chain/transaction.rs index 066d7b5..b56f550 100644 --- a/src/chain/transaction.rs +++ b/src/chain/transaction.rs @@ -1,28 +1,21 @@ -use core::panic; -use std::fmt; -use std::fmt::Debug; use super::wallet::Wallet; -use openssl::error::{Error, ErrorStack}; -use openssl::sign::{Signer, Verifier}; -use openssl::rsa::Rsa; -use openssl::pkey::PKey; -use openssl::hash::MessageDigest; use chrono::{DateTime, Utc}; +use core::panic; +use openssl::error::ErrorStack; use serde::{Deserialize, Serialize}; +use std::fmt; +use std::fmt::Debug; #[derive(Clone, Debug, Serialize, Deserialize)] // ---------------------------------------------- TransactionInfo definition ---------------------------------------- pub struct TransactionInfo { pub value: f32, - pub date: DateTime + pub date: DateTime, } impl TransactionInfo { pub fn new(value: f32, date: DateTime) -> TransactionInfo { - Self { - value, - date - } + Self { value, date } } pub fn to_string(&self) -> String { @@ -59,7 +52,7 @@ impl fmt::Display for TransactionDeserializeError { } impl std::error::Error for TransactionDeserializeError {} -/* +/* mod signature_def { use rsa::BigUint; use serde::{Deserialize, Serialize}; @@ -83,7 +76,6 @@ mod signature_def { } } */ - // ----------------------------------------------------------------------------------------------------------------- // ---------------------------------------------- Transaction definition ------------------------------------------- @@ -98,19 +90,23 @@ pub struct Transaction { } impl Transaction { - pub fn new(sender: Wallet, receiver: Wallet, transaction_info: TransactionInfo, signature: Vec) -> Result { - + pub fn new( + sender: Wallet, + receiver: Wallet, + transaction_info: TransactionInfo, + signature: Vec, + ) -> Result { let verify_signature = match sender.verify_transaction_info(&transaction_info, &signature) { Ok(value) => value, Err(e) => return Err(e), }; - + if verify_signature { Ok(Self { sender, receiver, signature, - transaction_info + transaction_info, }) } else { panic!("Signature couldn't be verified"); @@ -135,13 +131,16 @@ impl Transaction { pub fn deserialize(json: String) -> Result { let tx: Transaction = match serde_json::from_str(&json) { Ok(tx) => tx, - Err(e) => return Err(TransactionDeserializeError::SerdeError(e)) + Err(e) => return Err(TransactionDeserializeError::SerdeError(e)), }; if tx.transaction_info.value <= 0.0 { return Err(TransactionDeserializeError::InsufficientFunds); } - let verified = match tx.sender.verify_transaction_info(&tx.transaction_info, &tx.signature) { + let verified = match tx + .sender + .verify_transaction_info(&tx.transaction_info, &tx.signature) + { Ok(value) => value, Err(e) => return Err(TransactionDeserializeError::OpenSSLError(e)), }; diff --git a/src/chain/utils.rs b/src/chain/utils.rs index ff879a3..77945fb 100644 --- a/src/chain/utils.rs +++ b/src/chain/utils.rs @@ -1,39 +1,38 @@ -use sha2::{Sha256, Digest}; - - +use sha2::{Digest, Sha256}; pub const PROOF_OF_WORK_DIFFICULTY: u8 = 4; -pub struct HashedData{ - hash: [u8;32], +pub struct HashedData { + hash: [u8; 32], } impl HashedData { - pub fn new(data: &[u8]) -> Self{ - let new_data: Result<[u8; 32], _> = data.try_into().map_err(|_| "Slice has a different length than 32"); + pub fn new(data: &[u8]) -> Self { + let new_data: Result<[u8; 32], _> = data + .try_into() + .map_err(|_| "Slice has a different length than 32"); match new_data { - Ok(arr) => Self{ hash: arr }, - Err(e) => panic!("Couldnt convert data to fit into HashedData object\nError: {e}") + Ok(arr) => Self { hash: arr }, + Err(e) => panic!("Couldnt convert data to fit into HashedData object\nError: {e}"), } } - pub fn from_string(str: &String) -> Self{ + pub fn from_string(str: &String) -> Self { let mut hasher = Sha256::new(); hasher.update(str.as_bytes()); let hash_result = hasher.finalize(); // GenericArray Self { - hash: hash_result.into() // GenericArray to [u8; 32] + hash: hash_result.into(), // GenericArray to [u8; 32] } } - + pub fn get_hash(&self) -> [u8; 32] { self.hash } - pub fn hash_as_string(&self) -> String{ + pub fn hash_as_string(&self) -> String { let mut return_str = String::new(); return_str.push(self.hash[0] as char); return_str } } - diff --git a/src/chain/wallet.rs b/src/chain/wallet.rs index 5bbef9a..b0f8ee2 100644 --- a/src/chain/wallet.rs +++ b/src/chain/wallet.rs @@ -1,11 +1,11 @@ +use super::transaction::TransactionInfo; use openssl::error::ErrorStack; use serde::{Deserialize, Serialize}; -use super::transaction::TransactionInfo; -use openssl::sign::{Signer, Verifier}; -use openssl::rsa::Rsa; -use openssl::pkey::{PKey, Private, Public}; use openssl::hash::MessageDigest; +use openssl::pkey::{PKey, Private, Public}; +use openssl::rsa::Rsa; +use openssl::sign::{Signer, Verifier}; fn test_sign() { let rsa = Rsa::generate(2048).unwrap(); @@ -21,9 +21,9 @@ fn test_sign() { // --- Now, we are at the verification step --- // Step 3: Extract the public key from the PKey and use it for verification - let public_key = pkey.public_key_to_pem().unwrap(); // Extract public key in PEM format + let public_key = pkey.public_key_to_pem().unwrap(); // Extract public key in PEM format let rsa_public = Rsa::public_key_from_pem(&public_key).unwrap(); // Convert back to Rsa - let pkey_public = PKey::from_rsa(rsa_public).unwrap(); // Create a PKey for public key + let pkey_public = PKey::from_rsa(rsa_public).unwrap(); // Create a PKey for public key // Step 4: Verify the signature using the public key let mut verifier = Verifier::new(MessageDigest::sha256(), &pkey_public).unwrap(); @@ -40,12 +40,15 @@ fn test_sign() { // ---------------------------------------------- WalletPK definition ---------------------------------------------- #[derive(Debug)] -pub struct WalletPK{ - private_key: PKey +pub struct WalletPK { + private_key: PKey, } impl WalletPK { - pub fn sign_transaction(&mut self, transaction_info: &TransactionInfo) -> Result, ErrorStack>{ + pub fn sign_transaction( + &mut self, + transaction_info: &TransactionInfo, + ) -> Result, ErrorStack> { let mut signer = Signer::new(MessageDigest::sha256(), &self.private_key)?; signer.update(transaction_info.to_string().as_bytes())?; Ok(signer.sign_to_vec()?) @@ -55,8 +58,8 @@ impl WalletPK { // ---------------------------------------------- Wallet definition ------------------------------------------------ #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct Wallet{ - public_key: Vec +pub struct Wallet { + public_key: Vec, } impl Wallet { @@ -65,19 +68,18 @@ impl Wallet { let rsa = Rsa::generate(bits).unwrap(); let private_key = PKey::from_rsa(rsa).unwrap(); - let public_key = private_key.public_key_to_pem().expect("Error extracting public key from private key"); + let public_key = private_key + .public_key_to_pem() + .expect("Error extracting public key from private key"); - ( - Wallet{ - public_key - }, - WalletPK{ - private_key - } - ) + (Wallet { public_key }, WalletPK { private_key }) } - pub fn verify_transaction_info(&self, transaction_info: &TransactionInfo, signature: &[u8]) -> Result { + pub fn verify_transaction_info( + &self, + transaction_info: &TransactionInfo, + signature: &[u8], + ) -> Result { let public_key = self.to_pkey(); let mut verifier = Verifier::new(MessageDigest::sha256(), &public_key)?; verifier.update(transaction_info.to_string().as_bytes())?; @@ -86,12 +88,15 @@ impl Wallet { #[allow(unused)] pub fn to_pkey(&self) -> PKey { - let rsa_public = Rsa::public_key_from_pem(&self.public_key).expect("Error extracting Rsa object from public key"); - PKey::from_rsa(rsa_public).expect("Error extracting PKey object from Rsa object") + let rsa_public = Rsa::public_key_from_pem(&self.public_key) + .expect("Error extracting Rsa object from public key"); + PKey::from_rsa(rsa_public) + .expect("Error extracting PKey object from Rsa object") } - pub fn to_vec(&self) -> Vec { self.public_key.clone() } - + pub fn to_vec(&self) -> Vec { + self.public_key.clone() + } } // ----------------------------------------------------------------------------------------------------------------- diff --git a/src/lib.rs b/src/lib.rs index d0bbc29..2ad62a0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,2 @@ pub mod chain; pub mod node; - diff --git a/src/main.rs b/src/main.rs index 95106f5..f328e4d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1 +1 @@ -fn main(){} \ No newline at end of file +fn main() {} diff --git a/src/node/logger.rs b/src/node/logger.rs index c823c7c..780d59e 100644 --- a/src/node/logger.rs +++ b/src/node/logger.rs @@ -1,4 +1,4 @@ -use std::io::{self, Write}; +use std::io::{self}; use std::sync::Mutex; pub struct Logger { @@ -22,12 +22,12 @@ impl Logger { } } - fn log_internal(&self, log: String) { + fn log_internal(&self, log: String) { let mut logs = self.logs.lock().unwrap(); // Lock the Mutex to modify the logs logs.push(log); - + if let Err(e) = std::fs::write("/tmp/foo", logs.join("\n").as_bytes()) { - eprintln!("Unable to write to log file: {e}"); + eprintln!("Unable to write to log file: {e}"); } } pub fn log_error(&self, log: String) { @@ -39,7 +39,6 @@ impl Logger { self.temp_log(format!("[LOG] {}", log)); } - pub fn read_logs(&self) -> io::Result> { let logs = self.logs.lock().unwrap(); Ok(logs.clone()) @@ -48,4 +47,4 @@ impl Logger { let logs = self.temp_logs.lock().unwrap(); Ok(logs.clone()) } -} \ No newline at end of file +} diff --git a/src/node/mod.rs b/src/node/mod.rs index 6e6e06a..96abf27 100644 --- a/src/node/mod.rs +++ b/src/node/mod.rs @@ -1,23 +1,26 @@ -mod utils; +pub mod logger; mod resolve_requests; mod thread_pool; pub mod ui; -pub mod logger; +mod utils; -use std::fmt::Error; +use crate::chain::{transaction::Transaction, Chain}; use core::panic; -use std::time::Duration; use resolve_requests::{ - methods::{HTTPParseError, HTTPRequest, HTTPResponse}, - endpoints::resolve_endpoint + endpoints::resolve_endpoint, + methods::{HTTPParseError, HTTPRequest}, }; -use crate::chain::{transaction::Transaction, Chain}; +use std::time::Duration; use thread_pool::custom_thread_pool::ThreadPool; +use std::path::PathBuf; use std::{ - collections::HashMap, io::{prelude::*, BufReader}, net::{TcpListener, TcpStream}, sync::{mpsc::Receiver, Arc, Mutex}, thread + collections::HashMap, + io::{prelude::*, BufReader}, + net::{TcpListener, TcpStream}, + sync::{mpsc::Receiver, Arc, Mutex}, + thread, }; -use std::path::PathBuf; pub struct Node { chain: Chain, @@ -25,13 +28,12 @@ pub struct Node { logger: Arc, } -use once_cell::sync::Lazy; use crate::node::logger::Logger; +use once_cell::sync::Lazy; static NUMBER_OF_THREADS_IN_THREAD_POOL: Lazy = Lazy::new(num_cpus::get); impl Node { - // these configurations should be moved to a file pub const DEFAULT_PORT: u16 = 9473; pub const REFRESH_RATE_SERVER_IN_MS: u64 = 50; @@ -42,19 +44,18 @@ impl Node { Node { chain, transactions_list: Vec::new(), - logger + logger, } } - fn parse_http_request(mut buf_reader: BufReader) -> Result { - + fn parse_http_request( + mut buf_reader: BufReader, + ) -> Result { let mut http_headers: HashMap = HashMap::new(); let http_body: Option; - let mut line = String::new(); - // reading status_line let status_line: String; @@ -66,11 +67,20 @@ impl Node { status_line = line.trim().to_string(); - let mut tokens = status_line.split(' '); + let mut tokens = status_line.split(' '); let (method, path, http_version) = ( - tokens.next().ok_or(HTTPParseError::InvalidRequestLine)?.to_string(), - tokens.next().ok_or(HTTPParseError::InvalidRequestLine)?.to_string(), - tokens.next().ok_or(HTTPParseError::InvalidRequestLine)?.to_string() + tokens + .next() + .ok_or(HTTPParseError::InvalidRequestLine)? + .to_string(), + tokens + .next() + .ok_or(HTTPParseError::InvalidRequestLine)? + .to_string(), + tokens + .next() + .ok_or(HTTPParseError::InvalidRequestLine)? + .to_string(), ); // reading headers @@ -83,7 +93,6 @@ impl Node { let line = line.trim_end().to_string(); - if line.is_empty() { break; } @@ -93,16 +102,20 @@ impl Node { } else { return Err(HTTPParseError::InvalidRequestLine); }; - } // If method is GET, return before trying to read the body if method == "GET" { - return Ok(HTTPRequest::new(None, method, PathBuf::from(path), http_version, http_headers, None)) + return Ok(HTTPRequest::new( + None, + method, + PathBuf::from(path), + http_version, + http_headers, + None, + )); } - - // getting content_length from headers let content_length = match http_headers.get("content-length") { Some(value) => match value.parse::() { @@ -113,7 +126,7 @@ impl Node { }, None => { return Err(HTTPParseError::MissingContentLength); - }, + } }; // reading body @@ -125,24 +138,28 @@ impl Node { http_body = Some(String::from_utf8_lossy(&body).to_string()); - - - - if method == "POST"{ - return Ok(HTTPRequest::new(None, method, PathBuf::from(path), http_version, http_headers, http_body)) + if method == "POST" { + return Ok(HTTPRequest::new( + None, + method, + PathBuf::from(path), + http_version, + http_headers, + http_body, + )); } Err(HTTPParseError::InvalidStatusLine) } - fn handle_connection(stream: TcpStream) -> Result, Option>{ + fn handle_connection(stream: TcpStream) -> Result, Option> { let buf_reader = BufReader::new(&stream); let mut request_object: HTTPRequest = match Self::parse_http_request(buf_reader) { Ok(value) => value, Err(e) => { return Err(Some("Error processing HTTP request: {e}".parse().unwrap())); - }, + } }; request_object.set_stream(stream); @@ -155,9 +172,7 @@ impl Node { let port: u16; if default == true { port = Self::DEFAULT_PORT; - } - - else { + } else { port = match selected_port { port if (1..=65535).contains(&port) => port, _ => { @@ -167,18 +182,17 @@ impl Node { } } - let listener = TcpListener::bind(format!("127.0.0.1:{port}")).unwrap(); - listener.set_nonblocking(true).expect("Cannot set non-blocking"); + listener + .set_nonblocking(true) + .expect("Cannot set non-blocking"); - let thread_pool = match ThreadPool::new(*NUMBER_OF_THREADS_IN_THREAD_POOL){ + let thread_pool = match ThreadPool::new(*NUMBER_OF_THREADS_IN_THREAD_POOL) { Ok(value) => value, Err(e) => panic!("{e}"), }; - - loop { // Check for termination signal if let Ok(lock) = rx.try_lock() { @@ -198,10 +212,10 @@ impl Node { _ => {} }; }) - }, + } Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { thread::sleep(Duration::from_millis(Self::REFRESH_RATE_SERVER_IN_MS)); - }, + } Err(e) => { eprintln!("Error accepting connection: {}", e); break; @@ -214,5 +228,3 @@ impl Node { drop(thread_pool); } } - - diff --git a/src/node/resolve_requests.rs b/src/node/resolve_requests.rs index e03235f..df33a9d 100644 --- a/src/node/resolve_requests.rs +++ b/src/node/resolve_requests.rs @@ -20,9 +20,11 @@ pub mod endpoints { impl std::error::Error for HTTPResponseError {} } mod helpers { - use std::path::PathBuf; use crate::node::resolve_requests::endpoints::errors::HTTPResponseError; - use crate::node::resolve_requests::methods::{Content, GETData, HTTPRequest, HTTPResponse, Method, POSTData}; + use crate::node::resolve_requests::methods::{ + Content, GETData, HTTPRequest, HTTPResponse, ImageType, Method, POSTData, + }; + use std::path::PathBuf; const STATIC_FOLDER: &str = "src/node/static/"; @@ -31,21 +33,25 @@ pub mod endpoints { pub type POSTFunc = fn(&POSTData) -> HTTPResult; pub type GETFunc = fn(&GETData) -> HTTPResult; - pub fn path_not_found() -> HTTPResult{ - Err(HTTPResponseError::InvalidPath(Some("Path not found".to_string()))) + pub fn path_not_found() -> HTTPResult { + Err(HTTPResponseError::InvalidPath(Some( + "Path not found".to_string(), + ))) } - pub fn method_not_allowed() -> HTTPResult{ + pub fn method_not_allowed() -> HTTPResult { Err(HTTPResponseError::InvalidMethod(None)) } - pub trait Handler { fn call(&self, request: &HTTPRequest) -> HTTPResult; } + pub trait Handler { + fn call(&self, request: &HTTPRequest) -> HTTPResult; + } // Implement the trait for GETFunc impl Handler for GETFunc { fn call(&self, request: &HTTPRequest) -> HTTPResult { match request.get_method() { Method::GET(data) => self(&data), - _ => method_not_allowed() + _ => method_not_allowed(), } } } @@ -55,56 +61,57 @@ pub mod endpoints { fn call(&self, request: &HTTPRequest) -> HTTPResult { match request.get_method() { Method::POST(data) => self(&data), - _ => method_not_allowed() + _ => method_not_allowed(), } } } - pub fn return_image(path: &str) -> HTTPResult { - Ok(HTTPResponse::OK(Some(Content::Image(PathBuf::from(STATIC_FOLDER.to_owned() + path))))) + pub fn return_image(path: &str, image_type: ImageType) -> HTTPResult { + Ok(HTTPResponse::OK(Some(Content::Image( + PathBuf::from(STATIC_FOLDER.to_owned() + path), + image_type, + )))) } pub fn return_html(path: &str) -> HTTPResult { - Ok(HTTPResponse::OK(Some(Content::HTML(PathBuf::from(STATIC_FOLDER.to_owned() + path))))) + Ok(HTTPResponse::OK(Some(Content::HTML(PathBuf::from( + STATIC_FOLDER.to_owned() + path, + ))))) } pub fn post(request: HTTPRequest, f: POSTFunc) -> HTTPResult { let method = request.get_method(); if let Method::POST(data) = method { f(data) - } - else { + } else { Err(HTTPResponseError::InvalidMethod(None)) } } pub fn get(request: HTTPRequest, f: GETFunc) -> HTTPResult { if let Method::GET(data) = request.get_method() { f(data) - } - else { + } else { Err(HTTPResponseError::InvalidMethod(None)) } } - pub fn get_post( - request: HTTPRequest, - get: GETFunc, - post: POSTFunc - ) -> HTTPResult { + pub fn get_post(request: HTTPRequest, get: GETFunc, post: POSTFunc) -> HTTPResult { match request.get_method() { Method::POST(data) => post(data), - Method::GET(data) => get(data) + Method::GET(data) => get(data), } } } - use std::collections::HashMap; use super::methods::{HTTPRequest, HTTPResponse, Method}; use helpers::*; + use std::collections::HashMap; mod endpoints { use crate::chain::transaction::Transaction; use crate::node::resolve_requests::endpoints::errors::HTTPResponseError; - use crate::node::resolve_requests::endpoints::helpers::{return_html, return_image, HTTPResult}; - use crate::node::resolve_requests::methods::{GETData, POSTData}; + use crate::node::resolve_requests::endpoints::helpers::{ + return_html, return_image, HTTPResult, + }; + use crate::node::resolve_requests::methods::{GETData, ImageType, POSTData}; pub fn index(data: &GETData) -> HTTPResult { return_html("index.html") @@ -118,16 +125,19 @@ pub mod endpoints { return Err(HTTPResponseError::InvalidBody(None)); } }; - Err(HTTPResponseError::InternalServerError(Some("Something went wrong on the submit_transaction function".parse().unwrap()))) - + Err(HTTPResponseError::InternalServerError(Some( + "Something went wrong on the submit_transaction function" + .parse() + .unwrap(), + ))) } pub fn favicon(data: &GETData) -> HTTPResult { - return_image("favicon.ico") + return_image("fav.ico", ImageType::ICO) } } - use endpoints::*; use crate::node::resolve_requests::endpoints::errors::HTTPResponseError; + use endpoints::*; pub fn resolve_endpoint(mut request: HTTPRequest) -> Result, Option> { /* @@ -137,16 +147,13 @@ pub mod endpoints { need it */ - fn handle_error(request: &mut HTTPRequest, response: HTTPResponse, log: Option<&str>) -> Result<(), String> { - request.response(response); - Err(log.map(String::from).unwrap_or_default()) - } + fn add_endpoint<'a>( path: &'a str, endpoints: &mut HashMap<&'a str, HashMap<&str, Box>>, get: Option, - post: Option + post: Option, ) { let mut methods: HashMap<&str, Box> = HashMap::new(); @@ -158,74 +165,77 @@ pub mod endpoints { } endpoints.insert(path, methods); - } - let mut endpoints: HashMap<&str, HashMap<&str, Box>> = HashMap::new(); add_endpoint("/", &mut endpoints, Some(index), None); add_endpoint("/favicon.ico", &mut endpoints, Some(favicon), None); - let (path, method) = match request.get_method() { Method::GET(data) => (data.path.clone(), "GET"), Method::POST(data) => (data.path.clone(), "POST"), }; - let r = match endpoints.get(path.to_str().unwrap()) { + let r = match endpoints.get(path.to_str().unwrap()) { Some(methods) => match methods.get(method) { Some(handler) => handler.call(&request), None => method_not_allowed(), }, None => path_not_found(), }; - + let log = String::new(); match r { - Ok(value) => {request.response(value); Ok(Some("Success".to_string()))} - Err(e) => { - match e { - HTTPResponseError::InvalidMethod(log) => { - request.response(HTTPResponse::InvalidMethod); - Err(log) - }, - HTTPResponseError::InvalidPath(log) => { - request.response(HTTPResponse::BadRequest); - Err(log) - }, - HTTPResponseError::InvalidBody(log) => { - request.response(HTTPResponse::BadRequest); - Err(log) - }, - HTTPResponseError::InternalServerError(log) => { - request.response(HTTPResponse::InternalServerError); - Err(log) - }, - HTTPResponseError::BadRequest(log) => { - request.response(HTTPResponse::BadRequest); - Err(log) - }, - } + Ok(value) => { + request.response(value); + Ok(Some("Success".to_string())) } + Err(e) => match e { + HTTPResponseError::InvalidMethod(log) => { + request.response(HTTPResponse::InvalidMethod); + Err(log) + } + HTTPResponseError::InvalidPath(log) => { + request.response(HTTPResponse::BadRequest); + Err(log) + } + HTTPResponseError::InvalidBody(log) => { + request.response(HTTPResponse::BadRequest); + Err(log) + } + HTTPResponseError::InternalServerError(log) => { + request.response(HTTPResponse::InternalServerError); + Err(log) + } + HTTPResponseError::BadRequest(log) => { + request.response(HTTPResponse::BadRequest); + Err(log) + } + }, } } } pub mod methods { use core::panic; + use serde_json::json; use std::collections::HashMap; - use std::{fmt, fs}; - use std::net::TcpStream; use std::io::prelude::*; - use std::path::{Path, PathBuf}; - use serde_json::json; + use std::net::TcpStream; + use std::path::PathBuf; + use std::{fmt, fs}; + pub enum ImageType { + PNG, + ICO, + JPEG, + } pub enum Content { - HTML(PathBuf), // path to HTML file + HTML(PathBuf), // path to HTML file JSON(serde_json::Value), - Image(PathBuf), // path to image file + Image(PathBuf, ImageType), // path to image file PlainText(String), } pub enum HTTPResponse { @@ -235,24 +245,22 @@ pub mod methods { InternalServerError, } - #[derive(Debug, Clone)] pub struct GETData { - pub path: PathBuf + pub path: PathBuf, } #[derive(Debug, Clone)] pub struct POSTData { pub path: PathBuf, - pub body: Option + pub body: Option, } #[derive(Debug, Clone)] pub enum Method { GET(GETData), - POST(POSTData) + POST(POSTData), } - struct Response { status: u16, content_type: &'static str, @@ -271,7 +279,7 @@ pub mod methods { 200 => "OK", 400 => "Bad Request", 405 => "Method Not Allowed", - _ => "Unknown", + _ => "Unknown", }, self.content_type, self.body.len(), @@ -284,7 +292,7 @@ pub mod methods { Self { status, content_type, - body + body, } } } @@ -299,31 +307,34 @@ pub mod methods { impl HTTPRequest { pub fn new( - stream: Option, - method: String, path: PathBuf, - http_version: String, headers: - HashMap, - body: Option + stream: Option, + method: String, + path: PathBuf, + http_version: String, + headers: HashMap, + body: Option, ) -> HTTPRequest { HTTPRequest { stream, method: match method.as_str() { - "GET" => Method::GET(GETData {path}), - "POST" => Method::POST(POSTData {path, body}), - _ => panic!("Unavailable method") + "GET" => Method::GET(GETData { path }), + "POST" => Method::POST(POSTData { path, body }), + _ => panic!("Unavailable method"), }, http_version, - headers - + headers, } } - pub fn set_stream(&mut self, stream: TcpStream) { self.stream = Some(stream) } + pub fn set_stream(&mut self, stream: TcpStream) { + self.stream = Some(stream) + } - pub fn get_method(&self) -> &Method { &self.method } + pub fn get_method(&self) -> &Method { + &self.method + } fn make_response(status: HTTPResponse, accept: Option<&str>) -> std::io::Result { - fn response_ok_content(content: Content) -> std::io::Result { match content { Content::HTML(path) => Ok(Response { @@ -336,9 +347,13 @@ pub mod methods { content_type: "application/json", body: serde_json::to_vec(&value)?, }), - Content::Image(path) => Ok(Response { + Content::Image(path, img_type) => Ok(Response { status: 200, - content_type: "image/png", + content_type: match img_type { + ImageType::PNG => "image/png", + ImageType::ICO => "image/vnd.microsoft.icon", + ImageType::JPEG => "image/jpeg", + }, body: fs::read(path)?, }), Content::PlainText(text) => Ok(Response { @@ -350,14 +365,20 @@ pub mod methods { } fn response_ok_no_content(a: &str) -> std::io::Result { if a.contains("text/html") { - return Ok(Response::new(200, "text/html", fs::read("static/200.html").unwrap_or("NOT FOUND".into()))); - } - - else if a.contains("application/json") { + return Ok(Response::new( + 200, + "text/html", + fs::read("static/200.html").unwrap_or("NOT FOUND".into()), + )); + } else if a.contains("application/json") { let j = json!({ "msg": "OK", "status": 200 }); - return Ok(Response::new(200, "application/json", serde_json::to_vec(&j)?)) + return Ok(Response::new( + 200, + "application/json", + serde_json::to_vec(&j)?, + )); } - + Ok(Response::new(200, "text", "Success".into())) } @@ -371,45 +392,70 @@ pub mod methods { } HTTPResponse::InvalidMethod => { if accept.unwrap_or("").contains("text/html") { - Ok(Response::new(405, "text/html", fs::read("static/405.html").unwrap_or_else(|_| b"405 Invalid Method".to_vec()))) + Ok(Response::new( + 405, + "text/html", + fs::read("static/405.html") + .unwrap_or_else(|_| b"405 Invalid Method".to_vec()), + )) } else { - Ok(Response::new(405, "text/plain", b"405 Invalid Method".to_vec())) + Ok(Response::new( + 405, + "text/plain", + b"405 Invalid Method".to_vec(), + )) } } HTTPResponse::BadRequest => { if accept.unwrap_or("").contains("text/html") { - Ok(Response::new(400, "text/html", fs::read("static/400.html").unwrap_or_else(|_| b"400 Bad Request".to_vec()))) + Ok(Response::new( + 400, + "text/html", + fs::read("static/400.html") + .unwrap_or_else(|_| b"400 Bad Request".to_vec()), + )) } else { - Ok(Response::new(400, "text/plain", b"400 Bad Request".to_vec())) + Ok(Response::new( + 400, + "text/plain", + b"400 Bad Request".to_vec(), + )) } } HTTPResponse::InternalServerError => { if accept.unwrap_or("").contains("text/html") { - Ok(Response::new(500, "text/html", fs::read("static/500.html").unwrap_or_else(|_| b"500 Internal Server Error".to_vec()))) + Ok(Response::new( + 500, + "text/html", + fs::read("static/500.html") + .unwrap_or_else(|_| b"500 Internal Server Error".to_vec()), + )) } else { - Ok(Response::new(500, "text/plain", b"500 Internal Server Error".to_vec())) + Ok(Response::new( + 500, + "text/plain", + b"500 Internal Server Error".to_vec(), + )) } } } } - + pub fn response(&mut self, status: HTTPResponse) { let accept = self.headers.get("Accept").map(|s| s.as_str()); let resp = Self::make_response(status, accept).unwrap(); - if let Err(e) = self.stream.as_mut().unwrap().write_all(&resp.to_bytes()){ + if let Err(e) = self.stream.as_mut().unwrap().write_all(&resp.to_bytes()) { eprintln!("Error writing response to stream: {}", e); } - } } - #[derive(Debug)] pub enum HTTPParseError { InvalidStatusLine, InvalidRequestLine, MissingFields, - MissingContentLength + MissingContentLength, } impl fmt::Display for HTTPParseError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -417,13 +463,11 @@ pub mod methods { HTTPParseError::InvalidRequestLine => write!(f, "Invalid request line"), HTTPParseError::MissingFields => write!(f, "Missing required fields"), HTTPParseError::InvalidStatusLine => write!(f, "Invalid status line"), - HTTPParseError::MissingContentLength => write!(f, "Missing content-length field in headers"), + HTTPParseError::MissingContentLength => { + write!(f, "Missing content-length field in headers") + } } } } impl std::error::Error for HTTPParseError {} - - - - -} \ No newline at end of file +} diff --git a/src/node/static/fav.ico b/src/node/static/fav.ico new file mode 100644 index 0000000000000000000000000000000000000000..29543c549ecdbc9cb68e182d14270ab63972cf55 GIT binary patch literal 4286 zcmb7IX>3&28GWn6=FBM7KPvv7=|u@#*Dz&dTG_@&)^%n6*2CSq;5d@M#2< z$72WhZdga}ec5Ys$jQ=g^UlQhZ*%9+1B`2JAHdvwy)d@-z})e0l(~}asZl*gxHpQ{ zYyIH2VxRa%IN7{xJY`cBKl`10 zeI^pc*duX_J<9QL6l0p{i`0G@C+vtobn$N_B|9&&<00>@rpxzWi4Nimc3@90iko5x zHbiJE{a>Fg^byz0oxS8pe&ksENotMXiw^1>pza{$Np{P+tED{w|IR2Z>Johao(T0) zm&hZy4|xQL;jh>a@2@Ig&R>Jj+7l>g7Ck+%gd26WFpu~rv3(o|M`vs(ZzA(q@J!n2 zr@cDkX$TitdkhDOk6(Dsdk=1>2QH@*IipPIue3Ebatb{Ai#K><{1i*?HhN+kM|_(q6M6ia=vj<-EQtY4ao4VqNJ6n=IvBWPwU4#)Yz6$Gi8|ilk3Z~6g_(hdR5L$g$<&f;tYni`|)V-VevOA1r z$76(l*kdF8Lt04kjMzm@)X&;habPTjkBG@{Z4Q3=amVz_QA={e$AlItvykM_9yGb#B@GOLqoyz*)acpnZ2qL#=0{wYp$hu6Mrjxz?w9PEpqb4K)MdL zUhLCM`p2M@(D%aKV9nd2vnCAsN4Nwie%hYHJoo=$Gs-regTvtj&o>lK|1paGaRKJ6 zxzNA!1DKU93g4m+q<0r9A17_7Ip3Gg!Ih`)rtw3K?4{yw;@cwF!o~Wy4f@8j2+_9U zzcI#jU5rCDTn-n-fl-rYAh4hcIeIDbo}L1I)48Pmv`6YRSV!)xE0gxDJQ+`)P30%z z3SVljXeaRnD?>uN@4#HN4h7G@2=COHDEQ@DC|Y+K)^Fb~ zg;k6-eJwGgJqCM(Rnv=r^hN3}KNcHq$G`k!%xUdq&F$s;y4!ru@3==qa-!t->*1k| z?hyvuE)Q~x$D(k`ci~|S3TMv2=&fI%xV9a^#hYNw`7?qm8c;;ecKk$-!TLA1gyB!& zFF78|weeS-iMx{cjPdsF6fR@Hd}{S2db+(1di zURaB(S?k^8D?R&R?${jU46r^X2kMixm92Nvcbu(P?sSlOC|ugh~Sb#CTa&ACX;g+942 zsdJ#UdmzV>vqJD$`+7Awr0PNJ&wqjV?pzeD*o`3T^r_W{=zAANOqh)PanHfya>87` zlXkJsDr|{yhA`js>O0VDZYldk_sNb#I{zbyA?Fz0Gfd&Y9Pqv0p3E8B|0#TltJG*w zV;E&`egKbA0{8F&gx;v)87)3z4rSt|<~q*^Uu7o((u>sh5wPdk_aC_fy=B*rH3CHH;R zx8YsWk$NA+veq~u(`S(Q!Fev680l}0DGp*|LB&<}F`gS~|K}iBz6HTW8&vP{&3_l6 zdfHTflX{7jg>Ug$^vKy$Sn+ShHOWub&)!NHf=QA17f>8>{+6GP!{pu3pjb6s@Kt=C zya&Ddn(A@h89zeqxNpMa&Vl*vp+_)n4$5}MLhfJsU#_z!chi?ij*DAja{fy1P50jv z5522zFgWQzJQ)XaFgJD3=T|Uh^(o{%J6-hvmunb;Z+^%#^2`2CVka>S>DSxB@UCiy z@nKh*=R}@AUGc#=#ItP|`(>WK2ex!2Ci{7Bm0a*kK2~=kf9W}ttZhSa)hU#0zQ{9x z+?2lskHR=hjp@?2c~>^qccXCmRrogX9FlWQ_A;+qZX{kNR=V$UJ}QN;>X38gm*#8z zPg$%7;b2NmNdJE4D!k;Ouc1FTcf;6v8|F6luEz$)W!RRf|8aBSl*!A(-%wiXtwh_hX5L{kztAvv1IIpre0M)%iZWemaI% zc#oTXEQZ&~t&(d8ZuC#Sd3R9s$k`@#Wahc8|9=_fY|8vr=C(eWi%728qg~JTX!}rd zJ$=th?f(*fEaAEm-k|k7(6qdMO)KctwEQ-Xl-z@qshZ|0Pl|)jj|ks6vPF~s<&ctt f{Pr15%lSF=#x<>EP}7!llsnL;4Z}dW^EdwiYeUQ@ literal 0 HcmV?d00001 diff --git a/src/node/static/fav.png b/src/node/static/fav.png new file mode 100644 index 0000000000000000000000000000000000000000..b36ffd23e0a1b794595257f45ae7f8a733c0867d GIT binary patch literal 2252 zcmV;-2s8JIP)y{D4^000SaNLh0L01FcU01FcV0GgZ_00007 zbV*G`2k8VJ3lurUC)PRu00|bMAZYZ7-dHzK7B$t(Av7 zil9K~he8ko;v+ybk%Ul?M4|>0qlCn$Ajk(05{Qqqn2?YtCJjarNHjs7wx*P}l&Lcv z+Rk)l+Iig9IcM+XhjY%%bo#)ZoO^QbJ!h}A{_DT~Yi;2)OH~U%C?YQXZU6;j)?*%E z)BD^Ni-@Xf4)8^!neCJ9nJqbu@h%aWoMqSm4#2jKdhv<98{PQ6cWt{5!U0gh1WS?9 z`Ize3D+-IRds|iWKnTcOgL9Y6F)k!;Y5F}7ktjU&-il~o`^WM43y75j3~{LHsoRMl zwL8c!yK8m++)F>=Q{SEKlLlH+#bm?>?yUF%$uD#4VUQs3I?6S}!fJ#XOU3|EP#mIQBSU-~CXYsx7aL052F0MFls>cL ztHGknUKWusb)X`%ygA3VhI8$=ZanPbLm)79SZNMBCQ22)|J)dF>}(Sl$I}n>@Re%- zjZaQrnp1U(Yr#~%B|p!juqZJj-cm|ey>h=-?s+Cd!l-I3VZR=4?|bI3vjZTG))=%$ zEa$BBc;TN-Uf(&zZJR4>Sm|@`FGkq)QG8}X3HW89>jIx>Lg|bq2&p|pYtPe9fqGfG z4KM*nbbN17N1g!{G=izY4)f3Vk-X3T{tmt;+D`AFwrYF`Yh4NhqYA1$b_a64Zb|3C<;stLG`=a{PE*D%5viO|KF`n?ic)ZtaNy-*X~r-jVqG*b6(4&xF=oMa9J!ig z8AUR1HOKr?o13pGGSaB=$`d1F8UhgHYR19Id0liV$0`qeE0EDhEISmh8JHO#}mOS z{Q3uVzIKg4P(%`L8Y44mp<<>Tm;uT`U=Wvew`BRI&8=6%^`FhtXj@`irqNPvzdA>~ zyo9?R8N>mbFDY{C^%Z{pn=yXBqs152=U7xXs3z4RIS+M7d5T||)`63j5lQ>}%%y2r z&9A_c1p#~dBd-3=F<#phanTAdQ8~4A&-1zVBEy$&sL@@|G1V|>U;<7cXVT6Ero1dV z2?0St4dfJU3nn<(H+7jqmKBTg7=cH(O;De2Gc<81o<=SFgZo0gh29Ovl`O5MhrCLlSEI~by zXcZv9udkZ!1*od{-CGl>iDD%0gH5bC=WtG^eG$M@$Bmb^xN}PvS8epT{kkBTOaQ@S z_h7&W1I6SWClEVKxqu5pz~L{rA5*BhlQ@uDeoH;cmg1>m)50_=7-jmJ;imAwEoHuS zLz&)l15m-G8GZP7P44^Ear%cPK`pI+)nvay?=9zxNIPrCA|mQ{uNyb@t*C;FEhcZ! z=*+{Us-xgHxO|P}yt)t=wpM;xP4l@q5gG2|cxlCmvY9fq{_w9_t77_~QBk^)A7$6mgvp zQNh>cls4X16)}3IvD}Ley%V>0-i%Aj&jbcymzLw%5b=4xPuaCEq*C^|cwL?~y@G9Z z(veLmUA%+b(#vup5}oZ_(A_@px1sh2cc#OhA~_$=_i&LzolRw920mrNy^PN zL$uMH5jO26fARC=mv5>7&6$)Zh|3ucB>~{#6Q9J*_aDbjZc9W8HxsQ9#7_-dhlyQU zLo>2&s^6q|$%FXw7WV+7>A2?puanU<8tPVsgFE-O_HRQcwx?o}oC!`Dhcm3Y?jX13 zF7k^nx)3o3viT3tS;syjBJLCp%z?;=h|BKr@xv00?3HM2-;bH>`-!#(5``-+!<1LN z;n&w}@w?ZCer;j4N06NXrXf$yA7|r0A%&`a7GyjTu_<4&qq?TuTW23|XT4Juk%>8k aU;97ve=6$!#r@v^0000 fmt::Result { match self { - PoolCreationError::TooFewThreads => write!(f, "You must use at least one thread on the pool"), - PoolCreationError::TooManyThreads => write!(f, "You must use less than {} threads on the pool", u32::MAX), + PoolCreationError::TooFewThreads => { + write!(f, "You must use at least one thread on the pool") + } + PoolCreationError::TooManyThreads => { + write!(f, "You must use less than {} threads on the pool", u32::MAX) + } } } } impl std::error::Error for PoolCreationError {} - - - struct Worker { id: usize, - handle: thread::JoinHandle<()> + handle: thread::JoinHandle<()>, } impl Worker { @@ -41,19 +45,15 @@ pub mod custom_thread_pool { } } }); - Worker { - id, - handle: thread - } + Worker { id, handle: thread } } } - type Job = Box; pub struct ThreadPool { workers: Vec, - sender: Option> + sender: Option>, } impl ThreadPool { @@ -66,9 +66,10 @@ pub mod custom_thread_pool { /// The `new` function will panic if the size is zero. pub fn new(size: usize) -> Result { if size < 1 { - return Err(PoolCreationError::TooFewThreads) - } else if size > usize::MAX { // I think this is unreachable - return Err(PoolCreationError::TooManyThreads) + return Err(PoolCreationError::TooFewThreads); + } else if size > usize::MAX { + // I think this is unreachable + return Err(PoolCreationError::TooManyThreads); } let (sender, receiver) = mpsc::channel(); @@ -81,7 +82,10 @@ pub mod custom_thread_pool { workers.push(Worker::new(id, Arc::clone(&receiver))); } - Ok(ThreadPool { workers, sender: Some(sender) }) + Ok(ThreadPool { + workers, + sender: Some(sender), + }) } /// Receives a closure compatible with the thread::spawn() function. @@ -97,14 +101,13 @@ pub mod custom_thread_pool { impl Drop for ThreadPool { fn drop(&mut self) { println!("Dropping threadPool"); - + drop(self.sender.take()); - + for worker in self.workers.drain(..) { println!("Shutting down worker {}", worker.id); worker.handle.join().unwrap(); } } } - -} \ No newline at end of file +} diff --git a/src/node/ui.rs b/src/node/ui.rs index 1b3adf4..749cde4 100644 --- a/src/node/ui.rs +++ b/src/node/ui.rs @@ -1,14 +1,14 @@ -use std::sync::Arc; +use crate::node::logger::Logger; use color_eyre::Result; use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers}; use ratatui::{ - DefaultTerminal, Frame, + prelude::*, style::Stylize, text::Line, widgets::{Block, Paragraph}, - prelude::* + DefaultTerminal, Frame, }; -use crate::node::logger::Logger; +use std::sync::Arc; /// The main application which holds the state and logic of the application. @@ -16,7 +16,7 @@ pub struct App { /// Is the application running? running: bool, port: u16, - logger: Arc + logger: Arc, } impl App { @@ -25,7 +25,7 @@ impl App { Self { running: true, port, - logger + logger, } } @@ -42,24 +42,19 @@ impl App { fn render(&mut self, frame: &mut Frame) { let layout = Layout::default() .direction(Direction::Horizontal) - .constraints(vec![ - Constraint::Percentage(50), - Constraint::Percentage(50), - ]) + .constraints(vec![Constraint::Percentage(50), Constraint::Percentage(50)]) .split(frame.area()); let main_title = Line::from(" CleytoCoin node is running! ") .bold() .blue() .centered(); - let logs_title = Line::from(" LOGS ") - .bold() - .blue() - .centered(); + let logs_title = Line::from(" LOGS ").bold().blue().centered(); let port = self.port; - let text = format!("Node running in port {port} - \n\nPress `Esc`, `Ctrl-C` or `q` to stop running."); - + let text = format!( + "Node running in port {port} + \n\nPress `Esc`, `Ctrl-C` or `q` to stop running." + ); frame.render_widget( Paragraph::new(text) @@ -75,7 +70,6 @@ impl App { ) } - fn handle_crossterm_events(&mut self) -> Result<()> { match event::read()? { // it's important to check KeyEventKind::Press to avoid handling key release events @@ -87,7 +81,6 @@ impl App { Ok(()) } - fn on_key_event(&mut self, key: KeyEvent) { match (key.modifiers, key.code) { (_, KeyCode::Esc | KeyCode::Char('q')) @@ -97,8 +90,7 @@ impl App { } } - fn quit(&mut self) { self.running = false; } -} \ No newline at end of file +} diff --git a/src/node/utils.rs b/src/node/utils.rs index 2ac9e8f..2c15df4 100644 --- a/src/node/utils.rs +++ b/src/node/utils.rs @@ -1,7 +1,6 @@ // use serde::{Deserialize, Serialize}; - -/* +/* pub const RESPONSES_FOLDER: &str = "src/node/responses/"; pub const SUCCESS_FILE_JSON: &str = "sucess_response.json"; pub const ERROR_FILE_JSON: &str = "error_response.json"; @@ -17,4 +16,4 @@ pub struct SuccessJson { pub struct ErrorJson { msg: String, status_code: u8 -} */ \ No newline at end of file +} */ diff --git a/tests/create_block_and_add_chain.rs b/tests/create_block_and_add_chain.rs index ecfcc92..324c545 100644 --- a/tests/create_block_and_add_chain.rs +++ b/tests/create_block_and_add_chain.rs @@ -1,5 +1,10 @@ -use cleyto_coin::chain::{Chain, wallet::Wallet, block::Block, transaction::{Transaction, TransactionInfo}}; use chrono::Utc; +use cleyto_coin::chain::{ + block::Block, + transaction::{Transaction, TransactionInfo}, + wallet::Wallet, + Chain, +}; #[test] fn create_block_and_add_chain() { diff --git a/tests/create_transaction.rs b/tests/create_transaction.rs index 6933924..0699375 100644 --- a/tests/create_transaction.rs +++ b/tests/create_transaction.rs @@ -1,6 +1,6 @@ -use cleyto_coin::chain::transaction::{TransactionInfo, Transaction}; -use cleyto_coin::chain::wallet::Wallet; use chrono::Utc; +use cleyto_coin::chain::transaction::{Transaction, TransactionInfo}; +use cleyto_coin::chain::wallet::Wallet; #[test] fn create_transaction() { @@ -12,16 +12,24 @@ fn create_transaction() { Ok(signed_hashed_message) => signed_hashed_message, _ => panic!("error while signing transaction"), }; - println!("Transaction signature (signed using the wallet_pk):\n{:?}", signature); + println!( + "Transaction signature (signed using the wallet_pk):\n{:?}", + signature + ); // this will also be verified by the Transaction::new(); - if wallet_sender.verify_transaction_info(&transactioninfo, &signature).unwrap() == true { + if wallet_sender + .verify_transaction_info(&transactioninfo, &signature) + .unwrap() + == true + { println!("transaction verified (by the wallet)"); } else { println!("transaction not verified"); } - let transaction: Transaction = Transaction::new(wallet_sender, wallet_receiver, transactioninfo, signature).unwrap(); + let transaction: Transaction = + Transaction::new(wallet_sender, wallet_receiver, transactioninfo, signature).unwrap(); println!("transaction.to_string(): {}", transaction.to_string()); } diff --git a/tests/requests.rs b/tests/requests.rs index 59923ee..62c50d9 100644 --- a/tests/requests.rs +++ b/tests/requests.rs @@ -1,11 +1,14 @@ use cleyto_coin::node; -use std::{sync::{mpsc, Arc, Mutex}, thread}; +use std::{ + sync::{mpsc, Arc, Mutex}, + thread, +}; -use reqwest::blocking::Client; -use serde::Serialize; -use rand::Rng; use cleyto_coin::chain::Chain; use cleyto_coin::node::logger::Logger; +use rand::Rng; +use reqwest::blocking::Client; +use serde::Serialize; #[derive(Serialize)] struct RandomData { @@ -16,7 +19,9 @@ struct RandomData { fn post_request() -> Result<(), Box> { let mut rng = rand::rng(); let data = RandomData { - message: (0..10).map(|_| rng.sample(rand::distr::Alphanumeric) as char).collect(), + message: (0..10) + .map(|_| rng.sample(rand::distr::Alphanumeric) as char) + .collect(), value: rng.random_range(1..=1000), }; @@ -27,12 +32,11 @@ fn post_request() -> Result<(), Box> { .send()?; assert_eq!(res.status(), 200); - + Ok(()) } fn get_request() -> Result<(), Box> { - let client = Client::new(); let res = client .get(format!("http://localhost:{}", node::Node::DEFAULT_PORT)) @@ -44,7 +48,7 @@ fn get_request() -> Result<(), Box> { Ok(()) } -fn post_and_get_request(){ +fn post_and_get_request() { /* let (tx, rx) = mpsc::channel::<()>(); // Channel to kill thread @@ -66,7 +70,6 @@ fn post_and_get_request(){ Err(_) => println!("Get failed"), }; } - /* tx.send(()).expect("Failed to send termination signal."); @@ -74,7 +77,7 @@ fn post_and_get_request(){ server.join().expect("Server thread panicked."); */ } -fn thread_post(){ +fn thread_post() { let url = "http://localhost:9473/"; // Replace with your server URL let client = Client::new(); @@ -87,13 +90,16 @@ fn thread_post(){ let handle = thread::spawn(move || { let body = format!(r#"{{"message": "Hello #{}"}}"#, i); - match client.post(&url) + match client + .post(&url) .header("Content-Type", "application/json") .body(body) .send() { - - Ok(resp) => {assert_eq!(resp.status(), 200);println!("Thread #{i}: {}", resp.status())}, + Ok(resp) => { + assert_eq!(resp.status(), 200); + println!("Thread #{i}: {}", resp.status()) + } Err(err) => eprintln!("Thread #{i} failed: {err}"), } }); @@ -106,7 +112,7 @@ fn thread_post(){ } } -fn thread_get(){ +fn thread_get() { let url = "http://localhost:9473/"; // Replace with your server URL let client = Client::new(); @@ -119,12 +125,15 @@ fn thread_get(){ let handle = thread::spawn(move || { let body = format!(r#"{{"message": "Hello #{}"}}"#, i); - match client.get(&url) + match client + .get(&url) .header("Content-Type", "application/json") .send() { - - Ok(resp) => {assert_eq!(resp.status(), 200);println!("Thread #{i}: {}", resp.status())}, + Ok(resp) => { + assert_eq!(resp.status(), 200); + println!("Thread #{i}: {}", resp.status()) + } Err(err) => eprintln!("Thread #{i} failed: {err}"), } }); @@ -138,7 +147,7 @@ fn thread_get(){ } #[test] -fn main(){ +fn main() { let (tx, rx) = mpsc::channel::<()>(); // Channel to kill thread @@ -151,13 +160,11 @@ fn main(){ node.run(true, rx, 0); }); - thread_post(); thread_get(); - tx.send(()).expect("Failed to send termination signal."); // Wait for the server thread to finish (this will block until the server thread terminates) server.join().expect("Server thread panicked."); -} \ No newline at end of file +} diff --git a/tests/sign_and_verify_transactioninfo.rs b/tests/sign_and_verify_transactioninfo.rs index bae4307..11831a4 100644 --- a/tests/sign_and_verify_transactioninfo.rs +++ b/tests/sign_and_verify_transactioninfo.rs @@ -1,5 +1,8 @@ -use cleyto_coin::chain::{transaction::{Transaction, TransactionInfo}, wallet::Wallet}; use chrono::Utc; +use cleyto_coin::chain::{ + transaction::{Transaction, TransactionInfo}, + wallet::Wallet, +}; #[test] fn sign_and_verify_transactioninfo() { @@ -10,9 +13,16 @@ fn sign_and_verify_transactioninfo() { Ok(signed_hashed_message) => signed_hashed_message, _ => panic!("error while signing transaction"), }; - println!("Transaction signature (signed using the wallet_pk):\n{:?}", signature); - - if wallet.verify_transaction_info(&transactioninfo, &signature).unwrap() == true { + println!( + "Transaction signature (signed using the wallet_pk):\n{:?}", + signature + ); + + if wallet + .verify_transaction_info(&transactioninfo, &signature) + .unwrap() + == true + { println!("transaction verified (by the wallet)"); } else { println!("transaction not verified"); @@ -36,7 +46,10 @@ fn serialize_and_deserialize_transaction() { println!("serialized_transaction: \n{serialized_transaction}"); let deserialized_transaction = match Transaction::deserialize(serialized_transaction) { - Ok(value) => {println!("Success deserializing transaction"); value}, + Ok(value) => { + println!("Success deserializing transaction"); + value + } Err(_) => panic!("Error deserializing transaction"), }; -} \ No newline at end of file +} From 05008afd6093aa2e98efde5792687792bfd46de5 Mon Sep 17 00:00:00 2001 From: dantee-e Date: Wed, 30 Apr 2025 14:18:47 +0200 Subject: [PATCH 22/40] Added time to logger --- src/bin/node.rs | 3 ++- src/node/logger.rs | 11 +++++--- src/node/mod.rs | 2 +- src/node/resolve_requests.rs | 51 ++++++++++++++++++++++++++++++++---- 4 files changed, 56 insertions(+), 11 deletions(-) diff --git a/src/bin/node.rs b/src/bin/node.rs index 60324f1..161354d 100644 --- a/src/bin/node.rs +++ b/src/bin/node.rs @@ -20,7 +20,8 @@ fn main() -> color_eyre::Result<()> { let mut node = node::Node::new(Chain::new(), logger_clone_for_node); node.run(true, rx, 0); }); - + + // TODO Loggger nao scrolla color_eyre::install()?; let terminal = ratatui::init(); let result = App::new(Arc::clone(&logger), node::Node::DEFAULT_PORT).run(terminal); diff --git a/src/node/logger.rs b/src/node/logger.rs index 780d59e..aaa5bd0 100644 --- a/src/node/logger.rs +++ b/src/node/logger.rs @@ -1,5 +1,6 @@ use std::io::{self}; use std::sync::Mutex; +use chrono::prelude::Utc; pub struct Logger { logs: Mutex>, @@ -31,12 +32,14 @@ impl Logger { } } pub fn log_error(&self, log: String) { - self.log_internal(format!("[ERROR] {}", log)); - self.temp_log(format!("[ERROR] {}", log)); + let dt = Utc::now(); + self.log_internal(format!("[ERROR] {} | {}", dt.format("%Y-%m-%d %H:%M:%S").to_string(), log)); + self.temp_log(format!("[ERROR] {} | {}", dt.format("%Y-%m-%d %H:%M:%S").to_string(), log)); } pub fn log(&self, log: String) { - self.log_internal(format!("[LOG] {}", log)); - self.temp_log(format!("[LOG] {}", log)); + let dt = Utc::now(); + self.log_internal(format!("[LOG] {} | {}", dt.format("%Y-%m-%d %H:%M:%S").to_string(), log)); + self.temp_log(format!("[LOG] {} | {}", dt.format("%Y-%m-%d %H:%M:%S").to_string(), log)); } pub fn read_logs(&self) -> io::Result> { diff --git a/src/node/mod.rs b/src/node/mod.rs index 96abf27..2719f40 100644 --- a/src/node/mod.rs +++ b/src/node/mod.rs @@ -151,7 +151,7 @@ impl Node { Err(HTTPParseError::InvalidStatusLine) } - + fn handle_connection(stream: TcpStream) -> Result, Option> { let buf_reader = BufReader::new(&stream); diff --git a/src/node/resolve_requests.rs b/src/node/resolve_requests.rs index df33a9d..9b6c860 100644 --- a/src/node/resolve_requests.rs +++ b/src/node/resolve_requests.rs @@ -13,7 +13,12 @@ pub mod endpoints { impl fmt::Display for HTTPResponseError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - _ => todo!(), + // If log adds log, else just prints the type of the enum + HTTPResponseError::InvalidMethod(log) => {match log {None => {write!(f, "Invalid Method")}, Some(log) => {write!(f, "Invalid Method: {}", log)}}}, + HTTPResponseError::InvalidPath(log) => {match log {None => {write!(f, "Invalid Path")}, Some(log) => {write!(f, "Invalid Path: {}", log)}}}, + HTTPResponseError::InvalidBody(log) => {match log {None => {write!(f, "Invalid Body")}, Some(log) => {write!(f, "Invalid Body: {}", log)}}}, + HTTPResponseError::InternalServerError(log) => {match log {None => {write!(f, "Internal Server Error")}, Some(log) => {write!(f, "Internal Server Error: {}", log)}}}, + HTTPResponseError::BadRequest(log) => {match log {None => {write!(f, "Bad Request")}, Some(log) => {write!(f, "Bad Request: {}", log)}}}, } } } @@ -185,11 +190,10 @@ pub mod endpoints { None => path_not_found(), }; - let log = String::new(); match r { Ok(value) => { request.response(value); - Ok(Some("Success".to_string())) + Ok(Some(format!("Request {} to path {} was successful", method, path.to_str().unwrap()))) } Err(e) => match e { HTTPResponseError::InvalidMethod(log) => { @@ -218,12 +222,11 @@ pub mod endpoints { } pub mod methods { - use core::panic; use serde_json::json; use std::collections::HashMap; use std::io::prelude::*; use std::net::TcpStream; - use std::path::PathBuf; + use std::path::{PathBuf}; use std::{fmt, fs}; pub enum ImageType { @@ -260,6 +263,14 @@ pub mod methods { GET(GETData), POST(POSTData), } + impl fmt::Display for Method { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Method::GET(data) => write!(f, "GET {:?}", data.path), + Method::POST(data) => write!(f, "POST {:?}", data.path), + } + } + } struct Response { status: u16, @@ -450,6 +461,36 @@ pub mod methods { } } + impl fmt::Display for HTTPRequest { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Write the HTTP method and version + writeln!(f, "Method: {}", self.method)?; + writeln!(f, "HTTP Version: {}", self.http_version)?; + + // Write the stream status + writeln!( + f, + "Stream: {}", + if self.stream.is_some() { + "Connected" + } else { + "Disconnected" + } + )?; + + // Write the headers + writeln!(f, "Headers:")?; + if self.headers.is_empty() { + writeln!(f, " (none)")?; + } else { + for (key, value) in &self.headers { + writeln!(f, " {}: {}", key, value)?; + } + } + + Ok(()) + } + } #[derive(Debug)] pub enum HTTPParseError { InvalidStatusLine, From 5dd7b116e8ab09e48ef394691a321330a5a7fcfc Mon Sep 17 00:00:00 2001 From: dantee-e Date: Wed, 30 Apr 2025 18:07:56 +0200 Subject: [PATCH 23/40] sei la --- Cargo.lock | 13 ++++ Cargo.toml | 1 + src/bin/create_transaction.rs | 56 +++++++++++++++++ src/bin/node.rs | 2 +- src/bin/transaction.json | 1 + src/chain/transaction.rs | 109 ++++++++++++++++------------------ src/node/resolve_requests.rs | 62 ++++++++++++++----- 7 files changed, 172 insertions(+), 72 deletions(-) create mode 100644 src/bin/create_transaction.rs create mode 100644 src/bin/transaction.json diff --git a/Cargo.lock b/Cargo.lock index 410c8fa..c329cfc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -160,6 +160,7 @@ dependencies = [ "serde", "serde_json", "sha2", + "tokio", ] [[package]] @@ -1732,9 +1733,21 @@ dependencies = [ "mio", "pin-project-lite", "socket2", + "tokio-macros", "windows-sys 0.52.0", ] +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tokio-native-tls" version = "0.3.1" diff --git a/Cargo.toml b/Cargo.toml index 0d45dc9..5a805d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,4 +24,5 @@ openssl = "0.10.71" crossterm = "0.29" ratatui = "0.29.0" color-eyre = "0.6.3" +tokio = { version = "1.44.1", features = ["rt", "rt-multi-thread", "macros"] } diff --git a/src/bin/create_transaction.rs b/src/bin/create_transaction.rs new file mode 100644 index 0000000..0faa0fc --- /dev/null +++ b/src/bin/create_transaction.rs @@ -0,0 +1,56 @@ +use cleyto_coin::chain::transaction::{Transaction, TransactionInfo, TransactionValidationError}; +use cleyto_coin::chain::wallet::Wallet; +use chrono::Utc; +use std::fs; +use reqwest::Client; +use std::error::Error; + + +#[tokio::main] +async fn main() { + post_json().await.expect("TODO: panic message"); +} + +fn create_transaction_json() -> String { + let (wallet1, mut wallet1_pk) = Wallet::new(); + let (wallet2, wallet2_pk) = Wallet::new(); + let transaction_info = TransactionInfo::new(0.3, Utc::now()); + let signature = wallet1_pk.sign_transaction(&transaction_info).unwrap(); + let transaction = match Transaction::new(wallet1, wallet2, transaction_info, signature) { + Ok(tx) => tx, + Err(e) => { + match e { + TransactionValidationError::OpenSSLError(_) => panic!("{e}"), + TransactionValidationError::ValidationError => panic!("Validation error"), + } + } + }; + + transaction.serialize() +} + +async fn post_json() -> Result<(), Box> { + // Initialize the HTTP client + let client = Client::new(); + + // Read the JSON file + let json_content = fs::read_to_string("src/bin/transaction.json")?; + + // Send the POST request + let response = client + .post("http://localhost:9473/submit-transactions") + .header("Content-Type", "application/json") + .body(json_content) + .send() + .await?; + + // Check the response status + let status = response.status(); + let response_body = response.text().await?; + + // Print the response + println!("Response Status: {}", status); + println!("Response Body: {}", response_body); + + Ok(()) +} diff --git a/src/bin/node.rs b/src/bin/node.rs index 161354d..352c808 100644 --- a/src/bin/node.rs +++ b/src/bin/node.rs @@ -21,7 +21,7 @@ fn main() -> color_eyre::Result<()> { node.run(true, rx, 0); }); - // TODO Loggger nao scrolla + // TODO Logger nao scrolla color_eyre::install()?; let terminal = ratatui::init(); let result = App::new(Arc::clone(&logger), node::Node::DEFAULT_PORT).run(terminal); diff --git a/src/bin/transaction.json b/src/bin/transaction.json new file mode 100644 index 0000000..cdfb8d0 --- /dev/null +++ b/src/bin/transaction.json @@ -0,0 +1 @@ +{"sender":{"public_key":[45,45,45,45,45,66,69,71,73,78,32,80,85,66,76,73,67,32,75,69,89,45,45,45,45,45,10,77,73,73,66,73,106,65,78,66,103,107,113,104,107,105,71,57,119,48,66,65,81,69,70,65,65,79,67,65,81,56,65,77,73,73,66,67,103,75,67,65,81,69,65,116,106,113,49,53,100,85,105,108,82,78,52,51,86,86,85,116,74,113,104,10,75,110,80,50,105,118,47,43,84,78,86,110,43,113,50,51,109,110,65,106,85,85,77,65,48,119,90,90,76,87,121,53,53,111,56,101,54,71,107,86,72,84,103,97,57,122,70,78,48,107,77,84,51,106,68,90,102,97,89,120,67,85,67,98,10,102,47,83,108,70,119,66,102,107,103,103,50,66,108,98,47,75,121,82,55,80,108,121,67,100,66,51,67,117,76,43,55,70,74,83,88,111,107,114,108,118,81,87,87,65,107,51,74,70,102,52,69,112,68,77,113,119,80,116,116,65,88,86,72,10,98,79,82,69,71,84,75,47,84,54,86,78,111,74,103,67,65,51,112,116,50,65,77,70,114,116,80,107,67,72,101,89,84,69,101,57,75,107,50,90,112,106,113,115,97,56,114,102,105,49,97,67,66,110,89,69,55,68,51,103,121,57,102,99,10,85,85,121,115,108,56,49,43,110,84,51,85,67,120,114,119,90,86,85,88,105,122,67,122,99,99,112,109,47,100,108,47,48,84,117,111,70,79,87,103,119,57,108,97,105,97,43,73,88,88,50,78,90,76,49,120,111,43,98,48,78,83,52,122,10,113,53,49,90,89,111,49,112,117,80,78,107,76,103,47,122,80,103,117,107,78,73,110,82,80,80,86,90,75,115,119,108,116,115,84,67,122,78,88,88,67,57,84,87,73,111,70,71,53,109,47,117,72,121,97,43,65,77,98,105,85,104,119,75,10,76,81,73,68,65,81,65,66,10,45,45,45,45,45,69,78,68,32,80,85,66,76,73,67,32,75,69,89,45,45,45,45,45,10]},"receiver":{"public_key":[45,45,45,45,45,66,69,71,73,78,32,80,85,66,76,73,67,32,75,69,89,45,45,45,45,45,10,77,73,73,66,73,106,65,78,66,103,107,113,104,107,105,71,57,119,48,66,65,81,69,70,65,65,79,67,65,81,56,65,77,73,73,66,67,103,75,67,65,81,69,65,56,69,66,114,50,105,88,49,87,66,56,55,52,117,108,55,65,100,100,50,10,76,119,103,119,89,54,90,80,72,81,51,80,121,120,82,50,101,88,99,76,51,51,86,81,85,100,108,43,97,115,48,76,80,116,98,69,116,67,121,79,108,51,117,105,103,43,99,119,77,47,103,85,100,76,122,68,67,98,55,49,117,110,119,118,10,85,114,51,77,57,50,101,100,47,122,78,53,69,68,118,107,114,85,101,78,75,105,108,54,104,81,52,109,70,81,109,87,89,114,82,108,48,104,112,106,98,66,68,115,109,86,84,47,77,110,77,56,57,77,115,48,100,89,74,87,47,78,113,107,10,52,108,76,51,68,50,69,97,118,122,101,82,122,43,47,65,102,112,77,79,98,68,85,120,103,51,69,71,70,107,73,77,110,84,51,43,51,72,109,51,98,50,117,90,78,112,68,74,102,109,72,81,68,85,84,88,75,109,74,69,85,66,48,49,10,113,50,53,90,106,76,118,43,88,119,103,106,72,70,69,80,51,97,75,122,54,114,99,112,87,56,65,47,80,70,70,65,54,47,106,65,103,101,67,47,110,104,79,48,72,70,115,81,43,87,122,106,102,107,122,53,47,102,78,102,86,102,85,88,10,86,72,48,49,113,80,82,67,112,66,90,76,74,70,106,70,43,99,57,112,117,49,69,48,104,81,70,74,88,69,69,114,54,98,113,104,87,122,72,102,71,87,81,65,113,114,68,76,52,70,99,51,103,84,101,115,50,66,81,111,114,65,102,90,10,101,119,73,68,65,81,65,66,10,45,45,45,45,45,69,78,68,32,80,85,66,76,73,67,32,75,69,89,45,45,45,45,45,10]},"signature":[8,188,47,157,206,57,163,165,35,220,116,20,68,189,94,111,49,187,252,228,94,52,73,128,9,34,173,208,81,199,18,3,179,137,110,250,61,20,170,130,190,206,155,152,230,188,32,35,223,192,111,100,249,162,35,18,63,105,3,91,132,61,114,55,169,232,209,127,96,244,7,61,72,147,146,212,45,169,55,136,62,27,182,84,85,82,38,14,200,233,34,74,246,55,48,16,12,119,43,2,209,109,108,234,26,150,109,44,146,24,255,167,14,61,171,154,160,202,73,16,166,196,233,148,109,23,40,25,205,103,131,179,69,87,25,165,166,27,76,54,236,54,210,228,107,73,227,143,43,90,66,49,203,156,177,227,55,191,37,157,57,7,245,119,109,137,142,244,150,47,4,252,112,126,217,66,156,82,10,185,124,139,177,14,84,249,43,153,195,31,147,107,37,204,241,217,128,92,150,74,69,227,41,245,63,159,63,116,37,114,80,172,99,230,157,50,217,70,210,75,173,239,175,142,37,0,38,178,16,47,254,112,48,221,182,164,100,210,181,215,235,66,231,144,175,203,251,220,185,44,126,190,73,170,206,236],"transaction_info":{"value":0.3,"date":"2025-04-30T14:44:49.530070491Z"}} \ No newline at end of file diff --git a/src/chain/transaction.rs b/src/chain/transaction.rs index b56f550..cef05d6 100644 --- a/src/chain/transaction.rs +++ b/src/chain/transaction.rs @@ -1,13 +1,12 @@ use super::wallet::Wallet; use chrono::{DateTime, Utc}; -use core::panic; use openssl::error::ErrorStack; use serde::{Deserialize, Serialize}; use std::fmt; use std::fmt::Debug; #[derive(Clone, Debug, Serialize, Deserialize)] -// ---------------------------------------------- TransactionInfo definition ---------------------------------------- +// ---------------------------------------------- TransactionInfo definition ----------------------- pub struct TransactionInfo { pub value: f32, pub date: DateTime, @@ -26,65 +25,65 @@ impl TransactionInfo { ) } } -// ----------------------------------------------------------------------------------------------------------------- +// ------------------------------------------------------------------------------------------------- -// --------------------------------------- Transaction Serialization Utils ----------------------------------------- +// --------------------------------------- Transaction Serialization Utils ------------------------- // TODO move this to a errors file #[derive(Debug)] pub enum TransactionDeserializeError { - InvalidSignature, InsufficientFunds, MalformedTransaction, - OpenSSLError(ErrorStack), SerdeError(serde_json::Error), } impl fmt::Display for TransactionDeserializeError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - TransactionDeserializeError::InvalidSignature => write!(f, "Invalid signature"), TransactionDeserializeError::InsufficientFunds => write!(f, "Insufficient funds"), TransactionDeserializeError::MalformedTransaction => write!(f, "Malformed transaction"), - TransactionDeserializeError::OpenSSLError(e) => write!(f, "OpenSSL Error"), TransactionDeserializeError::SerdeError(value) => write!(f, "{}", value), } } } impl std::error::Error for TransactionDeserializeError {} -/* -mod signature_def { - use rsa::BigUint; - use serde::{Deserialize, Serialize}; - use serde_bytes; - use smallvec::SmallVec; - - type BigDigit = u64; - const VEC_SIZE: usize = 4; - #[derive(Serialize, Deserialize)] - #[serde(remote = "BigUint")] - struct BigUintDef { - data: SmallVec<[BigDigit; VEC_SIZE]>, +#[derive(Debug)] +pub enum TransactionValidationError { + OpenSSLError(ErrorStack), + ValidationError +} +impl fmt::Display for TransactionValidationError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + TransactionValidationError::OpenSSLError(e) => { + let mut error = String::new(); + error += "The validation of the transaction was not successful due to some \ + internal OpenSSL error:"; + for i in e.errors() { + error += &format!("\n{}", i); + } + write!(f, "{}", error) + }, + TransactionValidationError::ValidationError => { + write!(f, "The validation of the transaction was not successful, as the signature \ + did not match the provided transaction info.") + } + } + } +} +impl std::error::Error for TransactionValidationError {} - #[derive(Serialize, Deserialize)] - #[serde(remote = "rsa::pkcs1v15::Signature")] - pub struct SignatureDef { - inner: BigUint, - len: usize, - } -} */ -// ----------------------------------------------------------------------------------------------------------------- +// ------------------------------------------------------------------------------------------------- -// ---------------------------------------------- Transaction definition ------------------------------------------- +// ------------------------------------- Transaction definition ------------------------------------ #[derive(Clone, Serialize, Deserialize)] pub struct Transaction { pub sender: Wallet, pub receiver: Wallet, - // #[serde(with = "signature_def::SignatureDef")] pub signature: Vec, pub transaction_info: TransactionInfo, } @@ -94,22 +93,28 @@ impl Transaction { sender: Wallet, receiver: Wallet, transaction_info: TransactionInfo, - signature: Vec, - ) -> Result { - let verify_signature = match sender.verify_transaction_info(&transaction_info, &signature) { - Ok(value) => value, - Err(e) => return Err(e), + signature: Vec + ) -> Result { + let transaction = Self { + sender, + receiver, + signature, + transaction_info, }; - if verify_signature { - Ok(Self { - sender, - receiver, - signature, - transaction_info, - }) - } else { - panic!("Signature couldn't be verified"); + match transaction.verify(){ + Ok(()) => Ok(transaction), + Err(error) => return Err(error) + } + } + + pub(crate) fn verify(&self) -> Result<(), TransactionValidationError> { + match self.sender.verify_transaction_info(&self.transaction_info, &self.signature) { + Ok(value) => match value { + true => Ok(()), + false => Err(TransactionValidationError::ValidationError) + }, + Err(stack) => Err(TransactionValidationError::OpenSSLError(stack)), } } @@ -125,7 +130,7 @@ impl Transaction { pub fn serialize(&self) -> String { let value = serde_json::to_string(self).unwrap(); - println!("serialized transaction: {value}"); + // println!("serialized transaction: {value}"); value } pub fn deserialize(json: String) -> Result { @@ -137,17 +142,7 @@ impl Transaction { if tx.transaction_info.value <= 0.0 { return Err(TransactionDeserializeError::InsufficientFunds); } - let verified = match tx - .sender - .verify_transaction_info(&tx.transaction_info, &tx.signature) - { - Ok(value) => value, - Err(e) => return Err(TransactionDeserializeError::OpenSSLError(e)), - }; - if !verified { - return Err(TransactionDeserializeError::InvalidSignature); - } - // TODO check if wallets exist + Ok(tx) } } diff --git a/src/node/resolve_requests.rs b/src/node/resolve_requests.rs index 9b6c860..263d52f 100644 --- a/src/node/resolve_requests.rs +++ b/src/node/resolve_requests.rs @@ -111,12 +111,14 @@ pub mod endpoints { use std::collections::HashMap; mod endpoints { - use crate::chain::transaction::Transaction; + use std::path::PathBuf; + use serde_json::json; + use crate::chain::transaction::{Transaction, TransactionDeserializeError, TransactionValidationError}; use crate::node::resolve_requests::endpoints::errors::HTTPResponseError; use crate::node::resolve_requests::endpoints::helpers::{ return_html, return_image, HTTPResult, }; - use crate::node::resolve_requests::methods::{GETData, ImageType, POSTData}; + use crate::node::resolve_requests::methods::{Content, GETData, HTTPResponse, ImageType, POSTData}; pub fn index(data: &GETData) -> HTTPResult { return_html("index.html") @@ -124,22 +126,54 @@ pub mod endpoints { pub fn submit_transaction(data: &POSTData) -> HTTPResult { let body = data.body.clone().unwrap(); - let json: Transaction = match serde_json::from_str(body.as_str()) { - Ok(json) => json, - Err(_) => { - return Err(HTTPResponseError::InvalidBody(None)); + let transaction: Transaction = match Transaction::deserialize(body) { + Ok(tx) => tx, + Err(e) => { + return match e { + TransactionDeserializeError::InsufficientFunds => { + Err(HTTPResponseError::InvalidBody(None)) + } + TransactionDeserializeError::MalformedTransaction => { + Err(HTTPResponseError::InvalidBody(None)) + } + TransactionDeserializeError::SerdeError(_) => { + Err(HTTPResponseError::InvalidBody(None)) + } + } } }; - Err(HTTPResponseError::InternalServerError(Some( - "Something went wrong on the submit_transaction function" - .parse() - .unwrap(), - ))) + + match transaction.verify() { + Ok(()) => {}, + Err(e) => { + return match e { + TransactionValidationError::OpenSSLError(_) => { + Err(HTTPResponseError::InternalServerError(Some("Error in \ + the OpenSSL library when verifying a transaction".to_string()))) + } + TransactionValidationError::ValidationError => { + Err(HTTPResponseError::BadRequest(Some("Transaction submitted with \ + invalid signature".to_string()))) + } + } + } + }; + + Ok(HTTPResponse::OK(Some(Content::JSON( + json!({ + "msg": "The transaction was added to the pool.", + "status_code": "200" + }) + )))) } pub fn favicon(data: &GETData) -> HTTPResult { return_image("fav.ico", ImageType::ICO) } + + pub fn pinto_grandegorsso(data: &GETData) -> HTTPResult { + return_html("200.html") + } } use crate::node::resolve_requests::endpoints::errors::HTTPResponseError; use endpoints::*; @@ -152,8 +186,6 @@ pub mod endpoints { need it */ - - fn add_endpoint<'a>( path: &'a str, endpoints: &mut HashMap<&'a str, HashMap<&str, Box>>, @@ -161,7 +193,6 @@ pub mod endpoints { post: Option, ) { let mut methods: HashMap<&str, Box> = HashMap::new(); - if let Some(get) = get { methods.insert("GET", Box::new(get) as Box); } @@ -176,6 +207,8 @@ pub mod endpoints { add_endpoint("/", &mut endpoints, Some(index), None); add_endpoint("/favicon.ico", &mut endpoints, Some(favicon), None); + add_endpoint("/submit-transactions", &mut endpoints, None, Some(submit_transaction)); + add_endpoint("/pinto", &mut endpoints, Some(pinto_grandegorsso), None); let (path, method) = match request.get_method() { Method::GET(data) => (data.path.clone(), "GET"), @@ -241,6 +274,7 @@ pub mod methods { Image(PathBuf, ImageType), // path to image file PlainText(String), } + pub enum HTTPResponse { OK(Option), InvalidMethod, From f066a9ab968cad9fa3f4f5e885f818d664455ce4 Mon Sep 17 00:00:00 2001 From: dantee-e Date: Mon, 19 May 2025 14:56:03 +0200 Subject: [PATCH 24/40] Formatted --- src/bin/create_transaction.rs | 17 +++--- src/bin/node.rs | 2 +- src/chain/transaction.rs | 29 +++++----- src/node/logger.rs | 26 +++++++-- src/node/mod.rs | 4 +- src/node/resolve_requests.rs | 104 ++++++++++++++++++++++++---------- 6 files changed, 122 insertions(+), 60 deletions(-) diff --git a/src/bin/create_transaction.rs b/src/bin/create_transaction.rs index 0faa0fc..de14587 100644 --- a/src/bin/create_transaction.rs +++ b/src/bin/create_transaction.rs @@ -1,10 +1,9 @@ +use chrono::Utc; use cleyto_coin::chain::transaction::{Transaction, TransactionInfo, TransactionValidationError}; use cleyto_coin::chain::wallet::Wallet; -use chrono::Utc; -use std::fs; use reqwest::Client; use std::error::Error; - +use std::fs; #[tokio::main] async fn main() { @@ -13,17 +12,15 @@ async fn main() { fn create_transaction_json() -> String { let (wallet1, mut wallet1_pk) = Wallet::new(); - let (wallet2, wallet2_pk) = Wallet::new(); + let (wallet2, _) = Wallet::new(); let transaction_info = TransactionInfo::new(0.3, Utc::now()); let signature = wallet1_pk.sign_transaction(&transaction_info).unwrap(); let transaction = match Transaction::new(wallet1, wallet2, transaction_info, signature) { Ok(tx) => tx, - Err(e) => { - match e { - TransactionValidationError::OpenSSLError(_) => panic!("{e}"), - TransactionValidationError::ValidationError => panic!("Validation error"), - } - } + Err(e) => match e { + TransactionValidationError::OpenSSLError(_) => panic!("{e}"), + TransactionValidationError::ValidationError => panic!("Validation error"), + }, }; transaction.serialize() diff --git a/src/bin/node.rs b/src/bin/node.rs index 352c808..66d555a 100644 --- a/src/bin/node.rs +++ b/src/bin/node.rs @@ -20,7 +20,7 @@ fn main() -> color_eyre::Result<()> { let mut node = node::Node::new(Chain::new(), logger_clone_for_node); node.run(true, rx, 0); }); - + // TODO Logger nao scrolla color_eyre::install()?; let terminal = ratatui::init(); diff --git a/src/chain/transaction.rs b/src/chain/transaction.rs index cef05d6..06a59ff 100644 --- a/src/chain/transaction.rs +++ b/src/chain/transaction.rs @@ -47,11 +47,10 @@ impl fmt::Display for TransactionDeserializeError { } impl std::error::Error for TransactionDeserializeError {} - #[derive(Debug)] pub enum TransactionValidationError { OpenSSLError(ErrorStack), - ValidationError + ValidationError, } impl fmt::Display for TransactionValidationError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -64,18 +63,19 @@ impl fmt::Display for TransactionValidationError { error += &format!("\n{}", i); } write!(f, "{}", error) - }, + } TransactionValidationError::ValidationError => { - write!(f, "The validation of the transaction was not successful, as the signature \ - did not match the provided transaction info.") + write!( + f, + "The validation of the transaction was not successful, as the signature \ + did not match the provided transaction info." + ) } } - } } impl std::error::Error for TransactionValidationError {} - // ------------------------------------------------------------------------------------------------- // ------------------------------------- Transaction definition ------------------------------------ @@ -93,7 +93,7 @@ impl Transaction { sender: Wallet, receiver: Wallet, transaction_info: TransactionInfo, - signature: Vec + signature: Vec, ) -> Result { let transaction = Self { sender, @@ -102,17 +102,20 @@ impl Transaction { transaction_info, }; - match transaction.verify(){ + match transaction.verify() { Ok(()) => Ok(transaction), - Err(error) => return Err(error) + Err(error) => return Err(error), } } pub(crate) fn verify(&self) -> Result<(), TransactionValidationError> { - match self.sender.verify_transaction_info(&self.transaction_info, &self.signature) { + match self + .sender + .verify_transaction_info(&self.transaction_info, &self.signature) + { Ok(value) => match value { true => Ok(()), - false => Err(TransactionValidationError::ValidationError) + false => Err(TransactionValidationError::ValidationError), }, Err(stack) => Err(TransactionValidationError::OpenSSLError(stack)), } @@ -142,7 +145,7 @@ impl Transaction { if tx.transaction_info.value <= 0.0 { return Err(TransactionDeserializeError::InsufficientFunds); } - + Ok(tx) } } diff --git a/src/node/logger.rs b/src/node/logger.rs index aaa5bd0..4600b6e 100644 --- a/src/node/logger.rs +++ b/src/node/logger.rs @@ -1,6 +1,6 @@ +use chrono::prelude::Utc; use std::io::{self}; use std::sync::Mutex; -use chrono::prelude::Utc; pub struct Logger { logs: Mutex>, @@ -33,13 +33,29 @@ impl Logger { } pub fn log_error(&self, log: String) { let dt = Utc::now(); - self.log_internal(format!("[ERROR] {} | {}", dt.format("%Y-%m-%d %H:%M:%S").to_string(), log)); - self.temp_log(format!("[ERROR] {} | {}", dt.format("%Y-%m-%d %H:%M:%S").to_string(), log)); + self.log_internal(format!( + "[ERROR] {} | {}", + dt.format("%Y-%m-%d %H:%M:%S").to_string(), + log + )); + self.temp_log(format!( + "[ERROR] {} | {}", + dt.format("%Y-%m-%d %H:%M:%S").to_string(), + log + )); } pub fn log(&self, log: String) { let dt = Utc::now(); - self.log_internal(format!("[LOG] {} | {}", dt.format("%Y-%m-%d %H:%M:%S").to_string(), log)); - self.temp_log(format!("[LOG] {} | {}", dt.format("%Y-%m-%d %H:%M:%S").to_string(), log)); + self.log_internal(format!( + "[LOG] {} | {}", + dt.format("%Y-%m-%d %H:%M:%S").to_string(), + log + )); + self.temp_log(format!( + "[LOG] {} | {}", + dt.format("%Y-%m-%d %H:%M:%S").to_string(), + log + )); } pub fn read_logs(&self) -> io::Result> { diff --git a/src/node/mod.rs b/src/node/mod.rs index 2719f40..4c69a76 100644 --- a/src/node/mod.rs +++ b/src/node/mod.rs @@ -151,14 +151,14 @@ impl Node { Err(HTTPParseError::InvalidStatusLine) } - + fn handle_connection(stream: TcpStream) -> Result, Option> { let buf_reader = BufReader::new(&stream); let mut request_object: HTTPRequest = match Self::parse_http_request(buf_reader) { Ok(value) => value, Err(e) => { - return Err(Some("Error processing HTTP request: {e}".parse().unwrap())); + return Err(Some(format!("Error processing HTTP request: {e}"))); } }; diff --git a/src/node/resolve_requests.rs b/src/node/resolve_requests.rs index 263d52f..4c2e90d 100644 --- a/src/node/resolve_requests.rs +++ b/src/node/resolve_requests.rs @@ -14,11 +14,46 @@ pub mod endpoints { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { // If log adds log, else just prints the type of the enum - HTTPResponseError::InvalidMethod(log) => {match log {None => {write!(f, "Invalid Method")}, Some(log) => {write!(f, "Invalid Method: {}", log)}}}, - HTTPResponseError::InvalidPath(log) => {match log {None => {write!(f, "Invalid Path")}, Some(log) => {write!(f, "Invalid Path: {}", log)}}}, - HTTPResponseError::InvalidBody(log) => {match log {None => {write!(f, "Invalid Body")}, Some(log) => {write!(f, "Invalid Body: {}", log)}}}, - HTTPResponseError::InternalServerError(log) => {match log {None => {write!(f, "Internal Server Error")}, Some(log) => {write!(f, "Internal Server Error: {}", log)}}}, - HTTPResponseError::BadRequest(log) => {match log {None => {write!(f, "Bad Request")}, Some(log) => {write!(f, "Bad Request: {}", log)}}}, + HTTPResponseError::InvalidMethod(log) => match log { + None => { + write!(f, "Invalid Method") + } + Some(log) => { + write!(f, "Invalid Method: {}", log) + } + }, + HTTPResponseError::InvalidPath(log) => match log { + None => { + write!(f, "Invalid Path") + } + Some(log) => { + write!(f, "Invalid Path: {}", log) + } + }, + HTTPResponseError::InvalidBody(log) => match log { + None => { + write!(f, "Invalid Body") + } + Some(log) => { + write!(f, "Invalid Body: {}", log) + } + }, + HTTPResponseError::InternalServerError(log) => match log { + None => { + write!(f, "Internal Server Error") + } + Some(log) => { + write!(f, "Internal Server Error: {}", log) + } + }, + HTTPResponseError::BadRequest(log) => match log { + None => { + write!(f, "Bad Request") + } + Some(log) => { + write!(f, "Bad Request: {}", log) + } + }, } } } @@ -111,14 +146,18 @@ pub mod endpoints { use std::collections::HashMap; mod endpoints { - use std::path::PathBuf; - use serde_json::json; - use crate::chain::transaction::{Transaction, TransactionDeserializeError, TransactionValidationError}; + use crate::chain::transaction::{ + Transaction, TransactionDeserializeError, TransactionValidationError, + }; use crate::node::resolve_requests::endpoints::errors::HTTPResponseError; use crate::node::resolve_requests::endpoints::helpers::{ return_html, return_image, HTTPResult, }; - use crate::node::resolve_requests::methods::{Content, GETData, HTTPResponse, ImageType, POSTData}; + use crate::node::resolve_requests::methods::{ + Content, GETData, HTTPResponse, ImageType, POSTData, + }; + use serde_json::json; + use std::path::PathBuf; pub fn index(data: &GETData) -> HTTPResult { return_html("index.html") @@ -142,38 +181,37 @@ pub mod endpoints { } } }; - + match transaction.verify() { - Ok(()) => {}, + Ok(()) => {} Err(e) => { return match e { TransactionValidationError::OpenSSLError(_) => { - Err(HTTPResponseError::InternalServerError(Some("Error in \ - the OpenSSL library when verifying a transaction".to_string()))) + Err(HTTPResponseError::InternalServerError(Some( + "Error in the OpenSSL library when verifying a transaction" + .to_string(), + ))) } TransactionValidationError::ValidationError => { - Err(HTTPResponseError::BadRequest(Some("Transaction submitted with \ - invalid signature".to_string()))) + Err(HTTPResponseError::BadRequest(Some( + "Transaction submitted with \ + invalid signature" + .to_string(), + ))) } } } }; - Ok(HTTPResponse::OK(Some(Content::JSON( - json!({ - "msg": "The transaction was added to the pool.", - "status_code": "200" - }) - )))) + Ok(HTTPResponse::OK(Some(Content::JSON(json!({ + "msg": "The transaction was added to the pool.", + "status_code": "200" + }))))) } pub fn favicon(data: &GETData) -> HTTPResult { return_image("fav.ico", ImageType::ICO) } - - pub fn pinto_grandegorsso(data: &GETData) -> HTTPResult { - return_html("200.html") - } } use crate::node::resolve_requests::endpoints::errors::HTTPResponseError; use endpoints::*; @@ -207,8 +245,12 @@ pub mod endpoints { add_endpoint("/", &mut endpoints, Some(index), None); add_endpoint("/favicon.ico", &mut endpoints, Some(favicon), None); - add_endpoint("/submit-transactions", &mut endpoints, None, Some(submit_transaction)); - add_endpoint("/pinto", &mut endpoints, Some(pinto_grandegorsso), None); + add_endpoint( + "/submit-transactions", + &mut endpoints, + None, + Some(submit_transaction), + ); let (path, method) = match request.get_method() { Method::GET(data) => (data.path.clone(), "GET"), @@ -226,7 +268,11 @@ pub mod endpoints { match r { Ok(value) => { request.response(value); - Ok(Some(format!("Request {} to path {} was successful", method, path.to_str().unwrap()))) + Ok(Some(format!( + "Request {} to path {} was successful", + method, + path.to_str().unwrap() + ))) } Err(e) => match e { HTTPResponseError::InvalidMethod(log) => { @@ -259,7 +305,7 @@ pub mod methods { use std::collections::HashMap; use std::io::prelude::*; use std::net::TcpStream; - use std::path::{PathBuf}; + use std::path::PathBuf; use std::{fmt, fs}; pub enum ImageType { From e137dd94fd1c8edefcade25869c3aaf9ad8a76ba Mon Sep 17 00:00:00 2001 From: dantee-e Date: Mon, 19 May 2025 18:32:20 +0200 Subject: [PATCH 25/40] working version of currying --- src/node/mod.rs | 6 +- src/node/resolve_requests.rs | 594 ------------------------- src/node/resolve_requests/endpoints.rs | 358 +++++++++++++++ src/node/resolve_requests/methods.rs | 291 ++++++++++++ src/node/resolve_requests/mod.rs | 3 + src/node/static/index.html | 12 +- 6 files changed, 657 insertions(+), 607 deletions(-) delete mode 100644 src/node/resolve_requests.rs create mode 100644 src/node/resolve_requests/endpoints.rs create mode 100644 src/node/resolve_requests/methods.rs create mode 100644 src/node/resolve_requests/mod.rs diff --git a/src/node/mod.rs b/src/node/mod.rs index 4c69a76..269846b 100644 --- a/src/node/mod.rs +++ b/src/node/mod.rs @@ -6,10 +6,7 @@ mod utils; use crate::chain::{transaction::Transaction, Chain}; use core::panic; -use resolve_requests::{ - endpoints::resolve_endpoint, - methods::{HTTPParseError, HTTPRequest}, -}; +use resolve_requests::methods::{HTTPParseError, HTTPRequest}; use std::time::Duration; use thread_pool::custom_thread_pool::ThreadPool; @@ -30,6 +27,7 @@ pub struct Node { use crate::node::logger::Logger; use once_cell::sync::Lazy; +use resolve_requests::endpoints::endpoints::resolve_endpoint; static NUMBER_OF_THREADS_IN_THREAD_POOL: Lazy = Lazy::new(num_cpus::get); diff --git a/src/node/resolve_requests.rs b/src/node/resolve_requests.rs deleted file mode 100644 index 4c2e90d..0000000 --- a/src/node/resolve_requests.rs +++ /dev/null @@ -1,594 +0,0 @@ -pub mod endpoints { - mod errors { - use std::fmt; - - #[derive(Debug)] - pub enum HTTPResponseError { - InvalidMethod(Option), - InvalidPath(Option), - InvalidBody(Option), - InternalServerError(Option), - BadRequest(Option), - } - impl fmt::Display for HTTPResponseError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - // If log adds log, else just prints the type of the enum - HTTPResponseError::InvalidMethod(log) => match log { - None => { - write!(f, "Invalid Method") - } - Some(log) => { - write!(f, "Invalid Method: {}", log) - } - }, - HTTPResponseError::InvalidPath(log) => match log { - None => { - write!(f, "Invalid Path") - } - Some(log) => { - write!(f, "Invalid Path: {}", log) - } - }, - HTTPResponseError::InvalidBody(log) => match log { - None => { - write!(f, "Invalid Body") - } - Some(log) => { - write!(f, "Invalid Body: {}", log) - } - }, - HTTPResponseError::InternalServerError(log) => match log { - None => { - write!(f, "Internal Server Error") - } - Some(log) => { - write!(f, "Internal Server Error: {}", log) - } - }, - HTTPResponseError::BadRequest(log) => match log { - None => { - write!(f, "Bad Request") - } - Some(log) => { - write!(f, "Bad Request: {}", log) - } - }, - } - } - } - impl std::error::Error for HTTPResponseError {} - } - mod helpers { - use crate::node::resolve_requests::endpoints::errors::HTTPResponseError; - use crate::node::resolve_requests::methods::{ - Content, GETData, HTTPRequest, HTTPResponse, ImageType, Method, POSTData, - }; - use std::path::PathBuf; - - const STATIC_FOLDER: &str = "src/node/static/"; - - pub type HTTPResult = Result; - - pub type POSTFunc = fn(&POSTData) -> HTTPResult; - pub type GETFunc = fn(&GETData) -> HTTPResult; - - pub fn path_not_found() -> HTTPResult { - Err(HTTPResponseError::InvalidPath(Some( - "Path not found".to_string(), - ))) - } - pub fn method_not_allowed() -> HTTPResult { - Err(HTTPResponseError::InvalidMethod(None)) - } - - pub trait Handler { - fn call(&self, request: &HTTPRequest) -> HTTPResult; - } - - // Implement the trait for GETFunc - impl Handler for GETFunc { - fn call(&self, request: &HTTPRequest) -> HTTPResult { - match request.get_method() { - Method::GET(data) => self(&data), - _ => method_not_allowed(), - } - } - } - - // Implement the trait for POSTFunc - impl Handler for POSTFunc { - fn call(&self, request: &HTTPRequest) -> HTTPResult { - match request.get_method() { - Method::POST(data) => self(&data), - _ => method_not_allowed(), - } - } - } - - pub fn return_image(path: &str, image_type: ImageType) -> HTTPResult { - Ok(HTTPResponse::OK(Some(Content::Image( - PathBuf::from(STATIC_FOLDER.to_owned() + path), - image_type, - )))) - } - pub fn return_html(path: &str) -> HTTPResult { - Ok(HTTPResponse::OK(Some(Content::HTML(PathBuf::from( - STATIC_FOLDER.to_owned() + path, - ))))) - } - - pub fn post(request: HTTPRequest, f: POSTFunc) -> HTTPResult { - let method = request.get_method(); - if let Method::POST(data) = method { - f(data) - } else { - Err(HTTPResponseError::InvalidMethod(None)) - } - } - pub fn get(request: HTTPRequest, f: GETFunc) -> HTTPResult { - if let Method::GET(data) = request.get_method() { - f(data) - } else { - Err(HTTPResponseError::InvalidMethod(None)) - } - } - pub fn get_post(request: HTTPRequest, get: GETFunc, post: POSTFunc) -> HTTPResult { - match request.get_method() { - Method::POST(data) => post(data), - Method::GET(data) => get(data), - } - } - } - - use super::methods::{HTTPRequest, HTTPResponse, Method}; - use helpers::*; - use std::collections::HashMap; - - mod endpoints { - use crate::chain::transaction::{ - Transaction, TransactionDeserializeError, TransactionValidationError, - }; - use crate::node::resolve_requests::endpoints::errors::HTTPResponseError; - use crate::node::resolve_requests::endpoints::helpers::{ - return_html, return_image, HTTPResult, - }; - use crate::node::resolve_requests::methods::{ - Content, GETData, HTTPResponse, ImageType, POSTData, - }; - use serde_json::json; - use std::path::PathBuf; - - pub fn index(data: &GETData) -> HTTPResult { - return_html("index.html") - } - - pub fn submit_transaction(data: &POSTData) -> HTTPResult { - let body = data.body.clone().unwrap(); - let transaction: Transaction = match Transaction::deserialize(body) { - Ok(tx) => tx, - Err(e) => { - return match e { - TransactionDeserializeError::InsufficientFunds => { - Err(HTTPResponseError::InvalidBody(None)) - } - TransactionDeserializeError::MalformedTransaction => { - Err(HTTPResponseError::InvalidBody(None)) - } - TransactionDeserializeError::SerdeError(_) => { - Err(HTTPResponseError::InvalidBody(None)) - } - } - } - }; - - match transaction.verify() { - Ok(()) => {} - Err(e) => { - return match e { - TransactionValidationError::OpenSSLError(_) => { - Err(HTTPResponseError::InternalServerError(Some( - "Error in the OpenSSL library when verifying a transaction" - .to_string(), - ))) - } - TransactionValidationError::ValidationError => { - Err(HTTPResponseError::BadRequest(Some( - "Transaction submitted with \ - invalid signature" - .to_string(), - ))) - } - } - } - }; - - Ok(HTTPResponse::OK(Some(Content::JSON(json!({ - "msg": "The transaction was added to the pool.", - "status_code": "200" - }))))) - } - - pub fn favicon(data: &GETData) -> HTTPResult { - return_image("fav.ico", ImageType::ICO) - } - } - use crate::node::resolve_requests::endpoints::errors::HTTPResponseError; - use endpoints::*; - - pub fn resolve_endpoint(mut request: HTTPRequest) -> Result, Option> { - /* - TODO: This creates the endpoints var every time the resolve_endpoints function runs, - which is very inefficient. We should move the creation of the endpoints var to the - initialization of the program, and pass it around as a parameter to the functions that - need it - */ - - fn add_endpoint<'a>( - path: &'a str, - endpoints: &mut HashMap<&'a str, HashMap<&str, Box>>, - get: Option, - post: Option, - ) { - let mut methods: HashMap<&str, Box> = HashMap::new(); - if let Some(get) = get { - methods.insert("GET", Box::new(get) as Box); - } - if let Some(post) = post { - methods.insert("POST", Box::new(post) as Box); - } - - endpoints.insert(path, methods); - } - - let mut endpoints: HashMap<&str, HashMap<&str, Box>> = HashMap::new(); - - add_endpoint("/", &mut endpoints, Some(index), None); - add_endpoint("/favicon.ico", &mut endpoints, Some(favicon), None); - add_endpoint( - "/submit-transactions", - &mut endpoints, - None, - Some(submit_transaction), - ); - - let (path, method) = match request.get_method() { - Method::GET(data) => (data.path.clone(), "GET"), - Method::POST(data) => (data.path.clone(), "POST"), - }; - - let r = match endpoints.get(path.to_str().unwrap()) { - Some(methods) => match methods.get(method) { - Some(handler) => handler.call(&request), - None => method_not_allowed(), - }, - None => path_not_found(), - }; - - match r { - Ok(value) => { - request.response(value); - Ok(Some(format!( - "Request {} to path {} was successful", - method, - path.to_str().unwrap() - ))) - } - Err(e) => match e { - HTTPResponseError::InvalidMethod(log) => { - request.response(HTTPResponse::InvalidMethod); - Err(log) - } - HTTPResponseError::InvalidPath(log) => { - request.response(HTTPResponse::BadRequest); - Err(log) - } - HTTPResponseError::InvalidBody(log) => { - request.response(HTTPResponse::BadRequest); - Err(log) - } - HTTPResponseError::InternalServerError(log) => { - request.response(HTTPResponse::InternalServerError); - Err(log) - } - HTTPResponseError::BadRequest(log) => { - request.response(HTTPResponse::BadRequest); - Err(log) - } - }, - } - } -} - -pub mod methods { - use serde_json::json; - use std::collections::HashMap; - use std::io::prelude::*; - use std::net::TcpStream; - use std::path::PathBuf; - use std::{fmt, fs}; - - pub enum ImageType { - PNG, - ICO, - JPEG, - } - - pub enum Content { - HTML(PathBuf), // path to HTML file - JSON(serde_json::Value), - Image(PathBuf, ImageType), // path to image file - PlainText(String), - } - - pub enum HTTPResponse { - OK(Option), - InvalidMethod, - BadRequest, - InternalServerError, - } - - #[derive(Debug, Clone)] - pub struct GETData { - pub path: PathBuf, - } - #[derive(Debug, Clone)] - pub struct POSTData { - pub path: PathBuf, - pub body: Option, - } - - #[derive(Debug, Clone)] - pub enum Method { - GET(GETData), - POST(POSTData), - } - impl fmt::Display for Method { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Method::GET(data) => write!(f, "GET {:?}", data.path), - Method::POST(data) => write!(f, "POST {:?}", data.path), - } - } - } - - struct Response { - status: u16, - content_type: &'static str, - body: Vec, - } - - impl Response { - fn to_bytes(&self) -> Vec { - let mut v = Vec::new(); - let header = format!( - "HTTP/1.1 {} {}\r\n\ - Content-Type: {}\r\n\ - Content-Length: {}\r\n\r\n", - self.status, - match self.status { - 200 => "OK", - 400 => "Bad Request", - 405 => "Method Not Allowed", - _ => "Unknown", - }, - self.content_type, - self.body.len(), - ); - v.extend_from_slice(header.as_bytes()); - v.extend_from_slice(&self.body); - v - } - fn new(status: u16, content_type: &'static str, body: Vec) -> Self { - Self { - status, - content_type, - body, - } - } - } - - #[derive(Debug)] - pub struct HTTPRequest { - stream: Option, - pub headers: HashMap, - method: Method, - http_version: String, - } - - impl HTTPRequest { - pub fn new( - stream: Option, - method: String, - path: PathBuf, - http_version: String, - headers: HashMap, - body: Option, - ) -> HTTPRequest { - HTTPRequest { - stream, - method: match method.as_str() { - "GET" => Method::GET(GETData { path }), - "POST" => Method::POST(POSTData { path, body }), - _ => panic!("Unavailable method"), - }, - http_version, - headers, - } - } - - pub fn set_stream(&mut self, stream: TcpStream) { - self.stream = Some(stream) - } - - pub fn get_method(&self) -> &Method { - &self.method - } - - fn make_response(status: HTTPResponse, accept: Option<&str>) -> std::io::Result { - fn response_ok_content(content: Content) -> std::io::Result { - match content { - Content::HTML(path) => Ok(Response { - status: 200, - content_type: "text/html", - body: fs::read(path)?, - }), - Content::JSON(value) => Ok(Response { - status: 200, - content_type: "application/json", - body: serde_json::to_vec(&value)?, - }), - Content::Image(path, img_type) => Ok(Response { - status: 200, - content_type: match img_type { - ImageType::PNG => "image/png", - ImageType::ICO => "image/vnd.microsoft.icon", - ImageType::JPEG => "image/jpeg", - }, - body: fs::read(path)?, - }), - Content::PlainText(text) => Ok(Response { - status: 200, - content_type: "text/plain", - body: text.into_bytes(), - }), - } - } - fn response_ok_no_content(a: &str) -> std::io::Result { - if a.contains("text/html") { - return Ok(Response::new( - 200, - "text/html", - fs::read("static/200.html").unwrap_or("NOT FOUND".into()), - )); - } else if a.contains("application/json") { - let j = json!({ "msg": "OK", "status": 200 }); - return Ok(Response::new( - 200, - "application/json", - serde_json::to_vec(&j)?, - )); - } - - Ok(Response::new(200, "text", "Success".into())) - } - - match status { - HTTPResponse::OK(content_opt) => { - if let Some(content) = content_opt { - response_ok_content(content) - } else { - response_ok_no_content(accept.unwrap_or("text")) - } - } - HTTPResponse::InvalidMethod => { - if accept.unwrap_or("").contains("text/html") { - Ok(Response::new( - 405, - "text/html", - fs::read("static/405.html") - .unwrap_or_else(|_| b"405 Invalid Method".to_vec()), - )) - } else { - Ok(Response::new( - 405, - "text/plain", - b"405 Invalid Method".to_vec(), - )) - } - } - HTTPResponse::BadRequest => { - if accept.unwrap_or("").contains("text/html") { - Ok(Response::new( - 400, - "text/html", - fs::read("static/400.html") - .unwrap_or_else(|_| b"400 Bad Request".to_vec()), - )) - } else { - Ok(Response::new( - 400, - "text/plain", - b"400 Bad Request".to_vec(), - )) - } - } - HTTPResponse::InternalServerError => { - if accept.unwrap_or("").contains("text/html") { - Ok(Response::new( - 500, - "text/html", - fs::read("static/500.html") - .unwrap_or_else(|_| b"500 Internal Server Error".to_vec()), - )) - } else { - Ok(Response::new( - 500, - "text/plain", - b"500 Internal Server Error".to_vec(), - )) - } - } - } - } - - pub fn response(&mut self, status: HTTPResponse) { - let accept = self.headers.get("Accept").map(|s| s.as_str()); - let resp = Self::make_response(status, accept).unwrap(); - if let Err(e) = self.stream.as_mut().unwrap().write_all(&resp.to_bytes()) { - eprintln!("Error writing response to stream: {}", e); - } - } - } - - impl fmt::Display for HTTPRequest { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // Write the HTTP method and version - writeln!(f, "Method: {}", self.method)?; - writeln!(f, "HTTP Version: {}", self.http_version)?; - - // Write the stream status - writeln!( - f, - "Stream: {}", - if self.stream.is_some() { - "Connected" - } else { - "Disconnected" - } - )?; - - // Write the headers - writeln!(f, "Headers:")?; - if self.headers.is_empty() { - writeln!(f, " (none)")?; - } else { - for (key, value) in &self.headers { - writeln!(f, " {}: {}", key, value)?; - } - } - - Ok(()) - } - } - #[derive(Debug)] - pub enum HTTPParseError { - InvalidStatusLine, - InvalidRequestLine, - MissingFields, - MissingContentLength, - } - impl fmt::Display for HTTPParseError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - HTTPParseError::InvalidRequestLine => write!(f, "Invalid request line"), - HTTPParseError::MissingFields => write!(f, "Missing required fields"), - HTTPParseError::InvalidStatusLine => write!(f, "Invalid status line"), - HTTPParseError::MissingContentLength => { - write!(f, "Missing content-length field in headers") - } - } - } - } - impl std::error::Error for HTTPParseError {} -} diff --git a/src/node/resolve_requests/endpoints.rs b/src/node/resolve_requests/endpoints.rs new file mode 100644 index 0000000..0173405 --- /dev/null +++ b/src/node/resolve_requests/endpoints.rs @@ -0,0 +1,358 @@ +mod errors { + use std::fmt; + + #[derive(Debug)] + pub enum HTTPResponseError { + InvalidMethod(Option), + InvalidPath(Option), + InvalidBody(Option), + InternalServerError(Option), + BadRequest(Option), + } + impl fmt::Display for HTTPResponseError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + // If log adds log, else just prints the type of the enum + HTTPResponseError::InvalidMethod(log) => match log { + None => { + write!(f, "Invalid Method") + } + Some(log) => { + write!(f, "Invalid Method: {}", log) + } + }, + HTTPResponseError::InvalidPath(log) => match log { + None => { + write!(f, "Invalid Path") + } + Some(log) => { + write!(f, "Invalid Path: {}", log) + } + }, + HTTPResponseError::InvalidBody(log) => match log { + None => { + write!(f, "Invalid Body") + } + Some(log) => { + write!(f, "Invalid Body: {}", log) + } + }, + HTTPResponseError::InternalServerError(log) => match log { + None => { + write!(f, "Internal Server Error") + } + Some(log) => { + write!(f, "Internal Server Error: {}", log) + } + }, + HTTPResponseError::BadRequest(log) => match log { + None => { + write!(f, "Bad Request") + } + Some(log) => { + write!(f, "Bad Request: {}", log) + } + }, + } + } + } + impl std::error::Error for HTTPResponseError {} +} + +use super::methods::{ + Content, + GETData, + HTTPRequest, + HTTPResponse, + ImageType, + Method, + POSTData, +}; + +mod helpers { + use super::*; + use std::path::PathBuf; + use serde_json::{json, Value}; + use super::errors::HTTPResponseError; + + const STATIC_FOLDER: &str = "src/node/static/"; + + pub type HTTPResult = Result; + + + // These wrappers exist because of a bug in rust: + // https://github.com/rust-lang/rust/issues/63033 + pub type POSTFunc = fn(&POSTData) -> HTTPResult; + pub type GETFunc = fn(&GETData) -> HTTPResult; + pub struct GETFuncWrapper(GETFunc); + pub struct POSTFuncWrapper(POSTFunc); + pub fn path_not_found() -> HTTPResult { + Err(HTTPResponseError::InvalidPath(Some( + "Path not found".to_string(), + ))) + } + pub fn method_not_allowed() -> HTTPResult { + Err(HTTPResponseError::InvalidMethod(None)) + } + + pub trait Handler { + fn call(&self, request: &HTTPRequest) -> HTTPResult; + } + + // Implement the trait for GETFunc + impl Handler for GETFunc { + fn call(&self, request: &HTTPRequest) -> HTTPResult { + match request.get_method() { + Method::GET(data) => self(&data), + _ => method_not_allowed(), + } + } + } + + // Implement the trait for POSTFunc + impl Handler for POSTFunc { + fn call(&self, request: &HTTPRequest) -> HTTPResult { + match request.get_method() { + Method::POST(data) => self(&data), + _ => method_not_allowed(), + } + } + } + + impl Handler for GETFuncWrapper { + fn call(&self, request: &HTTPRequest) -> HTTPResult { + match request.get_method() { + Method::GET(data) => self.0(&data), + _ => method_not_allowed(), + } + } + } + + impl Handler for POSTFuncWrapper { + fn call(&self, request: &HTTPRequest) -> HTTPResult { + match request.get_method() { + Method::POST(data) => self.0(&data), + _ => method_not_allowed(), + } + } + } + + pub fn return_image(path: &str, image_type: ImageType) -> HTTPResult { + Ok(HTTPResponse::OK(Some(Content::Image( + PathBuf::from(STATIC_FOLDER.to_owned() + path), + image_type, + )))) + } + pub fn return_html(path: &str) -> HTTPResult { + Ok(HTTPResponse::OK(Some(Content::HTML(PathBuf::from( + STATIC_FOLDER.to_owned() + path, + ))))) + } + + pub fn return_json(json: serde_json::Value) -> HTTPResult { + Ok(HTTPResponse::OK(Some(Content::JSON( + json + )))) + } + + pub fn post(request: HTTPRequest, f: POSTFunc) -> HTTPResult { + let method = request.get_method(); + if let Method::POST(data) = method { + f(data) + } else { + Err(HTTPResponseError::InvalidMethod(None)) + } + } + pub fn get(request: HTTPRequest, f: GETFunc) -> HTTPResult { + if let Method::GET(data) = request.get_method() { + f(data) + } else { + Err(HTTPResponseError::InvalidMethod(None)) + } + } + pub fn get_post(request: HTTPRequest, get: GETFunc, post: POSTFunc) -> HTTPResult { + match request.get_method() { + Method::POST(data) => post(data), + Method::GET(data) => get(data), + } + } +} + + +pub mod endpoints { + use super::*; + use super::helpers::{return_html, return_image, HTTPResult, Handler, GETFunc, POSTFunc, GETFuncWrapper, POSTFuncWrapper, method_not_allowed, path_not_found, return_json}; + use super::errors::{ + HTTPResponseError + }; + + use std::collections::HashMap; + use chrono::Utc; + use crate::chain::transaction::{ + Transaction, TransactionDeserializeError, TransactionValidationError, + }; + use serde_json::json; + + pub fn index(data: &GETData) -> HTTPResult { + return_html("index.html") + } + + pub fn submit_transaction(data: &POSTData) -> HTTPResult { + let body = data.body.clone().unwrap(); + let transaction: Transaction = match Transaction::deserialize(body) { + Ok(tx) => tx, + Err(e) => { + return match e { + TransactionDeserializeError::InsufficientFunds => { + Err(HTTPResponseError::InvalidBody(None)) + } + TransactionDeserializeError::MalformedTransaction => { + Err(HTTPResponseError::InvalidBody(None)) + } + TransactionDeserializeError::SerdeError(_) => { + Err(HTTPResponseError::InvalidBody(None)) + } + } + } + }; + + match transaction.verify() { + Ok(()) => {} + Err(e) => { + return match e { + TransactionValidationError::OpenSSLError(_) => { + Err(HTTPResponseError::InternalServerError(Some( + "Error in the OpenSSL library when verifying a transaction" + .to_string(), + ))) + } + TransactionValidationError::ValidationError => { + Err(HTTPResponseError::BadRequest(Some( + "Transaction submitted with \ + invalid signature" + .to_string(), + ))) + } + } + } + }; + + Ok(HTTPResponse::OK(Some(Content::JSON(json!({ + "msg": "The transaction was added to the pool.", + "status_code": "200" + }))))) + } + + pub fn favicon(data: &GETData) -> HTTPResult { + return_image("fav.ico", ImageType::ICO) + } + + pub fn status(data: &GETData) -> HTTPResult { + return_json(json!({ + "status": "Fodeline", + "blockHeight": 123456, + "peers": 8, + "timestamp": Utc::now() + })) + } + + pub fn resolve_endpoint(mut request: HTTPRequest) -> Result, Option> { + /* + TODO: This creates the endpoints var every time the resolve_endpoints function runs, + which is very inefficient. We should move the creation of the endpoints var to the + initialization of the program, and pass it around as a parameter to the functions that + need it + */ + + fn curry_add_endpoint(endpoints: &mut HashMap>>) + -> impl FnMut( + String, + Option HTTPResult>, + Option HTTPResult> + ) + use<'_> { + |path: String, + get: Option HTTPResult>, + post: Option HTTPResult> + | { + let mut methods: HashMap> = HashMap::new(); + if let Some(get) = get { + methods.insert("GET".parse().unwrap(), Box::new(get) as Box); + } + if let Some(post) = post { + methods.insert("POST".parse().unwrap(), Box::new(post) as Box); + } + + endpoints.insert(path, methods); + } + } + + + + + let mut endpoints: HashMap>> = HashMap::new(); + + let add_endpoints = curry_add_endpoint(&mut endpoints); + + + add_endpoints("/".to_owned(), Some(index), None); + add_endpoint("/favicon.ico", Some(favicon), None); + add_endpoint( + "/submit-transactions", + &mut endpoints, + None, + Some(submit_transaction), + ); + add_endpoint( + "/status", + &mut endpoints, + Some(status), + None + ); + + let (path, method) = match request.get_method() { + Method::GET(data) => (data.path.clone(), "GET"), + Method::POST(data) => (data.path.clone(), "POST"), + }; + + let r = match endpoints.get(path.to_str().unwrap()) { + Some(methods) => match methods.get(method) { + Some(handler) => handler.call(&request), + None => method_not_allowed(), + }, + None => path_not_found(), + }; + + match r { + Ok(value) => { + request.response(value); + Ok(Some(format!( + "Request {} to path {} was successful", + method, + path.to_str().unwrap() + ))) + } + Err(e) => match e { + HTTPResponseError::InvalidMethod(log) => { + request.response(HTTPResponse::InvalidMethod); + Err(log) + } + HTTPResponseError::InvalidPath(log) => { + request.response(HTTPResponse::BadRequest); + Err(log) + } + HTTPResponseError::InvalidBody(log) => { + request.response(HTTPResponse::BadRequest); + Err(log) + } + HTTPResponseError::InternalServerError(log) => { + request.response(HTTPResponse::InternalServerError); + Err(log) + } + HTTPResponseError::BadRequest(log) => { + request.response(HTTPResponse::BadRequest); + Err(log) + } + }, + } + } +} \ No newline at end of file diff --git a/src/node/resolve_requests/methods.rs b/src/node/resolve_requests/methods.rs new file mode 100644 index 0000000..58a71c4 --- /dev/null +++ b/src/node/resolve_requests/methods.rs @@ -0,0 +1,291 @@ + +use serde_json::json; +use std::collections::HashMap; +use std::io::prelude::*; +use std::net::TcpStream; +use std::path::PathBuf; +use std::{fmt, fs}; + +pub enum ImageType { + PNG, + ICO, + JPEG, +} + +pub enum Content { + HTML(PathBuf), // path to HTML file + JSON(serde_json::Value), + Image(PathBuf, ImageType), // path to image file + PlainText(String), +} + +pub enum HTTPResponse { + OK(Option), + InvalidMethod, + BadRequest, + InternalServerError, +} + +#[derive(Debug, Clone)] +pub struct GETData { + pub path: PathBuf, +} +#[derive(Debug, Clone)] +pub struct POSTData { + pub path: PathBuf, + pub body: Option, +} + +#[derive(Debug, Clone)] +pub enum Method { + GET(GETData), + POST(POSTData), +} +impl fmt::Display for Method { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Method::GET(data) => write!(f, "GET {:?}", data.path), + Method::POST(data) => write!(f, "POST {:?}", data.path), + } + } +} + +struct Response { + status: u16, + content_type: &'static str, + body: Vec, +} + +impl Response { + fn to_bytes(&self) -> Vec { + let mut v = Vec::new(); + let header = format!( + "HTTP/1.1 {} {}\r\n\ + Content-Type: {}\r\n\ + Content-Length: {}\r\n\r\n", + self.status, + match self.status { + 200 => "OK", + 400 => "Bad Request", + 405 => "Method Not Allowed", + _ => "Unknown", + }, + self.content_type, + self.body.len(), + ); + v.extend_from_slice(header.as_bytes()); + v.extend_from_slice(&self.body); + v + } + fn new(status: u16, content_type: &'static str, body: Vec) -> Self { + Self { + status, + content_type, + body, + } + } +} + +#[derive(Debug)] +pub struct HTTPRequest { + stream: Option, + pub headers: HashMap, + method: Method, + http_version: String, +} + +impl HTTPRequest { + pub fn new( + stream: Option, + method: String, + path: PathBuf, + http_version: String, + headers: HashMap, + body: Option, + ) -> HTTPRequest { + HTTPRequest { + stream, + method: match method.as_str() { + "GET" => Method::GET(GETData { path }), + "POST" => Method::POST(POSTData { path, body }), + _ => panic!("Unavailable method"), + }, + http_version, + headers, + } + } + + pub fn set_stream(&mut self, stream: TcpStream) { + self.stream = Some(stream) + } + + pub fn get_method(&self) -> &Method { + &self.method + } + + fn make_response(status: HTTPResponse, accept: Option<&str>) -> std::io::Result { + fn response_ok_content(content: Content) -> std::io::Result { + match content { + Content::HTML(path) => Ok(Response { + status: 200, + content_type: "text/html", + body: fs::read(path)?, + }), + Content::JSON(value) => Ok(Response { + status: 200, + content_type: "application/json", + body: serde_json::to_vec(&value)?, + }), + Content::Image(path, img_type) => Ok(Response { + status: 200, + content_type: match img_type { + ImageType::PNG => "image/png", + ImageType::ICO => "image/vnd.microsoft.icon", + ImageType::JPEG => "image/jpeg", + }, + body: fs::read(path)?, + }), + Content::PlainText(text) => Ok(Response { + status: 200, + content_type: "text/plain", + body: text.into_bytes(), + }), + } + } + fn response_ok_no_content(a: &str) -> std::io::Result { + if a.contains("text/html") { + return Ok(Response::new( + 200, + "text/html", + fs::read("static/200.html").unwrap_or("NOT FOUND".into()), + )); + } else if a.contains("application/json") { + let j = json!({ "msg": "OK", "status": 200 }); + return Ok(Response::new( + 200, + "application/json", + serde_json::to_vec(&j)?, + )); + } + + Ok(Response::new(200, "text", "Success".into())) + } + + match status { + HTTPResponse::OK(content_opt) => { + if let Some(content) = content_opt { + response_ok_content(content) + } else { + response_ok_no_content(accept.unwrap_or("text")) + } + } + HTTPResponse::InvalidMethod => { + if accept.unwrap_or("").contains("text/html") { + Ok(Response::new( + 405, + "text/html", + fs::read("static/405.html") + .unwrap_or_else(|_| b"405 Invalid Method".to_vec()), + )) + } else { + Ok(Response::new( + 405, + "text/plain", + b"405 Invalid Method".to_vec(), + )) + } + } + HTTPResponse::BadRequest => { + if accept.unwrap_or("").contains("text/html") { + Ok(Response::new( + 400, + "text/html", + fs::read("static/400.html") + .unwrap_or_else(|_| b"400 Bad Request".to_vec()), + )) + } else { + Ok(Response::new( + 400, + "text/plain", + b"400 Bad Request".to_vec(), + )) + } + } + HTTPResponse::InternalServerError => { + if accept.unwrap_or("").contains("text/html") { + Ok(Response::new( + 500, + "text/html", + fs::read("static/500.html") + .unwrap_or_else(|_| b"500 Internal Server Error".to_vec()), + )) + } else { + Ok(Response::new( + 500, + "text/plain", + b"500 Internal Server Error".to_vec(), + )) + } + } + } + } + + pub fn response(&mut self, status: HTTPResponse) { + let accept = self.headers.get("Accept").map(|s| s.as_str()); + let resp = Self::make_response(status, accept).unwrap(); + if let Err(e) = self.stream.as_mut().unwrap().write_all(&resp.to_bytes()) { + eprintln!("Error writing response to stream: {}", e); + } + } +} + +impl fmt::Display for HTTPRequest { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Write the HTTP method and version + writeln!(f, "Method: {}", self.method)?; + writeln!(f, "HTTP Version: {}", self.http_version)?; + + // Write the stream status + writeln!( + f, + "Stream: {}", + if self.stream.is_some() { + "Connected" + } else { + "Disconnected" + } + )?; + + // Write the headers + writeln!(f, "Headers:")?; + if self.headers.is_empty() { + writeln!(f, " (none)")?; + } else { + for (key, value) in &self.headers { + writeln!(f, " {}: {}", key, value)?; + } + } + + Ok(()) + } +} +#[derive(Debug)] +pub enum HTTPParseError { + InvalidStatusLine, + InvalidRequestLine, + MissingFields, + MissingContentLength, +} +impl fmt::Display for HTTPParseError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + HTTPParseError::InvalidRequestLine => write!(f, "Invalid request line"), + HTTPParseError::MissingFields => write!(f, "Missing required fields"), + HTTPParseError::InvalidStatusLine => write!(f, "Invalid status line"), + HTTPParseError::MissingContentLength => { + write!(f, "Missing content-length field in headers") + } + } + } +} +impl std::error::Error for HTTPParseError {} diff --git a/src/node/resolve_requests/mod.rs b/src/node/resolve_requests/mod.rs new file mode 100644 index 0000000..c8f390a --- /dev/null +++ b/src/node/resolve_requests/mod.rs @@ -0,0 +1,3 @@ +pub mod endpoints; +pub mod methods; + diff --git a/src/node/static/index.html b/src/node/static/index.html index 39368c0..9550a9b 100644 --- a/src/node/static/index.html +++ b/src/node/static/index.html @@ -61,16 +61,10 @@

Cryptocoin Node Dashboard

async function fetchNodeStatus() { try { // Placeholder for actual API call to node - // const response = await fetch('http://localhost:3000/api/status'); - // const data = await response.json(); + const response = await fetch('http://localhost:9473/status'); + const data = await response.json(); - // Simulated data for demonstration - const data = { - status: "Online", - blockHeight: 123456, - peers: 8, - timestamp: new Date().toLocaleString() - }; + document.getElementById('nodeStatus').textContent = data.status; document.getElementById('blockHeight').textContent = data.blockHeight; From 567ad0cf1917bc257fc629fb5ac540bb24fa1180 Mon Sep 17 00:00:00 2001 From: Dante Ramacciotti Date: Mon, 2 Jun 2025 12:02:54 +0200 Subject: [PATCH 26/40] working version of currying with &str instead of String --- src/node/resolve_requests/endpoints.rs | 51 +++++++++----------------- 1 file changed, 18 insertions(+), 33 deletions(-) diff --git a/src/node/resolve_requests/endpoints.rs b/src/node/resolve_requests/endpoints.rs index 0173405..d28865e 100644 --- a/src/node/resolve_requests/endpoints.rs +++ b/src/node/resolve_requests/endpoints.rs @@ -72,7 +72,6 @@ use super::methods::{ mod helpers { use super::*; use std::path::PathBuf; - use serde_json::{json, Value}; use super::errors::HTTPResponseError; const STATIC_FOLDER: &str = "src/node/static/"; @@ -181,7 +180,7 @@ mod helpers { pub mod endpoints { use super::*; - use super::helpers::{return_html, return_image, HTTPResult, Handler, GETFunc, POSTFunc, GETFuncWrapper, POSTFuncWrapper, method_not_allowed, path_not_found, return_json}; + use super::helpers::{return_html, return_image, HTTPResult, Handler, GETFunc, POSTFunc, method_not_allowed, path_not_found, return_json}; use super::errors::{ HTTPResponseError }; @@ -193,7 +192,7 @@ pub mod endpoints { }; use serde_json::json; - pub fn index(data: &GETData) -> HTTPResult { + pub fn index(_: &GETData) -> HTTPResult { return_html("index.html") } @@ -243,11 +242,11 @@ pub mod endpoints { }))))) } - pub fn favicon(data: &GETData) -> HTTPResult { + pub fn favicon(_: &GETData) -> HTTPResult { return_image("fav.ico", ImageType::ICO) } - pub fn status(data: &GETData) -> HTTPResult { + pub fn status(_: &GETData) -> HTTPResult { return_json(json!({ "status": "Fodeline", "blockHeight": 123456, @@ -263,23 +262,20 @@ pub mod endpoints { initialization of the program, and pass it around as a parameter to the functions that need it */ - - fn curry_add_endpoint(endpoints: &mut HashMap>>) - -> impl FnMut( - String, - Option HTTPResult>, - Option HTTPResult> - ) + use<'_> { - |path: String, + + fn curry_add_endpoint<'a, 'b>( + endpoints: &'b mut HashMap<&'a str, HashMap<&'a str, Box>> + ) -> impl FnMut(&'a str, Option, Option) + 'b { + |path: &'a str, get: Option HTTPResult>, post: Option HTTPResult> | { - let mut methods: HashMap> = HashMap::new(); + let mut methods: HashMap<&'a str, Box> = HashMap::new(); if let Some(get) = get { - methods.insert("GET".parse().unwrap(), Box::new(get) as Box); + methods.insert("GET", Box::new(get) as Box); } if let Some(post) = post { - methods.insert("POST".parse().unwrap(), Box::new(post) as Box); + methods.insert("POST", Box::new(post) as Box); } endpoints.insert(path, methods); @@ -289,25 +285,14 @@ pub mod endpoints { - let mut endpoints: HashMap>> = HashMap::new(); + let mut endpoints: HashMap<&str, HashMap<&str, Box>> = HashMap::new(); - let add_endpoints = curry_add_endpoint(&mut endpoints); - - add_endpoints("/".to_owned(), Some(index), None); - add_endpoint("/favicon.ico", Some(favicon), None); - add_endpoint( - "/submit-transactions", - &mut endpoints, - None, - Some(submit_transaction), - ); - add_endpoint( - "/status", - &mut endpoints, - Some(status), - None - ); + { + let mut add_endpoints = curry_add_endpoint(&mut endpoints); + add_endpoints("/", Some(index), None); + } + let (path, method) = match request.get_method() { Method::GET(data) => (data.path.clone(), "GET"), From bbcd65ca693614cc70b24cbfbe820a82e5dee736 Mon Sep 17 00:00:00 2001 From: Dante Ramacciotti Date: Fri, 6 Jun 2025 12:24:29 +0200 Subject: [PATCH 27/40] Now the endpoints can manipulate the node state (Maybe insecure but I don't think so) --- src/bin/create_transaction.rs | 2 +- src/chain/block.rs | 2 +- src/chain/mod.rs | 2 +- src/node/mod.rs | 49 ++- src/node/resolve_requests/endpoints.rs | 472 +++++++++---------------- src/node/resolve_requests/errors.rs | 59 ++++ src/node/resolve_requests/helpers.rs | 91 +++++ src/node/resolve_requests/methods.rs | 4 +- src/node/resolve_requests/mod.rs | 3 +- 9 files changed, 351 insertions(+), 333 deletions(-) create mode 100644 src/node/resolve_requests/errors.rs create mode 100644 src/node/resolve_requests/helpers.rs diff --git a/src/bin/create_transaction.rs b/src/bin/create_transaction.rs index de14587..b3b914c 100644 --- a/src/bin/create_transaction.rs +++ b/src/bin/create_transaction.rs @@ -35,7 +35,7 @@ async fn post_json() -> Result<(), Box> { // Send the POST request let response = client - .post("http://localhost:9473/submit-transactions") + .post("http://localhost:9473/submit-transaction") .header("Content-Type", "application/json") .body(json_content) .send() diff --git a/src/chain/block.rs b/src/chain/block.rs index 12224d0..6bddd0e 100644 --- a/src/chain/block.rs +++ b/src/chain/block.rs @@ -82,7 +82,7 @@ impl Block { Self { previous_hash: String::from("Foguete nao da re"), transactions: Vec::new(), - index: 0, + index: 1, timestamp: Utc::now(), hash: String::from( "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks", diff --git a/src/chain/mod.rs b/src/chain/mod.rs index 7c7deb0..7376ace 100644 --- a/src/chain/mod.rs +++ b/src/chain/mod.rs @@ -33,7 +33,7 @@ impl Chain { } } - fn get_last_index(&mut self) -> u64 { + pub(crate) fn get_last_index(&mut self) -> u64 { match self.blocks.last() { Some(block) => block.get_index(), None => { diff --git a/src/node/mod.rs b/src/node/mod.rs index 269846b..ec309d7 100644 --- a/src/node/mod.rs +++ b/src/node/mod.rs @@ -1,15 +1,19 @@ pub mod logger; +pub mod ui; mod resolve_requests; mod thread_pool; -pub mod ui; mod utils; - -use crate::chain::{transaction::Transaction, Chain}; +use crate::chain::{ + transaction::Transaction, + Chain +}; use core::panic; -use resolve_requests::methods::{HTTPParseError, HTTPRequest}; +use resolve_requests::methods::{ + HTTPParseError, + HTTPRequest +}; use std::time::Duration; use thread_pool::custom_thread_pool::ThreadPool; - use std::path::PathBuf; use std::{ collections::HashMap, @@ -18,16 +22,22 @@ use std::{ sync::{mpsc::Receiver, Arc, Mutex}, thread, }; +use crate::node::logger::Logger; +use once_cell::sync::Lazy; +use resolve_requests::endpoints::resolve_endpoint; -pub struct Node { + +pub struct NodeState { + status: bool, chain: Chain, - transactions_list: Vec, + transactions: Vec, +} +pub struct Node { + state: Arc>, logger: Arc, } -use crate::node::logger::Logger; -use once_cell::sync::Lazy; -use resolve_requests::endpoints::endpoints::resolve_endpoint; + static NUMBER_OF_THREADS_IN_THREAD_POOL: Lazy = Lazy::new(num_cpus::get); @@ -40,8 +50,11 @@ impl Node { num_cpus::get(); Node { - chain, - transactions_list: Vec::new(), + state: Arc::new(Mutex::new(NodeState { + status: true, + chain, + transactions: Vec::new(), + })), logger, } } @@ -150,7 +163,8 @@ impl Node { Err(HTTPParseError::InvalidStatusLine) } - fn handle_connection(stream: TcpStream) -> Result, Option> { + fn handle_connection(state: Arc>, stream: TcpStream) -> Result, + Option> { let buf_reader = BufReader::new(&stream); let mut request_object: HTTPRequest = match Self::parse_http_request(buf_reader) { @@ -163,7 +177,7 @@ impl Node { request_object.set_stream(stream); // TODO add logging - resolve_endpoint(request_object) + resolve_endpoint(state, request_object) } pub fn run(&mut self, default: bool, rx: Arc>>, selected_port: u16) { @@ -203,9 +217,12 @@ impl Node { match listener.accept() { Ok((stream, _)) => { let logger = Arc::clone(&self.logger); + let state = Arc::clone(&self.state); thread_pool.execute(move || { - match Self::handle_connection(stream) { - Ok(Some(value)) => logger.log(format!("{value}")), + match Self::handle_connection(state, stream) { + Ok(Some(value)) => { + logger.log(format!("{value}")); + } Err(Some(value)) => logger.log_error(format!("{value}")), _ => {} }; diff --git a/src/node/resolve_requests/endpoints.rs b/src/node/resolve_requests/endpoints.rs index d28865e..79aabaa 100644 --- a/src/node/resolve_requests/endpoints.rs +++ b/src/node/resolve_requests/endpoints.rs @@ -1,343 +1,195 @@ -mod errors { - use std::fmt; - - #[derive(Debug)] - pub enum HTTPResponseError { - InvalidMethod(Option), - InvalidPath(Option), - InvalidBody(Option), - InternalServerError(Option), - BadRequest(Option), - } - impl fmt::Display for HTTPResponseError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - // If log adds log, else just prints the type of the enum - HTTPResponseError::InvalidMethod(log) => match log { - None => { - write!(f, "Invalid Method") - } - Some(log) => { - write!(f, "Invalid Method: {}", log) - } - }, - HTTPResponseError::InvalidPath(log) => match log { - None => { - write!(f, "Invalid Path") - } - Some(log) => { - write!(f, "Invalid Path: {}", log) - } - }, - HTTPResponseError::InvalidBody(log) => match log { - None => { - write!(f, "Invalid Body") - } - Some(log) => { - write!(f, "Invalid Body: {}", log) - } - }, - HTTPResponseError::InternalServerError(log) => match log { - None => { - write!(f, "Internal Server Error") - } - Some(log) => { - write!(f, "Internal Server Error: {}", log) - } - }, - HTTPResponseError::BadRequest(log) => match log { - None => { - write!(f, "Bad Request") - } - Some(log) => { - write!(f, "Bad Request: {}", log) - } - }, - } - } - } - impl std::error::Error for HTTPResponseError {} -} - use super::methods::{ - Content, + Content, GETData, - HTTPRequest, - HTTPResponse, - ImageType, + HTTPRequest, + HTTPResponse, + ImageType, Method, POSTData, }; +use super::errors::HTTPResponseError; +use super::helpers::{ + method_not_allowed, + path_not_found, + return_html, + return_image, + return_json, + GETFunc, + HTTPResult, + Handler, + POSTFunc, +}; +use crate::chain::transaction::{ + Transaction, + TransactionDeserializeError, + TransactionValidationError, +}; +use chrono::Utc; +use serde_json::json; +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; +use crate::node::NodeState; -mod helpers { - use super::*; - use std::path::PathBuf; - use super::errors::HTTPResponseError; - - const STATIC_FOLDER: &str = "src/node/static/"; - - pub type HTTPResult = Result; - - - // These wrappers exist because of a bug in rust: - // https://github.com/rust-lang/rust/issues/63033 - pub type POSTFunc = fn(&POSTData) -> HTTPResult; - pub type GETFunc = fn(&GETData) -> HTTPResult; - pub struct GETFuncWrapper(GETFunc); - pub struct POSTFuncWrapper(POSTFunc); - pub fn path_not_found() -> HTTPResult { - Err(HTTPResponseError::InvalidPath(Some( - "Path not found".to_string(), - ))) - } - pub fn method_not_allowed() -> HTTPResult { - Err(HTTPResponseError::InvalidMethod(None)) - } - - pub trait Handler { - fn call(&self, request: &HTTPRequest) -> HTTPResult; - } - - // Implement the trait for GETFunc - impl Handler for GETFunc { - fn call(&self, request: &HTTPRequest) -> HTTPResult { - match request.get_method() { - Method::GET(data) => self(&data), - _ => method_not_allowed(), - } - } - } +// pub type POSTFunc = fn(&POSTData, Arc>) -> HTTPResult; +// pub type GETFunc = fn(&GETData, Arc>) -> HTTPResult; - // Implement the trait for POSTFunc - impl Handler for POSTFunc { - fn call(&self, request: &HTTPRequest) -> HTTPResult { - match request.get_method() { - Method::POST(data) => self(&data), - _ => method_not_allowed(), - } - } - } +pub fn index(_: &GETData, _: Arc>) -> HTTPResult { + return_html("index.html") +} - impl Handler for GETFuncWrapper { - fn call(&self, request: &HTTPRequest) -> HTTPResult { - match request.get_method() { - Method::GET(data) => self.0(&data), - _ => method_not_allowed(), +pub fn submit_transaction(data: &POSTData, state: Arc>) -> HTTPResult { + let body = data.body.clone().unwrap(); + let transaction: Transaction = match Transaction::deserialize(body) { + Ok(tx) => tx, + Err(e) => { + return match e { + TransactionDeserializeError::InsufficientFunds => { + Err(HTTPResponseError::InvalidBody(None)) + } + TransactionDeserializeError::MalformedTransaction => { + Err(HTTPResponseError::InvalidBody(None)) + } + TransactionDeserializeError::SerdeError(_) => { + Err(HTTPResponseError::InvalidBody(None)) + } } } - } + }; - impl Handler for POSTFuncWrapper { - fn call(&self, request: &HTTPRequest) -> HTTPResult { - match request.get_method() { - Method::POST(data) => self.0(&data), - _ => method_not_allowed(), + match transaction.verify() { + Ok(()) => {} + Err(e) => { + return match e { + TransactionValidationError::OpenSSLError(_) => { + Err(HTTPResponseError::InternalServerError(Some( + "Error in the OpenSSL library when verifying a transaction".to_string(), + ))) + } + TransactionValidationError::ValidationError => { + Err(HTTPResponseError::BadRequest(Some( + "Transaction submitted with \ + invalid signature" + .to_string(), + ))) + } } } - } - - pub fn return_image(path: &str, image_type: ImageType) -> HTTPResult { - Ok(HTTPResponse::OK(Some(Content::Image( - PathBuf::from(STATIC_FOLDER.to_owned() + path), - image_type, - )))) - } - pub fn return_html(path: &str) -> HTTPResult { - Ok(HTTPResponse::OK(Some(Content::HTML(PathBuf::from( - STATIC_FOLDER.to_owned() + path, - ))))) - } - - pub fn return_json(json: serde_json::Value) -> HTTPResult { - Ok(HTTPResponse::OK(Some(Content::JSON( - json - )))) - } + }; + + state.lock().unwrap().transactions.push(transaction); - pub fn post(request: HTTPRequest, f: POSTFunc) -> HTTPResult { - let method = request.get_method(); - if let Method::POST(data) = method { - f(data) - } else { - Err(HTTPResponseError::InvalidMethod(None)) - } - } - pub fn get(request: HTTPRequest, f: GETFunc) -> HTTPResult { - if let Method::GET(data) = request.get_method() { - f(data) - } else { - Err(HTTPResponseError::InvalidMethod(None)) - } - } - pub fn get_post(request: HTTPRequest, get: GETFunc, post: POSTFunc) -> HTTPResult { - match request.get_method() { - Method::POST(data) => post(data), - Method::GET(data) => get(data), - } - } + Ok(HTTPResponse::OK(Some(Content::JSON(json!({ + "msg": "The transaction was added to the pool.", + "status_code": "200" + }))))) } +pub fn favicon(_: &GETData, _: Arc>) -> HTTPResult { + return_image("fav.ico", ImageType::ICO) +} -pub mod endpoints { - use super::*; - use super::helpers::{return_html, return_image, HTTPResult, Handler, GETFunc, POSTFunc, method_not_allowed, path_not_found, return_json}; - use super::errors::{ - HTTPResponseError - }; - - use std::collections::HashMap; - use chrono::Utc; - use crate::chain::transaction::{ - Transaction, TransactionDeserializeError, TransactionValidationError, +pub fn status(_: &GETData, state: Arc>) -> HTTPResult { + let mut state = match state.lock() { + Ok(guard) => guard, + Err(_) => panic!("Mutex lock was poisoned in function status on endpoints"), }; - use serde_json::json; - - pub fn index(_: &GETData) -> HTTPResult { - return_html("index.html") - } + + return_json(json!({ + "status": state.status, + "blockHeight": state.chain.get_last_index(), + "peers": 8, + "timestamp": Utc::now() + })) +} - pub fn submit_transaction(data: &POSTData) -> HTTPResult { - let body = data.body.clone().unwrap(); - let transaction: Transaction = match Transaction::deserialize(body) { - Ok(tx) => tx, - Err(e) => { - return match e { - TransactionDeserializeError::InsufficientFunds => { - Err(HTTPResponseError::InvalidBody(None)) - } - TransactionDeserializeError::MalformedTransaction => { - Err(HTTPResponseError::InvalidBody(None)) - } - TransactionDeserializeError::SerdeError(_) => { - Err(HTTPResponseError::InvalidBody(None)) - } - } +pub fn resolve_endpoint( + state: Arc>, + mut request: HTTPRequest +) -> Result, Option> { + /* + TODO: This creates the endpoints var every time the resolve_endpoints function runs, + which is inefficient. We should move the creation of the endpoints var to the + initialization of the program, and pass it around as a parameter to the functions that + need it + */ + + fn curry_add_endpoint<'a, 'b>( + endpoints: &'b mut HashMap<&'a str, HashMap<&'a str, Box>>, + ) -> impl FnMut(&'a str, Option, Option) + 'b { + | + path: &'a str, + get: Option, + post: Option, + | { + let mut methods: HashMap<&'a str, Box> = HashMap::new(); + if let Some(get) = get { + + methods.insert("GET", Box::new(get) as Box); } - }; - - match transaction.verify() { - Ok(()) => {} - Err(e) => { - return match e { - TransactionValidationError::OpenSSLError(_) => { - Err(HTTPResponseError::InternalServerError(Some( - "Error in the OpenSSL library when verifying a transaction" - .to_string(), - ))) - } - TransactionValidationError::ValidationError => { - Err(HTTPResponseError::BadRequest(Some( - "Transaction submitted with \ - invalid signature" - .to_string(), - ))) - } - } + if let Some(post) = post { + methods.insert("POST", Box::new(post) as Box); } - }; - - Ok(HTTPResponse::OK(Some(Content::JSON(json!({ - "msg": "The transaction was added to the pool.", - "status_code": "200" - }))))) - } - - pub fn favicon(_: &GETData) -> HTTPResult { - return_image("fav.ico", ImageType::ICO) - } - - pub fn status(_: &GETData) -> HTTPResult { - return_json(json!({ - "status": "Fodeline", - "blockHeight": 123456, - "peers": 8, - "timestamp": Utc::now() - })) - } - - pub fn resolve_endpoint(mut request: HTTPRequest) -> Result, Option> { - /* - TODO: This creates the endpoints var every time the resolve_endpoints function runs, - which is very inefficient. We should move the creation of the endpoints var to the - initialization of the program, and pass it around as a parameter to the functions that - need it - */ - fn curry_add_endpoint<'a, 'b>( - endpoints: &'b mut HashMap<&'a str, HashMap<&'a str, Box>> - ) -> impl FnMut(&'a str, Option, Option) + 'b { - |path: &'a str, - get: Option HTTPResult>, - post: Option HTTPResult> - | { - let mut methods: HashMap<&'a str, Box> = HashMap::new(); - if let Some(get) = get { - methods.insert("GET", Box::new(get) as Box); - } - if let Some(post) = post { - methods.insert("POST", Box::new(post) as Box); - } - - endpoints.insert(path, methods); - } + endpoints.insert(path, methods); } - - - - + } + fn initialize_endpoints<'a>() -> HashMap<&'a str, HashMap<&'a str, Box>> { let mut endpoints: HashMap<&str, HashMap<&str, Box>> = HashMap::new(); - - { let mut add_endpoints = curry_add_endpoint(&mut endpoints); add_endpoints("/", Some(index), None); + add_endpoints("/favicon", Some(favicon), None); + add_endpoints("/status", Some(status), None); + add_endpoints("/submit-transaction", None, Some(submit_transaction)); } - + endpoints + } - let (path, method) = match request.get_method() { - Method::GET(data) => (data.path.clone(), "GET"), - Method::POST(data) => (data.path.clone(), "POST"), - }; + let endpoints = initialize_endpoints(); - let r = match endpoints.get(path.to_str().unwrap()) { - Some(methods) => match methods.get(method) { - Some(handler) => handler.call(&request), - None => method_not_allowed(), - }, - None => path_not_found(), - }; + let (path, method) = match request.get_method() { + Method::GET(data) => (data.path.clone(), "GET"), + Method::POST(data) => (data.path.clone(), "POST"), + }; - match r { - Ok(value) => { - request.response(value); - Ok(Some(format!( - "Request {} to path {} was successful", - method, - path.to_str().unwrap() - ))) - } - Err(e) => match e { - HTTPResponseError::InvalidMethod(log) => { - request.response(HTTPResponse::InvalidMethod); - Err(log) - } - HTTPResponseError::InvalidPath(log) => { - request.response(HTTPResponse::BadRequest); - Err(log) - } - HTTPResponseError::InvalidBody(log) => { - request.response(HTTPResponse::BadRequest); - Err(log) - } - HTTPResponseError::InternalServerError(log) => { - request.response(HTTPResponse::InternalServerError); - Err(log) - } - HTTPResponseError::BadRequest(log) => { - request.response(HTTPResponse::BadRequest); - Err(log) - } - }, + let r = match endpoints.get(path.to_str().unwrap()) { + Some(methods) => match methods.get(method) { + Some(handler) => handler.call(&request, state), + None => method_not_allowed(), + }, + None => path_not_found(path.to_str().unwrap()), + }; + + match r { + Ok(value) => { + request.response(value); + Ok(Some(format!( + "Request {} to path {} was successful", + method, + path.to_str().unwrap() + ))) } + Err(e) => match e { + HTTPResponseError::InvalidMethod(log) => { + request.response(HTTPResponse::InvalidMethod); + Err(log) + } + HTTPResponseError::InvalidPath(log) => { + request.response(HTTPResponse::BadRequest); + Err(log) + } + HTTPResponseError::InvalidBody(log) => { + request.response(HTTPResponse::BadRequest); + Err(log) + } + HTTPResponseError::InternalServerError(log) => { + request.response(HTTPResponse::InternalServerError); + Err(log) + } + HTTPResponseError::BadRequest(log) => { + request.response(HTTPResponse::BadRequest); + Err(log) + } + }, } -} \ No newline at end of file +} diff --git a/src/node/resolve_requests/errors.rs b/src/node/resolve_requests/errors.rs new file mode 100644 index 0000000..46b71f4 --- /dev/null +++ b/src/node/resolve_requests/errors.rs @@ -0,0 +1,59 @@ + +use std::fmt; + +#[derive(Debug)] +pub enum HTTPResponseError { + InvalidMethod(Option), + InvalidPath(Option), + InvalidBody(Option), + InternalServerError(Option), + BadRequest(Option), +} +impl fmt::Display for HTTPResponseError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + // If log adds log, else just prints the type of the enum + HTTPResponseError::InvalidMethod(log) => match log { + None => { + write!(f, "Invalid Method") + } + Some(log) => { + write!(f, "Invalid Method: {}", log) + } + }, + HTTPResponseError::InvalidPath(log) => match log { + None => { + write!(f, "Invalid Path") + } + Some(log) => { + write!(f, "Invalid Path: {}", log) + } + }, + HTTPResponseError::InvalidBody(log) => match log { + None => { + write!(f, "Invalid Body") + } + Some(log) => { + write!(f, "Invalid Body: {}", log) + } + }, + HTTPResponseError::InternalServerError(log) => match log { + None => { + write!(f, "Internal Server Error") + } + Some(log) => { + write!(f, "Internal Server Error: {}", log) + } + }, + HTTPResponseError::BadRequest(log) => match log { + None => { + write!(f, "Bad Request") + } + Some(log) => { + write!(f, "Bad Request: {}", log) + } + }, + } + } +} +impl std::error::Error for HTTPResponseError {} \ No newline at end of file diff --git a/src/node/resolve_requests/helpers.rs b/src/node/resolve_requests/helpers.rs new file mode 100644 index 0000000..faad849 --- /dev/null +++ b/src/node/resolve_requests/helpers.rs @@ -0,0 +1,91 @@ + +use crate::node::resolve_requests::errors::HTTPResponseError; +use std::path::PathBuf; +use std::sync::{Arc, Mutex}; +use crate::node::NodeState; +use super::methods::{ + Content, + GETData, + HTTPRequest, + HTTPResponse, + ImageType, + Method, + POSTData +}; + +const STATIC_FOLDER: &str = "src/node/static/"; + +pub type HTTPResult = Result; + +pub type POSTFunc = fn(&POSTData, Arc>) -> HTTPResult; +pub type GETFunc = fn(&GETData, Arc>) -> HTTPResult; +pub fn path_not_found(s: &str) -> HTTPResult { + Err(HTTPResponseError::InvalidPath(Some(format!( + "Path {} was not found", + s + )))) +} +pub fn method_not_allowed() -> HTTPResult { + Err(HTTPResponseError::InvalidMethod(None)) +} + +pub trait Handler { + fn call(&self, request: &HTTPRequest, state: Arc>) -> HTTPResult; +} + +// Implement the trait for GETFunc +impl Handler for GETFunc { + fn call(&self, request: &HTTPRequest, state: Arc>) -> HTTPResult { + match request.get_method() { + Method::GET(data) => self(&data, state), + _ => method_not_allowed(), + } + } +} + +// Implement the trait for POSTFunc +impl Handler for POSTFunc { + fn call(&self, request: &HTTPRequest, state: Arc>) -> HTTPResult { + match request.get_method() { + Method::POST(data) => self(&data, state), + _ => method_not_allowed(), + } + } +} + +pub fn return_image(path: &str, image_type: ImageType) -> HTTPResult { + Ok(HTTPResponse::OK(Some(Content::Image( + PathBuf::from(STATIC_FOLDER.to_owned() + path), + image_type, + )))) +} +pub fn return_html(path: &str) -> HTTPResult { + Ok(HTTPResponse::OK(Some(Content::HTML(PathBuf::from( + STATIC_FOLDER.to_owned() + path, + ))))) +} +pub fn return_json(json: serde_json::Value) -> HTTPResult { + Ok(HTTPResponse::OK(Some(Content::JSON(json)))) +} + +pub fn post(request: HTTPRequest, f: POSTFunc, state: Arc>) -> HTTPResult { + let method = request.get_method(); + if let Method::POST(data) = method { + f(data, state) + } else { + Err(HTTPResponseError::InvalidMethod(None)) + } +} +pub fn get(request: HTTPRequest, f: GETFunc, state: Arc>) -> HTTPResult { + if let Method::GET(data) = request.get_method() { + f(data, state) + } else { + Err(HTTPResponseError::InvalidMethod(None)) + } +} +pub fn get_post(request: HTTPRequest, get: GETFunc, post: POSTFunc, state: Arc>) -> HTTPResult { + match request.get_method() { + Method::POST(data) => post(data, state), + Method::GET(data) => get(data, state), + } +} \ No newline at end of file diff --git a/src/node/resolve_requests/methods.rs b/src/node/resolve_requests/methods.rs index 58a71c4..73a5c65 100644 --- a/src/node/resolve_requests/methods.rs +++ b/src/node/resolve_requests/methods.rs @@ -1,4 +1,3 @@ - use serde_json::json; use std::collections::HashMap; use std::io::prelude::*; @@ -200,8 +199,7 @@ impl HTTPRequest { Ok(Response::new( 400, "text/html", - fs::read("static/400.html") - .unwrap_or_else(|_| b"400 Bad Request".to_vec()), + fs::read("static/400.html").unwrap_or_else(|_| b"400 Bad Request".to_vec()), )) } else { Ok(Response::new( diff --git a/src/node/resolve_requests/mod.rs b/src/node/resolve_requests/mod.rs index c8f390a..c1b19d2 100644 --- a/src/node/resolve_requests/mod.rs +++ b/src/node/resolve_requests/mod.rs @@ -1,3 +1,4 @@ pub mod endpoints; pub mod methods; - +pub mod helpers; +pub mod errors; From e113cb1feaa54d191b6e3324621e2a73fbae9d2f Mon Sep 17 00:00:00 2001 From: Dante Ramacciotti Date: Fri, 6 Jun 2025 17:14:54 +0200 Subject: [PATCH 28/40] Log with scrolling --- src/node/mod.rs | 31 ++++++-------- src/node/resolve_requests/endpoints.rs | 58 +++++++++----------------- src/node/resolve_requests/errors.rs | 3 +- src/node/resolve_requests/helpers.rs | 47 +++++++++++---------- src/node/resolve_requests/mod.rs | 4 +- src/node/ui.rs | 30 +++++++++---- 6 files changed, 83 insertions(+), 90 deletions(-) diff --git a/src/node/mod.rs b/src/node/mod.rs index ec309d7..26faa81 100644 --- a/src/node/mod.rs +++ b/src/node/mod.rs @@ -1,20 +1,16 @@ pub mod logger; -pub mod ui; mod resolve_requests; mod thread_pool; +pub mod ui; mod utils; -use crate::chain::{ - transaction::Transaction, - Chain -}; +use crate::chain::{transaction::Transaction, Chain}; +use crate::node::logger::Logger; use core::panic; -use resolve_requests::methods::{ - HTTPParseError, - HTTPRequest -}; -use std::time::Duration; -use thread_pool::custom_thread_pool::ThreadPool; +use once_cell::sync::Lazy; +use resolve_requests::endpoints::resolve_endpoint; +use resolve_requests::methods::{HTTPParseError, HTTPRequest}; use std::path::PathBuf; +use std::time::Duration; use std::{ collections::HashMap, io::{prelude::*, BufReader}, @@ -22,10 +18,7 @@ use std::{ sync::{mpsc::Receiver, Arc, Mutex}, thread, }; -use crate::node::logger::Logger; -use once_cell::sync::Lazy; -use resolve_requests::endpoints::resolve_endpoint; - +use thread_pool::custom_thread_pool::ThreadPool; pub struct NodeState { status: bool, @@ -37,8 +30,6 @@ pub struct Node { logger: Arc, } - - static NUMBER_OF_THREADS_IN_THREAD_POOL: Lazy = Lazy::new(num_cpus::get); impl Node { @@ -163,8 +154,10 @@ impl Node { Err(HTTPParseError::InvalidStatusLine) } - fn handle_connection(state: Arc>, stream: TcpStream) -> Result, - Option> { + fn handle_connection( + state: Arc>, + stream: TcpStream, + ) -> Result, Option> { let buf_reader = BufReader::new(&stream); let mut request_object: HTTPRequest = match Self::parse_http_request(buf_reader) { diff --git a/src/node/resolve_requests/endpoints.rs b/src/node/resolve_requests/endpoints.rs index 79aabaa..dec6bd0 100644 --- a/src/node/resolve_requests/endpoints.rs +++ b/src/node/resolve_requests/endpoints.rs @@ -1,34 +1,17 @@ -use super::methods::{ - Content, - GETData, - HTTPRequest, - HTTPResponse, - ImageType, - Method, - POSTData, -}; use super::errors::HTTPResponseError; use super::helpers::{ - method_not_allowed, - path_not_found, - return_html, - return_image, - return_json, - GETFunc, - HTTPResult, - Handler, - POSTFunc, + method_not_allowed, path_not_found, return_html, return_image, return_json, GETFunc, + HTTPResult, Handler, POSTFunc, }; +use super::methods::{Content, GETData, HTTPRequest, HTTPResponse, ImageType, Method, POSTData}; use crate::chain::transaction::{ - Transaction, - TransactionDeserializeError, - TransactionValidationError, + Transaction, TransactionDeserializeError, TransactionValidationError, }; +use crate::node::NodeState; use chrono::Utc; use serde_json::json; use std::collections::HashMap; use std::sync::{Arc, Mutex}; -use crate::node::NodeState; // pub type POSTFunc = fn(&POSTData, Arc>) -> HTTPResult; // pub type GETFunc = fn(&GETData, Arc>) -> HTTPResult; @@ -75,7 +58,7 @@ pub fn submit_transaction(data: &POSTData, state: Arc>) -> HTTP } } }; - + state.lock().unwrap().transactions.push(transaction); Ok(HTTPResponse::OK(Some(Content::JSON(json!({ @@ -93,7 +76,7 @@ pub fn status(_: &GETData, state: Arc>) -> HTTPResult { Ok(guard) => guard, Err(_) => panic!("Mutex lock was poisoned in function status on endpoints"), }; - + return_json(json!({ "status": state.status, "blockHeight": state.chain.get_last_index(), @@ -104,7 +87,7 @@ pub fn status(_: &GETData, state: Arc>) -> HTTPResult { pub fn resolve_endpoint( state: Arc>, - mut request: HTTPRequest + mut request: HTTPRequest, ) -> Result, Option> { /* TODO: This creates the endpoints var every time the resolve_endpoints function runs, @@ -115,15 +98,10 @@ pub fn resolve_endpoint( fn curry_add_endpoint<'a, 'b>( endpoints: &'b mut HashMap<&'a str, HashMap<&'a str, Box>>, - ) -> impl FnMut(&'a str, Option, Option) + 'b { - | - path: &'a str, - get: Option, - post: Option, - | { + ) -> impl FnMut(&'a str, Option, Option) + 'b { + |path: &'a str, get: Option, post: Option| { let mut methods: HashMap<&'a str, Box> = HashMap::new(); if let Some(get) = get { - methods.insert("GET", Box::new(get) as Box); } if let Some(post) = post { @@ -138,14 +116,14 @@ pub fn resolve_endpoint( { let mut add_endpoints = curry_add_endpoint(&mut endpoints); add_endpoints("/", Some(index), None); - add_endpoints("/favicon", Some(favicon), None); + add_endpoints("/favicon.ico", Some(favicon), None); add_endpoints("/status", Some(status), None); add_endpoints("/submit-transaction", None, Some(submit_transaction)); } endpoints } - let endpoints = initialize_endpoints(); + let endpoints = initialize_endpoints(); let (path, method) = match request.get_method() { Method::GET(data) => (data.path.clone(), "GET"), @@ -155,18 +133,22 @@ pub fn resolve_endpoint( let r = match endpoints.get(path.to_str().unwrap()) { Some(methods) => match methods.get(method) { Some(handler) => handler.call(&request, state), - None => method_not_allowed(), + None => method_not_allowed(Some(path.to_str().unwrap())), }, - None => path_not_found(path.to_str().unwrap()), + None => path_not_found(Some(path.to_str().unwrap())), }; match r { Ok(value) => { request.response(value); + let path = path.to_str().unwrap(); + // I don't give a single fuck about favicon + if path == "/favicon.ico" { + return Ok(None); + } Ok(Some(format!( "Request {} to path {} was successful", - method, - path.to_str().unwrap() + method, path ))) } Err(e) => match e { diff --git a/src/node/resolve_requests/errors.rs b/src/node/resolve_requests/errors.rs index 46b71f4..2483667 100644 --- a/src/node/resolve_requests/errors.rs +++ b/src/node/resolve_requests/errors.rs @@ -1,4 +1,3 @@ - use std::fmt; #[derive(Debug)] @@ -56,4 +55,4 @@ impl fmt::Display for HTTPResponseError { } } } -impl std::error::Error for HTTPResponseError {} \ No newline at end of file +impl std::error::Error for HTTPResponseError {} diff --git a/src/node/resolve_requests/helpers.rs b/src/node/resolve_requests/helpers.rs index faad849..72fb01d 100644 --- a/src/node/resolve_requests/helpers.rs +++ b/src/node/resolve_requests/helpers.rs @@ -1,17 +1,8 @@ - +use super::methods::{Content, GETData, HTTPRequest, HTTPResponse, ImageType, Method, POSTData}; use crate::node::resolve_requests::errors::HTTPResponseError; +use crate::node::NodeState; use std::path::PathBuf; use std::sync::{Arc, Mutex}; -use crate::node::NodeState; -use super::methods::{ - Content, - GETData, - HTTPRequest, - HTTPResponse, - ImageType, - Method, - POSTData -}; const STATIC_FOLDER: &str = "src/node/static/"; @@ -19,13 +10,22 @@ pub type HTTPResult = Result; pub type POSTFunc = fn(&POSTData, Arc>) -> HTTPResult; pub type GETFunc = fn(&GETData, Arc>) -> HTTPResult; -pub fn path_not_found(s: &str) -> HTTPResult { - Err(HTTPResponseError::InvalidPath(Some(format!( - "Path {} was not found", - s - )))) +pub fn path_not_found(s: Option<&str>) -> HTTPResult { + if s.is_some() { + return Err(HTTPResponseError::InvalidPath(Some(format!( + "Path {} was not found", + s.unwrap() + )))); + } + Err(HTTPResponseError::InvalidPath(None)) } -pub fn method_not_allowed() -> HTTPResult { +pub fn method_not_allowed(s: Option<&str>) -> HTTPResult { + if s.is_some() { + return Err(HTTPResponseError::InvalidMethod(Some(format!( + "Attempt of accessing the path {} with wrong method", + s.unwrap() + )))); + } Err(HTTPResponseError::InvalidMethod(None)) } @@ -38,7 +38,7 @@ impl Handler for GETFunc { fn call(&self, request: &HTTPRequest, state: Arc>) -> HTTPResult { match request.get_method() { Method::GET(data) => self(&data, state), - _ => method_not_allowed(), + _ => method_not_allowed(None), } } } @@ -48,7 +48,7 @@ impl Handler for POSTFunc { fn call(&self, request: &HTTPRequest, state: Arc>) -> HTTPResult { match request.get_method() { Method::POST(data) => self(&data, state), - _ => method_not_allowed(), + _ => method_not_allowed(None), } } } @@ -83,9 +83,14 @@ pub fn get(request: HTTPRequest, f: GETFunc, state: Arc>) -> HT Err(HTTPResponseError::InvalidMethod(None)) } } -pub fn get_post(request: HTTPRequest, get: GETFunc, post: POSTFunc, state: Arc>) -> HTTPResult { +pub fn get_post( + request: HTTPRequest, + get: GETFunc, + post: POSTFunc, + state: Arc>, +) -> HTTPResult { match request.get_method() { Method::POST(data) => post(data, state), Method::GET(data) => get(data, state), } -} \ No newline at end of file +} diff --git a/src/node/resolve_requests/mod.rs b/src/node/resolve_requests/mod.rs index c1b19d2..b4cdc8f 100644 --- a/src/node/resolve_requests/mod.rs +++ b/src/node/resolve_requests/mod.rs @@ -1,4 +1,4 @@ pub mod endpoints; -pub mod methods; -pub mod helpers; pub mod errors; +pub mod helpers; +pub mod methods; diff --git a/src/node/ui.rs b/src/node/ui.rs index 749cde4..a923c32 100644 --- a/src/node/ui.rs +++ b/src/node/ui.rs @@ -9,6 +9,7 @@ use ratatui::{ DefaultTerminal, Frame, }; use std::sync::Arc; +use std::time::Duration; /// The main application which holds the state and logic of the application. @@ -51,9 +52,21 @@ impl App { .centered(); let logs_title = Line::from(" LOGS ").bold().blue().centered(); let port = self.port; + + let mut lines = self.logger.read_temp_logs().unwrap(); + + let offset = lines.len() as i32 - frame.area().height as i32; + if offset > 0 { + for _ in 0..offset { + lines.remove(0); + } + } + let text = format!( "Node running in port {port} - \n\nPress `Esc`, `Ctrl-C` or `q` to stop running." + \n\nPress `Esc`, `Ctrl-C` or `q` to stop running.\n + lines size is {}", + lines.len() ); frame.render_widget( @@ -63,7 +76,7 @@ impl App { layout[0], ); frame.render_widget( - Paragraph::new(self.logger.read_temp_logs().unwrap().join("\n")) + Paragraph::new(lines.join("\n")) .block(Block::bordered().title(logs_title)) .left_aligned(), layout[1], @@ -71,12 +84,13 @@ impl App { } fn handle_crossterm_events(&mut self) -> Result<()> { - match event::read()? { - // it's important to check KeyEventKind::Press to avoid handling key release events - Event::Key(key) if key.kind == KeyEventKind::Press => self.on_key_event(key), - Event::Mouse(_) => {} - Event::Resize(_, _) => {} - _ => {} + if event::poll(Duration::from_millis(100))? { + match event::read()? { + Event::Key(key) if key.kind == KeyEventKind::Press => self.on_key_event(key), + Event::Mouse(_) => {} + Event::Resize(_, _) => {} + _ => {} + } } Ok(()) } From c5632828f051e439df232288cc460e1d53762664 Mon Sep 17 00:00:00 2001 From: Dante Ramacciotti Date: Fri, 6 Jun 2025 17:33:34 +0200 Subject: [PATCH 29/40] Log with scrolling --- src/chain/transaction.rs | 15 ---- src/chain/wallet.rs | 15 ---- src/node/mod.rs | 5 ++ src/node/resolve_requests/endpoints.rs | 3 +- tests/create_transaction.rs | 35 --------- tests/create_wallet.rs | 9 +++ tests/sign_and_verify_transactioninfo.rs | 55 -------------- tests/transactions.rs | 92 ++++++++++++++++++++++++ 8 files changed, 108 insertions(+), 121 deletions(-) delete mode 100644 tests/create_transaction.rs create mode 100644 tests/create_wallet.rs delete mode 100644 tests/sign_and_verify_transactioninfo.rs create mode 100644 tests/transactions.rs diff --git a/src/chain/transaction.rs b/src/chain/transaction.rs index 06a59ff..1f15fd4 100644 --- a/src/chain/transaction.rs +++ b/src/chain/transaction.rs @@ -149,18 +149,3 @@ impl Transaction { Ok(tx) } } - -// ---------------------------------------------- UNIT TESTS ------------------------------------------------------- -#[cfg(test)] -mod tests { - use crate::chain::transaction::TransactionInfo; - use chrono::Utc; - - #[test] //mark a function as a test. - fn test_transactioninfo_creation() { - let transaction: TransactionInfo = TransactionInfo::new(12345 as f32, Utc::now()); - println!("transaction info:\n{}", transaction.to_string()); - println!("{:?}", transaction); - } -} -// ----------------------------------------------------------------------------------------------------------------- diff --git a/src/chain/wallet.rs b/src/chain/wallet.rs index b0f8ee2..78b2ee4 100644 --- a/src/chain/wallet.rs +++ b/src/chain/wallet.rs @@ -99,18 +99,3 @@ impl Wallet { } } // ----------------------------------------------------------------------------------------------------------------- - -// ---------------------------------------------- UNIT TESTS ------------------------------------------------------- -#[cfg(test)] //ensures that the tests module is only included when running tests. -mod tests { - use crate::chain::wallet::Wallet; - - #[test] //mark a function as a test. - fn test_wallet_creation() { - let (wallet, wallet_pk) = Wallet::new(); - println!("wallet.to_string: {:?}", wallet.to_vec()); - println!("{:#?}", wallet); - println!("{:#?}", wallet_pk); - } -} -// ----------------------------------------------------------------------------------------------------------------- diff --git a/src/node/mod.rs b/src/node/mod.rs index 26faa81..04ed3ee 100644 --- a/src/node/mod.rs +++ b/src/node/mod.rs @@ -31,6 +31,11 @@ pub struct Node { } static NUMBER_OF_THREADS_IN_THREAD_POOL: Lazy = Lazy::new(num_cpus::get); +// 0 = None +// 1 = Prod +// 2 = Debug + +pub const LOG_LEVEL: u8 = 2; impl Node { // these configurations should be moved to a file diff --git a/src/node/resolve_requests/endpoints.rs b/src/node/resolve_requests/endpoints.rs index dec6bd0..4accb3d 100644 --- a/src/node/resolve_requests/endpoints.rs +++ b/src/node/resolve_requests/endpoints.rs @@ -1,3 +1,4 @@ +use super::super::LOG_LEVEL; use super::errors::HTTPResponseError; use super::helpers::{ method_not_allowed, path_not_found, return_html, return_image, return_json, GETFunc, @@ -143,7 +144,7 @@ pub fn resolve_endpoint( request.response(value); let path = path.to_str().unwrap(); // I don't give a single fuck about favicon - if path == "/favicon.ico" { + if path == "/favicon.ico" || LOG_LEVEL < 2 { return Ok(None); } Ok(Some(format!( diff --git a/tests/create_transaction.rs b/tests/create_transaction.rs deleted file mode 100644 index 0699375..0000000 --- a/tests/create_transaction.rs +++ /dev/null @@ -1,35 +0,0 @@ -use chrono::Utc; -use cleyto_coin::chain::transaction::{Transaction, TransactionInfo}; -use cleyto_coin::chain::wallet::Wallet; - -#[test] -fn create_transaction() { - let (wallet_sender, mut walletpk_sender) = Wallet::new(); - let (wallet_receiver, _) = Wallet::new(); - let transactioninfo: TransactionInfo = TransactionInfo::new(12345 as f32, Utc::now()); - - let signature = match walletpk_sender.sign_transaction(&transactioninfo) { - Ok(signed_hashed_message) => signed_hashed_message, - _ => panic!("error while signing transaction"), - }; - println!( - "Transaction signature (signed using the wallet_pk):\n{:?}", - signature - ); - - // this will also be verified by the Transaction::new(); - if wallet_sender - .verify_transaction_info(&transactioninfo, &signature) - .unwrap() - == true - { - println!("transaction verified (by the wallet)"); - } else { - println!("transaction not verified"); - } - - let transaction: Transaction = - Transaction::new(wallet_sender, wallet_receiver, transactioninfo, signature).unwrap(); - - println!("transaction.to_string(): {}", transaction.to_string()); -} diff --git a/tests/create_wallet.rs b/tests/create_wallet.rs new file mode 100644 index 0000000..415e837 --- /dev/null +++ b/tests/create_wallet.rs @@ -0,0 +1,9 @@ +use cleyto_coin::chain::wallet::Wallet; + +#[test] //mark a function as a test. +fn test_wallet_creation() { + let (wallet, wallet_pk) = Wallet::new(); + println!("wallet.to_string: {:?}", wallet.to_vec()); + println!("{:#?}", wallet); + println!("{:#?}", wallet_pk); +} diff --git a/tests/sign_and_verify_transactioninfo.rs b/tests/sign_and_verify_transactioninfo.rs deleted file mode 100644 index 11831a4..0000000 --- a/tests/sign_and_verify_transactioninfo.rs +++ /dev/null @@ -1,55 +0,0 @@ -use chrono::Utc; -use cleyto_coin::chain::{ - transaction::{Transaction, TransactionInfo}, - wallet::Wallet, -}; - -#[test] -fn sign_and_verify_transactioninfo() { - let (wallet, mut wallet_pk) = Wallet::new(); - let transactioninfo: TransactionInfo = TransactionInfo::new(12345 as f32, Utc::now()); - - let signature = match wallet_pk.sign_transaction(&transactioninfo) { - Ok(signed_hashed_message) => signed_hashed_message, - _ => panic!("error while signing transaction"), - }; - println!( - "Transaction signature (signed using the wallet_pk):\n{:?}", - signature - ); - - if wallet - .verify_transaction_info(&transactioninfo, &signature) - .unwrap() - == true - { - println!("transaction verified (by the wallet)"); - } else { - println!("transaction not verified"); - } -} - -#[test] -fn serialize_and_deserialize_transaction() { - let (wallet, mut wallet_pk) = Wallet::new(); - let (mallet, _) = Wallet::new(); - let transactioninfo: TransactionInfo = TransactionInfo::new(12345 as f32, Utc::now()); - - let signature = match wallet_pk.sign_transaction(&transactioninfo) { - Ok(signed_hashed_message) => signed_hashed_message, - _ => panic!("error while signing transaction"), - }; - - let transaction = Transaction::new(wallet, mallet, transactioninfo, signature).unwrap(); - - let serialized_transaction = transaction.serialize(); - println!("serialized_transaction: \n{serialized_transaction}"); - - let deserialized_transaction = match Transaction::deserialize(serialized_transaction) { - Ok(value) => { - println!("Success deserializing transaction"); - value - } - Err(_) => panic!("Error deserializing transaction"), - }; -} diff --git a/tests/transactions.rs b/tests/transactions.rs new file mode 100644 index 0000000..ef0d6b9 --- /dev/null +++ b/tests/transactions.rs @@ -0,0 +1,92 @@ +use chrono::Utc; +use cleyto_coin::chain::transaction::{Transaction, TransactionInfo}; +use cleyto_coin::chain::wallet::Wallet; + +#[test] +fn create_transaction() { + let (wallet_sender, mut walletpk_sender) = Wallet::new(); + let (wallet_receiver, _) = Wallet::new(); + let transactioninfo: TransactionInfo = TransactionInfo::new(12345 as f32, Utc::now()); + + let signature = match walletpk_sender.sign_transaction(&transactioninfo) { + Ok(signed_hashed_message) => signed_hashed_message, + _ => panic!("error while signing transaction"), + }; + println!( + "Transaction signature (signed using the wallet_pk):\n{:?}", + signature + ); + + // this will also be verified by the Transaction::new(); + if wallet_sender + .verify_transaction_info(&transactioninfo, &signature) + .unwrap() + == true + { + println!("transaction verified (by the wallet)"); + } else { + println!("transaction not verified"); + } + + let transaction: Transaction = + Transaction::new(wallet_sender, wallet_receiver, transactioninfo, signature).unwrap(); + + println!("transaction.to_string(): {}", transaction.to_string()); +} + +#[test] //mark a function as a test. +fn test_transaction_info_creation() { + let transaction: TransactionInfo = TransactionInfo::new(12345f32, Utc::now()); + println!("transaction info:\n{}", transaction.to_string()); + println!("{:?}", transaction); +} + +#[test] +fn sign_and_verify_transaction_info() { + let (wallet, mut wallet_pk) = Wallet::new(); + let transactioninfo: TransactionInfo = TransactionInfo::new(12345f32, Utc::now()); + + let signature = match wallet_pk.sign_transaction(&transactioninfo) { + Ok(signed_hashed_message) => signed_hashed_message, + _ => panic!("error while signing transaction"), + }; + println!( + "Transaction signature (signed using the wallet_pk):\n{:?}", + signature + ); + + if wallet + .verify_transaction_info(&transactioninfo, &signature) + .unwrap() + == true + { + println!("transaction verified (by the wallet)"); + } else { + println!("transaction not verified"); + } +} + +#[test] +fn serialize_and_deserialize_transaction() { + let (wallet, mut wallet_pk) = Wallet::new(); + let (mallet, _) = Wallet::new(); + let transactioninfo: TransactionInfo = TransactionInfo::new(12345f32, Utc::now()); + + let signature = match wallet_pk.sign_transaction(&transactioninfo) { + Ok(signed_hashed_message) => signed_hashed_message, + _ => panic!("error while signing transaction"), + }; + + let transaction = Transaction::new(wallet, mallet, transactioninfo, signature).unwrap(); + + let serialized_transaction = transaction.serialize(); + println!("serialized_transaction: \n{serialized_transaction}"); + + match Transaction::deserialize(serialized_transaction) { + Ok(value) => { + println!("Success deserializing transaction"); + value + } + Err(_) => panic!("Error deserializing transaction"), + }; +} From 32e2ac9c158c8aef348829efdae834b6e30124d4 Mon Sep 17 00:00:00 2001 From: Dante Ramacciotti Date: Wed, 6 Aug 2025 16:09:55 +0200 Subject: [PATCH 30/40] Changed the value on transactions to ints, was floats, fixed a bunch of warnings --- src/bin/create_transaction.rs | 36 +++++++-------- src/chain/block.rs | 2 +- src/chain/mod.rs | 1 + src/chain/transaction.rs | 46 +++++++++--------- src/chain/wallet.rs | 64 +++++++++++++------------- src/node/logger.rs | 9 ++-- src/node/mod.rs | 29 ++++++------ src/node/resolve_requests/endpoints.rs | 11 ++++- src/node/resolve_requests/helpers.rs | 56 +++++++++++----------- src/node/resolve_requests/methods.rs | 27 ++++++----- src/node/thread_pool.rs | 2 +- src/node/ui.rs | 1 - tests/create_block_and_add_chain.rs | 2 +- tests/requests.rs | 2 - tests/transactions.rs | 14 +++--- 15 files changed, 153 insertions(+), 149 deletions(-) diff --git a/src/bin/create_transaction.rs b/src/bin/create_transaction.rs index b3b914c..0d413d1 100644 --- a/src/bin/create_transaction.rs +++ b/src/bin/create_transaction.rs @@ -1,6 +1,6 @@ -use chrono::Utc; -use cleyto_coin::chain::transaction::{Transaction, TransactionInfo, TransactionValidationError}; -use cleyto_coin::chain::wallet::Wallet; +// use chrono::Utc; +// use cleyto_coin::chain::transaction::{Transaction, TransactionInfo, TransactionValidationError}; +// use cleyto_coin::chain::wallet::Wallet; use reqwest::Client; use std::error::Error; use std::fs; @@ -10,21 +10,21 @@ async fn main() { post_json().await.expect("TODO: panic message"); } -fn create_transaction_json() -> String { - let (wallet1, mut wallet1_pk) = Wallet::new(); - let (wallet2, _) = Wallet::new(); - let transaction_info = TransactionInfo::new(0.3, Utc::now()); - let signature = wallet1_pk.sign_transaction(&transaction_info).unwrap(); - let transaction = match Transaction::new(wallet1, wallet2, transaction_info, signature) { - Ok(tx) => tx, - Err(e) => match e { - TransactionValidationError::OpenSSLError(_) => panic!("{e}"), - TransactionValidationError::ValidationError => panic!("Validation error"), - }, - }; - - transaction.serialize() -} +// fn create_transaction_json() -> String { +// let (wallet1, mut wallet1_pk) = Wallet::new(); +// let (wallet2, _) = Wallet::new(); +// let transaction_info = TransactionInfo::new(0.3, Utc::now()); +// let signature = wallet1_pk.sign_transaction(&transaction_info).unwrap(); +// let transaction = match Transaction::new(wallet1, wallet2, transaction_info, signature) { +// Ok(tx) => tx, +// Err(e) => match e { +// TransactionValidationError::OpenSSLError(_) => panic!("{e}"), +// TransactionValidationError::ValidationError => panic!("Validation error"), +// }, +// }; +// +// transaction.serialize() +// } async fn post_json() -> Result<(), Box> { // Initialize the HTTP client diff --git a/src/chain/block.rs b/src/chain/block.rs index 6bddd0e..a3eba78 100644 --- a/src/chain/block.rs +++ b/src/chain/block.rs @@ -21,7 +21,7 @@ impl Block { } pub fn get_index(&self) -> u64 { - self.index.clone() + self.index } pub fn calculate_hash(&self) -> String { diff --git a/src/chain/mod.rs b/src/chain/mod.rs index 7376ace..e5ed154 100644 --- a/src/chain/mod.rs +++ b/src/chain/mod.rs @@ -4,6 +4,7 @@ pub mod utils; pub mod wallet; use block::Block; +#[derive(Default)] pub struct Chain { blocks: Vec, } diff --git a/src/chain/transaction.rs b/src/chain/transaction.rs index 1f15fd4..d23d5b2 100644 --- a/src/chain/transaction.rs +++ b/src/chain/transaction.rs @@ -4,25 +4,24 @@ use openssl::error::ErrorStack; use serde::{Deserialize, Serialize}; use std::fmt; use std::fmt::Debug; +use std::fmt::Display; #[derive(Clone, Debug, Serialize, Deserialize)] // ---------------------------------------------- TransactionInfo definition ----------------------- pub struct TransactionInfo { - pub value: f32, + pub value: i64, pub date: DateTime, } impl TransactionInfo { - pub fn new(value: f32, date: DateTime) -> TransactionInfo { + pub fn new(value: i64, date: DateTime) -> TransactionInfo { Self { value, date } } +} - pub fn to_string(&self) -> String { - format!( - "VALUE::{}::TIME::{}", - self.value.to_string(), - self.date.to_string() - ) +impl Display for TransactionInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "VALUE::{}::TIME::{}", self.value, self.date) } } // ------------------------------------------------------------------------------------------------- @@ -104,7 +103,7 @@ impl Transaction { match transaction.verify() { Ok(()) => Ok(transaction), - Err(error) => return Err(error), + Err(error) => Err(error), } } @@ -121,20 +120,8 @@ impl Transaction { } } - pub fn to_string(&self) -> String { - format!( - "SENDER::{:?}::RECEIVER::{:?}::{}::SIGNATURE::{:?}", - self.sender, - self.receiver.to_vec(), - self.transaction_info.to_string(), - self.signature - ) - } - pub fn serialize(&self) -> String { - let value = serde_json::to_string(self).unwrap(); - // println!("serialized transaction: {value}"); - value + serde_json::to_string(self).unwrap() } pub fn deserialize(json: String) -> Result { let tx: Transaction = match serde_json::from_str(&json) { @@ -142,10 +129,23 @@ impl Transaction { Err(e) => return Err(TransactionDeserializeError::SerdeError(e)), }; - if tx.transaction_info.value <= 0.0 { + if tx.transaction_info.value < 1 { return Err(TransactionDeserializeError::InsufficientFunds); } Ok(tx) } } + +impl Display for Transaction { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "SENDER::{:?}::RECEIVER::{:?}::{}::SIGNATURE::{:?}", + self.sender, + self.receiver.to_vec(), + self.transaction_info, + self.signature + ) + } +} diff --git a/src/chain/wallet.rs b/src/chain/wallet.rs index 78b2ee4..3c6a84c 100644 --- a/src/chain/wallet.rs +++ b/src/chain/wallet.rs @@ -7,36 +7,36 @@ use openssl::pkey::{PKey, Private, Public}; use openssl::rsa::Rsa; use openssl::sign::{Signer, Verifier}; -fn test_sign() { - let rsa = Rsa::generate(2048).unwrap(); - let pkey = PKey::from_rsa(rsa).unwrap(); - - // Data to be signed (this would normally be provided by the signer) - let data = b"hello, world!"; - - // Step 2: Sign the data with the private key (this would be done by the sender) - let mut signer = Signer::new(MessageDigest::sha256(), &pkey).unwrap(); - signer.update(data).unwrap(); - let signature = signer.sign_to_vec().unwrap(); - - // --- Now, we are at the verification step --- - // Step 3: Extract the public key from the PKey and use it for verification - let public_key = pkey.public_key_to_pem().unwrap(); // Extract public key in PEM format - let rsa_public = Rsa::public_key_from_pem(&public_key).unwrap(); // Convert back to Rsa - let pkey_public = PKey::from_rsa(rsa_public).unwrap(); // Create a PKey for public key - - // Step 4: Verify the signature using the public key - let mut verifier = Verifier::new(MessageDigest::sha256(), &pkey_public).unwrap(); - verifier.update(data).unwrap(); - let is_valid = verifier.verify(&signature).unwrap(); - - // Step 5: Check if the signature is valid - if is_valid { - println!("Signature is valid!"); - } else { - println!("Signature is invalid."); - } -} +// fn test_sign() { +// let rsa = Rsa::generate(2048).unwrap(); +// let pkey = PKey::from_rsa(rsa).unwrap(); +// +// // Data to be signed (this would normally be provided by the signer) +// let data = b"hello, world!"; +// +// // Step 2: Sign the data with the private key (this would be done by the sender) +// let mut signer = Signer::new(MessageDigest::sha256(), &pkey).unwrap(); +// signer.update(data).unwrap(); +// let signature = signer.sign_to_vec().unwrap(); +// +// // --- Now, we are at the verification step --- +// // Step 3: Extract the public key from the PKey and use it for verification +// let public_key = pkey.public_key_to_pem().unwrap(); // Extract public key in PEM format +// let rsa_public = Rsa::public_key_from_pem(&public_key).unwrap(); // Convert back to Rsa +// let pkey_public = PKey::from_rsa(rsa_public).unwrap(); // Create a PKey for public key +// +// // Step 4: Verify the signature using the public key +// let mut verifier = Verifier::new(MessageDigest::sha256(), &pkey_public).unwrap(); +// verifier.update(data).unwrap(); +// let is_valid = verifier.verify(&signature).unwrap(); +// +// // Step 5: Check if the signature is valid +// if is_valid { +// println!("Signature is valid!"); +// } else { +// println!("Signature is invalid."); +// } +// } // ---------------------------------------------- WalletPK definition ---------------------------------------------- #[derive(Debug)] @@ -51,7 +51,7 @@ impl WalletPK { ) -> Result, ErrorStack> { let mut signer = Signer::new(MessageDigest::sha256(), &self.private_key)?; signer.update(transaction_info.to_string().as_bytes())?; - Ok(signer.sign_to_vec()?) + signer.sign_to_vec() } } // ----------------------------------------------------------------------------------------------------------------- @@ -83,7 +83,7 @@ impl Wallet { let public_key = self.to_pkey(); let mut verifier = Verifier::new(MessageDigest::sha256(), &public_key)?; verifier.update(transaction_info.to_string().as_bytes())?; - Ok(verifier.verify(&signature)?) + verifier.verify(signature) } #[allow(unused)] diff --git a/src/node/logger.rs b/src/node/logger.rs index 4600b6e..56956a0 100644 --- a/src/node/logger.rs +++ b/src/node/logger.rs @@ -2,6 +2,7 @@ use chrono::prelude::Utc; use std::io::{self}; use std::sync::Mutex; +#[derive(Default)] pub struct Logger { logs: Mutex>, temp_logs: Mutex>, @@ -35,12 +36,12 @@ impl Logger { let dt = Utc::now(); self.log_internal(format!( "[ERROR] {} | {}", - dt.format("%Y-%m-%d %H:%M:%S").to_string(), + dt.format("%Y-%m-%d %H:%M:%S"), log )); self.temp_log(format!( "[ERROR] {} | {}", - dt.format("%Y-%m-%d %H:%M:%S").to_string(), + dt.format("%Y-%m-%d %H:%M:%S"), log )); } @@ -48,12 +49,12 @@ impl Logger { let dt = Utc::now(); self.log_internal(format!( "[LOG] {} | {}", - dt.format("%Y-%m-%d %H:%M:%S").to_string(), + dt.format("%Y-%m-%d %H:%M:%S"), log )); self.temp_log(format!( "[LOG] {} | {}", - dt.format("%Y-%m-%d %H:%M:%S").to_string(), + dt.format("%Y-%m-%d %H:%M:%S"), log )); } diff --git a/src/node/mod.rs b/src/node/mod.rs index 04ed3ee..12ef677 100644 --- a/src/node/mod.rs +++ b/src/node/mod.rs @@ -23,7 +23,7 @@ use thread_pool::custom_thread_pool::ThreadPool; pub struct NodeState { status: bool, chain: Chain, - transactions: Vec, + transactions_pool: Vec, } pub struct Node { state: Arc>, @@ -31,10 +31,10 @@ pub struct Node { } static NUMBER_OF_THREADS_IN_THREAD_POOL: Lazy = Lazy::new(num_cpus::get); + // 0 = None // 1 = Prod // 2 = Debug - pub const LOG_LEVEL: u8 = 2; impl Node { @@ -49,7 +49,7 @@ impl Node { state: Arc::new(Mutex::new(NodeState { status: true, chain, - transactions: Vec::new(), + transactions_pool: Vec::new(), })), logger, } @@ -59,12 +59,10 @@ impl Node { mut buf_reader: BufReader, ) -> Result { let mut http_headers: HashMap = HashMap::new(); - let http_body: Option; let mut line = String::new(); // reading status_line - let status_line: String; match buf_reader.read_line(&mut line) { Ok(n) if (n > 0) => n, @@ -72,7 +70,7 @@ impl Node { Err(_) => return Err(HTTPParseError::InvalidStatusLine), }; - status_line = line.trim().to_string(); + let status_line: String = line.trim().to_string(); let mut tokens = status_line.split(' '); let (method, path, http_version) = ( @@ -94,7 +92,7 @@ impl Node { loop { line.clear(); - if let Err(_) = buf_reader.read_line(&mut line) { + if buf_reader.read_line(&mut line).is_err() { return Err(HTTPParseError::InvalidRequestLine); } @@ -143,7 +141,7 @@ impl Node { return Err(HTTPParseError::InvalidRequestLine); } - http_body = Some(String::from_utf8_lossy(&body).to_string()); + let http_body: Option = Some(String::from_utf8_lossy(&body).to_string()); if method == "POST" { return Ok(HTTPRequest::new( @@ -179,18 +177,17 @@ impl Node { } pub fn run(&mut self, default: bool, rx: Arc>>, selected_port: u16) { - let port: u16; - if default == true { - port = Self::DEFAULT_PORT; + let port: u16 = if default { + Self::DEFAULT_PORT } else { - port = match selected_port { + match selected_port { port if (1..=65535).contains(&port) => port, _ => { println!("Invalid port! Using default: {}", Self::DEFAULT_PORT); Self::DEFAULT_PORT } } - } + }; let listener = TcpListener::bind(format!("127.0.0.1:{port}")).unwrap(); @@ -206,7 +203,7 @@ impl Node { loop { // Check for termination signal if let Ok(lock) = rx.try_lock() { - if let Ok(_) = lock.try_recv() { + if lock.try_recv().is_ok() { break; } }; @@ -219,9 +216,9 @@ impl Node { thread_pool.execute(move || { match Self::handle_connection(state, stream) { Ok(Some(value)) => { - logger.log(format!("{value}")); + logger.log(value); } - Err(Some(value)) => logger.log_error(format!("{value}")), + Err(Some(value)) => logger.log_error(value), _ => {} }; }) diff --git a/src/node/resolve_requests/endpoints.rs b/src/node/resolve_requests/endpoints.rs index 4accb3d..fcd83c6 100644 --- a/src/node/resolve_requests/endpoints.rs +++ b/src/node/resolve_requests/endpoints.rs @@ -60,7 +60,7 @@ pub fn submit_transaction(data: &POSTData, state: Arc>) -> HTTP } }; - state.lock().unwrap().transactions.push(transaction); + state.lock().unwrap().transactions_pool.push(transaction); Ok(HTTPResponse::OK(Some(Content::JSON(json!({ "msg": "The transaction was added to the pool.", @@ -68,6 +68,12 @@ pub fn submit_transaction(data: &POSTData, state: Arc>) -> HTTP }))))) } +pub fn get_transaction_pool(_: &GETData, state: Arc>) -> HTTPResult { + let transaction_pool: Vec = state.lock().unwrap().transactions_pool.clone(); + let response = serde_json::to_value(transaction_pool).unwrap(); + Ok(HTTPResponse::OK(Some(Content::JSON(response)))) +} + pub fn favicon(_: &GETData, _: Arc>) -> HTTPResult { return_image("fav.ico", ImageType::ICO) } @@ -81,7 +87,7 @@ pub fn status(_: &GETData, state: Arc>) -> HTTPResult { return_json(json!({ "status": state.status, "blockHeight": state.chain.get_last_index(), - "peers": 8, + "peers": 100000000, "timestamp": Utc::now() })) } @@ -120,6 +126,7 @@ pub fn resolve_endpoint( add_endpoints("/favicon.ico", Some(favicon), None); add_endpoints("/status", Some(status), None); add_endpoints("/submit-transaction", None, Some(submit_transaction)); + add_endpoints("/get-transaction-pool", Some(get_transaction_pool), None); } endpoints } diff --git a/src/node/resolve_requests/helpers.rs b/src/node/resolve_requests/helpers.rs index 72fb01d..98ae7b5 100644 --- a/src/node/resolve_requests/helpers.rs +++ b/src/node/resolve_requests/helpers.rs @@ -37,7 +37,7 @@ pub trait Handler { impl Handler for GETFunc { fn call(&self, request: &HTTPRequest, state: Arc>) -> HTTPResult { match request.get_method() { - Method::GET(data) => self(&data, state), + Method::GET(data) => self(data, state), _ => method_not_allowed(None), } } @@ -47,7 +47,7 @@ impl Handler for GETFunc { impl Handler for POSTFunc { fn call(&self, request: &HTTPRequest, state: Arc>) -> HTTPResult { match request.get_method() { - Method::POST(data) => self(&data, state), + Method::POST(data) => self(data, state), _ => method_not_allowed(None), } } @@ -68,29 +68,29 @@ pub fn return_json(json: serde_json::Value) -> HTTPResult { Ok(HTTPResponse::OK(Some(Content::JSON(json)))) } -pub fn post(request: HTTPRequest, f: POSTFunc, state: Arc>) -> HTTPResult { - let method = request.get_method(); - if let Method::POST(data) = method { - f(data, state) - } else { - Err(HTTPResponseError::InvalidMethod(None)) - } -} -pub fn get(request: HTTPRequest, f: GETFunc, state: Arc>) -> HTTPResult { - if let Method::GET(data) = request.get_method() { - f(data, state) - } else { - Err(HTTPResponseError::InvalidMethod(None)) - } -} -pub fn get_post( - request: HTTPRequest, - get: GETFunc, - post: POSTFunc, - state: Arc>, -) -> HTTPResult { - match request.get_method() { - Method::POST(data) => post(data, state), - Method::GET(data) => get(data, state), - } -} +// pub fn post(request: HTTPRequest, f: POSTFunc, state: Arc>) -> HTTPResult { +// let method = request.get_method(); +// if let Method::POST(data) = method { +// f(data, state) +// } else { +// Err(HTTPResponseError::InvalidMethod(None)) +// } +// } +// pub fn get(request: HTTPRequest, f: GETFunc, state: Arc>) -> HTTPResult { +// if let Method::GET(data) = request.get_method() { +// f(data, state) +// } else { +// Err(HTTPResponseError::InvalidMethod(None)) +// } +// } +// pub fn get_post( +// request: HTTPRequest, +// get: GETFunc, +// post: POSTFunc, +// state: Arc>, +// ) -> HTTPResult { +// match request.get_method() { +// Method::POST(data) => post(data, state), +// Method::GET(data) => get(data, state), +// } +// } diff --git a/src/node/resolve_requests/methods.rs b/src/node/resolve_requests/methods.rs index 73a5c65..d0b9b47 100644 --- a/src/node/resolve_requests/methods.rs +++ b/src/node/resolve_requests/methods.rs @@ -5,17 +5,19 @@ use std::net::TcpStream; use std::path::PathBuf; use std::{fmt, fs}; +#[allow(clippy::upper_case_acronyms)] pub enum ImageType { - PNG, + // PNG, ICO, - JPEG, + // JPEG, } +#[allow(clippy::upper_case_acronyms)] pub enum Content { HTML(PathBuf), // path to HTML file JSON(serde_json::Value), Image(PathBuf, ImageType), // path to image file - PlainText(String), + // PlainText(String), } pub enum HTTPResponse { @@ -36,6 +38,7 @@ pub struct POSTData { } #[derive(Debug, Clone)] +#[allow(clippy::upper_case_acronyms)] pub enum Method { GET(GETData), POST(POSTData), @@ -138,17 +141,17 @@ impl HTTPRequest { Content::Image(path, img_type) => Ok(Response { status: 200, content_type: match img_type { - ImageType::PNG => "image/png", + // ImageType::PNG => "image/png", ImageType::ICO => "image/vnd.microsoft.icon", - ImageType::JPEG => "image/jpeg", + // ImageType::JPEG => "image/jpeg", }, body: fs::read(path)?, }), - Content::PlainText(text) => Ok(Response { - status: 200, - content_type: "text/plain", - body: text.into_bytes(), - }), + // Content::PlainText(text) => Ok(Response { + // status: 200, + // content_type: "text/plain", + // body: text.into_bytes(), + // }), } } fn response_ok_no_content(a: &str) -> std::io::Result { @@ -271,14 +274,14 @@ impl fmt::Display for HTTPRequest { pub enum HTTPParseError { InvalidStatusLine, InvalidRequestLine, - MissingFields, + // MissingFields, MissingContentLength, } impl fmt::Display for HTTPParseError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { HTTPParseError::InvalidRequestLine => write!(f, "Invalid request line"), - HTTPParseError::MissingFields => write!(f, "Missing required fields"), + // HTTPParseError::MissingFields => write!(f, "Missing required fields"), HTTPParseError::InvalidStatusLine => write!(f, "Invalid status line"), HTTPParseError::MissingContentLength => { write!(f, "Missing content-length field in headers") diff --git a/src/node/thread_pool.rs b/src/node/thread_pool.rs index 7ed0e50..d97aa3d 100644 --- a/src/node/thread_pool.rs +++ b/src/node/thread_pool.rs @@ -67,7 +67,7 @@ pub mod custom_thread_pool { pub fn new(size: usize) -> Result { if size < 1 { return Err(PoolCreationError::TooFewThreads); - } else if size > usize::MAX { + } else if size > 100 { // I think this is unreachable return Err(PoolCreationError::TooManyThreads); } diff --git a/src/node/ui.rs b/src/node/ui.rs index a923c32..55d59df 100644 --- a/src/node/ui.rs +++ b/src/node/ui.rs @@ -12,7 +12,6 @@ use std::sync::Arc; use std::time::Duration; /// The main application which holds the state and logic of the application. - pub struct App { /// Is the application running? running: bool, diff --git a/tests/create_block_and_add_chain.rs b/tests/create_block_and_add_chain.rs index 324c545..89902a2 100644 --- a/tests/create_block_and_add_chain.rs +++ b/tests/create_block_and_add_chain.rs @@ -11,7 +11,7 @@ fn create_block_and_add_chain() { let (wallet1, mut wallet1_pk) = Wallet::new(); let (wallet2, _) = Wallet::new(); - let transaction_info = TransactionInfo::new(10.5, Utc::now()); + let transaction_info = TransactionInfo::new(105, Utc::now()); let signature = match wallet1_pk.sign_transaction(&transaction_info) { Ok(value) => value, diff --git a/tests/requests.rs b/tests/requests.rs index 62c50d9..2332da3 100644 --- a/tests/requests.rs +++ b/tests/requests.rs @@ -123,8 +123,6 @@ fn thread_get() { let url = url.to_string(); let handle = thread::spawn(move || { - let body = format!(r#"{{"message": "Hello #{}"}}"#, i); - match client .get(&url) .header("Content-Type", "application/json") diff --git a/tests/transactions.rs b/tests/transactions.rs index ef0d6b9..fbd6d08 100644 --- a/tests/transactions.rs +++ b/tests/transactions.rs @@ -6,7 +6,7 @@ use cleyto_coin::chain::wallet::Wallet; fn create_transaction() { let (wallet_sender, mut walletpk_sender) = Wallet::new(); let (wallet_receiver, _) = Wallet::new(); - let transactioninfo: TransactionInfo = TransactionInfo::new(12345 as f32, Utc::now()); + let transactioninfo: TransactionInfo = TransactionInfo::new(12345, Utc::now()); let signature = match walletpk_sender.sign_transaction(&transactioninfo) { Ok(signed_hashed_message) => signed_hashed_message, @@ -21,7 +21,6 @@ fn create_transaction() { if wallet_sender .verify_transaction_info(&transactioninfo, &signature) .unwrap() - == true { println!("transaction verified (by the wallet)"); } else { @@ -31,20 +30,20 @@ fn create_transaction() { let transaction: Transaction = Transaction::new(wallet_sender, wallet_receiver, transactioninfo, signature).unwrap(); - println!("transaction.to_string(): {}", transaction.to_string()); + println!("transaction.to_string(): {}", transaction); } #[test] //mark a function as a test. fn test_transaction_info_creation() { - let transaction: TransactionInfo = TransactionInfo::new(12345f32, Utc::now()); - println!("transaction info:\n{}", transaction.to_string()); + let transaction: TransactionInfo = TransactionInfo::new(123452, Utc::now()); + println!("transaction info:\n{}", transaction); println!("{:?}", transaction); } #[test] fn sign_and_verify_transaction_info() { let (wallet, mut wallet_pk) = Wallet::new(); - let transactioninfo: TransactionInfo = TransactionInfo::new(12345f32, Utc::now()); + let transactioninfo: TransactionInfo = TransactionInfo::new(1234532, Utc::now()); let signature = match wallet_pk.sign_transaction(&transactioninfo) { Ok(signed_hashed_message) => signed_hashed_message, @@ -58,7 +57,6 @@ fn sign_and_verify_transaction_info() { if wallet .verify_transaction_info(&transactioninfo, &signature) .unwrap() - == true { println!("transaction verified (by the wallet)"); } else { @@ -70,7 +68,7 @@ fn sign_and_verify_transaction_info() { fn serialize_and_deserialize_transaction() { let (wallet, mut wallet_pk) = Wallet::new(); let (mallet, _) = Wallet::new(); - let transactioninfo: TransactionInfo = TransactionInfo::new(12345f32, Utc::now()); + let transactioninfo: TransactionInfo = TransactionInfo::new(1234552, Utc::now()); let signature = match wallet_pk.sign_transaction(&transactioninfo) { Ok(signed_hashed_message) => signed_hashed_message, From 084707bb7df52c050969c1e3ae93688d1141de65 Mon Sep 17 00:00:00 2001 From: Dante Ramacciotti Date: Wed, 6 Aug 2025 17:55:47 +0200 Subject: [PATCH 31/40] TODO remake load tests to use proper endpoints (submit-transaction and get-transaction-pool) --- Cargo.lock | 40 ++++++++++++++++++++++++ Cargo.toml | 1 + src/node/thread_pool.rs | 3 +- tests/requests.rs | 69 ----------------------------------------- 4 files changed, 43 insertions(+), 70 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c329cfc..a05b9a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -150,6 +150,7 @@ dependencies = [ "chrono", "color-eyre", "crossterm 0.29.0", + "futures", "hex", "num_cpus", "once_cell", @@ -460,6 +461,21 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.31" @@ -476,12 +492,34 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "futures-sink" version = "0.3.31" @@ -500,8 +538,10 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ + "futures-channel", "futures-core", "futures-io", + "futures-macro", "futures-sink", "futures-task", "memchr", diff --git a/Cargo.toml b/Cargo.toml index 5a805d0..cfa2d53 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,4 +25,5 @@ crossterm = "0.29" ratatui = "0.29.0" color-eyre = "0.6.3" tokio = { version = "1.44.1", features = ["rt", "rt-multi-thread", "macros"] } +futures = "0.3.31" diff --git a/src/node/thread_pool.rs b/src/node/thread_pool.rs index d97aa3d..922f088 100644 --- a/src/node/thread_pool.rs +++ b/src/node/thread_pool.rs @@ -65,9 +65,10 @@ pub mod custom_thread_pool { /// /// The `new` function will panic if the size is zero. pub fn new(size: usize) -> Result { + println!("number of threads in thread pool is {}", size); if size < 1 { return Err(PoolCreationError::TooFewThreads); - } else if size > 100 { + } else if size > 100000 { // I think this is unreachable return Err(PoolCreationError::TooManyThreads); } diff --git a/tests/requests.rs b/tests/requests.rs index 2332da3..3389930 100644 --- a/tests/requests.rs +++ b/tests/requests.rs @@ -6,76 +6,7 @@ use std::{ use cleyto_coin::chain::Chain; use cleyto_coin::node::logger::Logger; -use rand::Rng; use reqwest::blocking::Client; -use serde::Serialize; - -#[derive(Serialize)] -struct RandomData { - message: String, - value: i32, -} - -fn post_request() -> Result<(), Box> { - let mut rng = rand::rng(); - let data = RandomData { - message: (0..10) - .map(|_| rng.sample(rand::distr::Alphanumeric) as char) - .collect(), - value: rng.random_range(1..=1000), - }; - - let client = Client::new(); - let res = client - .post(format!("http://localhost:{}", node::Node::DEFAULT_PORT)) - .json(&data) - .send()?; - - assert_eq!(res.status(), 200); - - Ok(()) -} - -fn get_request() -> Result<(), Box> { - let client = Client::new(); - let res = client - .get(format!("http://localhost:{}", node::Node::DEFAULT_PORT)) - .send()?; - - assert_eq!(res.status(), 200); - let body = res.text()?; - - Ok(()) -} - -fn post_and_get_request() { - /* let (tx, rx) = mpsc::channel::<()>(); - - // Channel to kill thread - let rx = Arc::new(Mutex::new(rx)); - - // Run server thread - let server = thread::spawn(move || { - let rx = Arc::clone(&rx); - node::Node::run(true, rx, 0); - }); */ - - for _ in 0..200 { - match post_request() { - Ok(_) => println!("Post successful"), - Err(_) => println!("Post failed"), - }; - match get_request() { - Ok(_) => println!("Get successful"), - Err(_) => println!("Get failed"), - }; - } - - /* tx.send(()).expect("Failed to send termination signal."); - - // Wait for the server thread to finish (this will block until the server thread terminates) - server.join().expect("Server thread panicked."); */ -} fn thread_post() { let url = "http://localhost:9473/"; // Replace with your server URL From 5b8e93a8219b19e5581436bc9094271f4e8e2f87 Mon Sep 17 00:00:00 2001 From: Dante Ramacciotti Date: Wed, 6 Aug 2025 17:55:47 +0200 Subject: [PATCH 32/40] TODO remake load tests to use proper endpoints (submit-transaction and get-transaction-pool) --- .gitignore | 3 +- Cargo.lock | 40 ++++++++++++++++++++++ Cargo.toml | 1 + src/node/thread_pool.rs | 3 +- tests/requests.rs | 73 ++--------------------------------------- 5 files changed, 47 insertions(+), 73 deletions(-) diff --git a/.gitignore b/.gitignore index 6b39d31..3d942c7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target -.idea/ \ No newline at end of file +.idea/ +token diff --git a/Cargo.lock b/Cargo.lock index c329cfc..a05b9a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -150,6 +150,7 @@ dependencies = [ "chrono", "color-eyre", "crossterm 0.29.0", + "futures", "hex", "num_cpus", "once_cell", @@ -460,6 +461,21 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.31" @@ -476,12 +492,34 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "futures-sink" version = "0.3.31" @@ -500,8 +538,10 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ + "futures-channel", "futures-core", "futures-io", + "futures-macro", "futures-sink", "futures-task", "memchr", diff --git a/Cargo.toml b/Cargo.toml index 5a805d0..cfa2d53 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,4 +25,5 @@ crossterm = "0.29" ratatui = "0.29.0" color-eyre = "0.6.3" tokio = { version = "1.44.1", features = ["rt", "rt-multi-thread", "macros"] } +futures = "0.3.31" diff --git a/src/node/thread_pool.rs b/src/node/thread_pool.rs index d97aa3d..922f088 100644 --- a/src/node/thread_pool.rs +++ b/src/node/thread_pool.rs @@ -65,9 +65,10 @@ pub mod custom_thread_pool { /// /// The `new` function will panic if the size is zero. pub fn new(size: usize) -> Result { + println!("number of threads in thread pool is {}", size); if size < 1 { return Err(PoolCreationError::TooFewThreads); - } else if size > 100 { + } else if size > 100000 { // I think this is unreachable return Err(PoolCreationError::TooManyThreads); } diff --git a/tests/requests.rs b/tests/requests.rs index 2332da3..539c1bd 100644 --- a/tests/requests.rs +++ b/tests/requests.rs @@ -6,76 +6,7 @@ use std::{ use cleyto_coin::chain::Chain; use cleyto_coin::node::logger::Logger; -use rand::Rng; use reqwest::blocking::Client; -use serde::Serialize; - -#[derive(Serialize)] -struct RandomData { - message: String, - value: i32, -} - -fn post_request() -> Result<(), Box> { - let mut rng = rand::rng(); - let data = RandomData { - message: (0..10) - .map(|_| rng.sample(rand::distr::Alphanumeric) as char) - .collect(), - value: rng.random_range(1..=1000), - }; - - let client = Client::new(); - let res = client - .post(format!("http://localhost:{}", node::Node::DEFAULT_PORT)) - .json(&data) - .send()?; - - assert_eq!(res.status(), 200); - - Ok(()) -} - -fn get_request() -> Result<(), Box> { - let client = Client::new(); - let res = client - .get(format!("http://localhost:{}", node::Node::DEFAULT_PORT)) - .send()?; - - assert_eq!(res.status(), 200); - let body = res.text()?; - - Ok(()) -} - -fn post_and_get_request() { - /* let (tx, rx) = mpsc::channel::<()>(); - - // Channel to kill thread - let rx = Arc::new(Mutex::new(rx)); - - // Run server thread - let server = thread::spawn(move || { - let rx = Arc::clone(&rx); - node::Node::run(true, rx, 0); - }); */ - - for _ in 0..200 { - match post_request() { - Ok(_) => println!("Post successful"), - Err(_) => println!("Post failed"), - }; - match get_request() { - Ok(_) => println!("Get successful"), - Err(_) => println!("Get failed"), - }; - } - - /* tx.send(()).expect("Failed to send termination signal."); - - // Wait for the server thread to finish (this will block until the server thread terminates) - server.join().expect("Server thread panicked."); */ -} fn thread_post() { let url = "http://localhost:9473/"; // Replace with your server URL @@ -113,7 +44,7 @@ fn thread_post() { } fn thread_get() { - let url = "http://localhost:9473/"; // Replace with your server URL + let url = "http://localhost:9473/get-transaction-pool"; // Replace with your server URL let client = Client::new(); let mut handles = vec![]; @@ -158,7 +89,7 @@ fn main() { node.run(true, rx, 0); }); - thread_post(); + // thread_post(); thread_get(); tx.send(()).expect("Failed to send termination signal."); From 67fe278be5489fcb981d36f248a2734d1988c3fe Mon Sep 17 00:00:00 2001 From: Dante Ramacciotti Date: Fri, 8 Aug 2025 11:34:45 +0200 Subject: [PATCH 33/40] Load tests break because of the client, not server. Not gonna fix it now --- src/chain/block.rs | 2 +- tests/requests.rs | 45 +++++++++++++++++++++++++++++++------------ tests/transactions.rs | 2 +- 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/src/chain/block.rs b/src/chain/block.rs index a3eba78..8533c9f 100644 --- a/src/chain/block.rs +++ b/src/chain/block.rs @@ -49,7 +49,7 @@ impl Block { )) .expect("Coudn't serialize the block to create the hash"); - println!("behold the serialized block:\n{serialized}"); + // println!("behold the serialized block:\n{serialized}"); let mut hasher = Sha256::new(); hasher.update(serialized.as_bytes()); diff --git a/tests/requests.rs b/tests/requests.rs index 539c1bd..9cb5629 100644 --- a/tests/requests.rs +++ b/tests/requests.rs @@ -1,3 +1,6 @@ +use chrono::Utc; +use cleyto_coin::chain::transaction::{Transaction, TransactionInfo}; +use cleyto_coin::chain::wallet::Wallet; use cleyto_coin::node; use std::{ sync::{mpsc, Arc, Mutex}, @@ -8,23 +11,36 @@ use cleyto_coin::chain::Chain; use cleyto_coin::node::logger::Logger; use reqwest::blocking::Client; -fn thread_post() { - let url = "http://localhost:9473/"; // Replace with your server URL +fn thread_post(n: u16) { + let url = "http://localhost:9473/submit-transaction"; // Replace with your server URL let client = Client::new(); let mut handles = vec![]; - for i in 0..10000 { + let (wallet1, mut wallet1_pk) = Wallet::new(); + let (wallet2, _) = Wallet::new(); + + let transaction_info = TransactionInfo::new(105, Utc::now()); + + let signature = match wallet1_pk.sign_transaction(&transaction_info) { + Ok(value) => value, + Err(e) => panic!("Error creating signed message: {e}"), + }; + + let new_transaction = Transaction::new(wallet1, wallet2, transaction_info, signature).unwrap(); + let json_transaction = new_transaction.serialize(); + println!("json_transaction is:\n{}", json_transaction); + + for i in 0..n { let client = client.clone(); let url = url.to_string(); + let transaction_copy = json_transaction.clone(); let handle = thread::spawn(move || { - let body = format!(r#"{{"message": "Hello #{}"}}"#, i); - match client .post(&url) .header("Content-Type", "application/json") - .body(body) + .body(transaction_copy) .send() { Ok(resp) => { @@ -43,13 +59,13 @@ fn thread_post() { } } -fn thread_get() { +fn thread_get(n: u16) { let url = "http://localhost:9473/get-transaction-pool"; // Replace with your server URL let client = Client::new(); let mut handles = vec![]; - for i in 0..10000 { + for i in 0..n { let client = client.clone(); let url = url.to_string(); @@ -61,9 +77,13 @@ fn thread_get() { { Ok(resp) => { assert_eq!(resp.status(), 200); - println!("Thread #{i}: {}", resp.status()) + println!("Thread #{i}: {:#?}", resp.status()) } - Err(err) => eprintln!("Thread #{i} failed: {err}"), + Err(err) => eprintln!( + "Thread #{i} failed (timeout) (connect): {} {}", + err.is_timeout(), + err.is_connect() + ), } }); @@ -89,8 +109,9 @@ fn main() { node.run(true, rx, 0); }); - // thread_post(); - thread_get(); + // 10.000 breaks the os (client), but the server seems fine + thread_get(1000); + thread_post(1000); tx.send(()).expect("Failed to send termination signal."); diff --git a/tests/transactions.rs b/tests/transactions.rs index fbd6d08..a47e925 100644 --- a/tests/transactions.rs +++ b/tests/transactions.rs @@ -33,7 +33,7 @@ fn create_transaction() { println!("transaction.to_string(): {}", transaction); } -#[test] //mark a function as a test. +#[test] fn test_transaction_info_creation() { let transaction: TransactionInfo = TransactionInfo::new(123452, Utc::now()); println!("transaction info:\n{}", transaction); From bec6a2e9e045fa7bb16dde1c614a3d82e411bd5d Mon Sep 17 00:00:00 2001 From: Dante Ramacciotti Date: Thu, 28 Aug 2025 17:12:18 +0200 Subject: [PATCH 34/40] Saving of state when offline implemented Added log saving and retrieving after shutdown (on the .share/local/cleyto_coin/logs or something like that), added the config toml (on .config/cleyto_coin), new binary for creating wallets and sending transactions. Node still doesn't check the U... something to see if the sender had enough to pay the receiver. Also changed how the private and public keys are held in the wallet (from [u8] to the PKey objects. --- Cargo.lock | 326 +++++++++++++++++++++++++--- Cargo.toml | 5 +- README.md | 69 ++++-- "\\" | 187 ++++++++++++++++ private.pem | 28 +++ public.pem | 9 + src/TODO.txt | 4 +- src/bin/cleyto_coin_wallet.rs | 179 +++++++++++++++ src/bin/create_transaction.rs | 26 ++- src/bin/node.rs | 32 ++- src/chain/block.rs | 90 ++++++-- src/chain/mod.rs | 7 +- src/chain/transaction.rs | 16 +- src/chain/wallet.rs | 130 ++++++----- src/node/logger.rs | 312 +++++++++++++++++++++++++- src/node/mod.rs | 66 +++++- src/node/thread_pool.rs | 2 +- src/node/ui.rs | 4 +- tests/create_block_and_add_chain.rs | 5 +- tests/create_wallet.rs | 2 +- tests/requests.rs | 8 +- tests/transactions.rs | 15 +- todo.txt | 0 23 files changed, 1346 insertions(+), 176 deletions(-) create mode 100644 "\\" create mode 100644 private.pem create mode 100644 public.pem create mode 100644 src/bin/cleyto_coin_wallet.rs create mode 100644 todo.txt diff --git a/Cargo.lock b/Cargo.lock index a05b9a4..40b30cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,12 +38,32 @@ dependencies = [ "libc", ] +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.4.0" @@ -71,6 +91,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.9.0" @@ -143,6 +169,21 @@ dependencies = [ "windows-link", ] +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags 1.3.2", + "strsim 0.8.0", + "textwrap", + "unicode-width 0.1.14", + "vec_map", +] + [[package]] name = "cleyto_coin" version = "0.1.0" @@ -150,6 +191,7 @@ dependencies = [ "chrono", "color-eyre", "crossterm 0.29.0", + "directories", "futures", "hex", "num_cpus", @@ -161,7 +203,9 @@ dependencies = [ "serde", "serde_json", "sha2", + "structopt", "tokio", + "toml", ] [[package]] @@ -245,7 +289,7 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ - "bitflags", + "bitflags 2.9.0", "crossterm_winapi", "mio", "parking_lot", @@ -261,7 +305,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "bitflags", + "bitflags 2.9.0", "crossterm_winapi", "derive_more", "document-features", @@ -312,8 +356,8 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim", - "syn", + "strsim 0.11.1", + "syn 2.0.100", ] [[package]] @@ -324,7 +368,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn", + "syn 2.0.100", ] [[package]] @@ -345,7 +389,7 @@ dependencies = [ "convert_case", "proc-macro2", "quote", - "syn", + "syn 2.0.100", ] [[package]] @@ -358,6 +402,27 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "directories" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.59.0", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -366,7 +431,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", ] [[package]] @@ -517,7 +582,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", ] [[package]] @@ -619,12 +684,30 @@ dependencies = [ "foldhash", ] +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "hermit-abi" version = "0.3.9" @@ -888,7 +971,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", ] [[package]] @@ -950,7 +1033,7 @@ dependencies = [ "indoc", "proc-macro2", "quote", - "syn", + "syn 2.0.100", ] [[package]] @@ -996,6 +1079,16 @@ version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +[[package]] +name = "libredox" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" +dependencies = [ + "bitflags 2.9.0", + "libc", +] + [[package]] name = "linux-raw-sys" version = "0.4.15" @@ -1110,7 +1203,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", ] @@ -1135,7 +1228,7 @@ version = "0.10.71" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd" dependencies = [ - "bitflags", + "bitflags 2.9.0", "cfg-if", "foreign-types", "libc", @@ -1152,7 +1245,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", ] [[package]] @@ -1173,6 +1266,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "owo-colors" version = "3.5.0" @@ -1241,6 +1340,30 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.94" @@ -1301,7 +1424,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" dependencies = [ - "bitflags", + "bitflags 2.9.0", "cassowary", "compact_str", "crossterm 0.28.1", @@ -1322,7 +1445,18 @@ version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" dependencies = [ - "bitflags", + "bitflags 2.9.0", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.15", + "libredox", + "thiserror", ] [[package]] @@ -1396,7 +1530,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags", + "bitflags 2.9.0", "errno", "libc", "linux-raw-sys 0.4.15", @@ -1409,7 +1543,7 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" dependencies = [ - "bitflags", + "bitflags 2.9.0", "errno", "libc", "linux-raw-sys 0.9.3", @@ -1488,7 +1622,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags", + "bitflags 2.9.0", "core-foundation", "core-foundation-sys", "libc", @@ -1522,7 +1656,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", ] [[package]] @@ -1537,6 +1671,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1642,12 +1785,42 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "structopt" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" +dependencies = [ + "clap", + "lazy_static", + "structopt-derive", +] + +[[package]] +name = "structopt-derive" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" +dependencies = [ + "heck 0.3.3", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "strum" version = "0.26.3" @@ -1663,11 +1836,11 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "rustversion", - "syn", + "syn 2.0.100", ] [[package]] @@ -1676,6 +1849,17 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.100" @@ -1704,7 +1888,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", ] [[package]] @@ -1713,7 +1897,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags", + "bitflags 2.9.0", "core-foundation", "system-configuration-sys", ] @@ -1741,6 +1925,35 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width 0.1.14", +] + +[[package]] +name = "thiserror" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "thread_local" version = "1.1.8" @@ -1785,7 +1998,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", ] [[package]] @@ -1821,6 +2034,45 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_parser" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" + [[package]] name = "tower" version = "0.5.2" @@ -1977,6 +2229,12 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + [[package]] name = "version_check" version = "0.9.5" @@ -2029,7 +2287,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn", + "syn 2.0.100", "wasm-bindgen-shared", ] @@ -2064,7 +2322,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2300,13 +2558,19 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +[[package]] +name = "winnow" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" + [[package]] name = "wit-bindgen-rt" version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags", + "bitflags 2.9.0", ] [[package]] @@ -2341,7 +2605,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", "synstructure", ] @@ -2362,7 +2626,7 @@ checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", ] [[package]] @@ -2382,7 +2646,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", "synstructure", ] @@ -2411,5 +2675,5 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", ] diff --git a/Cargo.toml b/Cargo.toml index cfa2d53..784dbef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ sha2 = "0.10" rand = "0.9.0" hex = "0.4.3" chrono = { version="0.4.40", features = ["serde"] } -serde = { version="1.0.219", features = ["derive"] } +serde = { version="1.0.219", features = ["derive", "rc"] } serde_json = "1.0.140" reqwest = { version = "0.12.15", features = ["blocking", "json"] } num_cpus = "1.16.0" @@ -26,4 +26,7 @@ ratatui = "0.29.0" color-eyre = "0.6.3" tokio = { version = "1.44.1", features = ["rt", "rt-multi-thread", "macros"] } futures = "0.3.31" +directories = "6.0.0" +toml = "0.9.5" +structopt = "0.3.26" diff --git a/README.md b/README.md index 4748da6..f6a0cb8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# CleytoCoin +# CleytoCoin ## Table of Contents @@ -11,9 +11,10 @@ - [Testing](#testing) - [Contributing](#contributing) -# CleytoCoin is still under development, so many of the features listed underneath aren't yet functional. +# CleytoCoin is still under development, so many of the features listed underneath aren't yet functional + We appreciate the interest and are working towards making CleytoCoin a functional and reliable cryptocurrency, but currently it's still in it's early development stages. -Feel free to give sugestions in the meantime of what you'd like to see implemented in our project! +Feel free to give suggestions in the meantime of what you'd like to see implemented in our project! ## About @@ -43,62 +44,86 @@ Ensure you have the following dependencies installed on your machine: Clone this repository to your local machine: ```bash -$ git clone https://github.com/dantee-e/CleytoCoin.git -$ cd CleytoCoin +git clone https://github.com/dantee-e/CleytoCoin.git +cd CleytoCoin ``` + To build and install the project: -``` bash -$ cargo build --release + +```bash +cargo build --release ``` + This will compile the project and generate an optimized binary in the `target/release` directory. ## Usage ### Starting the node + To start the cryptocurrency node, use the following command: -``` bash -cargo run --bin cleyto_coin + +```bash +cargo run --bin node ``` -The node will start and connect to the network. You can start mining or send/receive transactions. + +The node will start and connect to the network. For now, only full nodes are available and they don't have yet the capacity for mining ### Creating a wallet + To generate a new wallet, run the following: -``` bash -cargo run --bin cleyto_coin-wallet generate + +```bash +cargo run --bin cleyto-coin-wallet generate ``` + This will generate a private key and address for your wallet. ### Sending a transaction + To send a transaction, use the following command: -``` bash -cargo run --bin cleyto_coin-wallet send --to --amount --private-key + +```bash +cargo run --bin cleyto-coin-wallet send --to --amount --private-key ``` -### Mining +### Mining (not yet implemented) + Start mining by running: -``` bash -cargo run --bin cleyto_coin-miner start --mining-key + +```bash +cargo run --bin cleyto-coin-miner start --mining-key ``` ### Stopping the node -To stop the node, press `CTRL+C` or run the following: -``` bash -cargo run --bin cleyto_coin stop + +To stop the node, on the terminal window running your server, press `CTRL+C`, `q` or `Esc` + +**Running the server without the GUI is not yet implemented** +If you ran it without the GUI, use the command + +```bash +cargo run --bin cleyto-coin stop # Not yet implemented ``` ## Testing + To run the tests for the project, use the following command: -``` bash + +```bash cargo test ``` + This will run all unit tests, integration tests, and any other tests defined in this project. If you wish to run with output run: -``` bash + +```bash cargo test -- --nocapture ``` ## Contributing + We welcome contributions to the CleytoCoin project. If you have an idea or find a bug, please feel free to submit an issue or a pull request. + 1. Fork the repository 2. Create a new branch (`git checkout -b feature/feature-name`) 3. Make your changes diff --git "a/\\" "b/\\" new file mode 100644 index 0000000..2aa2ed9 --- /dev/null +++ "b/\\" @@ -0,0 +1,187 @@ +use chrono::prelude::Utc; +use serde::{Deserialize, Serialize}; +use std::{ + fmt::Display, + sync::{Mutex, MutexGuard, PoisonError}, +}; + +#[derive(Default)] +pub struct Logger { + logs: Mutex>, + temp_logs: Mutex>, +} + +pub enum LoggerError<'a> { + PoisonError(PoisonError>>), + FileWriteError(std::io::Error), +} +impl<'a> From>>> for LoggerError<'a> { + fn from(value: PoisonError>>) -> LoggerError { + LoggerError::PoisonError(value) + } +} +impl<'a> From for LoggerError<'a> { + fn from(value: std::io::Error) -> Self { + LoggerError::FileWriteError(value) + } +} +impl<'a> Display for LoggerError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::PoisonError(_) => write!(f, "PoisonError"), + Self::FileWriteError(e) => write!(f, "FileWriteError: {}", e), + } + } +} + +impl Logger { + pub fn new() -> Self { + Self { + logs: Mutex::new(Vec::new()), + temp_logs: Mutex::new(Vec::new()), + } + } + + fn temp_log(&self, log: String) { + let mut temp_logs = self.temp_logs.lock().unwrap(); // Lock the Mutex to modify the temp_logs + temp_logs.push(log); + if temp_logs.len() > 50 { + temp_logs.remove(0); // Remove the oldest log if there are more than 50 + } + } + + fn log_internal(&self, log: String) { + let mut logs = self.logs.lock().unwrap(); // Lock the Mutex to modify the logs + logs.push(log); + + if let Err(e) = std::fs::write("/tmp/foo", logs.join("\n").as_bytes()) { + eprintln!("Unable to write to log file: {e}"); + } + } + pub fn log_error(&self, log: String) { + let dt = Utc::now(); + self.log_internal(format!( + "[ERROR] {} | {}", + dt.format("%Y-%m-%d %H:%M:%S"), + log + )); + self.temp_log(format!( + "[ERROR] {} | {}", + dt.format("%Y-%m-%d %H:%M:%S"), + log + )); + } + pub fn log(&self, log: String) { + let dt = Utc::now(); + self.log_internal(format!( + "[LOG] {} | {}", + dt.format("%Y-%m-%d %H:%M:%S"), + log + )); + self.temp_log(format!( + "[LOG] {} | {}", + dt.format("%Y-%m-%d %H:%M:%S"), + log + )); + } + + pub fn read_logs(&self) -> std::io::Result> { + let logs = self.logs.lock().unwrap(); + Ok(logs.clone()) + } + pub fn read_temp_logs(&self) -> std::io::Result> { + let logs = self.temp_logs.lock().unwrap(); + Ok(logs.clone()) + } + + // std::sync::PoisonError>>> + pub fn write_logs_file( + &self, + path: std::path::PathBuf, + ) -> std::result::Result<(), LoggerError>>> { + let v = match self.logs.lock() { + Ok(v) => v, + Err(e) => { + println!("Poisoned mutex (this is not good)"); + return Err(LoggerError::from(e)); + } + }; + let contents = v.join("\n"); + std::fs::write(path, contents)?; + Ok(()) + } + pub fn read_logs_file(path: std::path::PathBuf) -> Result> { + let content = std::fs::read_to_string(path)?; + let logs = content.split("\n").map(|str| str.to_string()).collect(); + Ok(Logger { + logs: Mutex::new(logs), + temp_logs: Mutex::new(Vec::new()), + }) + } +} + +impl Serialize for Logger { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let v = match self.logs.lock() { + Ok(v) => v.join("\n"), + Err(e) => return Err(serde::ser::Error::custom(e.to_string())), + }; + serializer.serialize_str(v.as_str()) + } +} + +struct LoggerVisitor; +impl<'de> serde::de::Visitor<'de> for LoggerVisitor { + type Value = Logger; + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + let logged = v.split("\n").map(|s| s.to_string()).collect(); + Ok(Logger { + logs: Mutex::new(logged), + temp_logs: Mutex::new(Vec::new()), + }) + } + + fn visit_borrowed_str(self, v: &'de str) -> Result + where + E: serde::de::Error, + { + let v = v.to_string(); + let logged = v.split("\n").map(|s| s.to_string()).collect(); + Ok(Logger { + logs: Mutex::new(logged), + temp_logs: Mutex::new(Vec::new()), + }) + } + + fn visit_string(self, v: String) -> Result + where + E: serde::de::Error, + { + let v = v.to_string(); + let logged = v.split("\n").map(|s| s.to_string()).collect(); + Ok(Logger { + logs: Mutex::new(logged), + temp_logs: Mutex::new(Vec::new()), + }) + } + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("Expects string composed of substrings separated by \\n (newlines). Th") + } +} + +impl<'de> Deserialize<'de> for Logger { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_str(LoggerVisitor) + } +} diff --git a/private.pem b/private.pem new file mode 100644 index 0000000..678fce0 --- /dev/null +++ b/private.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDulpf6QC7Sp1ME +mTXwZyGCWLuc58W1CXw7O/iztpCk0N7KCJwPxajoyC2cBUkmuqWUxcZFCvkBQtBG +IGF2CP0R2POzVWurRfpyWqsXfF4a2pKCiFcwMnhTVfmniHGyn34fQjzZ36LG+nga +heecgTOUyI3c1Qdc+P/6QHqJnFgtDtU4SBKMlqRBvAQxyGaFyG+WSa54PqTzq2Pw +k9PGpFdQMA060O4nun8Xo0ulhPwV5HP47UBwTvRwzIxD7n/6fsYFsio18m+6Pv7m +ihCvSpqfDtF7doVcyx7gY2tJdJQ6/JCJ+VzgB3At7t9Aid78zZdVoWdzGgAihmWb +SgrO80f1AgMBAAECggEANyx9JcJ88Xr/UENr/VWkIqQJct4IUvbIE1yphUm1RmAE +/VyjVmRjzL0qCvgP9BRaoT5hPOsuLTP5vRXU4/ptJ5jvtmuNFBUBNzuvcCVzD6u3 +ENedrggl2kO6EZuUi3WlsW3ca5fzaSdX6e5IYuFf/8qnP8fFxaLRc1pA3BzbKUPg +BQYrEG0swSp6iNTzSpF8x7OJnMmHZ+LZA90jedTuGSTxmNzC0rIXfgEBgEnduUoN +o82u8x2SlApvNm8jp1ORhc7chsDQQSSMpkHD7EjGhl7ZWTb9Fyrnt1U7SYP+s6dA +Tq4uy6GHhPeagwceUgvfIkWuwCzZNQCsxdMZDsvy6wKBgQD9UNwuGzstiPodonia +vdVwF49uBQ5tQYmReDsAZ0QJDhHh8+XI7iD6qBnjsKDUYjpujyqeifDwulSoMDRS +9Mp9Y5m0KJGXQZWajmUUDgzKi9BhaiQG6ekRXKH3HAuo7ixtoLq8kmNztR/YScXW +VRmcDkeUR5Elphbdbxg+rdrg1wKBgQDxHcikdLtHLuBLKSKKnPSa9Arg2MFA79uo +AnVMkerVpJHQIio3IXrfdEiGiKcB54IS5R9P5zhK2YJ4Hex+jMpsM1BxS5wIkh7e +KTrISDYN6aaLkOgsXrtBPbUP1DNlAnldd4ORycM//M5CTT+SoYjNvr5WdCIAtKtu +5Pk0RqUoEwKBgGRUc3CbfnvRhebY49W+HFxSKI/V1tn1HCyOpyfIvsyrUu/gbwF/ +N3avv9lLqteTtDr331R9o/1kf4VB2meE5vJ00XrmmouDAWL+xRAFZJGQgM8kf/Jx +EMu8mbXK2N9mvhgJkgyAGn3Inj1HMuFqQUNq8HTNR7LWrrWze7HPoCJvAoGBAOD3 +FAx1KYM59mXKJINF/GdTCY9OTcljwgNY/86IWp4pJS8XoWG86CLzIPP9Li11roQo +qPVpW7lihfvCl7RfckJQAl8i15ubkhdgSosx7NVASnbvpeA4JuixRW1WZPl6vHf7 +mZN9t6HNIzjzUDxkYPruXDLXuh4Ec+tM0kL3IwNnAoGAbo+Manh2crkKQyOrKt+F +G4qmewTTMTdp+iMPXkWAxa9T9etk876s5X4cGfxQv8sBo6WbDkbztgbFVty2OT/S +G/mnwcGC4QkfKXgr6wYzko06f8VGqbug3RXa9Y4PrwuFHp5vtKAfEKKzypNSkuXZ +nHPp7GBCk/n6VspYy+iLgzg= +-----END PRIVATE KEY----- diff --git a/public.pem b/public.pem new file mode 100644 index 0000000..8f37534 --- /dev/null +++ b/public.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7paX+kAu0qdTBJk18Gch +gli7nOfFtQl8Ozv4s7aQpNDeygicD8Wo6MgtnAVJJrqllMXGRQr5AULQRiBhdgj9 +Edjzs1Vrq0X6clqrF3xeGtqSgohXMDJ4U1X5p4hxsp9+H0I82d+ixvp4GoXnnIEz +lMiN3NUHXPj/+kB6iZxYLQ7VOEgSjJakQbwEMchmhchvlkmueD6k86tj8JPTxqRX +UDANOtDuJ7p/F6NLpYT8FeRz+O1AcE70cMyMQ+5/+n7GBbIqNfJvuj7+5ooQr0qa +nw7Re3aFXMse4GNrSXSUOvyQiflc4AdwLe7fQIne/M2XVaFncxoAIoZlm0oKzvNH +9QIDAQAB +-----END PUBLIC KEY----- diff --git a/src/TODO.txt b/src/TODO.txt index f77fc3f..55cbb2e 100644 --- a/src/TODO.txt +++ b/src/TODO.txt @@ -1,7 +1,5 @@ -- Criar um node com uma port no localhost para receber transacoes, e de x em x tempos fechar o bloco - - Encontrar nodes - Quando fechar o bloco, emitir essa info pra outros nodes -- Minerar o bloco \ No newline at end of file +- Minerar o bloco diff --git a/src/bin/cleyto_coin_wallet.rs b/src/bin/cleyto_coin_wallet.rs new file mode 100644 index 0000000..1904693 --- /dev/null +++ b/src/bin/cleyto_coin_wallet.rs @@ -0,0 +1,179 @@ +use cleyto_coin::chain::{ + transaction::{self, Transaction, TransactionInfo}, + wallet::{Wallet, WalletPK}, +}; +use openssl::pkey::{PKey, Private}; +use reqwest::Client; +use std::path::PathBuf; +use structopt::StructOpt; + +/// CLI for key management and transactions +#[derive(Debug, StructOpt)] +#[structopt(name = "cleyto-coin-wallet")] +enum Args { + /// Generate a new keypair + Generate { + /// Where to store the generated private key + #[structopt(long, parse(from_os_str), default_value = "private.pem")] + private_key_file: PathBuf, + + /// Where to store the generated public key + #[structopt(long, parse(from_os_str), default_value = "public.pem")] + public_key_file: PathBuf, + + #[structopt(long, short)] + password: Option, + }, + + /// Send a transaction + Send { + /// Recipient’s public key as a string + #[structopt( + long = "recipient-key", + short = "r", + required_unless = "recipient-key-file" + )] + recipient_key: Option, + + /// Recipient’s public key from a file + #[structopt( + long = "recipient-key-file", + parse(from_os_str), + required_unless = "recipient-key" + )] + recipient_key_file: Option, + + /// Sender’s private key as a string + #[structopt(long = "sender-key", short = "s", required_unless = "sender-key-file")] + sender_key: Option, + + /// Sender’s private key from a file + #[structopt( + long = "sender-key-file", + parse(from_os_str), + required_unless = "sender-key" + )] + sender_key_file: Option, + + /// Transaction amount + #[structopt(long, short = "a")] + amount: i64, + }, +} + +async fn send_transaction(transaction: transaction::Transaction) { + let client = Client::new(); + + let transaction_json = transaction.serialize(); + + // Send the POST request + let response = client + .post("http://localhost:9473/submit-transaction") + .header("Content-Type", "application/json") + .body(transaction_json) + .send() + .await + .unwrap(); + + // Check the response status + let status = response.status(); + let response_body = response.text().await.unwrap(); + + // Print the response + println!("Response Status: {}", status); + println!("Response Body: {}", response_body); +} + +fn read_key_string_or_file(string: &Option, file: &Option) -> String { + if let Some(s) = string { + s.clone() + } else if let Some(path) = file { + std::fs::read_to_string(path).expect("Failed to read key file") + } else { + panic!("Key not provided"); + } +} + +pub async fn send( + recipient_key: Option, + recipient_key_file: Option, + sender_key: Option, + sender_key_file: Option, + amount: i64, +) { + let recipient_key_str = read_key_string_or_file(&recipient_key, &recipient_key_file); + let sender_key_str = read_key_string_or_file(&sender_key, &sender_key_file); + + // convert to PKey objects + let sender_pkey: PKey = PKey::private_key_from_pem(sender_key_str.as_bytes()) + .expect("Failed to parse sender private key"); + + let recipient_pkey: PKey = + PKey::public_key_from_pem(recipient_key_str.as_bytes()) + .expect("Failed to parse recipient public key"); + + // create wallets + let sender_wallet = WalletPK::from(sender_pkey); + let recipient_wallet = Wallet::from(recipient_pkey); + + // create transaction info + let transaction_info = TransactionInfo::new(amount); + + // sign the transaction + let signature = sender_wallet + .sign_transaction(&transaction_info) + .expect("Failed on signing of transaction"); + + let transaction = Transaction::new( + sender_wallet.public_wallet(), + recipient_wallet, + transaction_info, + signature, + ) + .inspect_err(|e| eprintln!("Failed creating the transaction: {e}")) + .unwrap(); + + send_transaction(transaction).await; +} + +fn generate(private_key_file: PathBuf, public_key_file: PathBuf, password: Option) { + let (wallet, walletpk) = Wallet::new(); + if let Some(password) = password { + std::fs::write(private_key_file, walletpk.to_pem_with_password(password)) + .expect("Could not write new wallet's private key to file"); + } else { + std::fs::write(private_key_file, walletpk.to_pem()) + .expect("Could not write new wallet's private key to file"); + } + + std::fs::write(public_key_file, wallet.to_pem()) + .expect("Could not write new wallet's public key to file"); +} + +#[tokio::main] +async fn main() { + let args = Args::from_args(); + match args { + Args::Generate { + private_key_file, + public_key_file, + password, + } => generate(private_key_file, public_key_file, password), + Args::Send { + recipient_key, + recipient_key_file, + sender_key, + sender_key_file, + amount, + } => { + send( + recipient_key, + recipient_key_file, + sender_key, + sender_key_file, + amount, + ) + .await + } + } +} diff --git a/src/bin/create_transaction.rs b/src/bin/create_transaction.rs index 0d413d1..1ded7d6 100644 --- a/src/bin/create_transaction.rs +++ b/src/bin/create_transaction.rs @@ -1,9 +1,7 @@ -// use chrono::Utc; -// use cleyto_coin::chain::transaction::{Transaction, TransactionInfo, TransactionValidationError}; -// use cleyto_coin::chain::wallet::Wallet; +use cleyto_coin::chain::transaction::{Transaction, TransactionInfo}; +use cleyto_coin::chain::wallet::Wallet; use reqwest::Client; use std::error::Error; -use std::fs; #[tokio::main] async fn main() { @@ -30,14 +28,28 @@ async fn post_json() -> Result<(), Box> { // Initialize the HTTP client let client = Client::new(); - // Read the JSON file - let json_content = fs::read_to_string("src/bin/transaction.json")?; + let (wallet_sender, walletpk_sender) = Wallet::new(); + let (wallet_receiver, _) = Wallet::new(); + let transactioninfo: TransactionInfo = TransactionInfo::new(12345); + + let signature = match walletpk_sender.sign_transaction(&transactioninfo) { + Ok(signed_hashed_message) => signed_hashed_message, + _ => panic!("error while signing transaction"), + }; + println!( + "Transaction signature (signed using the wallet_pk):\n{:?}", + signature + ); + + let transaction: Transaction = + Transaction::new(wallet_sender, wallet_receiver, transactioninfo, signature).unwrap(); + let transaction_json = transaction.serialize(); // Send the POST request let response = client .post("http://localhost:9473/submit-transaction") .header("Content-Type", "application/json") - .body(json_content) + .body(transaction_json) .send() .await?; diff --git a/src/bin/node.rs b/src/bin/node.rs index 66d555a..896859d 100644 --- a/src/bin/node.rs +++ b/src/bin/node.rs @@ -1,27 +1,32 @@ use cleyto_coin::chain::Chain; -use cleyto_coin::node::{self, logger::Logger, ui::App}; +use cleyto_coin::node::{self, ui::App}; use std::{ sync::{mpsc, Arc, Mutex}, thread, }; +use structopt::StructOpt; -fn main() -> color_eyre::Result<()> { - let logger = Arc::new(Logger::new()); +#[derive(StructOpt, Debug)] +struct Args { + #[structopt(short, long)] + gui: bool, +} +fn run_with_gui() -> color_eyre::Result<()> { let (tx, rx) = mpsc::channel::<()>(); // Channel to kill thread let rx = Arc::new(Mutex::new(rx)); + let (mut node, logger) = node::Node::new(Chain::new()); + // Run server thread - let logger_clone_for_node = Arc::clone(&logger); let server = thread::spawn(move || { let rx = Arc::clone(&rx); - let mut node = node::Node::new(Chain::new(), logger_clone_for_node); + node.run(true, rx, 0); }); - // TODO Logger nao scrolla color_eyre::install()?; let terminal = ratatui::init(); let result = App::new(Arc::clone(&logger), node::Node::DEFAULT_PORT).run(terminal); @@ -33,3 +38,18 @@ fn main() -> color_eyre::Result<()> { server.join().unwrap(); result } +fn run_server() { + let (_, rx) = mpsc::channel::<()>(); + let rx = Arc::new(Mutex::new(rx)); + let (mut node, _) = node::Node::new(Chain::new()); + node.run(true, rx, 0); +} + +fn main() { + let args = Args::from_args(); + match args.gui { + true => run_with_gui().unwrap(), + false => run_server(), + } + run_with_gui().unwrap(); +} diff --git a/src/chain/block.rs b/src/chain/block.rs index 8533c9f..4a3857c 100644 --- a/src/chain/block.rs +++ b/src/chain/block.rs @@ -1,11 +1,12 @@ use chrono::{DateTime, Utc}; -use sha2::{Digest, Sha256}; +use openssl::hash::{Hasher, MessageDigest}; +use serde::{Deserialize, Serialize}; use super::transaction::Transaction; use super::utils::PROOF_OF_WORK_DIFFICULTY; use super::Chain; -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] pub struct Block { previous_hash: String, transactions: Vec, @@ -24,20 +25,82 @@ impl Block { self.index } + /// This merkle tree does not work the same way as the bitcoin core one. If the number of + /// leaves is not a power of two, it copies the last transaction's hash until we have enough + /// leaves for a binary tree, and then it collapses the tree into the root, which is then + /// returned. + fn calculate_merkle_root(transactions: &[Transaction]) -> [u8; 32] { + // This gets the closest bigger power of 2 + let log_2 = f32::log2(transactions.len() as f32); + let mut closest_log_2: u32 = log_2 as u32; + if log_2 > closest_log_2 as f32 { + closest_log_2 += 1; + } + let mut closest_pow_2 = 2usize.pow(closest_log_2); + + // Runs first time to populate the hash_vec with the hash of all transactions + let mut hash_vec = (0..closest_pow_2) + .map(|i| { + let transaction_hash = { + let transaction = match transactions.get(i) { + Some(transaction) => transaction, + None => transactions.last().unwrap(), + }; + transaction.txid + }; + transaction_hash + }) + .collect::>(); + + // Fuck recursion + let result: [u8; 32]; + loop { + if closest_pow_2 == 1 { + result = hash_vec[0]; + break; + } + + for i in (0..closest_pow_2).step_by(2) { + // gets either the hashes in the right index or the last hash on the hash_vec + let (hash_1, hash_2) = { + ( + match hash_vec.get(i) { + Some(hash) => hash, + None => hash_vec.last().unwrap(), + }, + match hash_vec.get(i + 1) { + Some(hash) => hash, + None => hash_vec.last().unwrap(), + }, + ) + }; + let mut hasher = Hasher::new(MessageDigest::sha256()).unwrap(); + hasher.update(hash_1).unwrap(); + hasher.update(hash_2).unwrap(); + hash_vec[i / 2] = hasher.finish().unwrap().as_ref().try_into().unwrap(); + } + closest_pow_2 /= 2; + } + result + } + pub fn calculate_hash(&self) -> String { - let transactions_string = self - .transactions - .iter() - .map(|t| t.to_string()) // Calls the `to_string()` method of `Transaction` - .collect::>() // Collects into a Vec - .join("::END_OF_TRANSACTION::BEGIN_OF_TRANSACTION::"); // Joins all elements with "; " as separator + //This here should be replaced by the calculation of the merkle tree + // let transactions_string = self + // .transactions + // .iter() + // .map(|t| t.to_string()) // Calls the `to_string()` method of `Transaction` + // .collect::>() // Collects into a Vec + // .join("::END_OF_TRANSACTION::BEGIN_OF_TRANSACTION::"); // Joins all elements with "; " as separator + + let merkle_root = Self::calculate_merkle_root(&self.transactions); let serialized = serde_json::to_string(&( "BEGIN::BEGIN_PREVIOUS_HASH::", &self.previous_hash, "::END_PREVIOUS_HASH::BEGIN_TRANSACTIONS::", "BEGIN_OF_TRANSACTION::", - transactions_string, + merkle_root, "::END_OF_TRANSACTION", "::END_TRANSACTIONS::BEGIN_INDEX::", &self.index, @@ -51,11 +114,10 @@ impl Block { // println!("behold the serialized block:\n{serialized}"); - let mut hasher = Sha256::new(); - hasher.update(serialized.as_bytes()); - let result = hasher.finalize(); - let encoded_result = hex::encode(result); - println!("Hash = {encoded_result}"); + let mut hasher = Hasher::new(MessageDigest::sha256()).unwrap(); + hasher.update(serialized.as_bytes()).unwrap(); + let result = hasher.finish().unwrap(); + hex::encode(result) // Converts bytes to a hex string } diff --git a/src/chain/mod.rs b/src/chain/mod.rs index e5ed154..d485f2e 100644 --- a/src/chain/mod.rs +++ b/src/chain/mod.rs @@ -3,8 +3,9 @@ pub mod transaction; pub mod utils; pub mod wallet; use block::Block; +use serde::{Deserialize, Serialize}; -#[derive(Default)] +#[derive(Default, Serialize, Deserialize)] pub struct Chain { blocks: Vec, } @@ -24,7 +25,7 @@ impl Chain { genesis } - fn get_last_hash(&mut self) -> String { + pub fn get_last_hash(&mut self) -> String { match self.blocks.last() { Some(block) => block.get_hash(), None => { @@ -34,7 +35,7 @@ impl Chain { } } - pub(crate) fn get_last_index(&mut self) -> u64 { + pub fn get_last_index(&mut self) -> u64 { match self.blocks.last() { Some(block) => block.get_index(), None => { diff --git a/src/chain/transaction.rs b/src/chain/transaction.rs index d23d5b2..7e6c8a7 100644 --- a/src/chain/transaction.rs +++ b/src/chain/transaction.rs @@ -1,6 +1,7 @@ use super::wallet::Wallet; use chrono::{DateTime, Utc}; use openssl::error::ErrorStack; +use openssl::sha::Sha256; use serde::{Deserialize, Serialize}; use std::fmt; use std::fmt::Debug; @@ -14,7 +15,8 @@ pub struct TransactionInfo { } impl TransactionInfo { - pub fn new(value: i64, date: DateTime) -> TransactionInfo { + pub fn new(value: i64) -> TransactionInfo { + let date = Utc::now(); Self { value, date } } } @@ -85,6 +87,7 @@ pub struct Transaction { pub receiver: Wallet, pub signature: Vec, pub transaction_info: TransactionInfo, + pub txid: [u8; 32], } impl Transaction { @@ -94,13 +97,20 @@ impl Transaction { transaction_info: TransactionInfo, signature: Vec, ) -> Result { - let transaction = Self { + let mut transaction = Self { sender, receiver, signature, transaction_info, + txid: [0; 32], // This could be optimized by avoiding the creation of this Vec, which + // serves no function on its own, but I don't really see that being a problem }; + let to_hash = transaction.to_string(); + let mut hasher: Sha256 = Sha256::new(); + hasher.update(to_hash.as_bytes()); + transaction.txid = hasher.finish().to_owned(); + match transaction.verify() { Ok(()) => Ok(transaction), Err(error) => Err(error), @@ -143,7 +153,7 @@ impl Display for Transaction { f, "SENDER::{:?}::RECEIVER::{:?}::{}::SIGNATURE::{:?}", self.sender, - self.receiver.to_vec(), + self.receiver.to_pem(), self.transaction_info, self.signature ) diff --git a/src/chain/wallet.rs b/src/chain/wallet.rs index 3c6a84c..cd970e9 100644 --- a/src/chain/wallet.rs +++ b/src/chain/wallet.rs @@ -1,42 +1,12 @@ use super::transaction::TransactionInfo; use openssl::error::ErrorStack; -use serde::{Deserialize, Serialize}; use openssl::hash::MessageDigest; use openssl::pkey::{PKey, Private, Public}; use openssl::rsa::Rsa; use openssl::sign::{Signer, Verifier}; - -// fn test_sign() { -// let rsa = Rsa::generate(2048).unwrap(); -// let pkey = PKey::from_rsa(rsa).unwrap(); -// -// // Data to be signed (this would normally be provided by the signer) -// let data = b"hello, world!"; -// -// // Step 2: Sign the data with the private key (this would be done by the sender) -// let mut signer = Signer::new(MessageDigest::sha256(), &pkey).unwrap(); -// signer.update(data).unwrap(); -// let signature = signer.sign_to_vec().unwrap(); -// -// // --- Now, we are at the verification step --- -// // Step 3: Extract the public key from the PKey and use it for verification -// let public_key = pkey.public_key_to_pem().unwrap(); // Extract public key in PEM format -// let rsa_public = Rsa::public_key_from_pem(&public_key).unwrap(); // Convert back to Rsa -// let pkey_public = PKey::from_rsa(rsa_public).unwrap(); // Create a PKey for public key -// -// // Step 4: Verify the signature using the public key -// let mut verifier = Verifier::new(MessageDigest::sha256(), &pkey_public).unwrap(); -// verifier.update(data).unwrap(); -// let is_valid = verifier.verify(&signature).unwrap(); -// -// // Step 5: Check if the signature is valid -// if is_valid { -// println!("Signature is valid!"); -// } else { -// println!("Signature is invalid."); -// } -// } +use openssl::symm::Cipher; +use serde::{Deserialize, Serialize}; // ---------------------------------------------- WalletPK definition ---------------------------------------------- #[derive(Debug)] @@ -46,31 +16,96 @@ pub struct WalletPK { impl WalletPK { pub fn sign_transaction( - &mut self, + &self, transaction_info: &TransactionInfo, ) -> Result, ErrorStack> { let mut signer = Signer::new(MessageDigest::sha256(), &self.private_key)?; - signer.update(transaction_info.to_string().as_bytes())?; - signer.sign_to_vec() + signer.sign_oneshot_to_vec(transaction_info.to_string().as_bytes()) + } + pub fn to_pem_with_password(&self, password: String) -> Vec { + self.private_key + .private_key_to_pem_pkcs8_passphrase(Cipher::aes_256_cbc(), password.as_bytes()) + .unwrap() + } + pub fn to_pem(&self) -> Vec { + self.private_key.private_key_to_pem_pkcs8().unwrap() + } + pub fn public_wallet(&self) -> Wallet { + let public_key = PKey::public_key_from_pem( + &self + .private_key + .public_key_to_pem() + .expect("Could not extract Publick Key from Private Key"), + ) + .unwrap(); + + Wallet { public_key } + } +} +impl From> for WalletPK { + fn from(private_key: PKey) -> Self { + Self { private_key } } } + // ----------------------------------------------------------------------------------------------------------------- // ---------------------------------------------- Wallet definition ------------------------------------------------ -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug)] pub struct Wallet { - public_key: Vec, + public_key: PKey, // Should I store this as PEM or as PKey? } +impl<'de> Deserialize<'de> for Wallet { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let bytes: Vec = Deserialize::deserialize(deserializer)?; + let public_key = PKey::public_key_from_pem(&bytes).map_err(serde::de::Error::custom)?; + Ok(Wallet { public_key }) + } +} + +impl Serialize for Wallet { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let pem = self + .public_key + .public_key_to_pem() + .map_err(serde::ser::Error::custom)?; + serializer.serialize_bytes(&pem) + } +} + +impl From for Wallet { + fn from(value: String) -> Self { + let public_rsa = openssl::rsa::Rsa::public_key_from_pem(value.as_bytes()) + .expect("Could not read the public key"); + let public_key = + PKey::from_rsa(public_rsa).expect("Error converting from RSA to PKey"); + Self { public_key } + } +} +impl From> for Wallet { + fn from(public_key: PKey) -> Self { + Self { public_key } + } +} impl Wallet { pub fn new() -> (Self, WalletPK) { let bits: u32 = 2048; let rsa = Rsa::generate(bits).unwrap(); let private_key = PKey::from_rsa(rsa).unwrap(); - let public_key = private_key - .public_key_to_pem() - .expect("Error extracting public key from private key"); + let public_key = PKey::public_key_from_pem( + &private_key + .public_key_to_pem() + .expect("Could not extract Publick Key from Private Key"), + ) + .unwrap(); (Wallet { public_key }, WalletPK { private_key }) } @@ -80,22 +115,13 @@ impl Wallet { transaction_info: &TransactionInfo, signature: &[u8], ) -> Result { - let public_key = self.to_pkey(); - let mut verifier = Verifier::new(MessageDigest::sha256(), &public_key)?; + let mut verifier = Verifier::new(MessageDigest::sha256(), &self.public_key)?; verifier.update(transaction_info.to_string().as_bytes())?; verifier.verify(signature) } - #[allow(unused)] - pub fn to_pkey(&self) -> PKey { - let rsa_public = Rsa::public_key_from_pem(&self.public_key) - .expect("Error extracting Rsa object from public key"); - PKey::from_rsa(rsa_public) - .expect("Error extracting PKey object from Rsa object") - } - - pub fn to_vec(&self) -> Vec { - self.public_key.clone() + pub fn to_pem(&self) -> Vec { + self.public_key.public_key_to_pem().unwrap() } } // ----------------------------------------------------------------------------------------------------------------- diff --git a/src/node/logger.rs b/src/node/logger.rs index 56956a0..0b4d2de 100644 --- a/src/node/logger.rs +++ b/src/node/logger.rs @@ -1,6 +1,6 @@ use chrono::prelude::Utc; use std::io::{self}; -use std::sync::Mutex; +use std::sync::{Mutex, PoisonError}; #[derive(Default)] pub struct Logger { @@ -8,6 +8,22 @@ pub struct Logger { temp_logs: Mutex>, } +#[derive(Debug)] +pub enum LoggerError { + PoisonError(String), + FileWriteError(std::io::Error), +} +impl From> for LoggerError { + fn from(value: PoisonError) -> LoggerError { + LoggerError::PoisonError(value.to_string()) + } +} +impl From for LoggerError { + fn from(value: std::io::Error) -> Self { + LoggerError::FileWriteError(value) + } +} + impl Logger { pub fn new() -> Self { Self { @@ -67,4 +83,298 @@ impl Logger { let logs = self.temp_logs.lock().unwrap(); Ok(logs.clone()) } + + // std::sync::PoisonError>>> + pub fn write_logs_file( + &self, + path: &std::path::PathBuf, + ) -> std::result::Result<(), LoggerError> { + let v = match self.logs.lock() { + Ok(v) => v, + Err(e) => { + println!("Poisoned mutex (this is not good)"); + return Err(LoggerError::from(e)); + } + }; + let contents = v.join("\n"); + std::fs::write(path, contents)?; + Ok(()) + } + pub fn read_logs_file(path: &std::path::PathBuf) -> std::result::Result { + println!("config log path is {}", path.to_str().unwrap()); + let contents = std::fs::read_to_string(path)?; + let logs = contents.split("\n").map(|str| str.to_string()).collect(); + + Ok(Logger { + logs: Mutex::new(logs), + temp_logs: Mutex::new(Vec::new()), + }) + } } +// impl Serialize for Logger { +// fn serialize(&self, serializer: S) -> Result +// where +// S: serde::Serializer, +// { +// let v = match self.logs.lock() { +// Ok(v) => v.join("\n"), +// Err(e) => return Err(serde::ser::Error::custom(e.to_string())), +// }; +// serializer.serialize_str(v.as_str()) +// } +// } +// struct LoggerVisitor; +// impl serde::de::Visitor for LoggerError { +// fn visit_bool(self, v: bool) -> Result +// where +// E: serde::de::Error, +// { +// Err(serde::de::Error::invalid_type( +// serde::de::Unexpected::Bool(v), +// &self, +// )) +// } +// +// fn visit_i8(self, v: i8) -> Result +// where +// E: serde::de::Error, +// { +// self.visit_i64(v as i64) +// } +// +// fn visit_i16(self, v: i16) -> Result +// where +// E: serde::de::Error, +// { +// self.visit_i64(v as i64) +// } +// +// fn visit_i32(self, v: i32) -> Result +// where +// E: serde::de::Error, +// { +// self.visit_i64(v as i64) +// } +// +// fn visit_i64(self, v: i64) -> Result +// where +// E: serde::de::Error, +// { +// Err(serde::de::Error::invalid_type( +// serde::de::Unexpected::Signed(v), +// &self, +// )) +// } +// +// fn visit_i128(self, v: i128) -> Result +// where +// E: serde::de::Error, +// { +// let mut buf = [0u8; 58]; +// let mut writer = format::Buf::new(&mut buf); +// std::fmt::Write::write_fmt(&mut writer, format_args!("integer `{}` as i128", v)).unwrap(); +// Err(serde::de::Error::invalid_type( +// serde::de::Unexpected::Other(writer.as_str()), +// &self, +// )) +// } +// +// fn visit_u8(self, v: u8) -> Result +// where +// E: serde::de::Error, +// { +// self.visit_u64(v as u64) +// } +// +// fn visit_u16(self, v: u16) -> Result +// where +// E: serde::de::Error, +// { +// self.visit_u64(v as u64) +// } +// +// fn visit_u32(self, v: u32) -> Result +// where +// E: serde::de::Error, +// { +// self.visit_u64(v as u64) +// } +// +// fn visit_u64(self, v: u64) -> Result +// where +// E: serde::de::Error, +// { +// Err(serde::de::Error::invalid_type( +// serde::de::Unexpected::Unsigned(v), +// &self, +// )) +// } +// +// fn visit_u128(self, v: u128) -> Result +// where +// E: serde::de::Error, +// { +// let mut buf = [0u8; 57]; +// let mut writer = format::Buf::new(&mut buf); +// std::fmt::Write::write_fmt(&mut writer, format_args!("integer `{}` as u128", v)).unwrap(); +// Err(serde::de::Error::invalid_type( +// serde::de::Unexpected::Other(writer.as_str()), +// &self, +// )) +// } +// +// fn visit_f32(self, v: f32) -> Result +// where +// E: serde::de::Error, +// { +// self.visit_f64(v as f64) +// } +// +// fn visit_f64(self, v: f64) -> Result +// where +// E: serde::de::Error, +// { +// Err(serde::de::Error::invalid_type( +// serde::de::Unexpected::Float(v), +// &self, +// )) +// } +// +// fn visit_char(self, v: char) -> Result +// where +// E: serde::de::Error, +// { +// self.visit_str(v.encode_utf8(&mut [0u8; 4])) +// } +// +// fn visit_str(self, v: &str) -> Result +// where +// E: serde::de::Error, +// { +// Err(serde::de::Error::invalid_type( +// serde::de::Unexpected::Str(v), +// &self, +// )) +// } +// +// fn visit_borrowed_str(self, v: &'de str) -> Result +// where +// E: serde::de::Error, +// { +// self.visit_str(v) +// } +// +// fn visit_string(self, v: String) -> Result +// where +// E: serde::de::Error, +// { +// self.visit_str(&v) +// } +// +// fn visit_bytes(self, v: &[u8]) -> Result +// where +// E: serde::de::Error, +// { +// Err(serde::de::Error::invalid_type( +// serde::de::Unexpected::Bytes(v), +// &self, +// )) +// } +// +// fn visit_borrowed_bytes(self, v: &'de [u8]) -> Result +// where +// E: serde::de::Error, +// { +// self.visit_bytes(v) +// } +// +// fn visit_byte_buf(self, v: Vec) -> Result +// where +// E: serde::de::Error, +// { +// self.visit_bytes(&v) +// } +// +// fn visit_none(self) -> Result +// where +// E: serde::de::Error, +// { +// Err(serde::de::Error::invalid_type( +// serde::de::Unexpected::Option, +// &self, +// )) +// } +// +// fn visit_some(self, deserializer: D) -> Result +// where +// D: serde::Deserializer<'de>, +// { +// let _ = deserializer; +// Err(serde::de::Error::invalid_type( +// serde::de::Unexpected::Option, +// &self, +// )) +// } +// +// fn visit_unit(self) -> Result +// where +// E: serde::de::Error, +// { +// Err(serde::de::Error::invalid_type( +// serde::de::Unexpected::Unit, +// &self, +// )) +// } +// +// fn visit_newtype_struct(self, deserializer: D) -> Result +// where +// D: serde::Deserializer<'de>, +// { +// let _ = deserializer; +// Err(serde::de::Error::invalid_type( +// serde::de::Unexpected::NewtypeStruct, +// &self, +// )) +// } +// +// fn visit_seq(self, seq: A) -> Result +// where +// A: serde::de::SeqAccess<'de>, +// { +// let _ = seq; +// Err(serde::de::Error::invalid_type( +// serde::de::Unexpected::Seq, +// &self, +// )) +// } +// +// fn visit_map(self, map: A) -> Result +// where +// A: serde::de::MapAccess<'de>, +// { +// let _ = map; +// Err(serde::de::Error::invalid_type( +// serde::de::Unexpected::Map, +// &self, +// )) +// } +// +// fn visit_enum(self, data: A) -> Result +// where +// A: serde::de::EnumAccess<'de>, +// { +// let _ = data; +// Err(serde::de::Error::invalid_type( +// serde::de::Unexpected::Enum, +// &self, +// )) +// } +// } +// impl<'de> Deserialize<'de> for Logger { +// fn deserialize(deserializer: D) -> Result +// where +// D: serde::Deserializer<'de>, +// { +// deserializer.deserialize_str(LoggerVisitor) +// } +// } diff --git a/src/node/mod.rs b/src/node/mod.rs index 12ef677..b0c5b7f 100644 --- a/src/node/mod.rs +++ b/src/node/mod.rs @@ -6,9 +6,12 @@ mod utils; use crate::chain::{transaction::Transaction, Chain}; use crate::node::logger::Logger; use core::panic; +use directories::ProjectDirs; use once_cell::sync::Lazy; use resolve_requests::endpoints::resolve_endpoint; use resolve_requests::methods::{HTTPParseError, HTTPRequest}; +use serde::{Deserialize, Serialize}; +use std::fs; use std::path::PathBuf; use std::time::Duration; use std::{ @@ -20,14 +23,24 @@ use std::{ }; use thread_pool::custom_thread_pool::ThreadPool; +#[derive(Serialize, Deserialize)] pub struct NodeState { status: bool, chain: Chain, transactions_pool: Vec, } +#[derive(Debug, Deserialize, Default)] +struct NodeConfig { + log_path: PathBuf, +} + +#[derive(Serialize, Deserialize)] pub struct Node { state: Arc>, + #[serde(skip)] // The logs are manually saved on shutdown and reloaded on initialization logger: Arc, + #[serde(skip)] // The configs are best reloaded with every initialization + config: NodeConfig, } static NUMBER_OF_THREADS_IN_THREAD_POOL: Lazy = Lazy::new(num_cpus::get); @@ -37,22 +50,44 @@ static NUMBER_OF_THREADS_IN_THREAD_POOL: Lazy = Lazy::new(num_cpus::get); // 2 = Debug pub const LOG_LEVEL: u8 = 2; +fn load_config() -> NodeConfig { + let proj_dirs = ProjectDirs::from("", "CleytoCoin Big Mean Corp", "cleyto_coin") + .expect("Could not find the config directory"); + let config_path: PathBuf = proj_dirs.config_dir().join("config.toml"); + + let contents = fs::read_to_string(&config_path).unwrap_or_else(|_| { + fs::create_dir_all(proj_dirs.config_dir()) + .expect("Could not create the necessaty directories"); + let log_path = proj_dirs.data_dir().join("logs.log"); + let contents = format!(r#"log_path = "{}""#, log_path.to_str().unwrap()); + fs::write(&config_path, &contents).expect("Couldn't write to file"); + contents + }); + toml::from_str(&contents).expect("Invalid config format") +} + impl Node { // these configurations should be moved to a file pub const DEFAULT_PORT: u16 = 9473; pub const REFRESH_RATE_SERVER_IN_MS: u64 = 50; - pub fn new(chain: Chain, logger: Arc) -> Node { - num_cpus::get(); - - Node { - state: Arc::new(Mutex::new(NodeState { - status: true, - chain, - transactions_pool: Vec::new(), - })), - logger, - } + pub fn new(chain: Chain) -> (Node, Arc) { + let config = load_config(); + let logger = + Arc::new(Logger::read_logs_file(&config.log_path).unwrap_or_else(|_| Logger::new())); + let logger_clone = Arc::clone(&logger); + ( + Node { + state: Arc::new(Mutex::new(NodeState { + status: true, + chain, + transactions_pool: Vec::new(), + })), + logger, + config, + }, + logger_clone, + ) } fn parse_http_request( @@ -234,7 +269,14 @@ impl Node { } println!("Dropping thread pool"); + } +} - drop(thread_pool); +impl Drop for Node { + fn drop(&mut self) { + match self.logger.write_logs_file(&self.config.log_path) { + Ok(_) => {} + Err(e) => eprintln!("Error saving log file: {e:?}"), + } } } diff --git a/src/node/thread_pool.rs b/src/node/thread_pool.rs index 922f088..2ae0b1e 100644 --- a/src/node/thread_pool.rs +++ b/src/node/thread_pool.rs @@ -65,7 +65,7 @@ pub mod custom_thread_pool { /// /// The `new` function will panic if the size is zero. pub fn new(size: usize) -> Result { - println!("number of threads in thread pool is {}", size); + // println!("number of threads in thread pool is {}", size); if size < 1 { return Err(PoolCreationError::TooFewThreads); } else if size > 100000 { diff --git a/src/node/ui.rs b/src/node/ui.rs index 55d59df..7dd96c6 100644 --- a/src/node/ui.rs +++ b/src/node/ui.rs @@ -63,9 +63,7 @@ impl App { let text = format!( "Node running in port {port} - \n\nPress `Esc`, `Ctrl-C` or `q` to stop running.\n - lines size is {}", - lines.len() + \n\nPress `Esc`, `Ctrl-C` or `q` to stop running.\n" ); frame.render_widget( diff --git a/tests/create_block_and_add_chain.rs b/tests/create_block_and_add_chain.rs index 89902a2..c9ab1c9 100644 --- a/tests/create_block_and_add_chain.rs +++ b/tests/create_block_and_add_chain.rs @@ -1,4 +1,3 @@ -use chrono::Utc; use cleyto_coin::chain::{ block::Block, transaction::{Transaction, TransactionInfo}, @@ -8,10 +7,10 @@ use cleyto_coin::chain::{ #[test] fn create_block_and_add_chain() { - let (wallet1, mut wallet1_pk) = Wallet::new(); + let (wallet1, wallet1_pk) = Wallet::new(); let (wallet2, _) = Wallet::new(); - let transaction_info = TransactionInfo::new(105, Utc::now()); + let transaction_info = TransactionInfo::new(105); let signature = match wallet1_pk.sign_transaction(&transaction_info) { Ok(value) => value, diff --git a/tests/create_wallet.rs b/tests/create_wallet.rs index 415e837..6cc4207 100644 --- a/tests/create_wallet.rs +++ b/tests/create_wallet.rs @@ -3,7 +3,7 @@ use cleyto_coin::chain::wallet::Wallet; #[test] //mark a function as a test. fn test_wallet_creation() { let (wallet, wallet_pk) = Wallet::new(); - println!("wallet.to_string: {:?}", wallet.to_vec()); + println!("wallet.to_string: {:?}", wallet.to_pem()); println!("{:#?}", wallet); println!("{:#?}", wallet_pk); } diff --git a/tests/requests.rs b/tests/requests.rs index 9cb5629..7f7470e 100644 --- a/tests/requests.rs +++ b/tests/requests.rs @@ -1,4 +1,3 @@ -use chrono::Utc; use cleyto_coin::chain::transaction::{Transaction, TransactionInfo}; use cleyto_coin::chain::wallet::Wallet; use cleyto_coin::node; @@ -8,7 +7,6 @@ use std::{ }; use cleyto_coin::chain::Chain; -use cleyto_coin::node::logger::Logger; use reqwest::blocking::Client; fn thread_post(n: u16) { @@ -17,10 +15,10 @@ fn thread_post(n: u16) { let mut handles = vec![]; - let (wallet1, mut wallet1_pk) = Wallet::new(); + let (wallet1, wallet1_pk) = Wallet::new(); let (wallet2, _) = Wallet::new(); - let transaction_info = TransactionInfo::new(105, Utc::now()); + let transaction_info = TransactionInfo::new(105); let signature = match wallet1_pk.sign_transaction(&transaction_info) { Ok(value) => value, @@ -105,7 +103,7 @@ fn main() { // Run server thread let server = thread::spawn(move || { let rx = Arc::clone(&rx); - let mut node = node::Node::new(Chain::new(), Arc::new(Logger::new())); + let (mut node, _) = node::Node::new(Chain::new()); node.run(true, rx, 0); }); diff --git a/tests/transactions.rs b/tests/transactions.rs index a47e925..36b20b8 100644 --- a/tests/transactions.rs +++ b/tests/transactions.rs @@ -1,12 +1,11 @@ -use chrono::Utc; use cleyto_coin::chain::transaction::{Transaction, TransactionInfo}; use cleyto_coin::chain::wallet::Wallet; #[test] fn create_transaction() { - let (wallet_sender, mut walletpk_sender) = Wallet::new(); + let (wallet_sender, walletpk_sender) = Wallet::new(); let (wallet_receiver, _) = Wallet::new(); - let transactioninfo: TransactionInfo = TransactionInfo::new(12345, Utc::now()); + let transactioninfo: TransactionInfo = TransactionInfo::new(12345); let signature = match walletpk_sender.sign_transaction(&transactioninfo) { Ok(signed_hashed_message) => signed_hashed_message, @@ -35,15 +34,15 @@ fn create_transaction() { #[test] fn test_transaction_info_creation() { - let transaction: TransactionInfo = TransactionInfo::new(123452, Utc::now()); + let transaction: TransactionInfo = TransactionInfo::new(123452); println!("transaction info:\n{}", transaction); println!("{:?}", transaction); } #[test] fn sign_and_verify_transaction_info() { - let (wallet, mut wallet_pk) = Wallet::new(); - let transactioninfo: TransactionInfo = TransactionInfo::new(1234532, Utc::now()); + let (wallet, wallet_pk) = Wallet::new(); + let transactioninfo: TransactionInfo = TransactionInfo::new(1234532); let signature = match wallet_pk.sign_transaction(&transactioninfo) { Ok(signed_hashed_message) => signed_hashed_message, @@ -66,9 +65,9 @@ fn sign_and_verify_transaction_info() { #[test] fn serialize_and_deserialize_transaction() { - let (wallet, mut wallet_pk) = Wallet::new(); + let (wallet, wallet_pk) = Wallet::new(); let (mallet, _) = Wallet::new(); - let transactioninfo: TransactionInfo = TransactionInfo::new(1234552, Utc::now()); + let transactioninfo: TransactionInfo = TransactionInfo::new(1234552); let signature = match wallet_pk.sign_transaction(&transactioninfo) { Ok(signed_hashed_message) => signed_hashed_message, diff --git a/todo.txt b/todo.txt new file mode 100644 index 0000000..e69de29 From fb7a2eea3bf31b3bf438bbbf0dbfbc220a97b3bf Mon Sep 17 00:00:00 2001 From: Dante Ramacciotti Date: Fri, 29 Aug 2025 14:28:56 +0200 Subject: [PATCH 35/40] added some tests and finished implementing features on last commit I'm commiting now to start changing the server killing feature to a socket instead of a channel, so that I can kill it from another process and not only from another thread --- private.pem | 58 ++++--- public.pem | 14 +- src/bin/cleyto_coin_wallet.rs | 104 +----------- src/bin/node.rs | 47 +---- src/chain/wallet.rs | 2 +- src/lib.rs | 170 +++++++++++++++++++ src/main.rs | 1 - tests/create_headless_node.rs | 31 ++++ tests/create_wallet.rs | 9 - tests/create_wallets_and_send_transaction.rs | 49 ++++++ tests/requests.rs | 5 +- wallets/receiver/private.pem | 28 +++ wallets/receiver/public.pem | 9 + wallets/sender/private.pem | 30 ++++ wallets/sender/public.pem | 9 + 15 files changed, 380 insertions(+), 186 deletions(-) delete mode 100644 src/main.rs create mode 100644 tests/create_headless_node.rs delete mode 100644 tests/create_wallet.rs create mode 100644 tests/create_wallets_and_send_transaction.rs create mode 100644 wallets/receiver/private.pem create mode 100644 wallets/receiver/public.pem create mode 100644 wallets/sender/private.pem create mode 100644 wallets/sender/public.pem diff --git a/private.pem b/private.pem index 678fce0..ff1d226 100644 --- a/private.pem +++ b/private.pem @@ -1,28 +1,30 @@ ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDulpf6QC7Sp1ME -mTXwZyGCWLuc58W1CXw7O/iztpCk0N7KCJwPxajoyC2cBUkmuqWUxcZFCvkBQtBG -IGF2CP0R2POzVWurRfpyWqsXfF4a2pKCiFcwMnhTVfmniHGyn34fQjzZ36LG+nga -heecgTOUyI3c1Qdc+P/6QHqJnFgtDtU4SBKMlqRBvAQxyGaFyG+WSa54PqTzq2Pw -k9PGpFdQMA060O4nun8Xo0ulhPwV5HP47UBwTvRwzIxD7n/6fsYFsio18m+6Pv7m -ihCvSpqfDtF7doVcyx7gY2tJdJQ6/JCJ+VzgB3At7t9Aid78zZdVoWdzGgAihmWb -SgrO80f1AgMBAAECggEANyx9JcJ88Xr/UENr/VWkIqQJct4IUvbIE1yphUm1RmAE -/VyjVmRjzL0qCvgP9BRaoT5hPOsuLTP5vRXU4/ptJ5jvtmuNFBUBNzuvcCVzD6u3 -ENedrggl2kO6EZuUi3WlsW3ca5fzaSdX6e5IYuFf/8qnP8fFxaLRc1pA3BzbKUPg -BQYrEG0swSp6iNTzSpF8x7OJnMmHZ+LZA90jedTuGSTxmNzC0rIXfgEBgEnduUoN -o82u8x2SlApvNm8jp1ORhc7chsDQQSSMpkHD7EjGhl7ZWTb9Fyrnt1U7SYP+s6dA -Tq4uy6GHhPeagwceUgvfIkWuwCzZNQCsxdMZDsvy6wKBgQD9UNwuGzstiPodonia -vdVwF49uBQ5tQYmReDsAZ0QJDhHh8+XI7iD6qBnjsKDUYjpujyqeifDwulSoMDRS -9Mp9Y5m0KJGXQZWajmUUDgzKi9BhaiQG6ekRXKH3HAuo7ixtoLq8kmNztR/YScXW -VRmcDkeUR5Elphbdbxg+rdrg1wKBgQDxHcikdLtHLuBLKSKKnPSa9Arg2MFA79uo -AnVMkerVpJHQIio3IXrfdEiGiKcB54IS5R9P5zhK2YJ4Hex+jMpsM1BxS5wIkh7e -KTrISDYN6aaLkOgsXrtBPbUP1DNlAnldd4ORycM//M5CTT+SoYjNvr5WdCIAtKtu -5Pk0RqUoEwKBgGRUc3CbfnvRhebY49W+HFxSKI/V1tn1HCyOpyfIvsyrUu/gbwF/ -N3avv9lLqteTtDr331R9o/1kf4VB2meE5vJ00XrmmouDAWL+xRAFZJGQgM8kf/Jx -EMu8mbXK2N9mvhgJkgyAGn3Inj1HMuFqQUNq8HTNR7LWrrWze7HPoCJvAoGBAOD3 -FAx1KYM59mXKJINF/GdTCY9OTcljwgNY/86IWp4pJS8XoWG86CLzIPP9Li11roQo -qPVpW7lihfvCl7RfckJQAl8i15ubkhdgSosx7NVASnbvpeA4JuixRW1WZPl6vHf7 -mZN9t6HNIzjzUDxkYPruXDLXuh4Ec+tM0kL3IwNnAoGAbo+Manh2crkKQyOrKt+F -G4qmewTTMTdp+iMPXkWAxa9T9etk876s5X4cGfxQv8sBo6WbDkbztgbFVty2OT/S -G/mnwcGC4QkfKXgr6wYzko06f8VGqbug3RXa9Y4PrwuFHp5vtKAfEKKzypNSkuXZ -nHPp7GBCk/n6VspYy+iLgzg= ------END PRIVATE KEY----- +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFNTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQqeAwhIZ59VH8O4gD +IPuAbQICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEDkljmulTgAI0Ndf +Q66m1vIEggTQiM0d2VcX8terXyVCMTZjlXn6IxPDpQxJ+f02WfAinQWhGL05AQyC +TjEtnsSU2Xq6cKvPa/BfOHNhPO616DenTS4bAClH/AKi4dSX6xgKrxw3e2lgxAtE +v6Flrfk32JLdJamUjZjNiw/1HDlZ28YhZNC4us80AUxkkhL4NGt0NKLkOCCDgLZO +95aYqXtSO63CVV/16ul0YBTAjvVybY6aLbeXBwuIggQP+CDKBmruVQKe/ncssdnT +HOsmsk0od9f6QAVhuhHVMHBgmko2IeC+8LX95NFwDU02LsV9GOBbvockaRAidUhm +dZ0IpIbGZM9hDSLy6dXKYBjaoLJ7+iQLgtpKuIyLcI9ViJBrVKoEh5pVBsdpBrDb +m1GMrCrxyn2pbdCS3pV/MREm3KiI/4mVYgnn07uLvfma1EWMLVSWN7iWTNtDTcob +RjF5+U3kTJsSqL7vLkxQCC/+ma/VmywRjJodHor5PPk8pEHRBZ/X6EV88niSb2uC +iooTKQh76zwTmR3ArvHmraA6Vbopck4BKO1uScMzthkhO2mcQRXroLEVsTDD2GdO +Z4rjXVlXVCoHNoZ/BXHl6qK8lEvNKq5IgajRwcvZSyDzE0AWr6Jnr2MZ55y8FYFk +kEJ3uQgrGLXHmicxaBDCXM9P3J5LAEG16ZI6IaLl96t1MqbkVfv5HE6nbFzgs3dl +3CR1RwMXkojLgrRLdxehKKhRLB+1/tNqgFAoz2P0z0/l2vrhKFow/h5NN0pj1DcX +3txgtspSBKOu2Eo5TK0n9Bf0Kzd4n2QklokOabMEJ1eUsOvq3FvsoQznfZWvqXKN +lJrP6JiQfJnXZ4tSbU17yldwL8IIzUOwAkMC0DZtnbk+7TCHTjm4lbcf/vPQ872X +gQWG8hMutO12jgk798HF40pH00axXRanNEhiheI4Ad4spmsZ1YIixqKcCh83tnLf +LvQ4NU/hhnjrXprqkOEipVvsiNdOP8PH8ftPXDprmwXgt5yJoglj/m3dPyUYNWIb +ac38gKEvX/bSxU/c6+7SXUeJ2BKKCpSLYTzfO0QLKg1zKyybvAnNYW3IjrjOn8eN +5g7+XukYpH93d6zVv57dzxTUNu42Ms6eTecXIuV0Ox3BOQQ0jQJItzb/5S/zZzgi +eBI0Q/IoOrXyrO2ThDn/N26SFFbqmJMKVnXhgFEUKEOOdmiusAxYf/Fh21SVjzwT +SZ9X8zua0F2fw9QD8l3L06m5CmQI2xkJHPLGL9JUqavxmzXNRLfzesfoCeLOl3nY +qNMrV4SaXnqbFE2jtMCQqf8vl9zdIr50YiOg6LqHSAfAZbuGOZWTuu7bq+NP4aHm +VxLn5ykxEMxG60NTzTfj2vwjovR4hR29SqfOfHNnqWQk7JnuS5md2IofoaEdl6jY +IlU5yhD6uMxsfNKorJtdW0VU/BoAeMvlcGiN+yjOduuAdCe59Ji9gEEPoLXDCzOP +HatD1Ei5nrkx7Q9wpIDk4gwZSrVFsudEidjagFDn8J59ifEipDZ+P8M04X5d55yq +Hbi4VS2qCfcg0hXWwPS59ZPFLo8Ene5NYbE9z4TDYWfRwudRbRZKUMWGskWG2QYz +/L8euwJsuhs6UgmBdyZv0J4E7vvZCHHMZqmF7/1jaVKP6e0Fq3gVHB0= +-----END ENCRYPTED PRIVATE KEY----- diff --git a/public.pem b/public.pem index 8f37534..241ef93 100644 --- a/public.pem +++ b/public.pem @@ -1,9 +1,9 @@ -----BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7paX+kAu0qdTBJk18Gch -gli7nOfFtQl8Ozv4s7aQpNDeygicD8Wo6MgtnAVJJrqllMXGRQr5AULQRiBhdgj9 -Edjzs1Vrq0X6clqrF3xeGtqSgohXMDJ4U1X5p4hxsp9+H0I82d+ixvp4GoXnnIEz -lMiN3NUHXPj/+kB6iZxYLQ7VOEgSjJakQbwEMchmhchvlkmueD6k86tj8JPTxqRX -UDANOtDuJ7p/F6NLpYT8FeRz+O1AcE70cMyMQ+5/+n7GBbIqNfJvuj7+5ooQr0qa -nw7Re3aFXMse4GNrSXSUOvyQiflc4AdwLe7fQIne/M2XVaFncxoAIoZlm0oKzvNH -9QIDAQAB +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA99BKIwf+qgyOLuLzSaTh +Ncv/k7KtDi9+W6pBLNwF6ZWRyseeve0RFYX1UT8eIDIhkeBbYcd7JzoJTamkK5wP +knNnfs8ggCt8gjTmMesLkGi6BBDtCZ/Ls8pxxsDo6Hta5a7pQAk8t6Vvh1jQCfS/ +V3OxqOhv5X+zGsYCCEPnx/lHFyENJQ5fcmErbk5nzF1llv/KJxn8BkqPdgFg/YQH +mL3Lqlp4MqyrbeFMPJE7TPoqtRZhf7ys85M0+Dm21yqBq7avzIYhRqR95aZOo01c +6seQRL7YDYm4MRfWonNKiohQILx2YaSLYwnix1YW27dA7QJiXqpTDYaPwcqoHZpy +3wIDAQAB -----END PUBLIC KEY----- diff --git a/src/bin/cleyto_coin_wallet.rs b/src/bin/cleyto_coin_wallet.rs index 1904693..19e66da 100644 --- a/src/bin/cleyto_coin_wallet.rs +++ b/src/bin/cleyto_coin_wallet.rs @@ -1,9 +1,4 @@ -use cleyto_coin::chain::{ - transaction::{self, Transaction, TransactionInfo}, - wallet::{Wallet, WalletPK}, -}; -use openssl::pkey::{PKey, Private}; -use reqwest::Client; +use cleyto_coin::{generate, send}; use std::path::PathBuf; use structopt::StructOpt; @@ -55,101 +50,16 @@ enum Args { )] sender_key_file: Option, + /// Password used to encode the private key + #[structopt(long, short)] + password: Option, + /// Transaction amount #[structopt(long, short = "a")] amount: i64, }, } -async fn send_transaction(transaction: transaction::Transaction) { - let client = Client::new(); - - let transaction_json = transaction.serialize(); - - // Send the POST request - let response = client - .post("http://localhost:9473/submit-transaction") - .header("Content-Type", "application/json") - .body(transaction_json) - .send() - .await - .unwrap(); - - // Check the response status - let status = response.status(); - let response_body = response.text().await.unwrap(); - - // Print the response - println!("Response Status: {}", status); - println!("Response Body: {}", response_body); -} - -fn read_key_string_or_file(string: &Option, file: &Option) -> String { - if let Some(s) = string { - s.clone() - } else if let Some(path) = file { - std::fs::read_to_string(path).expect("Failed to read key file") - } else { - panic!("Key not provided"); - } -} - -pub async fn send( - recipient_key: Option, - recipient_key_file: Option, - sender_key: Option, - sender_key_file: Option, - amount: i64, -) { - let recipient_key_str = read_key_string_or_file(&recipient_key, &recipient_key_file); - let sender_key_str = read_key_string_or_file(&sender_key, &sender_key_file); - - // convert to PKey objects - let sender_pkey: PKey = PKey::private_key_from_pem(sender_key_str.as_bytes()) - .expect("Failed to parse sender private key"); - - let recipient_pkey: PKey = - PKey::public_key_from_pem(recipient_key_str.as_bytes()) - .expect("Failed to parse recipient public key"); - - // create wallets - let sender_wallet = WalletPK::from(sender_pkey); - let recipient_wallet = Wallet::from(recipient_pkey); - - // create transaction info - let transaction_info = TransactionInfo::new(amount); - - // sign the transaction - let signature = sender_wallet - .sign_transaction(&transaction_info) - .expect("Failed on signing of transaction"); - - let transaction = Transaction::new( - sender_wallet.public_wallet(), - recipient_wallet, - transaction_info, - signature, - ) - .inspect_err(|e| eprintln!("Failed creating the transaction: {e}")) - .unwrap(); - - send_transaction(transaction).await; -} - -fn generate(private_key_file: PathBuf, public_key_file: PathBuf, password: Option) { - let (wallet, walletpk) = Wallet::new(); - if let Some(password) = password { - std::fs::write(private_key_file, walletpk.to_pem_with_password(password)) - .expect("Could not write new wallet's private key to file"); - } else { - std::fs::write(private_key_file, walletpk.to_pem()) - .expect("Could not write new wallet's private key to file"); - } - - std::fs::write(public_key_file, wallet.to_pem()) - .expect("Could not write new wallet's public key to file"); -} - #[tokio::main] async fn main() { let args = Args::from_args(); @@ -158,12 +68,13 @@ async fn main() { private_key_file, public_key_file, password, - } => generate(private_key_file, public_key_file, password), + } => generate(&private_key_file, &public_key_file, &password), Args::Send { recipient_key, recipient_key_file, sender_key, sender_key_file, + password, amount, } => { send( @@ -171,6 +82,7 @@ async fn main() { recipient_key_file, sender_key, sender_key_file, + password, amount, ) .await diff --git a/src/bin/node.rs b/src/bin/node.rs index 896859d..bc1d5ad 100644 --- a/src/bin/node.rs +++ b/src/bin/node.rs @@ -1,9 +1,4 @@ -use cleyto_coin::chain::Chain; -use cleyto_coin::node::{self, ui::App}; -use std::{ - sync::{mpsc, Arc, Mutex}, - thread, -}; +use cleyto_coin::{run_server, run_server_with_gui}; use structopt::StructOpt; #[derive(StructOpt, Debug)] @@ -12,44 +7,12 @@ struct Args { gui: bool, } -fn run_with_gui() -> color_eyre::Result<()> { - let (tx, rx) = mpsc::channel::<()>(); - - // Channel to kill thread - let rx = Arc::new(Mutex::new(rx)); - - let (mut node, logger) = node::Node::new(Chain::new()); - - // Run server thread - let server = thread::spawn(move || { - let rx = Arc::clone(&rx); - - node.run(true, rx, 0); - }); - - color_eyre::install()?; - let terminal = ratatui::init(); - let result = App::new(Arc::clone(&logger), node::Node::DEFAULT_PORT).run(terminal); - ratatui::restore(); - - // Quits server - tx.send(())?; - - server.join().unwrap(); - result -} -fn run_server() { - let (_, rx) = mpsc::channel::<()>(); - let rx = Arc::new(Mutex::new(rx)); - let (mut node, _) = node::Node::new(Chain::new()); - node.run(true, rx, 0); -} - fn main() { let args = Args::from_args(); match args.gui { - true => run_with_gui().unwrap(), - false => run_server(), + true => run_server_with_gui().unwrap(), + false => { + run_server(); + } } - run_with_gui().unwrap(); } diff --git a/src/chain/wallet.rs b/src/chain/wallet.rs index cd970e9..7a8931c 100644 --- a/src/chain/wallet.rs +++ b/src/chain/wallet.rs @@ -22,7 +22,7 @@ impl WalletPK { let mut signer = Signer::new(MessageDigest::sha256(), &self.private_key)?; signer.sign_oneshot_to_vec(transaction_info.to_string().as_bytes()) } - pub fn to_pem_with_password(&self, password: String) -> Vec { + pub fn to_pem_with_password(&self, password: &String) -> Vec { self.private_key .private_key_to_pem_pkcs8_passphrase(Cipher::aes_256_cbc(), password.as_bytes()) .unwrap() diff --git a/src/lib.rs b/src/lib.rs index 2ad62a0..71f6d2f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,172 @@ +use crate::{ + chain::{ + transaction::{self, Transaction, TransactionInfo}, + wallet::{Wallet, WalletPK}, + Chain, + }, + node::ui::App, +}; +use openssl::pkey::{PKey, Private, Public}; +use reqwest::Client; +use std::path::PathBuf; +use std::{ + sync::{mpsc, mpsc::Sender, Arc, Mutex}, + thread, +}; + pub mod chain; pub mod node; + +async fn send_transaction(transaction: transaction::Transaction) { + let client = Client::new(); + + let transaction_json = transaction.serialize(); + + // Send the POST request + let response = client + .post("http://localhost:9473/submit-transaction") + .header("Content-Type", "application/json") + .body(transaction_json) + .send() + .await + .unwrap(); + + // Check the response status + let status = response.status(); + let response_body = response.text().await.unwrap(); + + // Print the response + println!("Response Status: {}", status); + println!("Response Body: {}", response_body); +} + +fn read_key_string_or_file(string: &Option, file: &Option) -> String { + if let Some(s) = string { + s.clone() + } else if let Some(path) = file { + std::fs::read_to_string(path).expect("Failed to read key file") + } else { + panic!("Key not provided"); + } +} + +pub fn generate(private_key_file: &PathBuf, public_key_file: &PathBuf, password: &Option) { + let (wallet, walletpk) = Wallet::new(); + + let parents = [ + private_key_file + .parent() + .expect("The path provided has no parent"), + public_key_file + .parent() + .expect("The path provided has no parent"), + ]; + + // Checks to see if parents exist. if not, creates them + for parent in parents { + if !parent.exists() { + std::fs::create_dir_all(parent).expect("Could not create parent directory"); + } + } + + if let Some(password) = password { + std::fs::write(private_key_file, walletpk.to_pem_with_password(password)) + .expect("Could not write new wallet's private key to file"); + } else { + std::fs::write(private_key_file, walletpk.to_pem()) + .expect("Could not write new wallet's private key to file"); + } + + std::fs::write(public_key_file, wallet.to_pem()) + .expect("Could not write new wallet's public key to file"); +} + +pub async fn send( + recipient_key: Option, + recipient_key_file: Option, + sender_key: Option, + sender_key_file: Option, + password: Option, + amount: i64, +) { + let recipient_key_str = read_key_string_or_file(&recipient_key, &recipient_key_file); + let sender_key_str = read_key_string_or_file(&sender_key, &sender_key_file); + + // convert to PKey objects + let sender_pkey: PKey = if let Some(password) = password { + PKey::private_key_from_pem_passphrase(sender_key_str.as_bytes(), password.as_bytes()) + .expect("Failed to parse sender private key") + } else { + PKey::private_key_from_pem(sender_key_str.as_bytes()) + .expect("Failed to parse sender private key") + }; + + let recipient_pkey: PKey = PKey::public_key_from_pem(recipient_key_str.as_bytes()) + .expect("Failed to parse recipient public key"); + + // create wallets + let sender_wallet = WalletPK::from(sender_pkey); + let recipient_wallet = Wallet::from(recipient_pkey); + + // create transaction info + let transaction_info = TransactionInfo::new(amount); + + // sign the transaction + let signature = sender_wallet + .sign_transaction(&transaction_info) + .expect("Failed on signing of transaction"); + + let transaction = Transaction::new( + sender_wallet.public_wallet(), + recipient_wallet, + transaction_info, + signature, + ) + .inspect_err(|e| eprintln!("Failed creating the transaction: {e}")) + .unwrap(); + + send_transaction(transaction).await; +} + +pub fn run_server_with_gui() -> color_eyre::Result<()> { + let (tx, rx) = mpsc::channel::<()>(); + + // Channel to kill thread + let rx = Arc::new(Mutex::new(rx)); + + let (mut node, logger) = node::Node::new(Chain::new()); + + // Run server thread + let server = thread::spawn(move || { + let rx = Arc::clone(&rx); + + node.run(true, rx, 0); + }); + + color_eyre::install()?; + let terminal = ratatui::init(); + let result = App::new(Arc::clone(&logger), node::Node::DEFAULT_PORT).run(terminal); + ratatui::restore(); + + // Quits server + tx.send(())?; + + server.join().unwrap(); + result +} + +/// Spawns thread with server and return the channel that sends the kill signal +pub fn run_server() -> Sender<()> { + let (tx, rx) = mpsc::channel::<()>(); + let rx = Arc::new(Mutex::new(rx)); + let (mut node, _) = node::Node::new(Chain::new()); + thread::spawn(move || { + node.run(true, rx, 0); + }); + tx +} + +/// Sends the kill signal to the server +pub fn kill_server(tx: Sender<()>) { + while tx.send(()).is_ok() {} +} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index f328e4d..0000000 --- a/src/main.rs +++ /dev/null @@ -1 +0,0 @@ -fn main() {} diff --git a/tests/create_headless_node.rs b/tests/create_headless_node.rs new file mode 100644 index 0000000..17b482b --- /dev/null +++ b/tests/create_headless_node.rs @@ -0,0 +1,31 @@ +use std::{ + sync::{mpsc, Arc, Mutex}, + thread, + time::Duration, +}; + +use cleyto_coin::{chain::Chain, kill_server, node}; + +#[test] +fn run_and_kill_node() { + let (tx, rx) = mpsc::channel::<()>(); + + // Channel to kill thread + let rx = Arc::new(Mutex::new(rx)); + + let (mut node, _) = node::Node::new(Chain::new()); + + // Run server thread + let server = thread::spawn(move || { + let rx = Arc::clone(&rx); + + node.run(true, rx, 0); + }); + let killer_server = thread::spawn(move || { + thread::sleep(Duration::from_millis(200)); + kill_server(tx); + }); + + killer_server.join().unwrap(); + server.join().unwrap(); +} diff --git a/tests/create_wallet.rs b/tests/create_wallet.rs deleted file mode 100644 index 6cc4207..0000000 --- a/tests/create_wallet.rs +++ /dev/null @@ -1,9 +0,0 @@ -use cleyto_coin::chain::wallet::Wallet; - -#[test] //mark a function as a test. -fn test_wallet_creation() { - let (wallet, wallet_pk) = Wallet::new(); - println!("wallet.to_string: {:?}", wallet.to_pem()); - println!("{:#?}", wallet); - println!("{:#?}", wallet_pk); -} diff --git a/tests/create_wallets_and_send_transaction.rs b/tests/create_wallets_and_send_transaction.rs new file mode 100644 index 0000000..28715b3 --- /dev/null +++ b/tests/create_wallets_and_send_transaction.rs @@ -0,0 +1,49 @@ +use std::path::PathBuf; + +use cleyto_coin::{generate, kill_server, run_server, send}; + +const SENDER_PUBLIC_KEY_PATH: &str = "./wallets/sender/public.pem"; +const SENDER_PRIVATE_KEY_PATH: &str = "./wallets/sender/private.pem"; +const SENDER_PASSWORD: &str = "palmeiras"; + +const RECEIVER_PUBLIC_KEY_PATH: &str = "./wallets/receiver/public.pem"; +const RECEIVER_PRIVATE_KEY_PATH: &str = "./wallets/receiver/private.pem"; + +#[test] +fn test_wallet_creation() { + let sender_private_key_file = PathBuf::from(SENDER_PRIVATE_KEY_PATH); + let sender_public_key_file = PathBuf::from(SENDER_PUBLIC_KEY_PATH); + let sender_password = Some(String::from(SENDER_PASSWORD)); + generate( + &sender_private_key_file, + &sender_public_key_file, + &sender_password, + ); + + let receiver_private_key_file = PathBuf::from(RECEIVER_PRIVATE_KEY_PATH); + let receiver_public_key_file = PathBuf::from(RECEIVER_PUBLIC_KEY_PATH); + generate(&receiver_private_key_file, &receiver_public_key_file, &None); +} + +#[tokio::test] //mark a function as a test. +async fn test_send_transaction() { + test_wallet_creation(); + let sender_private_key_file = PathBuf::from(SENDER_PRIVATE_KEY_PATH); + let sender_password = Some(String::from(SENDER_PASSWORD)); + + let receiver_public_key_file = PathBuf::from(RECEIVER_PUBLIC_KEY_PATH); + + let tx = run_server(); + + send( + None, + Some(receiver_public_key_file), + None, + Some(sender_private_key_file), + sender_password, + 100, + ) + .await; + + kill_server(tx); +} diff --git a/tests/requests.rs b/tests/requests.rs index 7f7470e..074fd39 100644 --- a/tests/requests.rs +++ b/tests/requests.rs @@ -108,8 +108,9 @@ fn main() { }); // 10.000 breaks the os (client), but the server seems fine - thread_get(1000); - thread_post(1000); + // Error accepting connection: Too many open files (os error 24) + thread_get(10); + thread_post(10); tx.send(()).expect("Failed to send termination signal."); diff --git a/wallets/receiver/private.pem b/wallets/receiver/private.pem new file mode 100644 index 0000000..5b53b60 --- /dev/null +++ b/wallets/receiver/private.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCScO+qHuey2+ni +KD5CngrQOncbedVDbRvT3eslLhrfsJodoxfeqeVT4yTTNagcPdYWKxRrKkAe7tQW +18qnqgcjDY2zsKXs0uUwNADqc4dtFqhiOnOkbkSNQP3DbtV87oAl5sJMMqe6ZLF8 +CNc9UKEXOkLdftV7zUJUgslK5BW1XxVDuvB6V81TdZOgKExDUsATdhAkaCPw79IH +4WBc+hX2h5hqSE9lUAOTVzOYX5YJo+YoBJBgw7ncNHAByeiowzpBk7Xo0sioeAsH +Fsoss3PCy81ZNHcO67hlSXtkj6+iwypaQE2MYL7F0mIc1F65zbFLPKmsBc3FxRMu +eLLfSA9vAgMBAAECggEAH+pDIVVo878f4u0YXJZrDuSfDrrnsrE1zl/gHpNEwhmQ +DQN9cUaftz+ax/k6sST84onzAkQMA3HTgUL4ZMAWnBoZFt9Z2hpAXtgGlxIkoq68 +deP9TgnGIO7YH16pYnfsL9Zx4/9qNKI7WCfpGPDYmuJUrkc1Ka0bVqXGTu8UmBlT +eKixGLyGUWUreCipuqdfepC/QeaA0+TpTyy+AuxCUWrkBYEnXPChrj/5LAb26Qo/ +TTKcZy2p1BSVVJbMj45rK0E9WJ3navTxr3J3Wx6CFLcHDVDPrIG3GNEJxlB6M40u +FMRpNNHA8ME62MZGkCg07K3WFKSSOuW9GHWhVgwiEQKBgQDCMCfzZ/aPxPJ6bD7S +JNd/lh4/++oGuNeP3sl2HNzigXkefPQEVKDSGy6T1ETiUcIO4DFRqcziCIYk6452 +XRp+jhzV60SBbIZEV8b8x8EZWB6i1wQfjldZ1VZzij1/KlwlY40UwDwvEh41T3FG +WOXUnB4MLmQVrn5S6Q2LhKjWdwKBgQDBDgPWtUGxvNXabdsj63Oh0JfbS4BXbV4n +9HgpD2AfaCFJuVSQf68g20NuEH3NOSSZeQIxi3aC6Q5a+1hDvfmXMmiUSNj252lC +i+Rx1MFdbICvMHp9uikWRIZ1QD83ldyV6URCL2dCNcDDrnL4JP3snrlVl2TV6DXH +GK/JPnS0yQKBgCs+1DbVyUgdQS7YWmksQT2trdDsBiM3GPJ2WAFQzjQ3R0Tt6N3q +BuKjoQzU8f5A3Om5LeKU4QbZhR5FwzVC6LqUZYGSYnA8Pe6MackzpyJAyJnVLprk +dTmRVC5o8+cVBO5irDGZ2g1ViJqwtXZIJ+7gUaS+1BUsBf/q3NRcV8ljAoGBAIrO +WeI5pwd1f8myrRqxgw14nocVTS0PPOC7fN4A6RsL0stgTrYqqC+Wl2/+uYUiLfeP +Mw2OeeJ8UU8HYxop8NhObTUY/67fCibH0LQrxfA3QHs1/wyBGmOhV4/cZte/j7/V +3bc8wCCx52yK9K0IY+6K3TT2SpEmiewiCtpGZsFJAoGBAKGWQGtqxLGkXov2MJvx +bARgKGFw6iz+wjh6jqDV9IL5PoxJZGv9dqUDqNdOOvikC2n2xeookwOGBdIrr38Z +nGguYTelZJ1pZKqTtav37kxt7gWZdR2akt36bXsL9Av8RyVOlasXgfAWqjGkOvfA +9c+bZX81a1t57xeMptsivxfq +-----END PRIVATE KEY----- diff --git a/wallets/receiver/public.pem b/wallets/receiver/public.pem new file mode 100644 index 0000000..f59a8ef --- /dev/null +++ b/wallets/receiver/public.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAknDvqh7nstvp4ig+Qp4K +0Dp3G3nVQ20b093rJS4a37CaHaMX3qnlU+Mk0zWoHD3WFisUaypAHu7UFtfKp6oH +Iw2Ns7Cl7NLlMDQA6nOHbRaoYjpzpG5EjUD9w27VfO6AJebCTDKnumSxfAjXPVCh +FzpC3X7Ve81CVILJSuQVtV8VQ7rwelfNU3WToChMQ1LAE3YQJGgj8O/SB+FgXPoV +9oeYakhPZVADk1czmF+WCaPmKASQYMO53DRwAcnoqMM6QZO16NLIqHgLBxbKLLNz +wsvNWTR3Duu4ZUl7ZI+vosMqWkBNjGC+xdJiHNReuc2xSzyprAXNxcUTLniy30gP +bwIDAQAB +-----END PUBLIC KEY----- diff --git a/wallets/sender/private.pem b/wallets/sender/private.pem new file mode 100644 index 0000000..a636d1c --- /dev/null +++ b/wallets/sender/private.pem @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFNTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQOIS9pyplMUZL7RTd +ndQMDAICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEF20TaldTcmbBglR +3RexxEAEggTQGDbzMuzcG7KHkFpEZVrkN+95UbcJggZXETC7iaMJQB6OHGRsHcMw +GwxQAuP9BtczlKu0s4iLmNnHwQUMU2nAniIfBpWTiIh7f2TE1CcywT6HbBHEuJQs +WyNWQaghsYpBJCscGyAReLkZ+gM4aqEiE4Oqv9dlzpb1vjRN0IqOaFIq5RCrGim7 +A7IrNbbv2YhfQg6TeRW5krNb7aGwuGbkmYILLIyRjbw8nnrvbVfe3ZZcR74QuXWq +F2gE6GmneC41MpmFJN0zY3d9+nldQ/fLMLPlq58SmnjEW2rXcxx50OONgMxr9tJB +yMyVLBWT031tDVynbRTEBzloK5TZsTCkJcLXHCDmVfhek3Rx2NhrwX+Ry7zPjHsI +PxxRUwP/htps80AgVSiBQJn17A6rRdBLT9yJFdQ44fyMNgFynTizaU0wZ7AG8jF8 +E0BmfoQiY3TkWeR5gMAhoFFN75UcSYu5ATkIJr5vQ1btxI2+tkEF95J80gvzzdxc +xbtFmT67nf6Z2Yqwy0QhydaoYjXiwQX74BvnbBdakmVJyPaNIEq1RChFmSTODYck +gm0vPkp/GO8WSpggAPCoHRqXaPOT8jRZ920PoVwJKdqRur8dC7tmEmdL+5I0WYSI +nao+R/cCTSs0SJtpG9GIXiDDQpD7MfiMcPxQXCgCkQTyRx/5gq5ZGM7Opy554ZlC +9Ux3aitJ0hQF/5bupvaa/OaWO1KkE/eMO+DoNQQUTgVgfX/mEjS0SRKd1ivPlJJ9 +aXewDymo+FgqowpcYknsoopyq+MQj7U11XH+2vfQJ9jQ511lGBPbvT3/4wFdjM4g +NEQBCV3k35NZeHov6Yh7+xRbopFDku5Vb+IxEKyqElyt8jsxDtbzobHRbg4afKf5 +6lLtd5wXcgnJPfb3vHIYfdJRIPY2sx3YWn1Umpx4HVm7cyd57GOirHvfmUeRSSVG +V33AIkIX/bSL3oOL4RYKcnXvGfV5qOI0umK3QxQ2uii/cUTLOMeT01670H38wIM3 +FhceAjPcIlm0AB9rPmpK3nvxjIqIs2KSu36dlGlcvoAmzHXgZdieNFpQmQs10DFm +BdNPoQ2FkxZXkhCAVdNlM4hI6fHjwKwy6No2GmnPuqGmEDrvwWsbV44Rv48B5Dim +hmzDSzNe18SgOOgmIFvyJ3BRdxEWrHzPIKL6iz+vG/+mJMfAwcBY0FpqNNuQP+1+ +tzl46gF6Sfm2wU73EQK2Tp7ICTNosMqfWDU8px79hMRqj3ww7BLXi1RMswVCxa4p +GZMdAWF0/ACjeKHFCKoqwVyUJEE+SEuGITx6r+khaneu92ooXf8SD5gn5wNyK+zd +9/reyUzV/jU6tsygX0EJ63fFSolAwSKYNnuJeWssGJOj882VWbDahFZe8AqqoVdE +STD+E60EwfzjYxhT8/i5GFXAF6IpR4Er1X2ksXcmAngt7Z7ZRn72nUrMoBP9bhRc +r7mz0U4dQwbCCRRITJQunDbhNknhFHbYuZ5AzHa6xHFK7fqeydgvwmRiUwL80Esu +sBJpGmugos/D2FGP5cL454nCXhqjx39w8Y+A6LG9iXYwz+8+BtoQa3zrJm0fkqMS +AEJP6dqgZv/dKR4BbS1HCZXPfhtkXY7JUNO74d7uazgJm7qN28jKMGQ= +-----END ENCRYPTED PRIVATE KEY----- diff --git a/wallets/sender/public.pem b/wallets/sender/public.pem new file mode 100644 index 0000000..4fd7402 --- /dev/null +++ b/wallets/sender/public.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr0+kMrWEaHrNlgU/KxPg +zDrtiVEqj5yXVTkCFeVCQ2UAIjyMuxi9EtazDryZS6EWun4NU/Lb0/8OsUcThAhN +gLmqTz2B6MYzNA2hy/oeQI/7Nsag8r20mR7BDlQHGZcE5kUmsKH5aC22mQ6vVNbi +JZ7lDIYDuv0+TEJoAgK6c0cbeWYWe6Qpy+xgICI57KaoRywwCk8XoS/afeS94EiD +jAG31PKC7AqLotKcJXmwpl8CzgcktJ2HzUPLyMMI+kli2jBnth2wE23P1QGEgEBv +r9EJASNjCS/gDwxuEAeRng8vCR60byLQ81gzISlCClc/yXxf4/b7iA1s+e2ooBV1 +nQIDAQAB +-----END PUBLIC KEY----- From e79789353441fc763b2e4b2e15bbd9f2d8840a2b Mon Sep 17 00:00:00 2001 From: Dante Ramacciotti Date: Fri, 29 Aug 2025 17:44:40 +0200 Subject: [PATCH 36/40] Updated readme, made tests work Updated readme with the latest functionalities, tests will from now on always use run_server_thread instead of creating the server process. The stuff not marked with [Under development] now works, and the server runs headless unless explicitly stated --blocking or --gui --- README.md | 65 +++++-- "\\" | 187 ------------------- src/bin/cleyto_coin_wallet.rs | 14 +- src/bin/node.rs | 38 +++- src/configs.rs | 1 + src/lib.rs | 55 ++++-- src/node/mod.rs | 72 ++++--- tests/create_headless_node.rs | 31 +-- tests/create_wallets_and_send_transaction.rs | 6 +- tests/requests.rs | 22 +-- wallets/receiver/private.pem | 52 +++--- wallets/receiver/public.pem | 14 +- wallets/sender/private.pem | 56 +++--- wallets/sender/public.pem | 14 +- 14 files changed, 256 insertions(+), 371 deletions(-) delete mode 100644 "\\" create mode 100644 src/configs.rs diff --git a/README.md b/README.md index f6a0cb8..1b56224 100644 --- a/README.md +++ b/README.md @@ -11,14 +11,14 @@ - [Testing](#testing) - [Contributing](#contributing) -# CleytoCoin is still under development, so many of the features listed underneath aren't yet functional +# CleytoCoin is still under development, so some of the features listed underneath aren't yet functional. Those are marked with [Under development] We appreciate the interest and are working towards making CleytoCoin a functional and reliable cryptocurrency, but currently it's still in it's early development stages. Feel free to give suggestions in the meantime of what you'd like to see implemented in our project! ## About -**CleytoCoin** is a cryptocurrency built using the Rust programming language. This project aims to create a secure, fast, and decentralized cryptocurrency to facilitate peer-to-peer transactions. +**CleytoCoin** is a cryptocurrency built using the Rust programming language. This project aims to create a decentralized cryptocurrency to facilitate peer-to-peer transactions. ## Features @@ -56,37 +56,80 @@ cargo build --release This will compile the project and generate an optimized binary in the `target/release` directory. -## Usage +## Node Usage ### Starting the node To start the cryptocurrency node, use the following command: ```bash -cargo run --bin node +cargo run --bin node start +``` + +With the option of running the GUI: + +```bash +cargo run --bin node start --gui ``` The node will start and connect to the network. For now, only full nodes are available and they don't have yet the capacity for mining -### Creating a wallet +### Killing the node + +To kill the node, we follow the same pattern as before: + +```bash +cargo run --bin node kill +``` + +## Wallet Usage + +The `cleyto-coin-wallet` CLI has two main commands: `generate` (to create a wallet) and `send` (to send transactions). -To generate a new wallet, run the following: +### Generating a wallet +Generates a new keypair (private and public keys) for your wallet. ```bash -cargo run --bin cleyto-coin-wallet generate +cargo run --bin cleyto-coin-wallet generate \ + --private-key-file \ + --public-key-file \ + [-p ] +vbnet ``` +--- + +### Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--private-key-file` | Path where the generated private key will be stored | `./private.pem` | +| `--public-key-file` | Path where the generated public key will be stored | `./public.pem` | +| `-p, --password` | Optional password to encrypt your private key | none | + + -This will generate a private key and address for your wallet. ### Sending a transaction -To send a transaction, use the following command: +To send a transaction, you can use the same binary, but with the `send` subcommand ```bash -cargo run --bin cleyto-coin-wallet send --to --amount --private-key +cargo run --bin cleyto-coin-wallet send \ + --recipient-key \ + --sender-key \ + --amount \ + [-p ] +``` +Or, using key files: +```bash +cargo run --bin cleyto-coin-wallet send \ + --recipient-key-file \ + --sender-key-file \ + --amount \ + [-p ] ``` -### Mining (not yet implemented) +### Mining [Under develpment] Start mining by running: diff --git "a/\\" "b/\\" deleted file mode 100644 index 2aa2ed9..0000000 --- "a/\\" +++ /dev/null @@ -1,187 +0,0 @@ -use chrono::prelude::Utc; -use serde::{Deserialize, Serialize}; -use std::{ - fmt::Display, - sync::{Mutex, MutexGuard, PoisonError}, -}; - -#[derive(Default)] -pub struct Logger { - logs: Mutex>, - temp_logs: Mutex>, -} - -pub enum LoggerError<'a> { - PoisonError(PoisonError>>), - FileWriteError(std::io::Error), -} -impl<'a> From>>> for LoggerError<'a> { - fn from(value: PoisonError>>) -> LoggerError { - LoggerError::PoisonError(value) - } -} -impl<'a> From for LoggerError<'a> { - fn from(value: std::io::Error) -> Self { - LoggerError::FileWriteError(value) - } -} -impl<'a> Display for LoggerError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::PoisonError(_) => write!(f, "PoisonError"), - Self::FileWriteError(e) => write!(f, "FileWriteError: {}", e), - } - } -} - -impl Logger { - pub fn new() -> Self { - Self { - logs: Mutex::new(Vec::new()), - temp_logs: Mutex::new(Vec::new()), - } - } - - fn temp_log(&self, log: String) { - let mut temp_logs = self.temp_logs.lock().unwrap(); // Lock the Mutex to modify the temp_logs - temp_logs.push(log); - if temp_logs.len() > 50 { - temp_logs.remove(0); // Remove the oldest log if there are more than 50 - } - } - - fn log_internal(&self, log: String) { - let mut logs = self.logs.lock().unwrap(); // Lock the Mutex to modify the logs - logs.push(log); - - if let Err(e) = std::fs::write("/tmp/foo", logs.join("\n").as_bytes()) { - eprintln!("Unable to write to log file: {e}"); - } - } - pub fn log_error(&self, log: String) { - let dt = Utc::now(); - self.log_internal(format!( - "[ERROR] {} | {}", - dt.format("%Y-%m-%d %H:%M:%S"), - log - )); - self.temp_log(format!( - "[ERROR] {} | {}", - dt.format("%Y-%m-%d %H:%M:%S"), - log - )); - } - pub fn log(&self, log: String) { - let dt = Utc::now(); - self.log_internal(format!( - "[LOG] {} | {}", - dt.format("%Y-%m-%d %H:%M:%S"), - log - )); - self.temp_log(format!( - "[LOG] {} | {}", - dt.format("%Y-%m-%d %H:%M:%S"), - log - )); - } - - pub fn read_logs(&self) -> std::io::Result> { - let logs = self.logs.lock().unwrap(); - Ok(logs.clone()) - } - pub fn read_temp_logs(&self) -> std::io::Result> { - let logs = self.temp_logs.lock().unwrap(); - Ok(logs.clone()) - } - - // std::sync::PoisonError>>> - pub fn write_logs_file( - &self, - path: std::path::PathBuf, - ) -> std::result::Result<(), LoggerError>>> { - let v = match self.logs.lock() { - Ok(v) => v, - Err(e) => { - println!("Poisoned mutex (this is not good)"); - return Err(LoggerError::from(e)); - } - }; - let contents = v.join("\n"); - std::fs::write(path, contents)?; - Ok(()) - } - pub fn read_logs_file(path: std::path::PathBuf) -> Result> { - let content = std::fs::read_to_string(path)?; - let logs = content.split("\n").map(|str| str.to_string()).collect(); - Ok(Logger { - logs: Mutex::new(logs), - temp_logs: Mutex::new(Vec::new()), - }) - } -} - -impl Serialize for Logger { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let v = match self.logs.lock() { - Ok(v) => v.join("\n"), - Err(e) => return Err(serde::ser::Error::custom(e.to_string())), - }; - serializer.serialize_str(v.as_str()) - } -} - -struct LoggerVisitor; -impl<'de> serde::de::Visitor<'de> for LoggerVisitor { - type Value = Logger; - - fn visit_str(self, v: &str) -> Result - where - E: serde::de::Error, - { - let logged = v.split("\n").map(|s| s.to_string()).collect(); - Ok(Logger { - logs: Mutex::new(logged), - temp_logs: Mutex::new(Vec::new()), - }) - } - - fn visit_borrowed_str(self, v: &'de str) -> Result - where - E: serde::de::Error, - { - let v = v.to_string(); - let logged = v.split("\n").map(|s| s.to_string()).collect(); - Ok(Logger { - logs: Mutex::new(logged), - temp_logs: Mutex::new(Vec::new()), - }) - } - - fn visit_string(self, v: String) -> Result - where - E: serde::de::Error, - { - let v = v.to_string(); - let logged = v.split("\n").map(|s| s.to_string()).collect(); - Ok(Logger { - logs: Mutex::new(logged), - temp_logs: Mutex::new(Vec::new()), - }) - } - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("Expects string composed of substrings separated by \\n (newlines). Th") - } -} - -impl<'de> Deserialize<'de> for Logger { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - deserializer.deserialize_str(LoggerVisitor) - } -} diff --git a/src/bin/cleyto_coin_wallet.rs b/src/bin/cleyto_coin_wallet.rs index 19e66da..8c1f7e4 100644 --- a/src/bin/cleyto_coin_wallet.rs +++ b/src/bin/cleyto_coin_wallet.rs @@ -9,11 +9,11 @@ enum Args { /// Generate a new keypair Generate { /// Where to store the generated private key - #[structopt(long, parse(from_os_str), default_value = "private.pem")] + #[structopt(long, parse(from_os_str), default_value = "./private.pem")] private_key_file: PathBuf, /// Where to store the generated public key - #[structopt(long, parse(from_os_str), default_value = "public.pem")] + #[structopt(long, parse(from_os_str), default_value = "./public.pem")] public_key_file: PathBuf, #[structopt(long, short)] @@ -23,11 +23,7 @@ enum Args { /// Send a transaction Send { /// Recipient’s public key as a string - #[structopt( - long = "recipient-key", - short = "r", - required_unless = "recipient-key-file" - )] + #[structopt(long = "recipient-key", required_unless = "recipient-key-file")] recipient_key: Option, /// Recipient’s public key from a file @@ -39,7 +35,7 @@ enum Args { recipient_key_file: Option, /// Sender’s private key as a string - #[structopt(long = "sender-key", short = "s", required_unless = "sender-key-file")] + #[structopt(long = "sender-key", short = "sk", required_unless = "sender-key-file")] sender_key: Option, /// Sender’s private key from a file @@ -55,7 +51,7 @@ enum Args { password: Option, /// Transaction amount - #[structopt(long, short = "a")] + #[structopt(long, short)] amount: i64, }, } diff --git a/src/bin/node.rs b/src/bin/node.rs index bc1d5ad..cf8cf25 100644 --- a/src/bin/node.rs +++ b/src/bin/node.rs @@ -1,18 +1,36 @@ -use cleyto_coin::{run_server, run_server_with_gui}; +use cleyto_coin::{kill_server, run_server, run_server_new_process, run_server_with_gui}; use structopt::StructOpt; -#[derive(StructOpt, Debug)] -struct Args { - #[structopt(short, long)] - gui: bool, +#[derive(Debug, StructOpt)] +#[structopt(name = "cleyto-coin-wallet")] +enum Args { + /// Kills the running server + Kill {}, + + /// Start the server. The flag --gui defines if headless or not + Start { + #[structopt(long)] + gui: bool, + + #[structopt(long = "blocking")] + blocking: bool, + }, } fn main() { let args = Args::from_args(); - match args.gui { - true => run_server_with_gui().unwrap(), - false => { - run_server(); - } + + match args { + Args::Kill {} => kill_server(), + Args::Start { gui, blocking } => match gui { + true => run_server_with_gui().unwrap(), + false => { + if blocking { + run_server(); + } else { + run_server_new_process(); + } + } + }, } } diff --git a/src/configs.rs b/src/configs.rs new file mode 100644 index 0000000..e701d80 --- /dev/null +++ b/src/configs.rs @@ -0,0 +1 @@ +pub const KILL_SERVER_SOCKET_PATH: &str = "/tmp/cleyto_coin/kill_server.sock"; diff --git a/src/lib.rs b/src/lib.rs index 71f6d2f..32c760f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,11 +8,15 @@ use crate::{ }; use openssl::pkey::{PKey, Private, Public}; use reqwest::Client; -use std::path::PathBuf; use std::{ - sync::{mpsc, mpsc::Sender, Arc, Mutex}, - thread, + io::Write, + os::unix::net::UnixListener, + path::PathBuf, + process::{Command, Stdio}, }; +use std::{sync::Arc, thread}; +mod configs; +use configs::KILL_SERVER_SOCKET_PATH; pub mod chain; pub mod node; @@ -129,18 +133,16 @@ pub async fn send( } pub fn run_server_with_gui() -> color_eyre::Result<()> { - let (tx, rx) = mpsc::channel::<()>(); - // Channel to kill thread - let rx = Arc::new(Mutex::new(rx)); + // let rx = Arc::new(Mutex::new(rx)); let (mut node, logger) = node::Node::new(Chain::new()); // Run server thread let server = thread::spawn(move || { - let rx = Arc::clone(&rx); + // let rx = Arc::clone(&rx); - node.run(true, rx, 0); + node.run(true, 0); }); color_eyre::install()?; @@ -149,24 +151,45 @@ pub fn run_server_with_gui() -> color_eyre::Result<()> { ratatui::restore(); // Quits server - tx.send(())?; + kill_server(); server.join().unwrap(); result } /// Spawns thread with server and return the channel that sends the kill signal -pub fn run_server() -> Sender<()> { - let (tx, rx) = mpsc::channel::<()>(); - let rx = Arc::new(Mutex::new(rx)); +/// Mostly useful for testing +pub fn run_server_thread() { let (mut node, _) = node::Node::new(Chain::new()); thread::spawn(move || { - node.run(true, rx, 0); + node.run(true, 0); }); - tx +} + +pub fn run_server() { + let (mut node, _) = node::Node::new(Chain::new()); + node.run(true, 0); +} +pub fn run_server_new_process() { + #[allow(clippy::zombie_processes)] + let child = Command::new(std::env::current_exe().unwrap()) + .arg("start") + .arg("--blocking") + .stdout(Stdio::null()) + .stdin(Stdio::null()) + .spawn() + .expect("Failed to start server process"); + println!("Spawned process with pid {}", child.id()); } /// Sends the kill signal to the server -pub fn kill_server(tx: Sender<()>) { - while tx.send(()).is_ok() {} +pub fn kill_server() { + if std::path::Path::new(KILL_SERVER_SOCKET_PATH).exists() { + std::fs::remove_file(KILL_SERVER_SOCKET_PATH).unwrap(); + } + let listener = UnixListener::bind(KILL_SERVER_SOCKET_PATH).expect("Could not bind socket"); + let (mut stream, _) = listener.accept().expect("No one connected to the listener"); + stream + .write_all("kill".as_bytes()) + .expect("Error writing kill signal"); } diff --git a/src/node/mod.rs b/src/node/mod.rs index b0c5b7f..cb63666 100644 --- a/src/node/mod.rs +++ b/src/node/mod.rs @@ -4,6 +4,7 @@ mod thread_pool; pub mod ui; mod utils; use crate::chain::{transaction::Transaction, Chain}; +use crate::configs::KILL_SERVER_SOCKET_PATH; use crate::node::logger::Logger; use core::panic; use directories::ProjectDirs; @@ -11,14 +12,15 @@ use once_cell::sync::Lazy; use resolve_requests::endpoints::resolve_endpoint; use resolve_requests::methods::{HTTPParseError, HTTPRequest}; use serde::{Deserialize, Serialize}; -use std::fs; +use std::fs::{self}; +use std::os::unix::net::UnixStream; use std::path::PathBuf; use std::time::Duration; use std::{ collections::HashMap, io::{prelude::*, BufReader}, net::{TcpListener, TcpStream}, - sync::{mpsc::Receiver, Arc, Mutex}, + sync::{Arc, Mutex}, thread, }; use thread_pool::custom_thread_pool::ThreadPool; @@ -29,9 +31,20 @@ pub struct NodeState { chain: Chain, transactions_pool: Vec, } -#[derive(Debug, Deserialize, Default)] +#[derive(Debug, Serialize, Deserialize)] struct NodeConfig { log_path: PathBuf, + socket_path: PathBuf, +} +impl Default for NodeConfig { + fn default() -> Self { + let proj_dirs = ProjectDirs::from("", "CleytoCoin Big Mean Corp", "cleyto_coin") + .expect("Could not find the config directory"); + Self { + log_path: proj_dirs.data_dir().join("logs.log"), + socket_path: PathBuf::from(KILL_SERVER_SOCKET_PATH), + } + } } #[derive(Serialize, Deserialize)] @@ -53,17 +66,18 @@ pub const LOG_LEVEL: u8 = 2; fn load_config() -> NodeConfig { let proj_dirs = ProjectDirs::from("", "CleytoCoin Big Mean Corp", "cleyto_coin") .expect("Could not find the config directory"); - let config_path: PathBuf = proj_dirs.config_dir().join("config.toml"); - - let contents = fs::read_to_string(&config_path).unwrap_or_else(|_| { - fs::create_dir_all(proj_dirs.config_dir()) - .expect("Could not create the necessaty directories"); - let log_path = proj_dirs.data_dir().join("logs.log"); - let contents = format!(r#"log_path = "{}""#, log_path.to_str().unwrap()); - fs::write(&config_path, &contents).expect("Couldn't write to file"); - contents - }); - toml::from_str(&contents).expect("Invalid config format") + let config_path = proj_dirs.config_dir().join("config.toml"); + + if let Ok(contents) = fs::read_to_string(&config_path) { + toml::from_str(&contents).expect("Invalid config format") + } else { + fs::create_dir_all(proj_dirs.config_dir()).expect("Could not create config directories"); + + let default_cfg = NodeConfig::default(); + let toml_str = toml::to_string_pretty(&default_cfg).unwrap(); + fs::write(&config_path, &toml_str).expect("Couldn't write default config"); + default_cfg + } } impl Node { @@ -211,7 +225,7 @@ impl Node { resolve_endpoint(state, request_object) } - pub fn run(&mut self, default: bool, rx: Arc>>, selected_port: u16) { + pub fn run(&mut self, default: bool, selected_port: u16) { let port: u16 = if default { Self::DEFAULT_PORT } else { @@ -224,9 +238,9 @@ impl Node { } }; - let listener = TcpListener::bind(format!("127.0.0.1:{port}")).unwrap(); + let tcp_listener = TcpListener::bind(format!("127.0.0.1:{port}")).unwrap(); - listener + tcp_listener .set_nonblocking(true) .expect("Cannot set non-blocking"); @@ -235,16 +249,28 @@ impl Node { Err(e) => panic!("{e}"), }; + // The termination signal will be a socket now + let parent = self.config.socket_path.parent().unwrap(); + std::fs::create_dir_all(parent).expect("Could not create temp dirs for parent socket"); + + let mut read_buffer: [u8; 100] = [0u8; 100]; loop { - // Check for termination signal - if let Ok(lock) = rx.try_lock() { - if lock.try_recv().is_ok() { - break; + if let Ok(mut listener) = UnixStream::connect(KILL_SERVER_SOCKET_PATH) { + let command: Option<&str> = match listener.read(&mut read_buffer) { + Ok(n) => str::from_utf8(&read_buffer[..n]).ok(), + Err(_) => None, + }; + + match command { + Some("kill") => break, + Some(&_) => {} + None => {} } - }; + } + // Check for local signal // Try accepting a connection - match listener.accept() { + match tcp_listener.accept() { Ok((stream, _)) => { let logger = Arc::clone(&self.logger); let state = Arc::clone(&self.state); diff --git a/tests/create_headless_node.rs b/tests/create_headless_node.rs index 17b482b..1589610 100644 --- a/tests/create_headless_node.rs +++ b/tests/create_headless_node.rs @@ -1,31 +1,10 @@ -use std::{ - sync::{mpsc, Arc, Mutex}, - thread, - time::Duration, -}; +use std::{thread, time::Duration}; -use cleyto_coin::{chain::Chain, kill_server, node}; +use cleyto_coin::{kill_server, run_server_thread}; #[test] fn run_and_kill_node() { - let (tx, rx) = mpsc::channel::<()>(); - - // Channel to kill thread - let rx = Arc::new(Mutex::new(rx)); - - let (mut node, _) = node::Node::new(Chain::new()); - - // Run server thread - let server = thread::spawn(move || { - let rx = Arc::clone(&rx); - - node.run(true, rx, 0); - }); - let killer_server = thread::spawn(move || { - thread::sleep(Duration::from_millis(200)); - kill_server(tx); - }); - - killer_server.join().unwrap(); - server.join().unwrap(); + run_server_thread(); + thread::sleep(Duration::from_millis(100)); + kill_server(); } diff --git a/tests/create_wallets_and_send_transaction.rs b/tests/create_wallets_and_send_transaction.rs index 28715b3..45fa6ed 100644 --- a/tests/create_wallets_and_send_transaction.rs +++ b/tests/create_wallets_and_send_transaction.rs @@ -1,6 +1,6 @@ use std::path::PathBuf; -use cleyto_coin::{generate, kill_server, run_server, send}; +use cleyto_coin::{generate, kill_server, run_server_thread, send}; const SENDER_PUBLIC_KEY_PATH: &str = "./wallets/sender/public.pem"; const SENDER_PRIVATE_KEY_PATH: &str = "./wallets/sender/private.pem"; @@ -33,7 +33,7 @@ async fn test_send_transaction() { let receiver_public_key_file = PathBuf::from(RECEIVER_PUBLIC_KEY_PATH); - let tx = run_server(); + run_server_thread(); send( None, @@ -45,5 +45,5 @@ async fn test_send_transaction() { ) .await; - kill_server(tx); + kill_server(); } diff --git a/tests/requests.rs b/tests/requests.rs index 074fd39..0a4fbcd 100644 --- a/tests/requests.rs +++ b/tests/requests.rs @@ -1,12 +1,8 @@ use cleyto_coin::chain::transaction::{Transaction, TransactionInfo}; use cleyto_coin::chain::wallet::Wallet; -use cleyto_coin::node; -use std::{ - sync::{mpsc, Arc, Mutex}, - thread, -}; +use cleyto_coin::{kill_server, run_server_thread}; +use std::thread; -use cleyto_coin::chain::Chain; use reqwest::blocking::Client; fn thread_post(n: u16) { @@ -95,25 +91,15 @@ fn thread_get(n: u16) { #[test] fn main() { - let (tx, rx) = mpsc::channel::<()>(); - // Channel to kill thread - let rx = Arc::new(Mutex::new(rx)); // Run server thread - let server = thread::spawn(move || { - let rx = Arc::clone(&rx); - let (mut node, _) = node::Node::new(Chain::new()); - node.run(true, rx, 0); - }); + run_server_thread(); // 10.000 breaks the os (client), but the server seems fine // Error accepting connection: Too many open files (os error 24) thread_get(10); thread_post(10); - tx.send(()).expect("Failed to send termination signal."); - - // Wait for the server thread to finish (this will block until the server thread terminates) - server.join().expect("Server thread panicked."); + kill_server(); } diff --git a/wallets/receiver/private.pem b/wallets/receiver/private.pem index 5b53b60..5727544 100644 --- a/wallets/receiver/private.pem +++ b/wallets/receiver/private.pem @@ -1,28 +1,28 @@ -----BEGIN PRIVATE KEY----- -MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCScO+qHuey2+ni -KD5CngrQOncbedVDbRvT3eslLhrfsJodoxfeqeVT4yTTNagcPdYWKxRrKkAe7tQW -18qnqgcjDY2zsKXs0uUwNADqc4dtFqhiOnOkbkSNQP3DbtV87oAl5sJMMqe6ZLF8 -CNc9UKEXOkLdftV7zUJUgslK5BW1XxVDuvB6V81TdZOgKExDUsATdhAkaCPw79IH -4WBc+hX2h5hqSE9lUAOTVzOYX5YJo+YoBJBgw7ncNHAByeiowzpBk7Xo0sioeAsH -Fsoss3PCy81ZNHcO67hlSXtkj6+iwypaQE2MYL7F0mIc1F65zbFLPKmsBc3FxRMu -eLLfSA9vAgMBAAECggEAH+pDIVVo878f4u0YXJZrDuSfDrrnsrE1zl/gHpNEwhmQ -DQN9cUaftz+ax/k6sST84onzAkQMA3HTgUL4ZMAWnBoZFt9Z2hpAXtgGlxIkoq68 -deP9TgnGIO7YH16pYnfsL9Zx4/9qNKI7WCfpGPDYmuJUrkc1Ka0bVqXGTu8UmBlT -eKixGLyGUWUreCipuqdfepC/QeaA0+TpTyy+AuxCUWrkBYEnXPChrj/5LAb26Qo/ -TTKcZy2p1BSVVJbMj45rK0E9WJ3navTxr3J3Wx6CFLcHDVDPrIG3GNEJxlB6M40u -FMRpNNHA8ME62MZGkCg07K3WFKSSOuW9GHWhVgwiEQKBgQDCMCfzZ/aPxPJ6bD7S -JNd/lh4/++oGuNeP3sl2HNzigXkefPQEVKDSGy6T1ETiUcIO4DFRqcziCIYk6452 -XRp+jhzV60SBbIZEV8b8x8EZWB6i1wQfjldZ1VZzij1/KlwlY40UwDwvEh41T3FG -WOXUnB4MLmQVrn5S6Q2LhKjWdwKBgQDBDgPWtUGxvNXabdsj63Oh0JfbS4BXbV4n -9HgpD2AfaCFJuVSQf68g20NuEH3NOSSZeQIxi3aC6Q5a+1hDvfmXMmiUSNj252lC -i+Rx1MFdbICvMHp9uikWRIZ1QD83ldyV6URCL2dCNcDDrnL4JP3snrlVl2TV6DXH -GK/JPnS0yQKBgCs+1DbVyUgdQS7YWmksQT2trdDsBiM3GPJ2WAFQzjQ3R0Tt6N3q -BuKjoQzU8f5A3Om5LeKU4QbZhR5FwzVC6LqUZYGSYnA8Pe6MackzpyJAyJnVLprk -dTmRVC5o8+cVBO5irDGZ2g1ViJqwtXZIJ+7gUaS+1BUsBf/q3NRcV8ljAoGBAIrO -WeI5pwd1f8myrRqxgw14nocVTS0PPOC7fN4A6RsL0stgTrYqqC+Wl2/+uYUiLfeP -Mw2OeeJ8UU8HYxop8NhObTUY/67fCibH0LQrxfA3QHs1/wyBGmOhV4/cZte/j7/V -3bc8wCCx52yK9K0IY+6K3TT2SpEmiewiCtpGZsFJAoGBAKGWQGtqxLGkXov2MJvx -bARgKGFw6iz+wjh6jqDV9IL5PoxJZGv9dqUDqNdOOvikC2n2xeookwOGBdIrr38Z -nGguYTelZJ1pZKqTtav37kxt7gWZdR2akt36bXsL9Av8RyVOlasXgfAWqjGkOvfA -9c+bZX81a1t57xeMptsivxfq +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC62M47RFlHGg2Q +8eUcoOv6x0Z4L+VTP9iumYxDY8IC9mjjwwPngJ2RWH5odT7i2HjpuCpArrtbtnBt +BJssNSzrmiamYPQDTsLdfgXa0EkaswFvT0Bo4CgVIfEYoEhY+gfRd8xwnnuAm+SE +TG331oFV0ykPYyJj6KfVKU4IP66E5TWP1hgdzscgsLfOItBK61rcG20LkuAoOZ1Y +n9sk/loV7+Oe2/VbAnXnRafiaqg1uXlmbD1tJaq290+L0xbnm48LX7TXx1S0gKP7 +eI0RUFEQWpMt3jPAJsEu8BXpUe3l05WoXqKcK/xac0jn884Xwgn4ojmRxWxCD+AS +BonnkMZ7AgMBAAECggEAGqQsL64srqMr2QVsnhrh2tWMGjn2EFszqZ2SLs+sC6EX +zpXxWhJwJRfBmmJRT6X/A5AzUhq38zk9qYW1HGYOnB9UMHf1gv05crxz2EHHkquA +Yl8h93IytOSsjE7aUfmAoODcgRhP0sQnnqeJLIPk2BhuK9f+QNGdEJs9mZZ+60rG +wKwtKFzE5AxFvbOp3glD8zNTOcWF1g95WwC/zl87O+App8i2P/ADIfQZjMFXseHM +eB0W1bdC79DQYEnSSK4PhDrZi9Dn5rKT0mHrUvwTxOFda5PypwdcjmYtX3EtNbTC +XnsksD3+eiR015RN0QI1MC2ciMFCXZKV1ZyUwNcQCQKBgQDrfvis8BMWpSAS0Ufx +kIThdjPXPyOpgMg53M567QnI4YdGm+FEixIL54HkqKPW4vv1Z1mJMszAq/PmbM7l +zjqBCmf7EcMU0dJENs9ztvxEFhbJTbHyMJxvTebwPbMNKa1jijngCgIAN3v/usIu +eLcRniud7p+KCRLu+7yHOIhYiQKBgQDLHXuNRYrH7L8Lh+epdxlQeD0TZFZZTraQ +tvnpxQBmN8D2wwABKPfgozOemfrbexuxVrzt378ttWGCiqmpELZtpfLlfh4ecjHR +jse1Sta1PmT76mioUq4fsk5JZChG/jOJFEDdDVgdCQKgzQ7Y8DrIupnDv5cpeFCV +alP4+Vzd4wKBgA/CpZ3dS0m+r1DTFL+D7oCwSnPjU92qMrw8nMz5RChff5AoKoFN +1V1alwTYjG57LmWUDxfxgGZ8m4wWSBp1HNE6Aw94dtfT2wCZsnSKEIP1/nxmaLxO +qImCayw01ODSozoALDNkOBIPpIrO0uQNTCMvtLeTGrZAOTYpPsrF68oRAoGAHTEY +j7496uvorTSUI/P9DGariHz0OBOrA3N4yZNJsmcy8YQnHQORVJTAF3xT5HV2Dm/D +6YAtztjxsbxSE/1V6r5eAYO1unR5GoAlzbV61EOqadZPwllXszCCNeQSMdnY+EuT +4vFHB61C1m+CQcWM/UpsTQjdJdWn50GH3hGgujcCgYEAnjLKWbtQHDHqSP4x4iPJ +1rTpNe79eD8caKxCz+p8I++WSGwnNm3qsqhivNUc518oQsaaFZkolnR+r/AlB0Us +eJ1x5kipkZCwzMX5V+3625VxUe6RNwAgwL3rhCDr6SOuu1WpvPRne/wFe55DrIwK ++gF2YhCyM++JU6n4YxmGtN8= -----END PRIVATE KEY----- diff --git a/wallets/receiver/public.pem b/wallets/receiver/public.pem index f59a8ef..4d906f4 100644 --- a/wallets/receiver/public.pem +++ b/wallets/receiver/public.pem @@ -1,9 +1,9 @@ -----BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAknDvqh7nstvp4ig+Qp4K -0Dp3G3nVQ20b093rJS4a37CaHaMX3qnlU+Mk0zWoHD3WFisUaypAHu7UFtfKp6oH -Iw2Ns7Cl7NLlMDQA6nOHbRaoYjpzpG5EjUD9w27VfO6AJebCTDKnumSxfAjXPVCh -FzpC3X7Ve81CVILJSuQVtV8VQ7rwelfNU3WToChMQ1LAE3YQJGgj8O/SB+FgXPoV -9oeYakhPZVADk1czmF+WCaPmKASQYMO53DRwAcnoqMM6QZO16NLIqHgLBxbKLLNz -wsvNWTR3Duu4ZUl7ZI+vosMqWkBNjGC+xdJiHNReuc2xSzyprAXNxcUTLniy30gP -bwIDAQAB +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAutjOO0RZRxoNkPHlHKDr ++sdGeC/lUz/YrpmMQ2PCAvZo48MD54CdkVh+aHU+4th46bgqQK67W7ZwbQSbLDUs +65ompmD0A07C3X4F2tBJGrMBb09AaOAoFSHxGKBIWPoH0XfMcJ57gJvkhExt99aB +VdMpD2MiY+in1SlOCD+uhOU1j9YYHc7HILC3ziLQSuta3BttC5LgKDmdWJ/bJP5a +Fe/jntv1WwJ150Wn4mqoNbl5Zmw9bSWqtvdPi9MW55uPC1+018dUtICj+3iNEVBR +EFqTLd4zwCbBLvAV6VHt5dOVqF6inCv8WnNI5/POF8IJ+KI5kcVsQg/gEgaJ55DG +ewIDAQAB -----END PUBLIC KEY----- diff --git a/wallets/sender/private.pem b/wallets/sender/private.pem index a636d1c..47191ba 100644 --- a/wallets/sender/private.pem +++ b/wallets/sender/private.pem @@ -1,30 +1,30 @@ -----BEGIN ENCRYPTED PRIVATE KEY----- -MIIFNTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQOIS9pyplMUZL7RTd -ndQMDAICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEF20TaldTcmbBglR -3RexxEAEggTQGDbzMuzcG7KHkFpEZVrkN+95UbcJggZXETC7iaMJQB6OHGRsHcMw -GwxQAuP9BtczlKu0s4iLmNnHwQUMU2nAniIfBpWTiIh7f2TE1CcywT6HbBHEuJQs -WyNWQaghsYpBJCscGyAReLkZ+gM4aqEiE4Oqv9dlzpb1vjRN0IqOaFIq5RCrGim7 -A7IrNbbv2YhfQg6TeRW5krNb7aGwuGbkmYILLIyRjbw8nnrvbVfe3ZZcR74QuXWq -F2gE6GmneC41MpmFJN0zY3d9+nldQ/fLMLPlq58SmnjEW2rXcxx50OONgMxr9tJB -yMyVLBWT031tDVynbRTEBzloK5TZsTCkJcLXHCDmVfhek3Rx2NhrwX+Ry7zPjHsI -PxxRUwP/htps80AgVSiBQJn17A6rRdBLT9yJFdQ44fyMNgFynTizaU0wZ7AG8jF8 -E0BmfoQiY3TkWeR5gMAhoFFN75UcSYu5ATkIJr5vQ1btxI2+tkEF95J80gvzzdxc -xbtFmT67nf6Z2Yqwy0QhydaoYjXiwQX74BvnbBdakmVJyPaNIEq1RChFmSTODYck -gm0vPkp/GO8WSpggAPCoHRqXaPOT8jRZ920PoVwJKdqRur8dC7tmEmdL+5I0WYSI -nao+R/cCTSs0SJtpG9GIXiDDQpD7MfiMcPxQXCgCkQTyRx/5gq5ZGM7Opy554ZlC -9Ux3aitJ0hQF/5bupvaa/OaWO1KkE/eMO+DoNQQUTgVgfX/mEjS0SRKd1ivPlJJ9 -aXewDymo+FgqowpcYknsoopyq+MQj7U11XH+2vfQJ9jQ511lGBPbvT3/4wFdjM4g -NEQBCV3k35NZeHov6Yh7+xRbopFDku5Vb+IxEKyqElyt8jsxDtbzobHRbg4afKf5 -6lLtd5wXcgnJPfb3vHIYfdJRIPY2sx3YWn1Umpx4HVm7cyd57GOirHvfmUeRSSVG -V33AIkIX/bSL3oOL4RYKcnXvGfV5qOI0umK3QxQ2uii/cUTLOMeT01670H38wIM3 -FhceAjPcIlm0AB9rPmpK3nvxjIqIs2KSu36dlGlcvoAmzHXgZdieNFpQmQs10DFm -BdNPoQ2FkxZXkhCAVdNlM4hI6fHjwKwy6No2GmnPuqGmEDrvwWsbV44Rv48B5Dim -hmzDSzNe18SgOOgmIFvyJ3BRdxEWrHzPIKL6iz+vG/+mJMfAwcBY0FpqNNuQP+1+ -tzl46gF6Sfm2wU73EQK2Tp7ICTNosMqfWDU8px79hMRqj3ww7BLXi1RMswVCxa4p -GZMdAWF0/ACjeKHFCKoqwVyUJEE+SEuGITx6r+khaneu92ooXf8SD5gn5wNyK+zd -9/reyUzV/jU6tsygX0EJ63fFSolAwSKYNnuJeWssGJOj882VWbDahFZe8AqqoVdE -STD+E60EwfzjYxhT8/i5GFXAF6IpR4Er1X2ksXcmAngt7Z7ZRn72nUrMoBP9bhRc -r7mz0U4dQwbCCRRITJQunDbhNknhFHbYuZ5AzHa6xHFK7fqeydgvwmRiUwL80Esu -sBJpGmugos/D2FGP5cL454nCXhqjx39w8Y+A6LG9iXYwz+8+BtoQa3zrJm0fkqMS -AEJP6dqgZv/dKR4BbS1HCZXPfhtkXY7JUNO74d7uazgJm7qN28jKMGQ= +MIIFNTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQP35poY8WMHxZ1Xv/ +N6Sy/wICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEFrg0obma7BnSavp +fzGthyUEggTQmH2qIChPR4CXmazecUhImySwgqlMQn0i3bNGlhtDOLa332KAgrrG +HZoyRedJCE7W8fumORaxOt+52IYxE5s99vRKy0A5TXjQcewCqXk9YPXhbZ4mjC8n +xskfIq7yWKoijSc4fvjK5QKzhoqhO9Xy8SIzRbddhZKEoxjmf6nuD9ptAaaAIGAW +NXiiC3MNtJafMcpZ02FOleUrnfsDhsrEoXXVQHBCZglVzBdAV0vXPNBwYUJfy4mH +6pAyhKHCPgDaps1L4vNYiFxFvty6NAEjG2XN8sjH4vaBCC8uRns2u499kOkYXBBv +tVlqRlHDSGAWlZcs7GlTsgcfR2QLqXhvOXaOnYnoRKsaZxqczdfndel//J1bu0cW +RNf3QI1LIRFo2AX3LvNiBuQk7yrCd+JvI74OW0MtTonMjPpcYs6lEe/c3tMEhLwB +UbFyR0DKCCt4ilEdjn3lzl6cMvhpOjrbMfvQ58mYyccxBWSmu1v1lLzb8e924AAV +b3wESFKBPzrS0LVNak1YUTYvyQH5R4P2o9DK+/KHiC0gDSzTubrzXgeGIsQiFbbp +Wuk3lH3HLo9uNNzXEwOdB7/KMOnuTPXcyvEmBSYsP5kzX6tSaz9u7RdAYj6H+S0o +nlR/VfSDMvWZ7ibDLoBvN2arrbAHgYyuuw+NaUs7YNkz2RqkWWSyFFNYrrbCiDQA +RKYYz/SFhxFvx9Yn5bDid0Wabx4dVYEdTWSnXD/+I4JZDqWDGjXp/9OlhiNG/jGL +vUnrSaD2cc2AM5V1sVtjjgIB4wokNZ87f2Pqbckz5QzOHDzavUsEmBlnt+GNhDNX +wAVL2FneqCekFqvVhjq8X6e8TVpplVzfFogasffBroTqup6FSJspy92zE8JMh9CM +sdiXwlOWmQ3q7MLGNeVQIe1tuJAFLh8l3hsgF3rT95Sxt5gtHNbmpQlznKMFymZs +6b/OYLqVg5CD6SnbTTDWrKp3+sS+AiDo4iAbcI0IPnd95OlURzGOCL6zIrwIyhQK +f94Ny7lJ1xRnDUwJarsjsX+ttURD2QG1Mw6Z9zgQzw8h5HzgVLQR7AbvVVL55A3I +fqUBjoU18Rb0Ho3jne6ap7fMysMiUp1DNqPgYVgBcrhYLpw1ZT09bazQ2eGivVaN +oFelA6luNCbAk13ocVRye12zQeVmpmL6eIegwIrYZmWhIyiWE+ixYEpSYA90nMCG +dY3xH1QQtQJNfjs38uLDr1WQ3MNWu20zG3bCFvE1FxVjtShBei6UV6ZuLFPp1yvb +C0vs3BiNn4EuPU9z8xxE4XqZGCkvlQR1BfxqRUiMrKokxc/q16eFheqDLsoDQ5F6 +fsIKNkDDi/neKuoVmy+ypvExC2fdUDpiMTwNKInszzrVmDWlxqZH7SsvM1Ptv8/T +YDbIDQg24F2k2fSNo99Rc312Jex3OaT2YLzTxdrzPl+CuV+L5krN9Ysn9XaIivlm +mDf7lltOA8gd+p1vGF+Paw1TW4BLdY0aTrkKMd58wGQxieS6h8UQYC6W2DK2DV5P +rqrHoUmSOm1ShTQNC4UbjtTe14vOjQY2rKmqf7IJlijM8BdJzHhLQpPiKd3ov3dV +io+RvQb23KsankznJgTwgXYqlpLY9dwSynsaCwYxLBAJvHGPUZ+alIY= -----END ENCRYPTED PRIVATE KEY----- diff --git a/wallets/sender/public.pem b/wallets/sender/public.pem index 4fd7402..3df2cfe 100644 --- a/wallets/sender/public.pem +++ b/wallets/sender/public.pem @@ -1,9 +1,9 @@ -----BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr0+kMrWEaHrNlgU/KxPg -zDrtiVEqj5yXVTkCFeVCQ2UAIjyMuxi9EtazDryZS6EWun4NU/Lb0/8OsUcThAhN -gLmqTz2B6MYzNA2hy/oeQI/7Nsag8r20mR7BDlQHGZcE5kUmsKH5aC22mQ6vVNbi -JZ7lDIYDuv0+TEJoAgK6c0cbeWYWe6Qpy+xgICI57KaoRywwCk8XoS/afeS94EiD -jAG31PKC7AqLotKcJXmwpl8CzgcktJ2HzUPLyMMI+kli2jBnth2wE23P1QGEgEBv -r9EJASNjCS/gDwxuEAeRng8vCR60byLQ81gzISlCClc/yXxf4/b7iA1s+e2ooBV1 -nQIDAQAB +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwDbWXqa8Ad1LfecOlzn6 +FlgExVOuLsf6DJa0STejFHZtrAos4aDXMihLRLNxDsxK8/GAwz2sIrBc6quSO+HU +/x3l2DLW/VEWtmVp8Ai0D03anmF7JNwXh6OHQsGaJeWfXz/219cLcQ/5PNGBlZlQ +NyTRUtaUlZC/ZWjF8HYcGMJ3Hb5tLsU7o1P/7SJW8e2UZwiIVGR7W1tPGuyKR/y4 +SCCFuZ8UYzX8YEPnOq7n40E+fR6m3dm5GybdRu+RreDbdpxr4tjrQGfGDOUyIHHh +NmJ9xK0WZzd7eaiFkoBVfdOMojH64iL7rvGJ2IEPQh+1hIwDFYOJ5ZPPwGp5V6HX +CQIDAQAB -----END PUBLIC KEY----- From c83a3119498c5ada3c1472e7a9dee3cad6c6c4b0 Mon Sep 17 00:00:00 2001 From: Dante Ramacciotti Date: Thu, 4 Sep 2025 17:32:20 +0200 Subject: [PATCH 37/40] Finished implementing UTXO Now there's an algorithm (untested) to select the UTXOs that will compose the transactions. I should still implement a way for the user to choose it manually, introduced some utilities functions and changed some old ones. I don't remember the rest tbh --- README.md | 17 +- src/bin/cleyto_coin_wallet.rs | 8 +- src/bin/create_transaction.rs | 14 +- src/chain/block.rs | 25 ++ src/chain/mod.rs | 32 ++- src/chain/ordered_vector.rs | 63 +++++ src/chain/transaction.rs | 101 ++++++-- src/chain/utxo.rs | 83 +++++++ src/chain/wallet.rs | 244 +++++++++++++++++-- src/lib.rs | 36 ++- src/node/logger.rs | 3 +- src/node/mod.rs | 9 +- src/node/resolve_requests/endpoints.rs | 33 +-- tests/create_block_and_add_chain.rs | 11 +- tests/create_wallets_and_send_transaction.rs | 3 +- tests/node.rs | 18 ++ tests/requests.rs | 11 +- tests/transactions.rs | 70 ++++-- todo.txt | 1 + wallets/receiver/private.pem | 52 ++-- wallets/receiver/public.pem | 14 +- wallets/sender/private.pem | 56 ++--- wallets/sender/public.pem | 14 +- 23 files changed, 736 insertions(+), 182 deletions(-) create mode 100644 src/chain/ordered_vector.rs create mode 100644 src/chain/utxo.rs create mode 100644 tests/node.rs diff --git a/README.md b/README.md index 1b56224..79a56ce 100644 --- a/README.md +++ b/README.md @@ -20,17 +20,9 @@ Feel free to give suggestions in the meantime of what you'd like to see implemen **CleytoCoin** is a cryptocurrency built using the Rust programming language. This project aims to create a decentralized cryptocurrency to facilitate peer-to-peer transactions. -## Features - -- **Proof of Work (PoW)** consensus mechanism -- Secure peer-to-peer transactions -- Fast block generation time -- High scalability and low latency -- Rust-based with a focus on performance and safety - ## Getting Started -Follow these instructions to get your local instance of **CleytoCoin** up and running. +Follow these instructions to get your local instance of a **CleytoCoin** node up and running. ### Prerequisites @@ -56,6 +48,10 @@ cargo build --release This will compile the project and generate an optimized binary in the `target/release` directory. +# How to use the binaries + +Thanks to the [StructOpt](https://crates.io/crates/structopt) crate, whenever you feel in doubt about one of the features of **CleytoCoin**, you can use the flag --help of the CLI tool to see flags and arguments for said feature. + ## Node Usage ### Starting the node @@ -72,6 +68,9 @@ With the option of running the GUI: cargo run --bin node start --gui ``` +The server with the GUI will block the terminal, while just running the start creates a new process, which has to be killed afterwards using the [kill command](#killing-the-node) + + The node will start and connect to the network. For now, only full nodes are available and they don't have yet the capacity for mining ### Killing the node diff --git a/src/bin/cleyto_coin_wallet.rs b/src/bin/cleyto_coin_wallet.rs index 8c1f7e4..7c130d8 100644 --- a/src/bin/cleyto_coin_wallet.rs +++ b/src/bin/cleyto_coin_wallet.rs @@ -52,7 +52,7 @@ enum Args { /// Transaction amount #[structopt(long, short)] - amount: i64, + amount: u64, }, } @@ -73,7 +73,7 @@ async fn main() { password, amount, } => { - send( + match send( recipient_key, recipient_key_file, sender_key, @@ -82,6 +82,10 @@ async fn main() { amount, ) .await + { + Ok(_) => {} + Err(e) => println!("Error {e} when sending transaction to server"), + } } } } diff --git a/src/bin/create_transaction.rs b/src/bin/create_transaction.rs index 1ded7d6..069e97f 100644 --- a/src/bin/create_transaction.rs +++ b/src/bin/create_transaction.rs @@ -1,10 +1,13 @@ use cleyto_coin::chain::transaction::{Transaction, TransactionInfo}; +use cleyto_coin::chain::utxo::UTXO; use cleyto_coin::chain::wallet::Wallet; use reqwest::Client; use std::error::Error; #[tokio::main] async fn main() { + todo!("The post_json function is not operational right now, because it only creates a random transacion without any arguments"); + #[allow(unreachable_code)] post_json().await.expect("TODO: panic message"); } @@ -30,7 +33,16 @@ async fn post_json() -> Result<(), Box> { let (wallet_sender, walletpk_sender) = Wallet::new(); let (wallet_receiver, _) = Wallet::new(); - let transactioninfo: TransactionInfo = TransactionInfo::new(12345); + + let input_utxos = vec![ + UTXO::new(1000, wallet_sender.clone()), + UTXO::new(2000, wallet_sender.clone()), + ]; + let output_utxos = vec![ + UTXO::new(2500, wallet_receiver.clone()), + UTXO::new(500, wallet_sender.clone()), + ]; + let transactioninfo: TransactionInfo = TransactionInfo::new(input_utxos, output_utxos); let signature = match walletpk_sender.sign_transaction(&transactioninfo) { Ok(signed_hashed_message) => signed_hashed_message, diff --git a/src/chain/block.rs b/src/chain/block.rs index 4a3857c..1de08b9 100644 --- a/src/chain/block.rs +++ b/src/chain/block.rs @@ -163,4 +163,29 @@ impl Block { self } + + pub fn test_block(chain: &Chain) -> Self { + let previous_hash = chain.get_last_hash(); + let index = chain.get_last_index() + 1; + let timestamp = Utc::now(); + + let transactions = vec![ + Transaction::default(), + Transaction::default(), + Transaction::default(), + ]; + + let mut block = Self { + previous_hash, + transactions, + index, + timestamp, + hash: String::new(), + nonce: 0, // temporary so that we can calculate hash + }; + + block.hash = block.calculate_hash(); + + block + } } diff --git a/src/chain/mod.rs b/src/chain/mod.rs index d485f2e..9f0e203 100644 --- a/src/chain/mod.rs +++ b/src/chain/mod.rs @@ -1,6 +1,8 @@ pub mod block; +mod ordered_vector; pub mod transaction; pub mod utils; +pub mod utxo; pub mod wallet; use block::Block; use serde::{Deserialize, Serialize}; @@ -12,7 +14,9 @@ pub struct Chain { impl Chain { pub fn new() -> Self { - Self { blocks: Vec::new() } + let mut chain = Self { blocks: Vec::new() }; + chain.create_genesis_block(); + chain } pub fn add_block(&mut self, block: Block) { @@ -25,23 +29,17 @@ impl Chain { genesis } - pub fn get_last_hash(&mut self) -> String { - match self.blocks.last() { - Some(block) => block.get_hash(), - None => { - let genesis_block = self.create_genesis_block(); - genesis_block.get_hash() - } - } + pub fn get_last_hash(&self) -> String { + self.blocks + .last() + .expect("Chain was created without genesis_block") + .get_hash() } - pub fn get_last_index(&mut self) -> u64 { - match self.blocks.last() { - Some(block) => block.get_index(), - None => { - let genesis_block = self.create_genesis_block(); - genesis_block.get_index() - } - } + pub fn get_last_index(&self) -> u64 { + self.blocks + .last() + .expect("Chain was created without genesis_block") + .get_index() } } diff --git a/src/chain/ordered_vector.rs b/src/chain/ordered_vector.rs new file mode 100644 index 0000000..7e99182 --- /dev/null +++ b/src/chain/ordered_vector.rs @@ -0,0 +1,63 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct OrderedVec +where + T: std::cmp::Ord, +{ + vec: Vec, +} + +impl OrderedVec +where + T: std::cmp::Ord, +{ + #[allow(dead_code)] + pub fn insert(&mut self, value: T) { + match self.vec.binary_search(&value) { + Ok(_) => {} + Err(pos) => self.vec.insert(pos, value), + } + } + #[allow(dead_code)] + pub fn last(&self) -> Option<&T> { + self.vec.last() + } + #[allow(dead_code)] + pub fn get(&self, index: Idx) -> Option<&T> + where + Idx: std::slice::SliceIndex<[T], Output = T>, + { + self.vec.get(index) + } + pub fn len(&self) -> usize { + self.vec.len() + } + pub fn get_slice(&self, range: std::ops::Range) -> &[T] { + &self.vec[range] + } +} + +impl std::ops::Index for OrderedVec +where + Idx: std::slice::SliceIndex<[T], Output = T>, + T: std::cmp::Ord, +{ + type Output = Idx::Output; + + fn index(&self, index: Idx) -> &Self::Output { + &self.vec[index] + } +} + +impl IntoIterator for OrderedVec +where + T: Ord, +{ + type Item = T; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.vec.into_iter() + } +} diff --git a/src/chain/transaction.rs b/src/chain/transaction.rs index 7e6c8a7..a7183bd 100644 --- a/src/chain/transaction.rs +++ b/src/chain/transaction.rs @@ -1,3 +1,4 @@ +use super::utxo::UTXO; use super::wallet::Wallet; use chrono::{DateTime, Utc}; use openssl::error::ErrorStack; @@ -10,20 +11,41 @@ use std::fmt::Display; #[derive(Clone, Debug, Serialize, Deserialize)] // ---------------------------------------------- TransactionInfo definition ----------------------- pub struct TransactionInfo { - pub value: i64, + pub inputs: Vec, + pub outputs: Vec, pub date: DateTime, } impl TransactionInfo { - pub fn new(value: i64) -> TransactionInfo { + pub fn new(inputs: Vec, outputs: Vec) -> TransactionInfo { let date = Utc::now(); - Self { value, date } + Self { + inputs, + outputs, + date, + } } } impl Display for TransactionInfo { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "VALUE::{}::TIME::{}", self.value, self.date) + let inputs: String = self + .inputs + .clone() + .into_iter() + .map(|input| input.to_string()) + .collect::>() + .join("::"); + + let outputs: String = self + .outputs + .clone() + .into_iter() + .map(|output| output.to_string()) + .collect::>() + .join("::"); + + write!(f, "INPUTS::{}:OUTPUTS::{}", inputs, outputs) } } // ------------------------------------------------------------------------------------------------- @@ -49,14 +71,17 @@ impl fmt::Display for TransactionDeserializeError { impl std::error::Error for TransactionDeserializeError {} #[derive(Debug)] -pub enum TransactionValidationError { +pub enum TransactionError { OpenSSLError(ErrorStack), + InsufficientInputs, ValidationError, + InsufficientFunds, + ConnectionError(String), } -impl fmt::Display for TransactionValidationError { +impl fmt::Display for TransactionError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - TransactionValidationError::OpenSSLError(e) => { + TransactionError::OpenSSLError(e) => { let mut error = String::new(); error += "The validation of the transaction was not successful due to some \ internal OpenSSL error:"; @@ -65,17 +90,36 @@ impl fmt::Display for TransactionValidationError { } write!(f, "{}", error) } - TransactionValidationError::ValidationError => { + TransactionError::ValidationError => { write!( f, "The validation of the transaction was not successful, as the signature \ did not match the provided transaction info." ) } + TransactionError::InsufficientInputs => { + write!( + f, + "The transaction was not validated because the inputed UTXOs where not \ + sufficient to cover the outuputed UTXOs." + ) + } + TransactionError::InsufficientFunds => { + write!( + f, + "It wasn't possible to execute the transaction because there weren't enough funds." + ) + } + TransactionError::ConnectionError(_) => { + write!( + f, + "The transaction was not sent to the server due to a connection error." + ) + } } } } -impl std::error::Error for TransactionValidationError {} +impl std::error::Error for TransactionError {} // ------------------------------------------------------------------------------------------------- @@ -96,7 +140,7 @@ impl Transaction { receiver: Wallet, transaction_info: TransactionInfo, signature: Vec, - ) -> Result { + ) -> Result { let mut transaction = Self { sender, receiver, @@ -106,6 +150,14 @@ impl Transaction { // serves no function on its own, but I don't really see that being a problem }; + let input_sum = UTXO::sum(&transaction.transaction_info.inputs); + let output_sum = UTXO::sum(&transaction.transaction_info.outputs); + let change: i64 = input_sum as i64 - output_sum as i64; + + if change < 0 { + return Err(TransactionError::InsufficientInputs); + } + let to_hash = transaction.to_string(); let mut hasher: Sha256 = Sha256::new(); hasher.update(to_hash.as_bytes()); @@ -117,16 +169,16 @@ impl Transaction { } } - pub(crate) fn verify(&self) -> Result<(), TransactionValidationError> { + pub(crate) fn verify(&self) -> Result<(), TransactionError> { match self .sender .verify_transaction_info(&self.transaction_info, &self.signature) { Ok(value) => match value { true => Ok(()), - false => Err(TransactionValidationError::ValidationError), + false => Err(TransactionError::ValidationError), }, - Err(stack) => Err(TransactionValidationError::OpenSSLError(stack)), + Err(stack) => Err(TransactionError::OpenSSLError(stack)), } } @@ -139,7 +191,11 @@ impl Transaction { Err(e) => return Err(TransactionDeserializeError::SerdeError(e)), }; - if tx.transaction_info.value < 1 { + let input_sum = UTXO::sum(&tx.transaction_info.inputs); + let output_sum = UTXO::sum(&tx.transaction_info.outputs); + let change = input_sum - output_sum; + + if change < 1 { return Err(TransactionDeserializeError::InsufficientFunds); } @@ -147,6 +203,23 @@ impl Transaction { } } +impl Default for Transaction { + fn default() -> Self { + let (sender, sender_pk) = Wallet::new(); + let (receiver, _) = Wallet::new(); + + let value: u32 = rand::random(); + + let transaction_info = TransactionInfo::new( + vec![UTXO::new(value as u64, sender.clone())], + vec![UTXO::new(value as u64, receiver.clone())], + ); + + let signature = sender_pk.sign_transaction(&transaction_info).unwrap(); + Transaction::new(sender, receiver, transaction_info, signature).unwrap() + } +} + impl Display for Transaction { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( diff --git a/src/chain/utxo.rs b/src/chain/utxo.rs new file mode 100644 index 0000000..27a944d --- /dev/null +++ b/src/chain/utxo.rs @@ -0,0 +1,83 @@ +use core::panic; +use std::{cmp::Ordering, fmt::Display}; + +use serde::{Deserialize, Serialize}; + +use super::wallet::Wallet; + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct UTXO { + value: u64, + owner: Wallet, +} +impl UTXO { + pub fn new(value: u64, owner: Wallet) -> Self { + Self { value, owner } + } + pub fn value(&self) -> u64 { + self.value + } + pub fn owner(&self) -> Wallet { + self.owner.clone() + } + pub fn sum(vec: &T) -> u64 + where + T: IntoIterator, + T: Clone, + { + vec.clone().into_iter().map(|utxo| utxo.value).sum() + } +} + +impl Display for UTXO { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let owner_pem = self.owner.to_pem(); + let owner = if let Ok(val) = String::from_utf8(owner_pem) { + val + } else { + panic!("Invalid UTF-8 when getting UTXO owner") + }; + write!(f, "VALUE::{}::OWNER::{}", self.value, owner) + } +} + +impl PartialEq for UTXO { + fn eq(&self, other: &Self) -> bool { + self.value == other.value && self.owner == other.owner + } +} +impl PartialOrd for UTXO { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl Eq for UTXO {} +impl Ord for UTXO { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.value.cmp(&other.value) + } + + fn max(self, other: Self) -> Self + where + Self: Sized, + { + if other.value < self.value { + self + } else { + other + } + } + + fn min(self, other: Self) -> Self + where + Self: Sized, + { + if other.value < self.value { + other + } else { + self + } + } +} + +// If you want to use sum for a collection of UTXOs, use the func UTXO::sum() diff --git a/src/chain/wallet.rs b/src/chain/wallet.rs index 7a8931c..1fe2b1a 100644 --- a/src/chain/wallet.rs +++ b/src/chain/wallet.rs @@ -1,3 +1,6 @@ +use crate::chain::ordered_vector::OrderedVec; +use crate::chain::utxo::UTXO; + use super::transaction::TransactionInfo; use openssl::error::ErrorStack; @@ -6,8 +9,22 @@ use openssl::pkey::{PKey, Private, Public}; use openssl::rsa::Rsa; use openssl::sign::{Signer, Verifier}; use openssl::symm::Cipher; +use serde::de::{self, Error}; use serde::{Deserialize, Serialize}; +// ------------------------------------------- Wallet errors definition -------------------------------------------- +#[derive(Debug)] +pub enum WalletError { + InsufficientFunds, +} +impl std::fmt::Display for WalletError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Insufficient funds on the wallet to conduct operation") + } +} +impl std::error::Error for WalletError {} +// ----------------------------------------------------------------------------------------------------------------- + // ---------------------------------------------- WalletPK definition ---------------------------------------------- #[derive(Debug)] pub struct WalletPK { @@ -39,7 +56,10 @@ impl WalletPK { ) .unwrap(); - Wallet { public_key } + Wallet { + public_key, + available_utxos: None, + } } } impl From> for WalletPK { @@ -51,33 +71,58 @@ impl From> for WalletPK { // ----------------------------------------------------------------------------------------------------------------- // ---------------------------------------------- Wallet definition ------------------------------------------------ -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct Wallet { + #[serde( + serialize_with = "serialize_public_key", + deserialize_with = "deserialize_public_key" + )] public_key: PKey, // Should I store this as PEM or as PKey? + available_utxos: Option>, } -impl<'de> Deserialize<'de> for Wallet { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let bytes: Vec = Deserialize::deserialize(deserializer)?; - let public_key = PKey::public_key_from_pem(&bytes).map_err(serde::de::Error::custom)?; - Ok(Wallet { public_key }) +impl PartialEq for Wallet { + fn eq(&self, other: &Self) -> bool { + self.to_pem() == other.to_pem() } } -impl Serialize for Wallet { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let pem = self - .public_key - .public_key_to_pem() - .map_err(serde::ser::Error::custom)?; - serializer.serialize_bytes(&pem) +fn serialize_public_key(key: &PKey, serializer: S) -> Result +where + S: serde::Serializer, +{ + let processed: Vec = key.public_key_to_pem().map_err(serde::ser::Error::custom)?; + processed.serialize(serializer) +} + +fn deserialize_public_key<'de, D>(deserializer: D) -> Result, D::Error> +where + D: de::Deserializer<'de>, +{ + struct StringVisitor; + impl<'de> de::Visitor<'de> for StringVisitor { + type Value = PKey; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("The PEM string as a vector of u8") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: Error, + { + PKey::public_key_from_pem(v).map_err(E::custom) + } + fn visit_str(self, v: &str) -> Result + where + E: de::Error, + { + PKey::public_key_from_pem(v.as_bytes()).map_err(E::custom) + } } + + // use our visitor to deserialize an `ActualValue` + deserializer.deserialize_any(StringVisitor) } impl From for Wallet { @@ -86,14 +131,21 @@ impl From for Wallet { .expect("Could not read the public key"); let public_key = PKey::from_rsa(public_rsa).expect("Error converting from RSA to PKey"); - Self { public_key } + Self { + public_key, + available_utxos: None, + } } } impl From> for Wallet { fn from(public_key: PKey) -> Self { - Self { public_key } + Self { + public_key, + available_utxos: None, + } } } +const MAX_UTXO_SEARCH_DEPTH: usize = 100; impl Wallet { pub fn new() -> (Self, WalletPK) { let bits: u32 = 2048; @@ -107,7 +159,13 @@ impl Wallet { ) .unwrap(); - (Wallet { public_key }, WalletPK { private_key }) + ( + Wallet { + public_key, + available_utxos: None, + }, + WalletPK { private_key }, + ) } pub fn verify_transaction_info( @@ -123,5 +181,145 @@ impl Wallet { pub fn to_pem(&self) -> Vec { self.public_key.public_key_to_pem().unwrap() } + + fn estimate_fee_per_utxo() -> u64 { + 0 + } + + // TODO write some tests for this bad boy + pub fn get_utxos(&self, amount: u64) -> Result, WalletError> { + let available_utxos = self.available_utxos.clone().expect("Found no UTXOs"); + if UTXO::sum(&available_utxos) < amount { + return Err(WalletError::InsufficientFunds); + } + let mut solutions: Vec> = Vec::new(); + + // Gets the first UTXO (if there is such a value) that's is bigger than the requested + // amount + let index = match available_utxos + .clone() + .into_iter() + .enumerate() + .find(|(_, utxo)| utxo.value() > amount) + { + Some((index, value)) => { + solutions.push(vec![value]); + index + } + None => available_utxos.len(), + }; + + // https://bitcoin.stackexchange.com/questions/1077/what-is-the-coin-selection-algorithm + // if the sum of all yout UTXO smaller than the target happens to match the target,they + // will be used + + fn calculate_recursion_depth( + max_depth: usize, + elements_tested: usize, + total_elements: usize, + ) -> u32 { + if total_elements == 0 { + return 0; + } + let fraction_used = elements_tested as f64 / total_elements as f64; + let new_depth = (max_depth as f64 * (1.0 - fraction_used)).ceil() as u32; + std::cmp::max(new_depth, 1) + } + + fn recursive( + slice: &[UTXO], + solutions: &mut Vec>, + target: u64, + x: &mut Option, + ) { + if slice.is_empty() { + return; + } + if let Some(ref mut depth) = x { + if *depth == 0 { + return; + } + *depth -= 1; + } + let mut sum = 0; + let k = slice.len() / 2; + let mut elements: Vec = vec![slice[k].clone()]; + sum += slice[k].value(); + + for i in 0..k { + sum += slice[k - i].value(); + elements.push(slice[k - i].clone()); + if sum > target { + // if there's no x yet, we calculate it here. The x is a function of the number + // of elements necessary in the first iteration. If many elements are needed in the + // first iteration, that means that if I continue for too many times there will be a + // lot of overlap. Therefore, we reduce the size of x + if x.is_none() { + *x = Some(calculate_recursion_depth( + MAX_UTXO_SEARCH_DEPTH, + i, + slice.len(), + )); + } + + solutions.push(elements); + break; + } + } + recursive(&slice[0..k], solutions, target, x); + recursive(&slice[k..], solutions, target, x); + } + + let dust_threshold = Self::estimate_fee_per_utxo() * 3; + let target = amount + dust_threshold; + let utxos_smaller_than_target = available_utxos.get_slice(0..index).to_vec(); + let sum: u64 = UTXO::sum(&utxos_smaller_than_target); + + if sum == amount { + return Ok(available_utxos.get_slice(0..index).to_vec()); + } + if sum > target { + // I'll just use the shit algorithm I invented. Goes like this: + // Inputs are target amount and the ordered vector V that contains all available UTXOs + // target = target_amount + dust threshold + // First, we get the smallest element bigger than the target on the vector with index i + // smallest elements vector = s = [0..i] + // + // We select the middle element of s (index k) and sum it with the following element (k+1). + // If it's still smaller than target, sum it with k-1. + // If still smaller than target, sum with k+2, and then k-2, k+3, and so on, until we find a solution + // and save it as an option in a solutions vector + // + // We do the same now, but starting the element in the middle of the slice s[k..], + // and again with the element in the middle of s[0..k]. This creates kind of a binary search through the vector. + // + // We repeat the process recursively x times, x being arbitrarily defined + recursive( + &utxos_smaller_than_target, + &mut solutions, + target, + &mut None, + ); + } + + let best_solution = solutions.into_iter().fold(None, |acc, new_vec| match acc { + None => Some((UTXO::sum(&new_vec), new_vec)), + Some((old_sum, old_vec)) => { + let new_sum = UTXO::sum(&new_vec); + if new_sum > old_sum { + Some((new_sum, new_vec)) + } else { + Some((old_sum, old_vec)) + } + } + }); + + match best_solution { + Some((_, vec)) => Ok(vec), + // This is probably safe for an unwrap but fuck it + None => Err(WalletError::InsufficientFunds), + } + } } + // ----------------------------------------------------------------------------------------------------------------- diff --git a/src/lib.rs b/src/lib.rs index 32c760f..806c1af 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,13 +1,14 @@ use crate::{ chain::{ - transaction::{self, Transaction, TransactionInfo}, + transaction::{self, Transaction, TransactionError, TransactionInfo}, + utxo::UTXO, wallet::{Wallet, WalletPK}, Chain, }, node::ui::App, }; use openssl::pkey::{PKey, Private, Public}; -use reqwest::Client; +use reqwest::{Client, StatusCode}; use std::{ io::Write, os::unix::net::UnixListener, @@ -21,7 +22,7 @@ use configs::KILL_SERVER_SOCKET_PATH; pub mod chain; pub mod node; -async fn send_transaction(transaction: transaction::Transaction) { +async fn send_transaction(transaction: transaction::Transaction) -> Result<(), TransactionError> { let client = Client::new(); let transaction_json = transaction.serialize(); @@ -38,10 +39,13 @@ async fn send_transaction(transaction: transaction::Transaction) { // Check the response status let status = response.status(); let response_body = response.text().await.unwrap(); + match status { + StatusCode::OK => Ok(()), - // Print the response - println!("Response Status: {}", status); - println!("Response Body: {}", response_body); + _ => Err(TransactionError::ConnectionError(format!( + "Error: {status}\n{response_body}" + ))), + } } fn read_key_string_or_file(string: &Option, file: &Option) -> String { @@ -91,8 +95,8 @@ pub async fn send( sender_key: Option, sender_key_file: Option, password: Option, - amount: i64, -) { + amount: u64, +) -> Result<(), TransactionError> { let recipient_key_str = read_key_string_or_file(&recipient_key, &recipient_key_file); let sender_key_str = read_key_string_or_file(&sender_key, &sender_key_file); @@ -112,8 +116,20 @@ pub async fn send( let sender_wallet = WalletPK::from(sender_pkey); let recipient_wallet = Wallet::from(recipient_pkey); + // find input utxos + let input_utxos = match sender_wallet.public_wallet().get_utxos(amount) { + Ok(vec) => vec, + Err(_) => return Err(TransactionError::InsufficientFunds), + }; + + // Create output UTXOs + let input_sum = UTXO::sum(&input_utxos); + let rec_utxo = UTXO::new(amount, recipient_wallet.clone()); + let change_utxo = UTXO::new(input_sum - amount, sender_wallet.public_wallet()); + let output_utxos = vec![change_utxo, rec_utxo]; + // create transaction info - let transaction_info = TransactionInfo::new(amount); + let transaction_info = TransactionInfo::new(input_utxos, output_utxos); // sign the transaction let signature = sender_wallet @@ -129,7 +145,7 @@ pub async fn send( .inspect_err(|e| eprintln!("Failed creating the transaction: {e}")) .unwrap(); - send_transaction(transaction).await; + send_transaction(transaction).await } pub fn run_server_with_gui() -> color_eyre::Result<()> { diff --git a/src/node/logger.rs b/src/node/logger.rs index 0b4d2de..76556af 100644 --- a/src/node/logger.rs +++ b/src/node/logger.rs @@ -1,8 +1,9 @@ use chrono::prelude::Utc; +use serde::{self, Deserialize, Serialize}; use std::io::{self}; use std::sync::{Mutex, PoisonError}; -#[derive(Default)] +#[derive(Default, Serialize, Deserialize)] pub struct Logger { logs: Mutex>, temp_logs: Mutex>, diff --git a/src/node/mod.rs b/src/node/mod.rs index cb63666..27e8a04 100644 --- a/src/node/mod.rs +++ b/src/node/mod.rs @@ -50,9 +50,13 @@ impl Default for NodeConfig { #[derive(Serialize, Deserialize)] pub struct Node { state: Arc>, - #[serde(skip)] // The logs are manually saved on shutdown and reloaded on initialization + + // The logs are manually saved on shutdown and reloaded on initialization + #[serde(skip)] logger: Arc, - #[serde(skip)] // The configs are best reloaded with every initialization + + // The configs are best reloaded with every initialization + #[serde(skip)] config: NodeConfig, } @@ -221,7 +225,6 @@ impl Node { request_object.set_stream(stream); - // TODO add logging resolve_endpoint(state, request_object) } diff --git a/src/node/resolve_requests/endpoints.rs b/src/node/resolve_requests/endpoints.rs index fcd83c6..1599f94 100644 --- a/src/node/resolve_requests/endpoints.rs +++ b/src/node/resolve_requests/endpoints.rs @@ -5,11 +5,10 @@ use super::helpers::{ HTTPResult, Handler, POSTFunc, }; use super::methods::{Content, GETData, HTTPRequest, HTTPResponse, ImageType, Method, POSTData}; -use crate::chain::transaction::{ - Transaction, TransactionDeserializeError, TransactionValidationError, -}; +use crate::chain::transaction::{Transaction, TransactionDeserializeError, TransactionError}; use crate::node::NodeState; use chrono::Utc; +use core::panic; use serde_json::json; use std::collections::HashMap; use std::sync::{Arc, Mutex}; @@ -44,19 +43,21 @@ pub fn submit_transaction(data: &POSTData, state: Arc>) -> HTTP Ok(()) => {} Err(e) => { return match e { - TransactionValidationError::OpenSSLError(_) => { - Err(HTTPResponseError::InternalServerError(Some( - "Error in the OpenSSL library when verifying a transaction".to_string(), - ))) - } - TransactionValidationError::ValidationError => { - Err(HTTPResponseError::BadRequest(Some( - "Transaction submitted with \ + TransactionError::OpenSSLError(_) => Err(HTTPResponseError::InternalServerError( + Some("Error in the OpenSSL library when verifying a transaction".to_string()), + )), + TransactionError::ValidationError => Err(HTTPResponseError::BadRequest(Some( + "Transaction submitted with \ invalid signature" - .to_string(), - ))) - } - } + .to_string(), + ))), + TransactionError::InsufficientInputs => Err(HTTPResponseError::BadRequest(Some( + "Transaction's outputs are bigger that its inputs".to_string(), + ))), + // TODO Should move both of those to another error enum, maybe client and server errors + TransactionError::InsufficientFunds => panic!("Not the server's problem"), + TransactionError::ConnectionError(_) => panic!("Not the server's problem"), + }; } }; @@ -79,7 +80,7 @@ pub fn favicon(_: &GETData, _: Arc>) -> HTTPResult { } pub fn status(_: &GETData, state: Arc>) -> HTTPResult { - let mut state = match state.lock() { + let state = match state.lock() { Ok(guard) => guard, Err(_) => panic!("Mutex lock was poisoned in function status on endpoints"), }; diff --git a/tests/create_block_and_add_chain.rs b/tests/create_block_and_add_chain.rs index c9ab1c9..b3b4972 100644 --- a/tests/create_block_and_add_chain.rs +++ b/tests/create_block_and_add_chain.rs @@ -1,6 +1,7 @@ use cleyto_coin::chain::{ block::Block, transaction::{Transaction, TransactionInfo}, + utxo::UTXO, wallet::Wallet, Chain, }; @@ -10,7 +11,15 @@ fn create_block_and_add_chain() { let (wallet1, wallet1_pk) = Wallet::new(); let (wallet2, _) = Wallet::new(); - let transaction_info = TransactionInfo::new(105); + let input_utxos = vec![ + UTXO::new(1000, wallet1.clone()), + UTXO::new(2000, wallet1.clone()), + ]; + let output_utxos = vec![ + UTXO::new(2500, wallet2.clone()), + UTXO::new(500, wallet2.clone()), + ]; + let transaction_info: TransactionInfo = TransactionInfo::new(input_utxos, output_utxos); let signature = match wallet1_pk.sign_transaction(&transaction_info) { Ok(value) => value, diff --git a/tests/create_wallets_and_send_transaction.rs b/tests/create_wallets_and_send_transaction.rs index 45fa6ed..4d424ba 100644 --- a/tests/create_wallets_and_send_transaction.rs +++ b/tests/create_wallets_and_send_transaction.rs @@ -43,7 +43,8 @@ async fn test_send_transaction() { sender_password, 100, ) - .await; + .await + .unwrap(); kill_server(); } diff --git a/tests/node.rs b/tests/node.rs new file mode 100644 index 0000000..6dbc5ef --- /dev/null +++ b/tests/node.rs @@ -0,0 +1,18 @@ +use cleyto_coin::{ + chain::{block::Block, Chain}, + node::Node, +}; + +#[test] +fn serialize_and_deserialize_node() { + let mut chain = Chain::new(); + + chain.add_block(Block::test_block(&chain)); + chain.add_block(Block::test_block(&chain)); + chain.add_block(Block::test_block(&chain)); + chain.add_block(Block::test_block(&chain)); + + let node1 = Node::new(chain); + let node_json = serde_json::to_string(&node1).expect("Could not serialize node"); + let _: Node = serde_json::from_str(&node_json).expect("Could not deserialize node"); +} diff --git a/tests/requests.rs b/tests/requests.rs index 0a4fbcd..1a90fab 100644 --- a/tests/requests.rs +++ b/tests/requests.rs @@ -1,4 +1,5 @@ use cleyto_coin::chain::transaction::{Transaction, TransactionInfo}; +use cleyto_coin::chain::utxo::UTXO; use cleyto_coin::chain::wallet::Wallet; use cleyto_coin::{kill_server, run_server_thread}; use std::thread; @@ -14,7 +15,15 @@ fn thread_post(n: u16) { let (wallet1, wallet1_pk) = Wallet::new(); let (wallet2, _) = Wallet::new(); - let transaction_info = TransactionInfo::new(105); + let input_utxos = vec![ + UTXO::new(1000, wallet1.clone()), + UTXO::new(2000, wallet1.clone()), + ]; + let output_utxos = vec![ + UTXO::new(2500, wallet2.clone()), + UTXO::new(500, wallet2.clone()), + ]; + let transaction_info: TransactionInfo = TransactionInfo::new(input_utxos, output_utxos); let signature = match wallet1_pk.sign_transaction(&transaction_info) { Ok(value) => value, diff --git a/tests/transactions.rs b/tests/transactions.rs index 36b20b8..43095ee 100644 --- a/tests/transactions.rs +++ b/tests/transactions.rs @@ -1,13 +1,23 @@ use cleyto_coin::chain::transaction::{Transaction, TransactionInfo}; +use cleyto_coin::chain::utxo::UTXO; use cleyto_coin::chain::wallet::Wallet; #[test] fn create_transaction() { let (wallet_sender, walletpk_sender) = Wallet::new(); let (wallet_receiver, _) = Wallet::new(); - let transactioninfo: TransactionInfo = TransactionInfo::new(12345); - let signature = match walletpk_sender.sign_transaction(&transactioninfo) { + let input_utxos = vec![ + UTXO::new(1000, wallet_sender.clone()), + UTXO::new(2000, wallet_sender.clone()), + ]; + let output_utxos = vec![ + UTXO::new(2500, wallet_receiver.clone()), + UTXO::new(500, wallet_receiver.clone()), + ]; + let transaction_info: TransactionInfo = TransactionInfo::new(input_utxos, output_utxos); + + let signature = match walletpk_sender.sign_transaction(&transaction_info) { Ok(signed_hashed_message) => signed_hashed_message, _ => panic!("error while signing transaction"), }; @@ -18,7 +28,7 @@ fn create_transaction() { // this will also be verified by the Transaction::new(); if wallet_sender - .verify_transaction_info(&transactioninfo, &signature) + .verify_transaction_info(&transaction_info, &signature) .unwrap() { println!("transaction verified (by the wallet)"); @@ -27,24 +37,45 @@ fn create_transaction() { } let transaction: Transaction = - Transaction::new(wallet_sender, wallet_receiver, transactioninfo, signature).unwrap(); + Transaction::new(wallet_sender, wallet_receiver, transaction_info, signature).unwrap(); println!("transaction.to_string(): {}", transaction); } #[test] fn test_transaction_info_creation() { - let transaction: TransactionInfo = TransactionInfo::new(123452); - println!("transaction info:\n{}", transaction); - println!("{:?}", transaction); + let (wallet_sender, _) = Wallet::new(); + let (wallet_receiver, _) = Wallet::new(); + + let input_utxos = vec![ + UTXO::new(1000, wallet_sender.clone()), + UTXO::new(2000, wallet_sender.clone()), + ]; + let output_utxos = vec![ + UTXO::new(2500, wallet_receiver.clone()), + UTXO::new(500, wallet_receiver.clone()), + ]; + let transaction_info: TransactionInfo = TransactionInfo::new(input_utxos, output_utxos); + println!("transaction info:\n{}", transaction_info); + println!("{:?}", transaction_info); } #[test] fn sign_and_verify_transaction_info() { - let (wallet, wallet_pk) = Wallet::new(); - let transactioninfo: TransactionInfo = TransactionInfo::new(1234532); + let (wallet_sender, wallet_pk) = Wallet::new(); + let (wallet_receiver, _) = Wallet::new(); + + let input_utxos = vec![ + UTXO::new(1000, wallet_sender.clone()), + UTXO::new(2000, wallet_sender.clone()), + ]; + let output_utxos = vec![ + UTXO::new(2500, wallet_receiver.clone()), + UTXO::new(500, wallet_receiver.clone()), + ]; + let transaction_info: TransactionInfo = TransactionInfo::new(input_utxos, output_utxos); - let signature = match wallet_pk.sign_transaction(&transactioninfo) { + let signature = match wallet_pk.sign_transaction(&transaction_info) { Ok(signed_hashed_message) => signed_hashed_message, _ => panic!("error while signing transaction"), }; @@ -53,8 +84,8 @@ fn sign_and_verify_transaction_info() { signature ); - if wallet - .verify_transaction_info(&transactioninfo, &signature) + if wallet_sender + .verify_transaction_info(&transaction_info, &signature) .unwrap() { println!("transaction verified (by the wallet)"); @@ -67,14 +98,23 @@ fn sign_and_verify_transaction_info() { fn serialize_and_deserialize_transaction() { let (wallet, wallet_pk) = Wallet::new(); let (mallet, _) = Wallet::new(); - let transactioninfo: TransactionInfo = TransactionInfo::new(1234552); - let signature = match wallet_pk.sign_transaction(&transactioninfo) { + let input_utxos = vec![ + UTXO::new(1000, wallet.clone()), + UTXO::new(2000, wallet.clone()), + ]; + let output_utxos = vec![ + UTXO::new(2500, mallet.clone()), + UTXO::new(500, mallet.clone()), + ]; + let transaction_info: TransactionInfo = TransactionInfo::new(input_utxos, output_utxos); + + let signature = match wallet_pk.sign_transaction(&transaction_info) { Ok(signed_hashed_message) => signed_hashed_message, _ => panic!("error while signing transaction"), }; - let transaction = Transaction::new(wallet, mallet, transactioninfo, signature).unwrap(); + let transaction = Transaction::new(wallet, mallet, transaction_info, signature).unwrap(); let serialized_transaction = transaction.serialize(); println!("serialized_transaction: \n{serialized_transaction}"); diff --git a/todo.txt b/todo.txt index e69de29..4d0a36f 100644 --- a/todo.txt +++ b/todo.txt @@ -0,0 +1 @@ +write tests for the new get_utxos function, check if it panics or if something bad happens diff --git a/wallets/receiver/private.pem b/wallets/receiver/private.pem index 5727544..8489cc6 100644 --- a/wallets/receiver/private.pem +++ b/wallets/receiver/private.pem @@ -1,28 +1,28 @@ -----BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC62M47RFlHGg2Q -8eUcoOv6x0Z4L+VTP9iumYxDY8IC9mjjwwPngJ2RWH5odT7i2HjpuCpArrtbtnBt -BJssNSzrmiamYPQDTsLdfgXa0EkaswFvT0Bo4CgVIfEYoEhY+gfRd8xwnnuAm+SE -TG331oFV0ykPYyJj6KfVKU4IP66E5TWP1hgdzscgsLfOItBK61rcG20LkuAoOZ1Y -n9sk/loV7+Oe2/VbAnXnRafiaqg1uXlmbD1tJaq290+L0xbnm48LX7TXx1S0gKP7 -eI0RUFEQWpMt3jPAJsEu8BXpUe3l05WoXqKcK/xac0jn884Xwgn4ojmRxWxCD+AS -BonnkMZ7AgMBAAECggEAGqQsL64srqMr2QVsnhrh2tWMGjn2EFszqZ2SLs+sC6EX -zpXxWhJwJRfBmmJRT6X/A5AzUhq38zk9qYW1HGYOnB9UMHf1gv05crxz2EHHkquA -Yl8h93IytOSsjE7aUfmAoODcgRhP0sQnnqeJLIPk2BhuK9f+QNGdEJs9mZZ+60rG -wKwtKFzE5AxFvbOp3glD8zNTOcWF1g95WwC/zl87O+App8i2P/ADIfQZjMFXseHM -eB0W1bdC79DQYEnSSK4PhDrZi9Dn5rKT0mHrUvwTxOFda5PypwdcjmYtX3EtNbTC -XnsksD3+eiR015RN0QI1MC2ciMFCXZKV1ZyUwNcQCQKBgQDrfvis8BMWpSAS0Ufx -kIThdjPXPyOpgMg53M567QnI4YdGm+FEixIL54HkqKPW4vv1Z1mJMszAq/PmbM7l -zjqBCmf7EcMU0dJENs9ztvxEFhbJTbHyMJxvTebwPbMNKa1jijngCgIAN3v/usIu -eLcRniud7p+KCRLu+7yHOIhYiQKBgQDLHXuNRYrH7L8Lh+epdxlQeD0TZFZZTraQ -tvnpxQBmN8D2wwABKPfgozOemfrbexuxVrzt378ttWGCiqmpELZtpfLlfh4ecjHR -jse1Sta1PmT76mioUq4fsk5JZChG/jOJFEDdDVgdCQKgzQ7Y8DrIupnDv5cpeFCV -alP4+Vzd4wKBgA/CpZ3dS0m+r1DTFL+D7oCwSnPjU92qMrw8nMz5RChff5AoKoFN -1V1alwTYjG57LmWUDxfxgGZ8m4wWSBp1HNE6Aw94dtfT2wCZsnSKEIP1/nxmaLxO -qImCayw01ODSozoALDNkOBIPpIrO0uQNTCMvtLeTGrZAOTYpPsrF68oRAoGAHTEY -j7496uvorTSUI/P9DGariHz0OBOrA3N4yZNJsmcy8YQnHQORVJTAF3xT5HV2Dm/D -6YAtztjxsbxSE/1V6r5eAYO1unR5GoAlzbV61EOqadZPwllXszCCNeQSMdnY+EuT -4vFHB61C1m+CQcWM/UpsTQjdJdWn50GH3hGgujcCgYEAnjLKWbtQHDHqSP4x4iPJ -1rTpNe79eD8caKxCz+p8I++WSGwnNm3qsqhivNUc518oQsaaFZkolnR+r/AlB0Us -eJ1x5kipkZCwzMX5V+3625VxUe6RNwAgwL3rhCDr6SOuu1WpvPRne/wFe55DrIwK -+gF2YhCyM++JU6n4YxmGtN8= +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDJGljHoOnQgY2y +Za1tabb7ZXGxp8aRq2OPFpcQ+IbNrDtgvh+ihj/n7lA4WVejj50n7xIUIP/TkEo/ ++ofZVYIwO9i6L5cDfY+zrAFpb6980nTCeLYk1CjGsNBEIW5UE53+sxXqeOH1QuyL +0ARrYDxvESyRifDArwksqKkgmmU5cJkD6uzv2JKPAmuRU6qlB6chwFQFkaPdIVc+ +G3ayPzGuzh9PmtnRlM0zeDrn3aEmEXC8Fr22MkAnk5xQb+ePPRyhqjd6U6g5IHvw +i8czKRfKdTzS9k875B15+L4fWi8QcP4vVJHbQc8lRgD6a1NDJczd2w88eeHKylNl +LPxOClJ5AgMBAAECggEACEASda4o/tWHJf3R4vPmqThSNRhNpzBBggn1N0xa4MPj +c7vuZFE0UfmO76DgCDpzbMNRbzijW4nx4d9kr1/jWsM5yE7817cmVsKvFCTxgszH +iYBA82XY073xQrpJt8f46rgXer/BgA8M0UzpbCujOb3vTCaSlUpuX+yoyPOh73yX +yVsDNlN71cIKqM0sLdzFNuYcO37IXEMWfgAZ71bfRge9YmPKZJtA3IPktgmlFe49 +b0rGoKIWqiIgyv96CtvFyTmFOWuJ6ik1l/eYJ38dSPkfRk/ol0QjghmvcObiR747 +q0AOiVUD4oVc3w/Mj1Gdzxciol6v0vZQk35cbZavGQKBgQDrNmpqqTB9onBcBmEu +mPOU5ZNoNpe5O+XK157VaS8VKxebudypZaqPo2JdsF/hkx9tPkrJfMs8kAAPRMFE +6JhWu8pt+sQ294kMU0wHUySH4IA85d+PcB5ZCUi8IghDbzB8NuWp5GWkYwzoy0YZ +30Ja7NKW3/+aHXU8AZ5wFTSgYwKBgQDa4DcDQmafT6s4mFtRSuSdiJoOYI7IQM0X +X5Mrlw/c00d8dpdUoPOPBmWZv3ywcY67zsW9sNwx2F+qhLSxiTVPi+5HykNFS7RH +EVxt4wHinX4mAJcjqMqEj2xivCmQWs7QabN7GNzQGF5ZdfwQZ64lASEwTJwYKq9V +zerZinyCcwKBgC/9/mCc/OPljP953cJgOvMalKUi3npRGmX08NeiipTLIhoIJln6 +AH8mWx+6qRWhqzvjBeduqxlEWH7FDJo+yzaHQpqGHBsLDs+Q/2ZPNJj02bWTQbZG +riqElm6skvsPaNkvalTr4UFVZIDrWPZWc3eR8rYOJl0PvafsvKMp8H75AoGBAI7Z +qW36J2owoApW/bqHy7+5SPq7MFUoXfK0USQw+oxgZJap+8ijJ3MgdK0s2d96rfKL +WGmehYgOtRlgdWItr9qT9Fdsfg07BJUhkpaxgyh5K7z3w6zlXA+6X73tGp95dON3 +KUndBzjVvrZal8HJOVIzc4rHZVUsfrTcqTuD9BFbAoGAe3qcjPZs6mUt02I56TWD +vIhFeVFF7KDDhVikjoNqM6f4tW2TzQoE0mjvxSx5vNgqOSk9bUgk0MeTKMYiIbdU +N5+zaSyMT98tr88FNBzJ4MyV/IU4RYDAZ3tZH+xtdM/85Flhs7YMaPGgGlTluMUT +z7sLsP3M1pZEZGQoSRhhma0= -----END PRIVATE KEY----- diff --git a/wallets/receiver/public.pem b/wallets/receiver/public.pem index 4d906f4..ac08f7a 100644 --- a/wallets/receiver/public.pem +++ b/wallets/receiver/public.pem @@ -1,9 +1,9 @@ -----BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAutjOO0RZRxoNkPHlHKDr -+sdGeC/lUz/YrpmMQ2PCAvZo48MD54CdkVh+aHU+4th46bgqQK67W7ZwbQSbLDUs -65ompmD0A07C3X4F2tBJGrMBb09AaOAoFSHxGKBIWPoH0XfMcJ57gJvkhExt99aB -VdMpD2MiY+in1SlOCD+uhOU1j9YYHc7HILC3ziLQSuta3BttC5LgKDmdWJ/bJP5a -Fe/jntv1WwJ150Wn4mqoNbl5Zmw9bSWqtvdPi9MW55uPC1+018dUtICj+3iNEVBR -EFqTLd4zwCbBLvAV6VHt5dOVqF6inCv8WnNI5/POF8IJ+KI5kcVsQg/gEgaJ55DG -ewIDAQAB +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyRpYx6Dp0IGNsmWtbWm2 ++2VxsafGkatjjxaXEPiGzaw7YL4fooY/5+5QOFlXo4+dJ+8SFCD/05BKP/qH2VWC +MDvYui+XA32Ps6wBaW+vfNJ0wni2JNQoxrDQRCFuVBOd/rMV6njh9ULsi9AEa2A8 +bxEskYnwwK8JLKipIJplOXCZA+rs79iSjwJrkVOqpQenIcBUBZGj3SFXPht2sj8x +rs4fT5rZ0ZTNM3g6592hJhFwvBa9tjJAJ5OcUG/njz0coao3elOoOSB78IvHMykX +ynU80vZPO+Qdefi+H1ovEHD+L1SR20HPJUYA+mtTQyXM3dsPPHnhyspTZSz8TgpS +eQIDAQAB -----END PUBLIC KEY----- diff --git a/wallets/sender/private.pem b/wallets/sender/private.pem index 47191ba..6f983a3 100644 --- a/wallets/sender/private.pem +++ b/wallets/sender/private.pem @@ -1,30 +1,30 @@ -----BEGIN ENCRYPTED PRIVATE KEY----- -MIIFNTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQP35poY8WMHxZ1Xv/ -N6Sy/wICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEFrg0obma7BnSavp -fzGthyUEggTQmH2qIChPR4CXmazecUhImySwgqlMQn0i3bNGlhtDOLa332KAgrrG -HZoyRedJCE7W8fumORaxOt+52IYxE5s99vRKy0A5TXjQcewCqXk9YPXhbZ4mjC8n -xskfIq7yWKoijSc4fvjK5QKzhoqhO9Xy8SIzRbddhZKEoxjmf6nuD9ptAaaAIGAW -NXiiC3MNtJafMcpZ02FOleUrnfsDhsrEoXXVQHBCZglVzBdAV0vXPNBwYUJfy4mH -6pAyhKHCPgDaps1L4vNYiFxFvty6NAEjG2XN8sjH4vaBCC8uRns2u499kOkYXBBv -tVlqRlHDSGAWlZcs7GlTsgcfR2QLqXhvOXaOnYnoRKsaZxqczdfndel//J1bu0cW -RNf3QI1LIRFo2AX3LvNiBuQk7yrCd+JvI74OW0MtTonMjPpcYs6lEe/c3tMEhLwB -UbFyR0DKCCt4ilEdjn3lzl6cMvhpOjrbMfvQ58mYyccxBWSmu1v1lLzb8e924AAV -b3wESFKBPzrS0LVNak1YUTYvyQH5R4P2o9DK+/KHiC0gDSzTubrzXgeGIsQiFbbp -Wuk3lH3HLo9uNNzXEwOdB7/KMOnuTPXcyvEmBSYsP5kzX6tSaz9u7RdAYj6H+S0o -nlR/VfSDMvWZ7ibDLoBvN2arrbAHgYyuuw+NaUs7YNkz2RqkWWSyFFNYrrbCiDQA -RKYYz/SFhxFvx9Yn5bDid0Wabx4dVYEdTWSnXD/+I4JZDqWDGjXp/9OlhiNG/jGL -vUnrSaD2cc2AM5V1sVtjjgIB4wokNZ87f2Pqbckz5QzOHDzavUsEmBlnt+GNhDNX -wAVL2FneqCekFqvVhjq8X6e8TVpplVzfFogasffBroTqup6FSJspy92zE8JMh9CM -sdiXwlOWmQ3q7MLGNeVQIe1tuJAFLh8l3hsgF3rT95Sxt5gtHNbmpQlznKMFymZs -6b/OYLqVg5CD6SnbTTDWrKp3+sS+AiDo4iAbcI0IPnd95OlURzGOCL6zIrwIyhQK -f94Ny7lJ1xRnDUwJarsjsX+ttURD2QG1Mw6Z9zgQzw8h5HzgVLQR7AbvVVL55A3I -fqUBjoU18Rb0Ho3jne6ap7fMysMiUp1DNqPgYVgBcrhYLpw1ZT09bazQ2eGivVaN -oFelA6luNCbAk13ocVRye12zQeVmpmL6eIegwIrYZmWhIyiWE+ixYEpSYA90nMCG -dY3xH1QQtQJNfjs38uLDr1WQ3MNWu20zG3bCFvE1FxVjtShBei6UV6ZuLFPp1yvb -C0vs3BiNn4EuPU9z8xxE4XqZGCkvlQR1BfxqRUiMrKokxc/q16eFheqDLsoDQ5F6 -fsIKNkDDi/neKuoVmy+ypvExC2fdUDpiMTwNKInszzrVmDWlxqZH7SsvM1Ptv8/T -YDbIDQg24F2k2fSNo99Rc312Jex3OaT2YLzTxdrzPl+CuV+L5krN9Ysn9XaIivlm -mDf7lltOA8gd+p1vGF+Paw1TW4BLdY0aTrkKMd58wGQxieS6h8UQYC6W2DK2DV5P -rqrHoUmSOm1ShTQNC4UbjtTe14vOjQY2rKmqf7IJlijM8BdJzHhLQpPiKd3ov3dV -io+RvQb23KsankznJgTwgXYqlpLY9dwSynsaCwYxLBAJvHGPUZ+alIY= +MIIFNTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQjWPn1hghIOF4nfRA +m43WcQICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEELMuQ1uX/Sm/LOD +4vWpb+gEggTQf+VfrPAkqZQf9ioamfQunk3s826GEj6UmET6AMTzq/fwCuk7FqTc +y4AYtZgcl0ZCI0sUZckARe21MbDLK3Khnkl1DisgSpJ9wQ7GH2+4i7dkOntSVnx4 +213pycsECx4F4KR0vuQZYCt28YY7DF2nH2qZHPFlfNZsUEkFYC975Uc/a1DHBesA +15tsasnjxREVE0VNITmfq02dyRFb9dodasTVSjorlX/UqjNc3ELmyoxuSd+32Mwp +5S4cwX6WaVvhm00ywEWYhDB5Q7yLLTp1X95M1EGF2tpph4iYLjrvNVYpXA/BRJVq +M/U1vur80UB2XHTiKdVwg7SOw3K+qP4eznM0+XokklRCWd/rvzJnFAEEqOsojvM2 +YyNhuPGaIcoLzOZQiGDpSnBy7saT+goX3GjScBceBJtxn/7ZxUe0BVwK3OgG7Chk +XXEg7VH2OoB+WBjP75vAyRhm0z06OvZu1C/zwigsVv/gg4FgcN0i26Mi3xprpH4+ +qvgyICAi1VNWUIUcBUx+g5rjxiMOCsMPJi3ipAHsd78VTlhgAXvpJMGhzAY3QXpj +y8NzJ1HAuQgGim+UfnUd5T6OgzFLc2/nbWjbkh0i3pdTz9Tm9nwqZ6f2pU6dATzZ +mGHAPvU0MNZa+nMOmSPCsbNpFGSLVjt0uet0KS4zAe4j+mG9IOmv2CvBoWuCQeok +k3akNEhqVWhz3HNRSS2cqkXSxtfEf6/SEDpuzLq7y53ZL5933dgTiHnvEePkMKhO +w9/kTdLb1VO63RpI7Zn0Ms7/fbC72dPt9U6ucazMD8/035AlO1y6Ql4ce3WyBQUz +0JKRLH/vKMVUS2vlXzsGN+DQ2+gzO8/Vu1n5rAnNi5LM4JIdzhOmtzdUT3zW53uu +H6iwwbpEmF/gi5X6udr8jAl9xTN3KiHNkfYj/9BitZho5Ogs1GtRnMDkkQVU/NPq +oEWAGo0zLN6WX8aQVGBVqFnkScXcul2RCQaH8H/scsLz04w+Vs9Ium+YDgklbYOF +WRFhu+sNiakU7CYXU5wI9f21IdfxpLsy/y1JThgusllihI2hUXi5DaBFobm/NiHX +7BiJq0Vb8IbF9YRVFRid/KliU50WHz9hsWrLrB2m6buSw4lMJbkoaXIkXM/xUtDQ +go9UWMZqW5YmIIu3O9tmFUMd7fj7XsTk6atUcY/9r8mIoPQAn/YMNniKCY3sKgJS +3o30RpuT4pj2MsqMBSc0AjLgl+cdcROChhyr+CmeF9Cocej/gyXZ/qTj//+tru0G +h2fOTUsViaa8LQuP8rxDkP3r6Lgd3agHxh2q6HfNKFfxFtoZ6LMFi5i9yspuXqSW +CycsTRrSJg4RTh60PbiWwSvQeeMuX1NSL+ZvVONmaXxMexquJxiLYEKDqdBnFLiY +BfMDB942nBkTeZCr9QQ1hqCFR4bCvzODYwvaXXeiAbQZu20GCVYu9PLX/MTI2Rwe +MO/Kux/TxTV3lB20raaZVxaeBlxHXqio70JzHOyzMcrBeAjnlKFRhev3ZGKWusXi +Fh/yIUDd1PmGNqga92+6AZMPMfN+zv+ml66h4763PKqwFuOtR7r1AJCxCG2yLPON +sOg0pNZ8qdjUca3GTT/rS6xlAUzf1ZThOMEDH0tPGiQJt/8yGSCPawk= -----END ENCRYPTED PRIVATE KEY----- diff --git a/wallets/sender/public.pem b/wallets/sender/public.pem index 3df2cfe..d2fc9f0 100644 --- a/wallets/sender/public.pem +++ b/wallets/sender/public.pem @@ -1,9 +1,9 @@ -----BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwDbWXqa8Ad1LfecOlzn6 -FlgExVOuLsf6DJa0STejFHZtrAos4aDXMihLRLNxDsxK8/GAwz2sIrBc6quSO+HU -/x3l2DLW/VEWtmVp8Ai0D03anmF7JNwXh6OHQsGaJeWfXz/219cLcQ/5PNGBlZlQ -NyTRUtaUlZC/ZWjF8HYcGMJ3Hb5tLsU7o1P/7SJW8e2UZwiIVGR7W1tPGuyKR/y4 -SCCFuZ8UYzX8YEPnOq7n40E+fR6m3dm5GybdRu+RreDbdpxr4tjrQGfGDOUyIHHh -NmJ9xK0WZzd7eaiFkoBVfdOMojH64iL7rvGJ2IEPQh+1hIwDFYOJ5ZPPwGp5V6HX -CQIDAQAB +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr9CsChZCtB4EfkUQYNMp +6NiqLb/0rfxtFxeDifWbK2kQuTAhJxO0KfvhE8uSR49bmhe+cQzlpVgjAEfi74bD +THSk5f2yIXh6tNMH/zuiX3vOFyawmgqP5Vl8KFvTYgQm5oRaMXjTOZWPlTLFMkPN +mV0Sx4q2mHEPAHu6TTUN5IUfvA07ECGRtNMpqyRcYSuQo/fkGehUDakjbISEhNJw +sffJllUWtZsfaIVpm3hgBw7CwhvW1liAOxfC3+xMzEOyfehWOy8ssAK77euFsLAX +QA/sVSfJnt05cC5naCwzls3K91AsNVUP7W31cbVXj0Nz24OD31CzjZHiWIUqM3c3 +lwIDAQAB -----END PUBLIC KEY----- From 45b76c45b63039548d3055a192b3dc30f12b86c3 Mon Sep 17 00:00:00 2001 From: Dante Ramacciotti Date: Fri, 5 Sep 2025 16:50:04 +0200 Subject: [PATCH 38/40] fucking coin selection not working, trying to implement the branch and bound. I'm tired so fuck it, do it next week --- src/chain/mod.rs | 2 +- src/chain/ordered_vector.rs | 38 +++++++++++++++++-- src/chain/transaction.rs | 1 + src/chain/wallet.rs | 65 +++++++++++++++++++++++++-------- tests/test_ordered_vec.rs | 37 +++++++++++++++++++ tests/wallet_utxos.rs | 73 +++++++++++++++++++++++++++++++++++++ 6 files changed, 196 insertions(+), 20 deletions(-) create mode 100644 tests/test_ordered_vec.rs create mode 100644 tests/wallet_utxos.rs diff --git a/src/chain/mod.rs b/src/chain/mod.rs index 9f0e203..44cd460 100644 --- a/src/chain/mod.rs +++ b/src/chain/mod.rs @@ -1,5 +1,5 @@ pub mod block; -mod ordered_vector; +pub mod ordered_vector; pub mod transaction; pub mod utils; pub mod utxo; diff --git a/src/chain/ordered_vector.rs b/src/chain/ordered_vector.rs index 7e99182..4f7034f 100644 --- a/src/chain/ordered_vector.rs +++ b/src/chain/ordered_vector.rs @@ -1,3 +1,5 @@ +use std::cmp::Reverse; + use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Serialize, Deserialize)] @@ -12,9 +14,14 @@ impl OrderedVec where T: std::cmp::Ord, { - #[allow(dead_code)] - pub fn insert(&mut self, value: T) { - match self.vec.binary_search(&value) { + pub fn new() -> Self { + OrderedVec { vec: Vec::new() } + } + pub fn insert(&mut self, value: T) + where + T: Ord, + { + match self.vec.binary_search_by_key(&Reverse(&value), Reverse) { Ok(_) => {} Err(pos) => self.vec.insert(pos, value), } @@ -33,11 +40,23 @@ where pub fn len(&self) -> usize { self.vec.len() } + pub fn is_empty(&self) -> bool { + self.vec.len() == 0 + } pub fn get_slice(&self, range: std::ops::Range) -> &[T] { &self.vec[range] } } +impl Default for OrderedVec +where + T: std::cmp::Ord, +{ + fn default() -> Self { + Self::new() + } +} + impl std::ops::Index for OrderedVec where Idx: std::slice::SliceIndex<[T], Output = T>, @@ -61,3 +80,16 @@ where self.vec.into_iter() } } + +impl From> for OrderedVec +where + T: Ord, +{ + fn from(vec: Vec) -> Self { + let mut ord_vec = OrderedVec::new(); + for value in vec { + ord_vec.insert(value); + } + ord_vec + } +} diff --git a/src/chain/transaction.rs b/src/chain/transaction.rs index a7183bd..8d86bea 100644 --- a/src/chain/transaction.rs +++ b/src/chain/transaction.rs @@ -134,6 +134,7 @@ pub struct Transaction { pub txid: [u8; 32], } +// TODO eventually, I want to make the transactions not need to have the sender adress impl Transaction { pub fn new( sender: Wallet, diff --git a/src/chain/wallet.rs b/src/chain/wallet.rs index 1fe2b1a..ea01c2b 100644 --- a/src/chain/wallet.rs +++ b/src/chain/wallet.rs @@ -182,11 +182,21 @@ impl Wallet { self.public_key.public_key_to_pem().unwrap() } - fn estimate_fee_per_utxo() -> u64 { + fn estimate_fee_per_utxo(_utxo: &UTXO) -> u64 { 0 } - // TODO write some tests for this bad boy + pub fn add_utxos(&mut self, new_vec: Vec) { + match self.available_utxos { + Some(ref mut ord_vec) => { + for utxo in new_vec { + ord_vec.insert(utxo); + } + } + None => self.available_utxos = Some(OrderedVec::from(new_vec)), + } + } + pub fn get_utxos(&self, amount: u64) -> Result, WalletError> { let available_utxos = self.available_utxos.clone().expect("Found no UTXOs"); if UTXO::sum(&available_utxos) < amount { @@ -200,17 +210,20 @@ impl Wallet { .clone() .into_iter() .enumerate() - .find(|(_, utxo)| utxo.value() > amount) + .find(|(_, utxo)| utxo.value() >= amount) { - Some((index, value)) => { - solutions.push(vec![value]); + Some((index, utxo)) => { + if utxo.value() == amount { + return Ok(vec![utxo]); + } + solutions.push(vec![utxo]); index } None => available_utxos.len(), }; // https://bitcoin.stackexchange.com/questions/1077/what-is-the-coin-selection-algorithm - // if the sum of all yout UTXO smaller than the target happens to match the target,they + // if the sum of all your UTXO smaller than the target happens to match the target,they // will be used fn calculate_recursion_depth( @@ -228,27 +241,23 @@ impl Wallet { fn recursive( slice: &[UTXO], + k: usize, solutions: &mut Vec>, target: u64, x: &mut Option, ) { - if slice.is_empty() { - return; - } - if let Some(ref mut depth) = x { - if *depth == 0 { + if let Some(x) = x { + if *x == 0 { return; } - *depth -= 1; } let mut sum = 0; - let k = slice.len() / 2; let mut elements: Vec = vec![slice[k].clone()]; sum += slice[k].value(); for i in 0..k { sum += slice[k - i].value(); - elements.push(slice[k - i].clone()); + elements.push(slice[k - 1].clone()); if sum > target { // if there's no x yet, we calculate it here. The x is a function of the number // of elements necessary in the first iteration. If many elements are needed in the @@ -266,8 +275,30 @@ impl Wallet { break; } } - recursive(&slice[0..k], solutions, target, x); - recursive(&slice[k..], solutions, target, x); + recursive(slice, k / 2, solutions, target, x); + recursive(slice, k * 3 / 2, solutions, target, x); + } + + // TODO finish this shit + fn branch_and_bound_entrypoint( + slice: &[UTXO], // must be ordered from biggest to smallest + k: usize, + solutions: &mut Vec>, + target: u64, + x: &mut Option, + ) { + struct UTXOEstimate { + utxo: UTXO, + estimated_value: u64, + } + fn waste(transaction_info: &TransactionInfo) {} + let new_slice: Vec = slice + .iter() + .map(|utxo| UTXOEstimate { + utxo: utxo.clone(), + estimated_value: Wallet::estimate_fee_per_utxo(utxo), + }) + .collect(); } let dust_threshold = Self::estimate_fee_per_utxo() * 3; @@ -294,8 +325,10 @@ impl Wallet { // and again with the element in the middle of s[0..k]. This creates kind of a binary search through the vector. // // We repeat the process recursively x times, x being arbitrarily defined + let first_k = utxos_smaller_than_target.len() / 2; recursive( &utxos_smaller_than_target, + first_k, &mut solutions, target, &mut None, diff --git a/tests/test_ordered_vec.rs b/tests/test_ordered_vec.rs new file mode 100644 index 0000000..02401aa --- /dev/null +++ b/tests/test_ordered_vec.rs @@ -0,0 +1,37 @@ +use cleyto_coin::chain::ordered_vector::OrderedVec; +use cleyto_coin::chain::utxo::UTXO; +use cleyto_coin::chain::wallet::Wallet; + +#[test] +fn test_ordered_vec() { + let (wallet1, _) = Wallet::new(); + + let input_utxos = vec![ + UTXO::new(50000, wallet1.clone()), + UTXO::new(32000, wallet1.clone()), + UTXO::new(25000, wallet1.clone()), + UTXO::new(15000, wallet1.clone()), + UTXO::new(12000, wallet1.clone()), + UTXO::new(10000, wallet1.clone()), + UTXO::new(8500, wallet1.clone()), + UTXO::new(7200, wallet1.clone()), + UTXO::new(6000, wallet1.clone()), + UTXO::new(5500, wallet1.clone()), + UTXO::new(3000, wallet1.clone()), + UTXO::new(2500, wallet1.clone()), + UTXO::new(2000, wallet1.clone()), + UTXO::new(1500, wallet1.clone()), + UTXO::new(1200, wallet1.clone()), + UTXO::new(1000, wallet1.clone()), + UTXO::new(800, wallet1.clone()), + UTXO::new(600, wallet1.clone()), + UTXO::new(400, wallet1.clone()), + UTXO::new(300, wallet1.clone()), + ]; + + let vec = OrderedVec::from(input_utxos); + + for i in vec { + println!("utxo of value {}", i.value()); + } +} diff --git a/tests/wallet_utxos.rs b/tests/wallet_utxos.rs new file mode 100644 index 0000000..5039c84 --- /dev/null +++ b/tests/wallet_utxos.rs @@ -0,0 +1,73 @@ +use cleyto_coin::chain::{utxo::UTXO, wallet::Wallet}; + +#[test] +fn test_get_utxo_wallet() { + let (mut wallet1, _) = Wallet::new(); + + let input_utxos = vec![ + // Large UTXOs - good for covering big amounts efficiently + UTXO::new(50000, wallet1.clone()), + UTXO::new(32000, wallet1.clone()), + UTXO::new(25000, wallet1.clone()), + // Medium UTXOs - typical transaction amounts + UTXO::new(15000, wallet1.clone()), + UTXO::new(12000, wallet1.clone()), + UTXO::new(10000, wallet1.clone()), + UTXO::new(8500, wallet1.clone()), + UTXO::new(7200, wallet1.clone()), + UTXO::new(6000, wallet1.clone()), + UTXO::new(5500, wallet1.clone()), + // Small UTXOs - test efficiency vs dust management + UTXO::new(3000, wallet1.clone()), + UTXO::new(2500, wallet1.clone()), + UTXO::new(2000, wallet1.clone()), + UTXO::new(1500, wallet1.clone()), + UTXO::new(1200, wallet1.clone()), + UTXO::new(1000, wallet1.clone()), + // Very small UTXOs - potential dust scenarios + UTXO::new(800, wallet1.clone()), + UTXO::new(600, wallet1.clone()), + UTXO::new(400, wallet1.clone()), + UTXO::new(300, wallet1.clone()), + ]; + + fn print_utxo_vec(input_utxos: Vec) { + for utxo in input_utxos { + println!("utxo: ({})", utxo.value()); + } + } + + wallet1.add_utxos(input_utxos); + + println!("Checkpoint 1"); + assert_eq!( + wallet1.get_utxos(50000).unwrap(), + vec![UTXO::new(50000, wallet1.clone())] + ); + println!("Checkpoint 2"); + assert!(wallet1.get_utxos(100000000).is_err()); + println!("Checkpoint 3"); + + print_utxo_vec(wallet1.get_utxos(30000).unwrap()); + println!("Checkpoint 4"); + + print_utxo_vec(wallet1.get_utxos(40000).unwrap()); + println!("Checkpoint 5"); + + print_utxo_vec(wallet1.get_utxos(25461).unwrap()); + println!("Checkpoint 6"); + + // let (wallet2, wallet2_pk) = Wallet::new(); + // + // let input_utxos = vec![ + // UTXO::new(1000, wallet1.clone()), + // UTXO::new(2000, wallet1.clone()), + // ]; + // let output_utxos = vec![ + // UTXO::new(2500, wallet2.clone()), + // UTXO::new(500, wallet2.clone()), + // ]; + // let transaction_info = TransactionInfo::new(input_utxos, output_utxos); + // let signature = wallet2_pk.sign_transaction(&transaction_info).unwrap(); + // let transaction = Transaction::new(wallet2, wallet1, transaction_info, signature); +} From 942903d84d7bea1581fa4509bee0c7ec3ec46726 Mon Sep 17 00:00:00 2001 From: Dante Ramacciotti Date: Fri, 12 Sep 2025 18:50:50 +0200 Subject: [PATCH 39/40] Finished implementing the branch and bound algorithm Finished the implementation of the BnB algorithm for coin selection as described here https://bitcoin.stackexchange.com/questions/119919/how-does-the-branch-and-bound-coin-selection-algorithm-work. Missing implementation of non exact algorithm now, and the constants are all fucked up I think --- backup_get_utxos | 278 ++++++++++ src/chain/mod.rs | 1 + src/chain/wallet.rs | 502 ++++++++++-------- src/chain/wallet_pk.rs | 51 ++ ...wallet_utxos.rs => test_coin_selection.rs} | 4 +- 5 files changed, 628 insertions(+), 208 deletions(-) create mode 100644 backup_get_utxos create mode 100644 src/chain/wallet_pk.rs rename tests/{wallet_utxos.rs => test_coin_selection.rs} (95%) diff --git a/backup_get_utxos b/backup_get_utxos new file mode 100644 index 0000000..34630ec --- /dev/null +++ b/backup_get_utxos @@ -0,0 +1,278 @@ +pub fn get_utxos(&self, amount: u64) -> Result, WalletError> { + let available_utxos = self.available_utxos.clone().expect("Found no UTXOs"); + if UTXO::sum(&available_utxos) < amount { + return Err(WalletError::InsufficientFunds); + } + let mut solutions: Vec> = Vec::new(); + + // Gets the first UTXO (if there is such a value) that's is bigger than the requested + // amount + let index = match available_utxos + .clone() + .into_iter() + .enumerate() + .find(|(_, utxo)| utxo.value() >= amount) + { + Some((index, utxo)) => { + if utxo.value() == amount { + return Ok(vec![utxo]); + } + solutions.push(vec![utxo]); + index + } + None => available_utxos.len(), + }; + + // https://bitcoin.stackexchange.com/questions/1077/what-is-the-coin-selection-algorithm + // if the sum of all your UTXO smaller than the target happens to match the target,they + // will be used + + // the two functions under here are just my bullshit + fn calculate_recursion_depth( + max_depth: usize, + elements_tested: usize, + total_elements: usize, + ) -> u32 { + if total_elements == 0 { + return 0; + } + let fraction_used = elements_tested as f64 / total_elements as f64; + let new_depth = (max_depth as f64 * (1.0 - fraction_used)).ceil() as u32; + std::cmp::max(new_depth, 1) + } + + fn recursive( + slice: &[UTXO], + k: usize, + solutions: &mut Vec>, + target: u64, + x: &mut Option, + ) { + if let Some(x) = x { + if *x == 0 { + return; + } + } + let mut sum = 0; + let mut elements: Vec = vec![slice[k].clone()]; + sum += slice[k].value(); + + for i in 0..k { + sum += slice[k - i].value(); + elements.push(slice[k - 1].clone()); + if sum > target { + // if there's no x yet, we calculate it here. The x is a function of the number + // of elements necessary in the first iteration. If many elements are needed in the + // first iteration, that means that if I continue for too many times there will be a + // lot of overlap. Therefore, we reduce the size of x + if x.is_none() { + *x = Some(calculate_recursion_depth( + MAX_UTXO_SEARCH_DEPTH, + i, + slice.len(), + )); + } + + solutions.push(elements); + break; + } + } + recursive(slice, k / 2, solutions, target, x); + recursive(slice, k * 3 / 2, solutions, target, x); + } + + const UTXO_WEIGHT: u64 = 148; // idk something like that + const LONG_TERM_FEE_RATE: u64 = 1; + fn branch_and_bound_entrypoint( + slice: &[UTXO], // must be ordered from biggest to smallest + max_repetitions: usize, + target: u64, + dust_threshold: u64, + fee_rate: u64, + ) -> (Vec, u64) { + #[derive(Clone)] + struct UTXOEstimate { + utxo: UTXO, + estimated_value: u64, + weight: u64, + } + + // TODO reorder the new_slice to make sure it's ordered even after calculating the + // estimated_value + let mut total_sum = 0u64; + let new_slice: Vec = slice + .iter() + .map(|utxo| { + total_sum += utxo.value(); + UTXOEstimate { + utxo: utxo.clone(), + estimated_value: Wallet::estimate_fee_per_utxo(utxo), + weight: UTXO_WEIGHT, + } + }) + .collect(); + + fn waste(utxos: &[UTXOEstimate], fee_rate: u64) -> u64 { + let mut total_weight = 0u64; + for utxo in utxos { + total_weight += utxo.weight; + } + total_weight * (fee_rate - LONG_TERM_FEE_RATE) + } + + #[allow(clippy::too_many_arguments)] + fn branch_and_bound( + slice: &[UTXOEstimate], + cur_sum: u64, + cur_vec: Vec, + sum_left: u64, + solution_vec: &mut Vec, + solution_sum: &mut u64, + solution_waste: &mut u64, + target_interval: (u64, u64), + fee_rate: u64, + max_repetitions: usize, + ) { + if max_repetitions == 0 { + return; + } + let sum_left = sum_left - slice[0].estimated_value; + + // include + let mut cur_vec_include = cur_vec.clone(); + cur_vec_include.push(slice[0].clone()); + let cur_sum_include = cur_sum + slice[0].estimated_value; + let waste_include = waste(&cur_vec_include, fee_rate); + if cur_sum_include > target_interval.0 + && cur_sum_include < target_interval.1 + && waste_include < *solution_waste + { + // if here, algorithm is done in this branch + *solution_sum = cur_sum_include; + *solution_vec = cur_vec.clone(); + *solution_waste = waste_include; + return; + } + if cur_sum_include < target_interval.1 + && sum_left > target_interval.0 - cur_sum_include + { + branch_and_bound( + &slice[1..], + cur_sum_include, + cur_vec_include, + sum_left, + solution_vec, + solution_sum, + solution_waste, + target_interval, + fee_rate, + max_repetitions - 1, + ); + } + + // omit + + if sum_left > target_interval.0 - cur_sum { + branch_and_bound( + &slice[1..], + cur_sum, + cur_vec, + sum_left, + solution_vec, + solution_sum, + solution_waste, + target_interval, + fee_rate, + max_repetitions - 1, + ) + } + } + + let mut solution: Vec = Vec::new(); + let mut solution_sum = u64::MAX; + let mut solution_waste = u64::MAX; + branch_and_bound( + &new_slice, + 0u64, + Vec::new(), + total_sum, + &mut solution, + &mut solution_sum, + &mut solution_waste, + (target, target + dust_threshold), + fee_rate, + max_repetitions - 1, + ); + + let solution_utxo = solution + .into_iter() + .map(|utxo_estimate| utxo_estimate.utxo) + .collect(); + (solution_utxo, solution_sum) + } + + let dust_threshold = Self::estimate_fee_per_utxo(&available_utxos[0]) * 3; + let target = amount + dust_threshold; + let utxos_smaller_than_target = available_utxos.get_slice(0..index).to_vec(); + let sum: u64 = UTXO::sum(&utxos_smaller_than_target); + + let (bnb_solution_vec, _) = branch_and_bound_entrypoint( + &utxos_smaller_than_target, + 100_000, + amount, + dust_threshold, + 1, + ); + + if bnb_solution_vec.is_empty() { + return Ok(bnb_solution_vec); + } + + // if sum == amount { + // return Ok(available_utxos.get_slice(0..index).to_vec()); + // } + // if sum > target { + // // I'll just use the shit algorithm I invented. Goes like this: + // // Inputs are target amount and the ordered vector V that contains all available UTXOs + // // target = target_amount + dust threshold + // // First, we get the smallest element bigger than the target on the vector with index i + // // smallest elements vector = s = [0..i] + // // + // // We select the middle element of s (index k) and sum it with the following element (k+1). + // // If it's still smaller than target, sum it with k-1. + // // If still smaller than target, sum with k+2, and then k-2, k+3, and so on, until we find a solution + // // and save it as an option in a solutions vector + // // + // // We do the same now, but starting the element in the middle of the slice s[k..], + // // and again with the element in the middle of s[0..k]. This creates kind of a binary search through the vector. + // // + // // We repeat the process recursively x times, x being arbitrarily defined + // let first_k = utxos_smaller_than_target.len() / 2; + // recursive( + // &utxos_smaller_than_target, + // first_k, + // &mut solutions, + // target, + // &mut None, + // ); + // } + + let best_solution = solutions.into_iter().fold(None, |acc, new_vec| match acc { + None => Some((UTXO::sum(&new_vec), new_vec)), + Some((old_sum, old_vec)) => { + let new_sum = UTXO::sum(&new_vec); + if new_sum > old_sum { + Some((new_sum, new_vec)) + } else { + Some((old_sum, old_vec)) + } + } + }); + + match best_solution { + Some((_, vec)) => Ok(vec), + // This is probably safe for an unwrap but fuck it + None => Err(WalletError::InsufficientFunds), + } + } + diff --git a/src/chain/mod.rs b/src/chain/mod.rs index 44cd460..02ecd1f 100644 --- a/src/chain/mod.rs +++ b/src/chain/mod.rs @@ -4,6 +4,7 @@ pub mod transaction; pub mod utils; pub mod utxo; pub mod wallet; +mod wallet_pk; use block::Block; use serde::{Deserialize, Serialize}; diff --git a/src/chain/wallet.rs b/src/chain/wallet.rs index ea01c2b..c45f0c7 100644 --- a/src/chain/wallet.rs +++ b/src/chain/wallet.rs @@ -4,11 +4,11 @@ use crate::chain::utxo::UTXO; use super::transaction::TransactionInfo; use openssl::error::ErrorStack; +pub use super::wallet_pk::WalletPK; use openssl::hash::MessageDigest; -use openssl::pkey::{PKey, Private, Public}; +use openssl::pkey::{PKey, Public}; use openssl::rsa::Rsa; -use openssl::sign::{Signer, Verifier}; -use openssl::symm::Cipher; +use openssl::sign::Verifier; use serde::de::{self, Error}; use serde::{Deserialize, Serialize}; @@ -25,68 +25,7 @@ impl std::fmt::Display for WalletError { impl std::error::Error for WalletError {} // ----------------------------------------------------------------------------------------------------------------- -// ---------------------------------------------- WalletPK definition ---------------------------------------------- -#[derive(Debug)] -pub struct WalletPK { - private_key: PKey, -} - -impl WalletPK { - pub fn sign_transaction( - &self, - transaction_info: &TransactionInfo, - ) -> Result, ErrorStack> { - let mut signer = Signer::new(MessageDigest::sha256(), &self.private_key)?; - signer.sign_oneshot_to_vec(transaction_info.to_string().as_bytes()) - } - pub fn to_pem_with_password(&self, password: &String) -> Vec { - self.private_key - .private_key_to_pem_pkcs8_passphrase(Cipher::aes_256_cbc(), password.as_bytes()) - .unwrap() - } - pub fn to_pem(&self) -> Vec { - self.private_key.private_key_to_pem_pkcs8().unwrap() - } - pub fn public_wallet(&self) -> Wallet { - let public_key = PKey::public_key_from_pem( - &self - .private_key - .public_key_to_pem() - .expect("Could not extract Publick Key from Private Key"), - ) - .unwrap(); - - Wallet { - public_key, - available_utxos: None, - } - } -} -impl From> for WalletPK { - fn from(private_key: PKey) -> Self { - Self { private_key } - } -} - -// ----------------------------------------------------------------------------------------------------------------- - -// ---------------------------------------------- Wallet definition ------------------------------------------------ -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct Wallet { - #[serde( - serialize_with = "serialize_public_key", - deserialize_with = "deserialize_public_key" - )] - public_key: PKey, // Should I store this as PEM or as PKey? - available_utxos: Option>, -} - -impl PartialEq for Wallet { - fn eq(&self, other: &Self) -> bool { - self.to_pem() == other.to_pem() - } -} - +// ------------------------------------------------- Serde stuff --------------------------------------------------- fn serialize_public_key(key: &PKey, serializer: S) -> Result where S: serde::Serializer, @@ -124,6 +63,14 @@ where // use our visitor to deserialize an `ActualValue` deserializer.deserialize_any(StringVisitor) } +// ----------------------------------------------------------------------------------------------------------------- + +// ------------------------------------------------- Traits stuff -------------------------------------------------- +impl PartialEq for Wallet { + fn eq(&self, other: &Self) -> bool { + self.to_pem() == other.to_pem() + } +} impl From for Wallet { fn from(value: String) -> Self { @@ -145,19 +92,41 @@ impl From> for Wallet { } } } +// ----------------------------------------------------------------------------------------------------------------- + +// ---------------------------------------------- Wallet definition ------------------------------------------------ +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Wallet { + #[serde( + serialize_with = "serialize_public_key", + deserialize_with = "deserialize_public_key" + )] + pub(crate) public_key: PKey, + pub(crate) available_utxos: Option>, +} + const MAX_UTXO_SEARCH_DEPTH: usize = 100; +const UTXO_WEIGHT: u64 = 64; // typical weight of a P2PKH output - replace with real weight +const LONG_TERM_FEE_RATE: u64 = 1; // placeholder - replace with real rate + impl Wallet { + /* --------------------------------------------------------------------- * + * Construction & Utilities * + * --------------------------------------------------------------------- */ + + /// Creates a fresh key pair and returns `(wallet, private_key_wrapper)`. pub fn new() -> (Self, WalletPK) { - let bits: u32 = 2048; - let rsa = Rsa::generate(bits).unwrap(); - let private_key = PKey::from_rsa(rsa).unwrap(); + // 2048‑bit RSA key – adjust size if you need stronger security. + let rsa = Rsa::generate(2048).expect("RSA generation failed"); + let private_key = PKey::from_rsa(rsa).expect("Invalid RSA key"); + // Export the public part as PEM and immediately parse it back. let public_key = PKey::public_key_from_pem( &private_key .public_key_to_pem() - .expect("Could not extract Publick Key from Private Key"), + .expect("Could not extract public key from private key"), ) - .unwrap(); + .expect("Failed to parse PEM public key"); ( Wallet { @@ -168,6 +137,7 @@ impl Wallet { ) } + /// Verify a signed `TransactionInfo` using the stored public key. pub fn verify_transaction_info( &self, transaction_info: &TransactionInfo, @@ -178,17 +148,22 @@ impl Wallet { verifier.verify(signature) } + /// Export the public key as PEM bytes. pub fn to_pem(&self) -> Vec { - self.public_key.public_key_to_pem().unwrap() + self.public_key + .public_key_to_pem() + .expect("PEM conversion failed") } + /// Rough fee estimate per UTXO – replace with a real estimator later. fn estimate_fee_per_utxo(_utxo: &UTXO) -> u64 { - 0 + 100 } + /// Insert a batch of new UTXOs, keeping the internal ordering intact. pub fn add_utxos(&mut self, new_vec: Vec) { - match self.available_utxos { - Some(ref mut ord_vec) => { + match &mut self.available_utxos { + Some(ord_vec) => { for utxo in new_vec { ord_vec.insert(utxo); } @@ -197,161 +172,274 @@ impl Wallet { } } + /* --------------------------------------------------------------------- * + * Coin‑Selection Logic * + * --------------------------------------------------------------------- */ + /// Public entry point – selects a set of UTXOs whose summed value covers + /// `amount`. Returns an error if the wallet does not contain enough funds. pub fn get_utxos(&self, amount: u64) -> Result, WalletError> { - let available_utxos = self.available_utxos.clone().expect("Found no UTXOs"); - if UTXO::sum(&available_utxos) < amount { + let utxos = self + .available_utxos + .as_ref() + .ok_or(WalletError::InsufficientFunds)? + .clone(); + + // Quick check – if the total balance is insufficient we can bail early. + if UTXO::sum(&utxos) < amount { return Err(WalletError::InsufficientFunds); } - let mut solutions: Vec> = Vec::new(); - // Gets the first UTXO (if there is such a value) that's is bigger than the requested - // amount - let index = match available_utxos + // 1️⃣ Simple exact‑match shortcut. + if let Some(single) = utxos.clone().into_iter().find(|u| u.value() == amount) { + return Ok(vec![single.clone()]); + } + + for utxo in utxos.clone() { + println!("UTXO of value {}", utxo.value()); + } + // 2️⃣ Find the smallest UTXO that already exceeds the target. + let first_over_idx = utxos .clone() .into_iter() - .enumerate() - .find(|(_, utxo)| utxo.value() >= amount) - { - Some((index, utxo)) => { - if utxo.value() == amount { - return Ok(vec![utxo]); + .position(|u| u.value() < amount) + .unwrap_or(0); + + println!("first_over_idx is {first_over_idx}"); + + // If we found a single “big enough” UTXO, keep it as a candidate solution. + let mut candidate_solutions = Vec::new(); + if let Some(utxo) = utxos.get(first_over_idx - 1) { + candidate_solutions.push(vec![utxo.clone()]); + } + + // 3️⃣ Branch‑and‑bound selection on the remaining (smaller) UTXOs. + + let dust_threshold = Self::estimate_fee_per_utxo(&utxos[0]) * 3; + let smaller_utxos = &utxos.get_slice(first_over_idx..utxos.len()); + println!("len of smaller_utxos is {}", smaller_utxos.len()); + + println!("The amount is {amount} and the dust_threshold is {dust_threshold}"); + let (bnb_solution, _) = self.branch_and_bound( + smaller_utxos, + amount, + dust_threshold, + 100_000, // max repetitions + ); + println!("Finished bnb"); + + if !bnb_solution.is_empty() { + return Ok(bnb_solution); + } else { + println!("bnb solution is empty"); + return Err(WalletError::InsufficientFunds); + } + + #[allow(unreachable_code)] + // 4️⃣ Pick the “best” solution (the one with the highest sum that still + // satisfies the target). If none exists we fall back to the exact‑match + // error (already handled at the top). + let target = amount + dust_threshold; + Self::dantes_crazy_coin_selection_algorithm( + smaller_utxos, + MAX_UTXO_SEARCH_DEPTH, + &mut candidate_solutions, + target, + -1, + ); + let best = candidate_solutions.into_iter().max_by_key(UTXO::sum); + + best.ok_or(WalletError::InsufficientFunds) + } + + /// Core branch‑and‑bound algorithm – returns a tuple `(selected_utxos, total_value)`. + fn branch_and_bound( + &self, + slice: &[UTXO], + target: u64, + dust_threshold: u64, + max_reps: usize, + ) -> (Vec, u64) { + println!("Starting branch_and_bound"); + // Transform each UTXO into an enriched struct that carries an estimated + // “effective value” (value minus fee) and its weight. + #[derive(Clone)] + struct UtxoEstimate { + utxo: UTXO, + effective_value: u64, + weight: u64, + } + + let mut total_sum = 0u64; + let estimates: Vec = slice + .iter() + .map(|u| { + total_sum += u.value(); + UtxoEstimate { + utxo: u.clone(), + effective_value: u.value() - Self::estimate_fee_per_utxo(u), + weight: UTXO_WEIGHT, } - solutions.push(vec![utxo]); - index - } - None => available_utxos.len(), - }; - - // https://bitcoin.stackexchange.com/questions/1077/what-is-the-coin-selection-algorithm - // if the sum of all your UTXO smaller than the target happens to match the target,they - // will be used - - fn calculate_recursion_depth( - max_depth: usize, - elements_tested: usize, - total_elements: usize, - ) -> u32 { - if total_elements == 0 { - return 0; - } - let fraction_used = elements_tested as f64 / total_elements as f64; - let new_depth = (max_depth as f64 * (1.0 - fraction_used)).ceil() as u32; - std::cmp::max(new_depth, 1) + }) + .collect(); + + for est in estimates.clone() { + println!( + "UTXOEstimate (effective_value: {}, weight: {} )", + est.utxo.value() - Self::estimate_fee_per_utxo(&est.utxo), + UTXO_WEIGHT + ); + } + + // Helper: compute “waste” (extra fee paid beyond the long‑term rate). + fn waste(estimates: &[UtxoEstimate], fee_rate: u64) -> u64 { + let total_weight: u64 = estimates.iter().map(|e| e.weight).sum(); + // println!( + // total_weight * (fee_rate - LONG_TERM_FEE_RATE), + // total_weight, + // fee_rate + // ); + total_weight * (fee_rate - LONG_TERM_FEE_RATE) } - fn recursive( - slice: &[UTXO], - k: usize, - solutions: &mut Vec>, - target: u64, - x: &mut Option, + // Recursive branch‑and‑bound search. + #[allow(clippy::too_many_arguments)] + fn recurse( + remaining: &[UtxoEstimate], + cur_sum: u64, + cur_set: Vec, + sum_left: u64, + best_set: &mut Vec, + best_sum: &mut u64, + best_waste: &mut u64, + target_interval: (u64, u64), + fee_rate: u64, + reps_left: usize, ) { - if let Some(x) = x { - if *x == 0 { - return; - } + if reps_left == 0 || remaining.is_empty() { + return; } - let mut sum = 0; - let mut elements: Vec = vec![slice[k].clone()]; - sum += slice[k].value(); - - for i in 0..k { - sum += slice[k - i].value(); - elements.push(slice[k - 1].clone()); - if sum > target { - // if there's no x yet, we calculate it here. The x is a function of the number - // of elements necessary in the first iteration. If many elements are needed in the - // first iteration, that means that if I continue for too many times there will be a - // lot of overlap. Therefore, we reduce the size of x - if x.is_none() { - *x = Some(calculate_recursion_depth( - MAX_UTXO_SEARCH_DEPTH, - i, - slice.len(), - )); - } - - solutions.push(elements); - break; - } + + // Update the amount left after discarding the current head’s fee. + let sum_left = sum_left - remaining[0].effective_value; + + // ---------- Include the head ---------- + let mut incl_set = cur_set.clone(); + incl_set.push(remaining[0].clone()); + let incl_sum = cur_sum + remaining[0].effective_value; + let incl_waste = waste(&incl_set, fee_rate); + + if incl_sum >= target_interval.0 + && incl_sum <= target_interval.1 + && incl_waste < *best_waste + { + *best_sum = incl_sum; + *best_set = incl_set.clone(); + *best_waste = incl_waste; + // Found a feasible solution – we can stop exploring this branch. + return; } - recursive(slice, k / 2, solutions, target, x); - recursive(slice, k * 3 / 2, solutions, target, x); - } - // TODO finish this shit - fn branch_and_bound_entrypoint( - slice: &[UTXO], // must be ordered from biggest to smallest - k: usize, - solutions: &mut Vec>, - target: u64, - x: &mut Option, - ) { - struct UTXOEstimate { - utxo: UTXO, - estimated_value: u64, + if incl_sum < target_interval.1 + && sum_left as i64 > target_interval.0 as i64 - incl_sum as i64 + { + recurse( + &remaining[1..], + incl_sum, + incl_set, + sum_left, + best_set, + best_sum, + best_waste, + target_interval, + fee_rate, + reps_left - 1, + ); + } + + // ---------- Exclude the head ---------- + if sum_left as i64 > target_interval.0 as i64 - cur_sum as i64 { + recurse( + &remaining[1..], + cur_sum, + cur_set, + sum_left, + best_set, + best_sum, + best_waste, + target_interval, + fee_rate, + reps_left - 1, + ); } - fn waste(transaction_info: &TransactionInfo) {} - let new_slice: Vec = slice - .iter() - .map(|utxo| UTXOEstimate { - utxo: utxo.clone(), - estimated_value: Wallet::estimate_fee_per_utxo(utxo), - }) - .collect(); } - let dust_threshold = Self::estimate_fee_per_utxo() * 3; - let target = amount + dust_threshold; - let utxos_smaller_than_target = available_utxos.get_slice(0..index).to_vec(); - let sum: u64 = UTXO::sum(&utxos_smaller_than_target); + // Initialise the search. + let mut best_set = Vec::new(); + let mut best_sum = u64::MAX; + let mut best_waste = u64::MAX; + recurse( + &estimates, + 0, + Vec::new(), + total_sum, + &mut best_set, + &mut best_sum, + &mut best_waste, + (target, target + dust_threshold), + 2, // fee_rate placeholder – replace with real rate + max_reps, + ); + + // Strip the auxiliary data and return plain UTXOs. + let selected = best_set.into_iter().map(|e| e.utxo).collect::>(); + let total_selected = UTXO::sum(&selected); + (selected, total_selected) + } - if sum == amount { - return Ok(available_utxos.get_slice(0..index).to_vec()); + fn calculate_recursion_depth( + max_depth: usize, + elements_tested: usize, + total_elements: usize, + ) -> i32 { + if total_elements == 0 { + return 0; } - if sum > target { - // I'll just use the shit algorithm I invented. Goes like this: - // Inputs are target amount and the ordered vector V that contains all available UTXOs - // target = target_amount + dust threshold - // First, we get the smallest element bigger than the target on the vector with index i - // smallest elements vector = s = [0..i] - // - // We select the middle element of s (index k) and sum it with the following element (k+1). - // If it's still smaller than target, sum it with k-1. - // If still smaller than target, sum with k+2, and then k-2, k+3, and so on, until we find a solution - // and save it as an option in a solutions vector - // - // We do the same now, but starting the element in the middle of the slice s[k..], - // and again with the element in the middle of s[0..k]. This creates kind of a binary search through the vector. - // - // We repeat the process recursively x times, x being arbitrarily defined - let first_k = utxos_smaller_than_target.len() / 2; - recursive( - &utxos_smaller_than_target, - first_k, - &mut solutions, - target, - &mut None, - ); + let fraction_used = elements_tested as f64 / total_elements as f64; + let new_depth = (max_depth as f64 * (1.0 - fraction_used)).ceil() as i32; + std::cmp::max(new_depth, 1) + } + fn dantes_crazy_coin_selection_algorithm( + slice: &[UTXO], + k: usize, + solutions: &mut Vec>, + target: u64, + mut x: i32, // initialize with -1 + ) { + if x == 0 { + return; } - - let best_solution = solutions.into_iter().fold(None, |acc, new_vec| match acc { - None => Some((UTXO::sum(&new_vec), new_vec)), - Some((old_sum, old_vec)) => { - let new_sum = UTXO::sum(&new_vec); - if new_sum > old_sum { - Some((new_sum, new_vec)) - } else { - Some((old_sum, old_vec)) + let mut sum = 0; + let mut elements: Vec = vec![slice[k].clone()]; + sum += slice[k].value(); + + for i in 0..k { + sum += slice[k - i].value(); + elements.push(slice[k - 1].clone()); + if sum > target { + // if there's no x yet, we calculate it here. The x is a function of the number + // of elements necessary in the first iteration. If many elements are needed in the + // first iteration, that means that if I continue for too many times there will be a + // lot of overlap. Therefore, we reduce the size of x + if x == -1 { + x = Self::calculate_recursion_depth(MAX_UTXO_SEARCH_DEPTH, i, slice.len()); } - } - }); - match best_solution { - Some((_, vec)) => Ok(vec), - // This is probably safe for an unwrap but fuck it - None => Err(WalletError::InsufficientFunds), + solutions.push(elements); + break; + } } + Self::dantes_crazy_coin_selection_algorithm(slice, k / 2, solutions, target, x - 1); + Self::dantes_crazy_coin_selection_algorithm(slice, k * 3 / 2, solutions, target, x - 1); } } diff --git a/src/chain/wallet_pk.rs b/src/chain/wallet_pk.rs new file mode 100644 index 0000000..7bdfb9a --- /dev/null +++ b/src/chain/wallet_pk.rs @@ -0,0 +1,51 @@ +use super::transaction::TransactionInfo; +use super::wallet::Wallet; +use openssl::error::ErrorStack; +use openssl::hash::MessageDigest; +use openssl::pkey::{PKey, Private}; +use openssl::sign::Signer; +use openssl::symm::Cipher; +// ---------------------------------------------- WalletPK definition ---------------------------------------------- +#[derive(Debug)] +pub struct WalletPK { + pub(crate) private_key: PKey, +} + +impl WalletPK { + pub fn sign_transaction( + &self, + transaction_info: &TransactionInfo, + ) -> Result, ErrorStack> { + let mut signer = Signer::new(MessageDigest::sha256(), &self.private_key)?; + signer.sign_oneshot_to_vec(transaction_info.to_string().as_bytes()) + } + pub fn to_pem_with_password(&self, password: &String) -> Vec { + self.private_key + .private_key_to_pem_pkcs8_passphrase(Cipher::aes_256_cbc(), password.as_bytes()) + .unwrap() + } + pub fn to_pem(&self) -> Vec { + self.private_key.private_key_to_pem_pkcs8().unwrap() + } + pub fn public_wallet(&self) -> Wallet { + let public_key = PKey::public_key_from_pem( + &self + .private_key + .public_key_to_pem() + .expect("Could not extract Publick Key from Private Key"), + ) + .unwrap(); + + Wallet { + public_key, + available_utxos: None, + } + } +} +impl From> for WalletPK { + fn from(private_key: PKey) -> Self { + Self { private_key } + } +} + +// ----------------------------------------------------------------------------------------------------------------- diff --git a/tests/wallet_utxos.rs b/tests/test_coin_selection.rs similarity index 95% rename from tests/wallet_utxos.rs rename to tests/test_coin_selection.rs index 5039c84..18b048f 100644 --- a/tests/wallet_utxos.rs +++ b/tests/test_coin_selection.rs @@ -11,12 +11,14 @@ fn test_get_utxo_wallet() { UTXO::new(25000, wallet1.clone()), // Medium UTXOs - typical transaction amounts UTXO::new(15000, wallet1.clone()), + UTXO::new(15000, wallet1.clone()), UTXO::new(12000, wallet1.clone()), - UTXO::new(10000, wallet1.clone()), + UTXO::new(10500, wallet1.clone()), UTXO::new(8500, wallet1.clone()), UTXO::new(7200, wallet1.clone()), UTXO::new(6000, wallet1.clone()), UTXO::new(5500, wallet1.clone()), + UTXO::new(5000, wallet1.clone()), // Small UTXOs - test efficiency vs dust management UTXO::new(3000, wallet1.clone()), UTXO::new(2500, wallet1.clone()), From a4ba6395086932bba8b9ff39864ba2e32f1ad1e6 Mon Sep 17 00:00:00 2001 From: Dante Ramacciotti Date: Mon, 15 Sep 2025 15:15:07 +0200 Subject: [PATCH 40/40] Finished coin selection algorithms yeah --- backup_get_utxos | 278 ----------------------------------- src/chain/wallet.rs | 256 +++++++++++++++++++++----------- tests/test_coin_selection.rs | 35 +---- 3 files changed, 173 insertions(+), 396 deletions(-) delete mode 100644 backup_get_utxos diff --git a/backup_get_utxos b/backup_get_utxos deleted file mode 100644 index 34630ec..0000000 --- a/backup_get_utxos +++ /dev/null @@ -1,278 +0,0 @@ -pub fn get_utxos(&self, amount: u64) -> Result, WalletError> { - let available_utxos = self.available_utxos.clone().expect("Found no UTXOs"); - if UTXO::sum(&available_utxos) < amount { - return Err(WalletError::InsufficientFunds); - } - let mut solutions: Vec> = Vec::new(); - - // Gets the first UTXO (if there is such a value) that's is bigger than the requested - // amount - let index = match available_utxos - .clone() - .into_iter() - .enumerate() - .find(|(_, utxo)| utxo.value() >= amount) - { - Some((index, utxo)) => { - if utxo.value() == amount { - return Ok(vec![utxo]); - } - solutions.push(vec![utxo]); - index - } - None => available_utxos.len(), - }; - - // https://bitcoin.stackexchange.com/questions/1077/what-is-the-coin-selection-algorithm - // if the sum of all your UTXO smaller than the target happens to match the target,they - // will be used - - // the two functions under here are just my bullshit - fn calculate_recursion_depth( - max_depth: usize, - elements_tested: usize, - total_elements: usize, - ) -> u32 { - if total_elements == 0 { - return 0; - } - let fraction_used = elements_tested as f64 / total_elements as f64; - let new_depth = (max_depth as f64 * (1.0 - fraction_used)).ceil() as u32; - std::cmp::max(new_depth, 1) - } - - fn recursive( - slice: &[UTXO], - k: usize, - solutions: &mut Vec>, - target: u64, - x: &mut Option, - ) { - if let Some(x) = x { - if *x == 0 { - return; - } - } - let mut sum = 0; - let mut elements: Vec = vec![slice[k].clone()]; - sum += slice[k].value(); - - for i in 0..k { - sum += slice[k - i].value(); - elements.push(slice[k - 1].clone()); - if sum > target { - // if there's no x yet, we calculate it here. The x is a function of the number - // of elements necessary in the first iteration. If many elements are needed in the - // first iteration, that means that if I continue for too many times there will be a - // lot of overlap. Therefore, we reduce the size of x - if x.is_none() { - *x = Some(calculate_recursion_depth( - MAX_UTXO_SEARCH_DEPTH, - i, - slice.len(), - )); - } - - solutions.push(elements); - break; - } - } - recursive(slice, k / 2, solutions, target, x); - recursive(slice, k * 3 / 2, solutions, target, x); - } - - const UTXO_WEIGHT: u64 = 148; // idk something like that - const LONG_TERM_FEE_RATE: u64 = 1; - fn branch_and_bound_entrypoint( - slice: &[UTXO], // must be ordered from biggest to smallest - max_repetitions: usize, - target: u64, - dust_threshold: u64, - fee_rate: u64, - ) -> (Vec, u64) { - #[derive(Clone)] - struct UTXOEstimate { - utxo: UTXO, - estimated_value: u64, - weight: u64, - } - - // TODO reorder the new_slice to make sure it's ordered even after calculating the - // estimated_value - let mut total_sum = 0u64; - let new_slice: Vec = slice - .iter() - .map(|utxo| { - total_sum += utxo.value(); - UTXOEstimate { - utxo: utxo.clone(), - estimated_value: Wallet::estimate_fee_per_utxo(utxo), - weight: UTXO_WEIGHT, - } - }) - .collect(); - - fn waste(utxos: &[UTXOEstimate], fee_rate: u64) -> u64 { - let mut total_weight = 0u64; - for utxo in utxos { - total_weight += utxo.weight; - } - total_weight * (fee_rate - LONG_TERM_FEE_RATE) - } - - #[allow(clippy::too_many_arguments)] - fn branch_and_bound( - slice: &[UTXOEstimate], - cur_sum: u64, - cur_vec: Vec, - sum_left: u64, - solution_vec: &mut Vec, - solution_sum: &mut u64, - solution_waste: &mut u64, - target_interval: (u64, u64), - fee_rate: u64, - max_repetitions: usize, - ) { - if max_repetitions == 0 { - return; - } - let sum_left = sum_left - slice[0].estimated_value; - - // include - let mut cur_vec_include = cur_vec.clone(); - cur_vec_include.push(slice[0].clone()); - let cur_sum_include = cur_sum + slice[0].estimated_value; - let waste_include = waste(&cur_vec_include, fee_rate); - if cur_sum_include > target_interval.0 - && cur_sum_include < target_interval.1 - && waste_include < *solution_waste - { - // if here, algorithm is done in this branch - *solution_sum = cur_sum_include; - *solution_vec = cur_vec.clone(); - *solution_waste = waste_include; - return; - } - if cur_sum_include < target_interval.1 - && sum_left > target_interval.0 - cur_sum_include - { - branch_and_bound( - &slice[1..], - cur_sum_include, - cur_vec_include, - sum_left, - solution_vec, - solution_sum, - solution_waste, - target_interval, - fee_rate, - max_repetitions - 1, - ); - } - - // omit - - if sum_left > target_interval.0 - cur_sum { - branch_and_bound( - &slice[1..], - cur_sum, - cur_vec, - sum_left, - solution_vec, - solution_sum, - solution_waste, - target_interval, - fee_rate, - max_repetitions - 1, - ) - } - } - - let mut solution: Vec = Vec::new(); - let mut solution_sum = u64::MAX; - let mut solution_waste = u64::MAX; - branch_and_bound( - &new_slice, - 0u64, - Vec::new(), - total_sum, - &mut solution, - &mut solution_sum, - &mut solution_waste, - (target, target + dust_threshold), - fee_rate, - max_repetitions - 1, - ); - - let solution_utxo = solution - .into_iter() - .map(|utxo_estimate| utxo_estimate.utxo) - .collect(); - (solution_utxo, solution_sum) - } - - let dust_threshold = Self::estimate_fee_per_utxo(&available_utxos[0]) * 3; - let target = amount + dust_threshold; - let utxos_smaller_than_target = available_utxos.get_slice(0..index).to_vec(); - let sum: u64 = UTXO::sum(&utxos_smaller_than_target); - - let (bnb_solution_vec, _) = branch_and_bound_entrypoint( - &utxos_smaller_than_target, - 100_000, - amount, - dust_threshold, - 1, - ); - - if bnb_solution_vec.is_empty() { - return Ok(bnb_solution_vec); - } - - // if sum == amount { - // return Ok(available_utxos.get_slice(0..index).to_vec()); - // } - // if sum > target { - // // I'll just use the shit algorithm I invented. Goes like this: - // // Inputs are target amount and the ordered vector V that contains all available UTXOs - // // target = target_amount + dust threshold - // // First, we get the smallest element bigger than the target on the vector with index i - // // smallest elements vector = s = [0..i] - // // - // // We select the middle element of s (index k) and sum it with the following element (k+1). - // // If it's still smaller than target, sum it with k-1. - // // If still smaller than target, sum with k+2, and then k-2, k+3, and so on, until we find a solution - // // and save it as an option in a solutions vector - // // - // // We do the same now, but starting the element in the middle of the slice s[k..], - // // and again with the element in the middle of s[0..k]. This creates kind of a binary search through the vector. - // // - // // We repeat the process recursively x times, x being arbitrarily defined - // let first_k = utxos_smaller_than_target.len() / 2; - // recursive( - // &utxos_smaller_than_target, - // first_k, - // &mut solutions, - // target, - // &mut None, - // ); - // } - - let best_solution = solutions.into_iter().fold(None, |acc, new_vec| match acc { - None => Some((UTXO::sum(&new_vec), new_vec)), - Some((old_sum, old_vec)) => { - let new_sum = UTXO::sum(&new_vec); - if new_sum > old_sum { - Some((new_sum, new_vec)) - } else { - Some((old_sum, old_vec)) - } - } - }); - - match best_solution { - Some((_, vec)) => Ok(vec), - // This is probably safe for an unwrap but fuck it - None => Err(WalletError::InsufficientFunds), - } - } - diff --git a/src/chain/wallet.rs b/src/chain/wallet.rs index c45f0c7..45f0958 100644 --- a/src/chain/wallet.rs +++ b/src/chain/wallet.rs @@ -108,7 +108,15 @@ pub struct Wallet { const MAX_UTXO_SEARCH_DEPTH: usize = 100; const UTXO_WEIGHT: u64 = 64; // typical weight of a P2PKH output - replace with real weight const LONG_TERM_FEE_RATE: u64 = 1; // placeholder - replace with real rate - +const MAX_ITERATIONS_NON_EXACT_COIN_SELECTION: u64 = 10_000; + +// Helper for the coin selection algorithms +#[derive(Clone)] +struct UtxoEstimate { + utxo: UTXO, + effective_value: u64, + weight: u64, +} impl Wallet { /* --------------------------------------------------------------------- * * Construction & Utilities * @@ -194,9 +202,6 @@ impl Wallet { return Ok(vec![single.clone()]); } - for utxo in utxos.clone() { - println!("UTXO of value {}", utxo.value()); - } // 2️⃣ Find the smallest UTXO that already exceeds the target. let first_over_idx = utxos .clone() @@ -204,102 +209,88 @@ impl Wallet { .position(|u| u.value() < amount) .unwrap_or(0); - println!("first_over_idx is {first_over_idx}"); - - // If we found a single “big enough” UTXO, keep it as a candidate solution. - let mut candidate_solutions = Vec::new(); - if let Some(utxo) = utxos.get(first_over_idx - 1) { - candidate_solutions.push(vec![utxo.clone()]); - } - // 3️⃣ Branch‑and‑bound selection on the remaining (smaller) UTXOs. let dust_threshold = Self::estimate_fee_per_utxo(&utxos[0]) * 3; let smaller_utxos = &utxos.get_slice(first_over_idx..utxos.len()); - println!("len of smaller_utxos is {}", smaller_utxos.len()); - println!("The amount is {amount} and the dust_threshold is {dust_threshold}"); + let mut total_sum = 0; + let estimates: Vec = smaller_utxos + .iter() + .map(|u| { + total_sum += u.value(); + UtxoEstimate { + utxo: u.clone(), + effective_value: u.value() - Self::estimate_fee_per_utxo(u), + weight: UTXO_WEIGHT, + } + }) + .collect(); let (bnb_solution, _) = self.branch_and_bound( - smaller_utxos, + &estimates, amount, dust_threshold, 100_000, // max repetitions + total_sum, ); - println!("Finished bnb"); if !bnb_solution.is_empty() { return Ok(bnb_solution); } else { println!("bnb solution is empty"); - return Err(WalletError::InsufficientFunds); } - #[allow(unreachable_code)] - // 4️⃣ Pick the “best” solution (the one with the highest sum that still + // 4️⃣ Pick the “best” solution (the one with the lowest waste that still // satisfies the target). If none exists we fall back to the exact‑match // error (already handled at the top). + + // If we found a single “big enough” UTXO, keep it as a candidate solution. + let mut candidate_solutions = Vec::new(); + if first_over_idx > 0 { + if let Some(utxo) = utxos.get(first_over_idx - 1) { + candidate_solutions.push(vec![utxo.clone()]); + } + } + let target = amount + dust_threshold; - Self::dantes_crazy_coin_selection_algorithm( - smaller_utxos, - MAX_UTXO_SEARCH_DEPTH, - &mut candidate_solutions, - target, - -1, - ); - let best = candidate_solutions.into_iter().max_by_key(UTXO::sum); + let solution = Self::dantes_crazy_algorithm_entrypoint(smaller_utxos, target); - best.ok_or(WalletError::InsufficientFunds) + if solution.is_empty() { + return Err(WalletError::InsufficientFunds); + } + Ok(solution) } + // Helper: compute “waste” (extra fee paid beyond the long‑term rate). + fn waste(estimates: &[UtxoEstimate], fee_rate: u64) -> u64 { + let total_weight: u64 = estimates.iter().map(|e| e.weight).sum(); + // println!( + // "waste is {}, total_weight is {}, fee rate is {}", + // total_weight * (fee_rate - LONG_TERM_FEE_RATE), + // total_weight, + // fee_rate + // ); + total_weight * (fee_rate - LONG_TERM_FEE_RATE) + } /// Core branch‑and‑bound algorithm – returns a tuple `(selected_utxos, total_value)`. fn branch_and_bound( &self, - slice: &[UTXO], + estimates: &[UtxoEstimate], target: u64, dust_threshold: u64, max_reps: usize, + total_sum: u64, ) -> (Vec, u64) { - println!("Starting branch_and_bound"); // Transform each UTXO into an enriched struct that carries an estimated // “effective value” (value minus fee) and its weight. - #[derive(Clone)] - struct UtxoEstimate { - utxo: UTXO, - effective_value: u64, - weight: u64, - } - let mut total_sum = 0u64; - let estimates: Vec = slice - .iter() - .map(|u| { - total_sum += u.value(); - UtxoEstimate { - utxo: u.clone(), - effective_value: u.value() - Self::estimate_fee_per_utxo(u), - weight: UTXO_WEIGHT, - } - }) - .collect(); - - for est in estimates.clone() { - println!( - "UTXOEstimate (effective_value: {}, weight: {} )", - est.utxo.value() - Self::estimate_fee_per_utxo(&est.utxo), - UTXO_WEIGHT - ); - } - - // Helper: compute “waste” (extra fee paid beyond the long‑term rate). - fn waste(estimates: &[UtxoEstimate], fee_rate: u64) -> u64 { - let total_weight: u64 = estimates.iter().map(|e| e.weight).sum(); - // println!( - // total_weight * (fee_rate - LONG_TERM_FEE_RATE), - // total_weight, - // fee_rate - // ); - total_weight * (fee_rate - LONG_TERM_FEE_RATE) - } + // for est in estimates.clone() { + // println!( + // "UTXOEstimate (effective_value: {}, weight: {} )", + // est.utxo.value() - Self::estimate_fee_per_utxo(&est.utxo), + // UTXO_WEIGHT + // ); + // } // Recursive branch‑and‑bound search. #[allow(clippy::too_many_arguments)] @@ -326,7 +317,7 @@ impl Wallet { let mut incl_set = cur_set.clone(); incl_set.push(remaining[0].clone()); let incl_sum = cur_sum + remaining[0].effective_value; - let incl_waste = waste(&incl_set, fee_rate); + let incl_waste = Wallet::waste(&incl_set, fee_rate); if incl_sum >= target_interval.0 && incl_sum <= target_interval.1 @@ -335,7 +326,7 @@ impl Wallet { *best_sum = incl_sum; *best_set = incl_set.clone(); *best_waste = incl_waste; - // Found a feasible solution – we can stop exploring this branch. + // Found a feasible solution - we can stop exploring this branch. return; } @@ -356,7 +347,7 @@ impl Wallet { ); } - // ---------- Exclude the head ---------- + // ---------- Omit the head ---------- if sum_left as i64 > target_interval.0 as i64 - cur_sum as i64 { recurse( &remaining[1..], @@ -378,7 +369,7 @@ impl Wallet { let mut best_sum = u64::MAX; let mut best_waste = u64::MAX; recurse( - &estimates, + estimates, 0, Vec::new(), total_sum, @@ -406,40 +397,131 @@ impl Wallet { } let fraction_used = elements_tested as f64 / total_elements as f64; let new_depth = (max_depth as f64 * (1.0 - fraction_used)).ceil() as i32; + println!("The result of the calculate_recursion_depth func is {new_depth}"); std::cmp::max(new_depth, 1) } + + fn dantes_crazy_algorithm_entrypoint(slice: &[UTXO], target: u64) -> Vec { + let mut solution: Vec = Vec::new(); + let mut solution_waste = u64::MAX; + + let slice_estimates: Vec = slice + .iter() + .map(|utxo| UtxoEstimate { + utxo: utxo.clone(), + effective_value: utxo.value() - Wallet::estimate_fee_per_utxo(utxo), + weight: UTXO_WEIGHT, + }) + .collect(); + + Self::dantes_crazy_coin_selection_algorithm( + &slice_estimates, + slice_estimates.len() / 2, + &mut solution, + &mut solution_waste, + target, + -1, + 2, // TODO get the real fee_rate + 1, + ); + + solution.into_iter().map(|estimate| estimate.utxo).collect() + } + fn dantes_crazy_coin_selection_algorithm( - slice: &[UTXO], - k: usize, - solutions: &mut Vec>, + slice: &[UtxoEstimate], + middle_index: usize, + solution: &mut Vec, + solution_waste: &mut u64, target: u64, - mut x: i32, // initialize with -1 + mut number_of_iterations: i32, // initialize with -1 + fee_rate: u64, + position: u64, ) { - if x == 0 { + if number_of_iterations == -1 { + println!("number_of_iterations is {number_of_iterations}"); + } + if position == MAX_ITERATIONS_NON_EXACT_COIN_SELECTION + || number_of_iterations == 0 + || slice.is_empty() + { return; } let mut sum = 0; - let mut elements: Vec = vec![slice[k].clone()]; - sum += slice[k].value(); - - for i in 0..k { - sum += slice[k - i].value(); - elements.push(slice[k - 1].clone()); + let mut elements: Vec = match slice.get(middle_index) { + Some(_) => vec![slice[middle_index].clone()], + None => return, + }; + sum += slice[middle_index].utxo.value(); + + if number_of_iterations == -1 { + println!("number_of_iterations is still {number_of_iterations}"); + } + for k in 2..slice.len() + 1 { + // Gets the middle_index + or - k/2 depending on whether k is even or odd. Also checks + // if the resulting index is smaller than 0, and if so skips the iteration + let index = match k % 2 { + 1 => { + let x = middle_index as i32 - (k as i32 / 2); + if x < 0 { + continue; + } else { + x as usize + } + } + 0 => middle_index + k / 2, + _ => unreachable!(), + }; + + if let Some(x) = slice.get(index) { + sum += x.utxo.value(); + elements.push(x.clone()); + // println!( + // "Pushing {} to sum {} on position {position} with target {target}", + // slice[index].utxo.value(), + // sum + // ); + } if sum > target { // if there's no x yet, we calculate it here. The x is a function of the number // of elements necessary in the first iteration. If many elements are needed in the // first iteration, that means that if I continue for too many times there will be a // lot of overlap. Therefore, we reduce the size of x - if x == -1 { - x = Self::calculate_recursion_depth(MAX_UTXO_SEARCH_DEPTH, i, slice.len()); + if number_of_iterations == -1 { + number_of_iterations = + Self::calculate_recursion_depth(MAX_UTXO_SEARCH_DEPTH, k, slice.len()); + println!("Caculated the recursion depth to be {number_of_iterations}"); + } + let new_waste = Self::waste(&elements, fee_rate); + if new_waste < *solution_waste { + *solution = elements.clone(); + *solution_waste = new_waste; } - solutions.push(elements); break; } } - Self::dantes_crazy_coin_selection_algorithm(slice, k / 2, solutions, target, x - 1); - Self::dantes_crazy_coin_selection_algorithm(slice, k * 3 / 2, solutions, target, x - 1); + + Self::dantes_crazy_coin_selection_algorithm( + slice, + middle_index / 2, + solution, + solution_waste, + target, + number_of_iterations - 1, + fee_rate, + position * 2, + ); + Self::dantes_crazy_coin_selection_algorithm( + slice, + middle_index * 3 / 2, + solution, + solution_waste, + target, + number_of_iterations - 1, + fee_rate, + position * 2 + 1, + ); } } diff --git a/tests/test_coin_selection.rs b/tests/test_coin_selection.rs index 18b048f..63255b0 100644 --- a/tests/test_coin_selection.rs +++ b/tests/test_coin_selection.rs @@ -7,29 +7,15 @@ fn test_get_utxo_wallet() { let input_utxos = vec![ // Large UTXOs - good for covering big amounts efficiently UTXO::new(50000, wallet1.clone()), - UTXO::new(32000, wallet1.clone()), UTXO::new(25000, wallet1.clone()), // Medium UTXOs - typical transaction amounts - UTXO::new(15000, wallet1.clone()), - UTXO::new(15000, wallet1.clone()), - UTXO::new(12000, wallet1.clone()), - UTXO::new(10500, wallet1.clone()), - UTXO::new(8500, wallet1.clone()), - UTXO::new(7200, wallet1.clone()), - UTXO::new(6000, wallet1.clone()), - UTXO::new(5500, wallet1.clone()), + UTXO::new(10000, wallet1.clone()), UTXO::new(5000, wallet1.clone()), // Small UTXOs - test efficiency vs dust management UTXO::new(3000, wallet1.clone()), - UTXO::new(2500, wallet1.clone()), - UTXO::new(2000, wallet1.clone()), - UTXO::new(1500, wallet1.clone()), UTXO::new(1200, wallet1.clone()), UTXO::new(1000, wallet1.clone()), // Very small UTXOs - potential dust scenarios - UTXO::new(800, wallet1.clone()), - UTXO::new(600, wallet1.clone()), - UTXO::new(400, wallet1.clone()), UTXO::new(300, wallet1.clone()), ]; @@ -40,13 +26,14 @@ fn test_get_utxo_wallet() { } wallet1.add_utxos(input_utxos); - println!("Checkpoint 1"); + assert_eq!( wallet1.get_utxos(50000).unwrap(), vec![UTXO::new(50000, wallet1.clone())] ); println!("Checkpoint 2"); + assert!(wallet1.get_utxos(100000000).is_err()); println!("Checkpoint 3"); @@ -56,20 +43,6 @@ fn test_get_utxo_wallet() { print_utxo_vec(wallet1.get_utxos(40000).unwrap()); println!("Checkpoint 5"); - print_utxo_vec(wallet1.get_utxos(25461).unwrap()); + print_utxo_vec(wallet1.get_utxos(60000).unwrap()); println!("Checkpoint 6"); - - // let (wallet2, wallet2_pk) = Wallet::new(); - // - // let input_utxos = vec![ - // UTXO::new(1000, wallet1.clone()), - // UTXO::new(2000, wallet1.clone()), - // ]; - // let output_utxos = vec![ - // UTXO::new(2500, wallet2.clone()), - // UTXO::new(500, wallet2.clone()), - // ]; - // let transaction_info = TransactionInfo::new(input_utxos, output_utxos); - // let signature = wallet2_pk.sign_transaction(&transaction_info).unwrap(); - // let transaction = Transaction::new(wallet2, wallet1, transaction_info, signature); }