bless
This commit is contained in:
parent
f42c4ace56
commit
fd08a642ed
19
Cargo.lock
generated
19
Cargo.lock
generated
@ -136,8 +136,10 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
|
"hex",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"sha2",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-tungstenite",
|
"tokio-tungstenite",
|
||||||
@ -419,6 +421,12 @@ version = "0.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hex"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
version = "1.3.1"
|
version = "1.3.1"
|
||||||
@ -881,6 +889,17 @@ dependencies = [
|
|||||||
"digest",
|
"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]]
|
[[package]]
|
||||||
name = "shlex"
|
name = "shlex"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
|
|||||||
@ -6,8 +6,10 @@ edition = "2024"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = "0.4.41"
|
chrono = "0.4.41"
|
||||||
clap = { version = "4.5.45", features = ["derive"] }
|
clap = { version = "4.5.45", features = ["derive"] }
|
||||||
|
hex = "0.4.3"
|
||||||
serde = { version = "1.0.219", features = ["derive"] }
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
serde_json = "1.0.143"
|
serde_json = "1.0.143"
|
||||||
|
sha2 = "0.10.9"
|
||||||
thiserror = "2.0.16"
|
thiserror = "2.0.16"
|
||||||
tokio = { version = "1.47.1", features = ["full"] }
|
tokio = { version = "1.47.1", features = ["full"] }
|
||||||
tokio-tungstenite = "0.27.0"
|
tokio-tungstenite = "0.27.0"
|
||||||
|
|||||||
@ -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": ""}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|||||||
19
src/args.rs
19
src/args.rs
@ -1,5 +1,5 @@
|
|||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use crate::tx::Tx;
|
use crate::core;
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(version, about, long_about = None)]
|
#[command(version, about, long_about = None)]
|
||||||
@ -16,18 +16,31 @@ pub enum Commands {
|
|||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
tx_command: TxCmd
|
tx_command: TxCmd
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Show accounts and balances
|
/// Show accounts and balances
|
||||||
#[command(short_flag = 'l')]
|
#[command(short_flag = 'l')]
|
||||||
List,
|
List,
|
||||||
|
|
||||||
|
/// Start as seed node
|
||||||
#[command(short_flag = 's')]
|
#[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)]
|
#[derive(Subcommand, Debug)]
|
||||||
pub enum TxCmd {
|
pub enum TxCmd {
|
||||||
/// Add a new transaction to the DB
|
/// Add a new transaction to the DB
|
||||||
#[command(short_flag = 'a')]
|
#[command(short_flag = 'a')]
|
||||||
Add(Tx)
|
Add(core::Tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Args {
|
impl Args {
|
||||||
|
|||||||
22
src/block.rs
22
src/block.rs
@ -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<Tx>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Block {
|
|
||||||
pub fn get_header(&self) -> &BlockHeader {
|
|
||||||
&self.head
|
|
||||||
}
|
|
||||||
}
|
|
||||||
7
src/core.rs
Normal file
7
src/core.rs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
pub mod block;
|
||||||
|
pub mod blockchain;
|
||||||
|
pub mod tx;
|
||||||
|
|
||||||
|
pub use block::*;
|
||||||
|
pub use blockchain::*;
|
||||||
|
pub use tx::*;
|
||||||
43
src/core/block.rs
Normal file
43
src/core/block.rs
Normal file
@ -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<core::Tx>
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
150
src/core/blockchain.rs
Normal file
150
src/core/blockchain.rs
Normal file
@ -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<Account, u32>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Blockchain {
|
||||||
|
genesis: Genesis,
|
||||||
|
balances: std::collections::HashMap<Account, u32>,
|
||||||
|
blocks: Vec<core::Block>,
|
||||||
|
tx_mempool: Vec<core::Tx>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Account, u32>, blocks: Vec<core::Block>, tx_mempool: Vec<core::Tx>, 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<String, u32> {
|
||||||
|
&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<core::Block>) -> Result<Blockchain, ValidationError> {
|
||||||
|
log!(DEBUG, "Starting Chain Build from Genesis");
|
||||||
|
let chain = Blockchain {
|
||||||
|
genesis,
|
||||||
|
blocks,
|
||||||
|
balances: HashMap::new(),
|
||||||
|
tx_mempool: vec![]
|
||||||
|
};
|
||||||
|
|
||||||
|
chain.validate_chain()?;
|
||||||
|
Ok(chain)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
use crate::node::Account;
|
use crate::core::Account;
|
||||||
use crate::error::TxError;
|
use crate::error::TxError;
|
||||||
|
|
||||||
#[derive(serde::Deserialize, serde::Serialize, Debug, clap::Args, Clone)]
|
#[derive(serde::Deserialize, serde::Serialize, Debug, clap::Args, Clone)]
|
||||||
@ -10,6 +10,10 @@ pub struct Tx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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> {
|
pub fn validate(&self) -> Result<(), TxError> {
|
||||||
if self.from.is_empty() {
|
if self.from.is_empty() {
|
||||||
return Err(TxError::FromEmpty)
|
return Err(TxError::FromEmpty)
|
||||||
23
src/main.rs
23
src/main.rs
@ -2,18 +2,17 @@ use error::{ BlockchainError, handle_error };
|
|||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod log;
|
pub mod log;
|
||||||
pub mod node;
|
|
||||||
pub mod tx;
|
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod block;
|
|
||||||
pub mod args;
|
pub mod args;
|
||||||
pub mod network_native;
|
pub mod network;
|
||||||
pub mod network_browser;
|
pub mod core;
|
||||||
|
|
||||||
use crate::tx::Tx;
|
use crate::network::native::NativeNode;
|
||||||
use crate::args::{get_args, TxCmd, Commands};
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,9 +36,13 @@ async fn main() {
|
|||||||
Commands::List => {
|
Commands::List => {
|
||||||
list_accounts()
|
list_accounts()
|
||||||
}
|
}
|
||||||
Commands::Seed => {
|
Commands::Seed { addr: _ } => {
|
||||||
network_native::NativeNode::run_as_seed().await;
|
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!");
|
||||||
}
|
}
|
||||||
|
|||||||
1
src/network.rs
Normal file
1
src/network.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub mod native;
|
||||||
375
src/network/native.rs
Normal file
375
src/network/native.rs
Normal file
@ -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<ProtocolMessage>
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct NativeNode {
|
||||||
|
addr: String,
|
||||||
|
tcp: HashMap<String, TcpPeer>,
|
||||||
|
ws: Vec<web_sys::WebSocket>,
|
||||||
|
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<core::Block>
|
||||||
|
},
|
||||||
|
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<ProtocolMessage> },
|
||||||
|
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<ProtocolMessage>) {
|
||||||
|
let peer = TcpPeer {
|
||||||
|
id: id.clone(),
|
||||||
|
sender
|
||||||
|
};
|
||||||
|
|
||||||
|
self.tcp.insert(id, peer);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_message(stream: &mut TcpStream, message: &ProtocolMessage) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
// 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<ProtocolMessage, NetworkError> {
|
||||||
|
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<Self, ValidationError> {
|
||||||
|
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::<core::Genesis>(&buf).unwrap();
|
||||||
|
|
||||||
|
buf.clear();
|
||||||
|
db_file.read_to_string(&mut buf).unwrap();
|
||||||
|
|
||||||
|
let blocks = serde_json::from_str::<Vec<core::Block>>(&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<NodeCommand>
|
||||||
|
) {
|
||||||
|
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::<ProtocolMessage>(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<NodeCommand>,
|
||||||
|
mut response_receiver: tokio::sync::mpsc::Receiver<ProtocolMessage>
|
||||||
|
) {
|
||||||
|
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<NodeCommand>) {
|
||||||
|
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::<u32>().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::<NodeCommand>(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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<TcpPeer>,
|
|
||||||
ws: Vec<web_sys::WebSocket>,
|
|
||||||
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<Account, u32> = {
|
|
||||||
if let Ok(genesis) = serde_json::from_str::<crate::node::Genesis>(&buf) {
|
|
||||||
genesis.balances.clone()
|
|
||||||
} else {
|
|
||||||
HashMap::new()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
buf.clear();
|
|
||||||
db_file.read_to_string(&mut buf).unwrap();
|
|
||||||
|
|
||||||
let block = serde_json::from_str::<Vec<Block>>(&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<dyn std::error::Error>> {
|
|
||||||
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) => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
104
src/node.rs
104
src/node.rs
@ -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<Account, u32>
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Blockchain {
|
|
||||||
balances: std::collections::HashMap<Account, u32>,
|
|
||||||
blocks: Vec<Block>,
|
|
||||||
tx_mempool: Vec<Tx>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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<Account, u32>, blocks: Vec<Block>, tx_mempool: Vec<Tx>) -> 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<String, u32> {
|
|
||||||
&self.balances
|
|
||||||
}
|
|
||||||
pub fn blocks(&self) -> &[Block] {
|
|
||||||
&self.blocks
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user