From fd08a642ed55baacace87f3754e06e83a2262a0a Mon Sep 17 00:00:00 2001 From: victor Date: Mon, 25 Aug 2025 16:30:59 +0200 Subject: [PATCH] bless --- Cargo.lock | 19 ++ Cargo.toml | 2 + database/tx.db | 16 +- src/args.rs | 19 +- src/block.rs | 22 -- src/core.rs | 7 + src/core/block.rs | 43 +++ src/core/blockchain.rs | 150 +++++++++++ src/{ => core}/tx.rs | 6 +- src/main.rs | 23 +- src/network.rs | 1 + src/network/native.rs | 375 +++++++++++++++++++++++++++ src/{ => network}/network_browser.rs | 0 src/network_native.rs | 125 --------- src/node.rs | 104 -------- 15 files changed, 632 insertions(+), 280 deletions(-) delete mode 100644 src/block.rs create mode 100644 src/core.rs create mode 100644 src/core/block.rs create mode 100644 src/core/blockchain.rs rename src/{ => core}/tx.rs (85%) create mode 100644 src/network.rs create mode 100644 src/network/native.rs rename src/{ => network}/network_browser.rs (100%) delete mode 100644 src/network_native.rs delete mode 100644 src/node.rs diff --git a/Cargo.lock b/Cargo.lock index 3d28f7c..09a2607 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -136,8 +136,10 @@ version = "0.1.0" dependencies = [ "chrono", "clap", + "hex", "serde", "serde_json", + "sha2", "thiserror", "tokio", "tokio-tungstenite", @@ -419,6 +421,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "http" version = "1.3.1" @@ -881,6 +889,17 @@ dependencies = [ "digest", ] +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "shlex" version = "1.3.0" diff --git a/Cargo.toml b/Cargo.toml index dfb4961..8e3d69d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,8 +6,10 @@ edition = "2024" [dependencies] chrono = "0.4.41" clap = { version = "4.5.45", features = ["derive"] } +hex = "0.4.3" serde = { version = "1.0.219", features = ["derive"] } serde_json = "1.0.143" +sha2 = "0.10.9" thiserror = "2.0.16" tokio = { version = "1.47.1", features = ["full"] } tokio-tungstenite = "0.27.0" diff --git a/database/tx.db b/database/tx.db index b189925..fe51488 100644 --- a/database/tx.db +++ b/database/tx.db @@ -1,15 +1 @@ -[ - { - "head": { - "previous_hash": "0000", - "timestamp": 1234567890, - "merkle_root": "abc123", - "block_hash": "def456", - "nonce": 5 - }, - "tx": [ - {"from": "alice", "to": "bob", "value": 10, "data": ""}, - {"from": "bob", "to": "charlie", "value": 5, "data": ""} - ] - } -] +[] diff --git a/src/args.rs b/src/args.rs index 6c4dcaa..abef15c 100644 --- a/src/args.rs +++ b/src/args.rs @@ -1,5 +1,5 @@ use clap::{Parser, Subcommand}; -use crate::tx::Tx; +use crate::core; #[derive(Parser, Debug)] #[command(version, about, long_about = None)] @@ -16,18 +16,31 @@ pub enum Commands { #[command(subcommand)] tx_command: TxCmd }, + /// Show accounts and balances #[command(short_flag = 'l')] List, + + /// Start as seed node #[command(short_flag = 's')] - Seed + Seed { + #[arg(short = 'a')] + addr: String + }, + + /// listen on addr + #[command(short_flag = 'r')] + Run { + #[arg(short = 'a')] + addr: String + }, } #[derive(Subcommand, Debug)] pub enum TxCmd { /// Add a new transaction to the DB #[command(short_flag = 'a')] - Add(Tx) + Add(core::Tx) } impl Args { diff --git a/src/block.rs b/src/block.rs deleted file mode 100644 index 9583bf0..0000000 --- a/src/block.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::tx::Tx; - -#[derive(Debug, serde::Deserialize, serde::Serialize)] -struct BlockHeader { - previous_hash: String, - timestamp: u64, - merkle_root: String, - block_hash: String, - nonce: u32 -} - -#[derive(Debug, serde::Deserialize, serde::Serialize)] -pub struct Block { - head: BlockHeader, - tx: Vec -} - -impl Block { - pub fn get_header(&self) -> &BlockHeader { - &self.head - } -} diff --git a/src/core.rs b/src/core.rs new file mode 100644 index 0000000..b8e71de --- /dev/null +++ b/src/core.rs @@ -0,0 +1,7 @@ +pub mod block; +pub mod blockchain; +pub mod tx; + +pub use block::*; +pub use blockchain::*; +pub use tx::*; diff --git a/src/core/block.rs b/src/core/block.rs new file mode 100644 index 0000000..3a8a47d --- /dev/null +++ b/src/core/block.rs @@ -0,0 +1,43 @@ +use crate::core; + +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct BlockHeader { + previous_hash: String, + timestamp: u64, + merkle_root: String, + block_hash: String, + nonce: u32 +} + +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct Block { + head: BlockHeader, + tx: Vec +} + +impl BlockHeader { + pub fn previous_hash(&self) -> &str { + &self.previous_hash + } + pub fn timestamp(&self) -> u64 { + self.timestamp + } + pub fn nonce(&self) -> u32 { + self.nonce + } + pub fn merkle_root(&self) -> &str { + &self.merkle_root + } + pub fn block_hash(&self) -> &str { + &self.block_hash + } +} + +impl Block { + pub fn head(&self) -> &BlockHeader { + &self.head + } + pub fn tx(&self) -> &[core::Tx] { + &self.tx + } +} diff --git a/src/core/blockchain.rs b/src/core/blockchain.rs new file mode 100644 index 0000000..53c9f93 --- /dev/null +++ b/src/core/blockchain.rs @@ -0,0 +1,150 @@ +use sha2::Digest; + +use crate::log::*; + +use crate::core; +use crate::error::{ BlockchainError, TxError }; +use std::collections::HashMap; + +pub type Account = String; + +#[derive(Debug)] +pub enum ValidationError { + InvalidBlockHash +} + +#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] +pub struct Genesis { + pub genesis_time: String, + pub chain_id: String, + pub balances: std::collections::HashMap +} + +#[derive(Debug)] +pub struct Blockchain { + genesis: Genesis, + balances: std::collections::HashMap, + blocks: Vec, + tx_mempool: Vec, +} + +impl Genesis { + pub fn new() -> Self { + Self { + genesis_time: String::new(), + chain_id: String::new(), + balances: std::collections::HashMap::new() + } + } +} + +#[allow(dead_code)] +impl Blockchain { + pub fn open_account(&mut self, tx: core::Tx) -> Result<(), BlockchainError> { + if !tx.is_new_account() { + Err(BlockchainError::InvalidAccountCreation) + } else { + self.add(tx) + } + } + + pub fn add(&mut self, tx: core::Tx) -> Result<(), BlockchainError> { + self.apply(&tx)?; + self.tx_mempool.push(tx); + Ok(()) + } + + pub fn apply(&mut self, tx: &core::Tx) -> Result<(), BlockchainError> { + match tx.validate() { + Ok(_) => {}, + Err(e) => return Err(BlockchainError::Tx(e)) + } + + if let Some(from_balance) = self.balances.get_mut(tx.get_from()) { + if *from_balance > tx.get_value() { + *from_balance -= tx.get_value(); + } else { + return Err(BlockchainError::Tx(TxError::FromInsuffitientFonds)) + } + } else { + return Err(BlockchainError::Tx(TxError::UnknownAccount(tx.get_from().to_string()))) + } + + if let Some(to_balance) = self.balances.get_mut(&tx.get_to().to_string()) { + *to_balance += tx.get_value() + } else { + if tx.is_new_account() { + self.balances.insert(tx.get_to().to_string(), tx.get_value()); + } else { + return Err(BlockchainError::Tx(TxError::UnknownAccount(tx.get_to().to_string()))) + } + } + + Ok(()) + } + + pub fn new(balances: HashMap, blocks: Vec, tx_mempool: Vec, genesis: Genesis) -> Blockchain { + return Self { + genesis, + balances, + blocks, + tx_mempool + } + } + +} + +pub fn calculate_block_hash(block: &core::Block) -> String { + let mut hasher = sha2::Sha256::new(); + let head = block.head(); + + hasher.update(head.nonce().to_be_bytes()); + hasher.update(head.previous_hash()); + hasher.update(head.timestamp().to_be_bytes()); + hasher.update(head.merkle_root()); + + let res = hasher.finalize(); + hex::encode(res) +} + +impl Blockchain { + pub fn get_balances(&self) -> &std::collections::HashMap { + &self.balances + } + + pub fn blocks(&self) -> &[core::Block] { + &self.blocks + } + + pub fn genesis(&self) -> &Genesis { + &self.genesis + } + + fn validate_chain(&self) -> Result<(), ValidationError>{ + log!(DEBUG, "Validating Chain"); + let blocks = self.blocks(); + for block in blocks { + let head = block.head(); + let hash = calculate_block_hash(block); + + if hash != head.block_hash() { + log!(ERROR, "Hash {} does not equal block_hash() {}", hash, head.block_hash()); + return Err(ValidationError::InvalidBlockHash) + } + } + Ok(()) + } + + pub fn from_genesis(genesis: Genesis, blocks: Vec) -> Result { + log!(DEBUG, "Starting Chain Build from Genesis"); + let chain = Blockchain { + genesis, + blocks, + balances: HashMap::new(), + tx_mempool: vec![] + }; + + chain.validate_chain()?; + Ok(chain) + } +} diff --git a/src/tx.rs b/src/core/tx.rs similarity index 85% rename from src/tx.rs rename to src/core/tx.rs index 7b87960..377343b 100644 --- a/src/tx.rs +++ b/src/core/tx.rs @@ -1,4 +1,4 @@ -use crate::node::Account; +use crate::core::Account; use crate::error::TxError; #[derive(serde::Deserialize, serde::Serialize, Debug, clap::Args, Clone)] @@ -10,6 +10,10 @@ pub struct Tx { } impl Tx { + pub fn new(from: Account, to: Account, value: u32, data: String) -> Self { + Self { from, to, value, data } + } + pub fn validate(&self) -> Result<(), TxError> { if self.from.is_empty() { return Err(TxError::FromEmpty) diff --git a/src/main.rs b/src/main.rs index 8798fff..e97e074 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,18 +2,17 @@ use error::{ BlockchainError, handle_error }; #[macro_use] pub mod log; -pub mod node; -pub mod tx; pub mod error; -pub mod block; pub mod args; -pub mod network_native; -pub mod network_browser; +pub mod network; +pub mod core; -use crate::tx::Tx; +use crate::network::native::NativeNode; use crate::args::{get_args, TxCmd, Commands}; -fn add_transaction(tx: Tx) -> Result<(), BlockchainError> { +const SEED_ADDR: &str = "127.0.0.1:8333"; + +fn add_transaction(tx: core::Tx) -> Result<(), BlockchainError> { Ok(()) } @@ -37,9 +36,13 @@ async fn main() { Commands::List => { list_accounts() } - Commands::Seed => { - network_native::NativeNode::run_as_seed().await; + Commands::Seed { addr: _ } => { + NativeNode::seed(SEED_ADDR.to_string()).run_native().await; + } + Commands::Run{ addr } => { + dbg!(&addr); + NativeNode::bootstrap(addr).await.unwrap().run_native().await; } } - println!("Hello, world!"); + println!("Hello, world!"); } diff --git a/src/network.rs b/src/network.rs new file mode 100644 index 0000000..ce12f2f --- /dev/null +++ b/src/network.rs @@ -0,0 +1 @@ +pub mod native; diff --git a/src/network/native.rs b/src/network/native.rs new file mode 100644 index 0000000..2e88134 --- /dev/null +++ b/src/network/native.rs @@ -0,0 +1,375 @@ +use crate::core::{self, ValidationError}; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::net::{TcpStream}; + +use std::io::{self, Read, Write}; +use std::collections::HashMap; +use crate::log::*; +use tokio::sync::mpsc; + +struct TcpPeer { + id: String, + sender: tokio::sync::mpsc::Sender +} + +pub struct NativeNode { + addr: String, + tcp: HashMap, + ws: Vec, + chain: core::Blockchain, + db_file: std::fs::File +} + +async fn handle_browser_connection(websocket: warp::ws::WebSocket) { + println!("Browser connected via WebSocket on addr {:?}", websocket); +} + +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] +pub enum ProtocolMessage { + BootstrapRequest { + node_id: String, + version: String + }, + BootstrapResponse { + genesis: core::Genesis, + blocks: Vec + }, + Handshake { node_id: String, version: String }, + Block { height: u64, data: String }, + Transaction(core::Tx), + Ping { peer_id: String }, + Pong { peer_id: String }, + Disconnect { peer_id: String }, +} + +#[derive(Debug)] +enum NodeCommand { + AddPeer { peer_id: String, sender: tokio::sync::mpsc::Sender }, + ProcessMessage { peer_id: String, message: ProtocolMessage }, + Transaction { tx: core::Tx } +} + +#[derive(Debug, Clone)] +pub struct NetworkError { + pub message: String, +} + +impl std::fmt::Display for NetworkError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.message) + } +} + +impl std::error::Error for NetworkError {} + + +impl NativeNode { + + fn add_tcp_peer(&mut self, id: String, sender: tokio::sync::mpsc::Sender) { + let peer = TcpPeer { + id: id.clone(), + sender + }; + + self.tcp.insert(id, peer); + } + + async fn send_message(stream: &mut TcpStream, message: &ProtocolMessage) -> Result<(), Box> { + // Serialize message to JSON + let json = serde_json::to_string(message)?; + let data = json.as_bytes(); + + // Send length prefix (4 bytes) + let len = data.len() as u32; + stream.write_all(&len.to_be_bytes()).await?; + + // Send the actual data + stream.write_all(data).await?; + stream.flush().await?; + + println!("Sent: {:?}", message); + Ok(()) + } + + async fn receive_message(stream: &mut tokio::net::TcpStream) -> Result { + let mut len_bytes = [0u8; 4]; + stream.read_exact(&mut len_bytes).await + .map_err(|e| NetworkError { message: format!("Failed to read length: {}", e) })?; + + let len = u32::from_be_bytes(len_bytes) as usize; + + let mut data = vec![0u8; len]; + stream.read_exact(&mut data).await + .map_err(|e| NetworkError { message: format!("Failed to read data: {}", e) })?; + + let json = String::from_utf8(data) + .map_err(|e| NetworkError { message: format!("Invalid UTF-8: {}", e) })?; + + let message: ProtocolMessage = serde_json::from_str(&json) + .map_err(|e| NetworkError { message: format!("JSON parse error: {}", e) })?; + + Ok(message) + } + + fn persist(&mut self) { + for t in self.chain.blocks() { + let json = serde_json::to_string(&t).unwrap(); + self.db_file.write(json.as_bytes()).unwrap(); + } + } + + pub fn new(chain: core::Blockchain, db_file: std::fs::File, addr: String) -> Self { + Self { + tcp: HashMap::new(), + ws: Vec::new(), + chain, + addr, + db_file + } + } + + pub async fn bootstrap(addr: &str) -> Result { + const SEED_NODES: [&str; 1] = [ + "127.0.0.1:8333" + ]; + + log!(INFO, "Running As Native Node"); + + let mut stream = tokio::net::TcpStream::connect(SEED_NODES[0]).await.unwrap(); + + let mes = ProtocolMessage::BootstrapRequest { node_id: "".to_string(), version: "".to_string() }; + + NativeNode::send_message(&mut stream, &mes).await.unwrap(); + let response = NativeNode::receive_message(&mut stream).await.unwrap(); + + match response { + ProtocolMessage::BootstrapResponse { genesis, blocks } => { + let chain = core::Blockchain::from_genesis(genesis, blocks)?; + let node = Self::new(chain, std::fs::File::open("./database/tx.db").unwrap(), addr.to_string()); + Ok(node) + }, + _ => { + log!(ERROR, "Invalid Response from BootstrapRequest: {:?}", &response); + Err(ValidationError::InvalidBlockHash) + } + } + } + + pub fn seed(addr: String) -> Self { + log!(INFO, "Running As Seed Node"); + let cwd = std::env::current_dir().unwrap(); + let mut genpath = std::path::PathBuf::from(&cwd); + genpath.push("database"); + genpath.push("genesis.json"); + let mut gen_file = std::fs::File::open(genpath).unwrap(); + + let mut buf = String::new(); + gen_file.read_to_string(&mut buf).unwrap(); + + let mut db_file: std::fs::File = { + let mut db_path = std::path::PathBuf::from(&cwd); + db_path.push("database"); + db_path.push("tx.db"); + + std::fs::OpenOptions::new().read(true).write(true).create(true).open(&db_path).unwrap() + }; + + let genesis = serde_json::from_str::(&buf).unwrap(); + + buf.clear(); + db_file.read_to_string(&mut buf).unwrap(); + + let blocks = serde_json::from_str::>(&buf).unwrap(); + let chain = core::Blockchain::from_genesis(genesis, blocks).unwrap(); + + Self::new(chain, db_file, addr) + } + + async fn accept_connections( + listner: tokio::net::TcpListener, + request_sender: tokio::sync::mpsc::Sender + ) { + log!(INFO, "Starting to accept connections"); + + let mut connection_count: i32 = 0; + + while let Ok((stream, addr)) = listner.accept().await { + connection_count += 1; + let peer_id = format!("peer_{}", connection_count); + + let (response_sender, mut response_receiver) = mpsc::channel::(100); + + log!(INFO, "New Connection from {}", addr); + let add_peer = NodeCommand::AddPeer { + peer_id: peer_id.clone(), + sender: response_sender + }; + + if let Err(_) = request_sender.send(add_peer).await { + log!(ERROR, "Failed to send AddPeer to {}", addr); + break; + } + + NativeNode::start_peer_handler(stream, peer_id, request_sender.clone(), response_receiver).await; + } + + } + + async fn start_peer_handler( + mut stream: tokio::net::TcpStream, + peer_id: String, + request_sender: tokio::sync::mpsc::Sender, + mut response_receiver: tokio::sync::mpsc::Receiver + ) { + let peer_id_clone = peer_id.clone(); + + tokio::spawn(async move { + log!(INFO, "Started Message Handler for {}", peer_id_clone); + + loop { + match NativeNode::receive_message(&mut stream).await { + Ok(message) => { + log!(INFO, "Received Message from {peer_id_clone}"); + + let command = NodeCommand::ProcessMessage { + peer_id: peer_id.clone(), + message: message.clone() + }; + + if request_sender.send(command).await.is_err() { + log!(ERROR, "Failed to send command to main thread from {peer_id}"); + break; + } + + match message { + ProtocolMessage::Ping { peer_id: _ } => { + let resp = response_receiver.recv().await.unwrap(); + if !matches!(&resp, ProtocolMessage::Pong { peer_id: _ }) { + log!{ERROR, "Invalid Response to Ping Request"}; + } else { + NativeNode::send_message(&mut stream, &resp).await; + } + }, + ProtocolMessage::BootstrapRequest { node_id, version } => { + let resp = response_receiver.recv().await.unwrap(); + if !matches!(&resp, ProtocolMessage::BootstrapResponse { genesis, blocks }) { + log!{ERROR, "Invalid Response to Ping Request"}; + } else { + NativeNode::send_message(&mut stream, &resp).await; + } + } + _ => {} + } + }, + Err(e) => { + log!(ERROR, "Failed to read message from {peer_id_clone}: {e}"); + break; + } + } + } + }); + } + + async fn process_message(&mut self, peer_id: String, message: &ProtocolMessage) { + + match message { + ProtocolMessage::BootstrapRequest { node_id, version } => { + log!(INFO, "Received BootstrapRequest from {peer_id}"); + let peer = &self.tcp[&peer_id]; + let resp = ProtocolMessage::BootstrapResponse { + genesis: self.chain.genesis().clone(), + blocks: self.chain.blocks().to_vec() + }; + peer.sender.send(resp).await.unwrap(); + log!(INFO, "Send BootstrapResponse to {peer_id}"); + }, + ProtocolMessage::Ping {peer_id} => { + log!(INFO, "Received Ping from {peer_id}"); + let peer = &self.tcp[peer_id]; + let resp = ProtocolMessage::Pong { peer_id: "seed".to_string() }; + peer.sender.send(resp).await.unwrap(); + }, + _ => { + log!(DEBUG, "TODO: implement this message type"); + } + } + } + + async fn cli(command_sender: mpsc::Sender) { + loop { + let mut input = String::new(); + match io::stdin().read_line(&mut input) { + Ok(_) => { + let input = input.trim(); + if input.is_empty() { + continue ; + } + + let parts: Vec<&str> = input.split_whitespace().collect(); + let command = parts[0]; + let args = &parts[1..]; + + match command { + "tx" => { + if args.len() != 4 { + log!(ERROR, "Invalid arg count! Expected {}", 4); + } + let from = args[0]; + let to = args[1]; + let value = args[2].parse::().unwrap(); + let data = args[3]; + + let tx = core::Tx::new( + from.to_string(), + to.to_string(), + value, + data.to_string() + ); + + + } + _ => { + log!(ERROR, "Unkown command {command}"); + continue; + } + } + + }, + Err(_) => {} + } + } + } + + pub async fn run_native(&mut self) { + let tcp_listner = tokio::net::TcpListener::bind(&self.addr).await.unwrap(); + + let (channel_write, mut channel_read) = mpsc::channel::(100); + + tokio::spawn({ + let c = channel_write.clone(); + async move { + NativeNode::accept_connections(tcp_listner, c).await; + }}); + + tokio::spawn({ + let c = channel_write.clone(); + async move { + NativeNode::cli(c).await; + } + }); + + while let Some(command) = channel_read.recv().await { + match command { + NodeCommand::AddPeer { peer_id, sender } => { + self.add_tcp_peer(peer_id, sender); + }, + NodeCommand::ProcessMessage { peer_id, message } => { + self.process_message(peer_id, &message).await; + }, + NodeCommand::Transaction { tx } => { + self.chain.apply(&tx).unwrap(); + } + } + } + } +} diff --git a/src/network_browser.rs b/src/network/network_browser.rs similarity index 100% rename from src/network_browser.rs rename to src/network/network_browser.rs diff --git a/src/network_native.rs b/src/network_native.rs deleted file mode 100644 index 7a49d63..0000000 --- a/src/network_native.rs +++ /dev/null @@ -1,125 +0,0 @@ -use crate::node::{ Account, Blockchain, }; -use crate::block::Block; -use warp::Filter; -use std::io::{Read, Write}; -use std::collections::HashMap; -use crate::log::*; - -use crate::network_native; - -struct TcpPeer { - stream: tokio::net::TcpStream, - addr: std::net::SocketAddr -} - -pub struct NativeNode { - tcp: Vec, - ws: Vec, - chain: crate::node::Blockchain, - db_file: std::fs::File -} - -async fn handle_browser_connection(websocket: warp::ws::WebSocket) { - println!("Browser connected via WebSocket on addr {:?}", websocket); -} - -async fn handle_tcp_connections(listner: tokio::net::TcpListener) { - loop { - match listner.accept().await { - Ok((stream, addr)) => { - - }, - Err(e) => { - println!("Failed to accept TCP connection: {}", e) - } - } - } -} - - -impl NativeNode{ - - pub fn persist(&mut self) { - for t in self.chain.blocks() { - let json = serde_json::to_string(&t).unwrap(); - self.db_file.write(json.as_bytes()).unwrap(); - } - } - - pub fn new(chain: Blockchain, db_file: std::fs::File) -> Self { - Self { - tcp: Vec::new(), - ws: Vec::new(), - chain, - db_file - } - } - - pub fn new_seed() -> Self { - log!(INFO, "Running As Seed Node"); - let cwd = std::env::current_dir().unwrap(); - let mut genpath = std::path::PathBuf::from(&cwd); - genpath.push("database"); - genpath.push("genesis.json"); - let mut gen_file = std::fs::File::open(genpath).unwrap(); - - let mut buf = String::new(); - gen_file.read_to_string(&mut buf).unwrap(); - - let mut db_file: std::fs::File = { - let mut db_path = std::path::PathBuf::from(&cwd); - db_path.push("database"); - db_path.push("tx.db"); - - std::fs::OpenOptions::new().read(true).write(true).create(true).open(&db_path).unwrap() - }; - - let balances: HashMap = { - if let Ok(genesis) = serde_json::from_str::(&buf) { - genesis.balances.clone() - } else { - HashMap::new() - } - }; - - buf.clear(); - db_file.read_to_string(&mut buf).unwrap(); - - let block = serde_json::from_str::>(&buf).unwrap(); - let chain = Blockchain::new(balances, block, vec![]); - - Self::new(chain, db_file) - } - - pub async fn connect_to_peer(&mut self, addr: &str) -> Result<(), Box> { - println!("Attempting to connect to peer: {}", addr); - - let stream = tokio::net::TcpStream::connect(addr).await?; - let peer_addr = stream.peer_addr()?; - - println!("Connected to peer: {}", peer_addr); - - let peer = TcpPeer { - stream, - addr: peer_addr - }; - - - Ok(()) - } - - pub async fn run_as_seed() { - let node = NativeNode::new_seed(); - let tcp_listner = tokio::net::TcpListener::bind("0.0.0.0:8333").await.unwrap(); - let websocket_route = warp::path("ws") - .and(warp::ws()) - .map(|ws: warp::ws::Ws| { - ws.on_upgrade(handle_browser_connection) - }); - let web_server = warp::serve(websocket_route).run(([0, 0, 0, 0], 8080)); - tokio::select! { - _ = web_server => {} - _ = handle_tcp_connections(tcp_listner) => {} - } - } -} diff --git a/src/node.rs b/src/node.rs deleted file mode 100644 index d1810d2..0000000 --- a/src/node.rs +++ /dev/null @@ -1,104 +0,0 @@ -use crate::tx::Tx; -use crate::error::{ BlockchainError, TxError }; -use std::collections::HashMap; - -use crate::block::Block; - -pub type Account = String; - -#[derive(serde::Deserialize, serde::Serialize, Debug)] -pub struct Genesis { - pub genesis_time: String, - pub chain_id: String, - pub balances: std::collections::HashMap -} - -#[derive(Debug)] -pub struct Blockchain { - balances: std::collections::HashMap, - blocks: Vec, - tx_mempool: Vec, -} - -#[allow(dead_code)] -impl Blockchain { - pub fn open_account(&mut self, tx: Tx) -> Result<(), BlockchainError> { - if !tx.is_new_account() { - Err(BlockchainError::InvalidAccountCreation) - } else { - self.add(tx) - } - } - - pub fn add(&mut self, tx: Tx) -> Result<(), BlockchainError> { - self.apply(&tx)?; - self.tx_mempool.push(tx); - Ok(()) - } - - pub fn apply(&mut self, tx: &Tx) -> Result<(), BlockchainError> { - match tx.validate() { - Ok(_) => {}, - Err(e) => return Err(BlockchainError::Tx(e)) - } - - if let Some(from_balance) = self.balances.get_mut(tx.get_from()) { - if *from_balance > tx.get_value() { - *from_balance -= tx.get_value(); - } else { - return Err(BlockchainError::Tx(TxError::FromInsuffitientFonds)) - } - } else { - return Err(BlockchainError::Tx(TxError::UnknownAccount(tx.get_from().to_string()))) - } - - if let Some(to_balance) = self.balances.get_mut(&tx.get_to().to_string()) { - *to_balance += tx.get_value() - } else { - if tx.is_new_account() { - self.balances.insert(tx.get_to().to_string(), tx.get_value()); - } else { - return Err(BlockchainError::Tx(TxError::UnknownAccount(tx.get_to().to_string()))) - } - } - - Ok(()) - } - - pub fn new(balances: HashMap, blocks: Vec, tx_mempool: Vec) -> Blockchain { - return Self { - balances, - blocks, - tx_mempool - } - } - -} - -#[derive(Debug)] -enum BlockchainEvent { - // Connection events - // NewTcpPeer(TcpPeer), - // NewBrowserPeer(BrowserPeer), - PeerDisconnected(String), - - // Message events - // MessageReceived { message: NetworkMessage, from: String }, - - // User commands - ConnectToPeer(String), - ListPeers, - - // Blockchain events - MineBlock, - BlockMined(Block), -} - -impl Blockchain { - pub fn get_balances(&self) -> &std::collections::HashMap { - &self.balances - } - pub fn blocks(&self) -> &[Block] { - &self.blocks - } -}