diff --git a/Cargo.lock b/Cargo.lock index 0600e21..bcd5b26 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -723,6 +723,12 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" +[[package]] +name = "dyn-clone" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" + [[package]] name = "ed25519" version = "2.2.3" @@ -772,6 +778,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "erased-serde" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7" +dependencies = [ + "serde", + "typeid", +] + [[package]] name = "errno" version = "0.3.11" @@ -1502,6 +1518,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "inventory" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab08d7cd2c5897f2c949e5383ea7c7db03fb19130ffcfbf7eda795137ae3cb83" +dependencies = [ + "rustversion", +] + [[package]] name = "ipconfig" version = "0.3.2" @@ -2321,13 +2346,18 @@ dependencies = [ "chrono", "clap", "clap_derive", + "dyn-clone", + "hex", "libp2p", "rand 0.8.5", "serde", "serde_json", + "sha256", + "thiserror 2.0.12", "tokio", "tracing", "tracing-subscriber", + "typetag", "uuid", ] @@ -2558,7 +2588,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2928,6 +2958,19 @@ dependencies = [ "digest", ] +[[package]] +name = "sha256" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f880fc8562bdeb709793f00eb42a2ad0e672c4f883bbe59122b926eca935c8f6" +dependencies = [ + "async-trait", + "bytes", + "hex", + "sha2", + "tokio", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -3313,12 +3356,42 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + [[package]] name = "typenum" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +[[package]] +name = "typetag" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f22b40dd7bfe8c14230cf9702081366421890435b2d625fa92b4acc4c3de6f" +dependencies = [ + "erased-serde", + "inventory", + "once_cell", + "serde", + "typetag-impl", +] + +[[package]] +name = "typetag-impl" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35f5380909ffc31b4de4f4bdf96b877175a016aa2ca98cee39fcfd8c4d53d952" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "uint" version = "0.10.0" diff --git a/Cargo.toml b/Cargo.toml index 2a570f7..a84ca9d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,14 +22,25 @@ libp2p = { version = "0.55.0", features = [ # Serde serde = { version = "^1", features = ["derive"] } serde_json = "^1" +typetag = "0.2.20" + +# Cryptography +hex = "0.4.3" +sha256 = "1.5.0" # Utils chrono = "0.4.40" clap = { version = "4.5.28", features = ["derive"] } clap_derive = "4.5.32" +dyn-clone = "1.0.17" rand = { version = "0.8", features = ["std_rng"] } uuid = { version = "1.15.1", features = ["serde", "v4"] } +thiserror = "2.0.12" + # Tracing tracing = "0.1.41" tracing-subscriber = { version = "0.3", features = ["registry", "env-filter"] } + +# Node db +# rocksdb = "0.23.0" diff --git a/src/block/mod.rs b/src/block/mod.rs new file mode 100644 index 0000000..9d4e935 --- /dev/null +++ b/src/block/mod.rs @@ -0,0 +1,94 @@ +use std::fmt::{Debug, Display}; + +use chrono::Utc; +use dyn_clone::DynClone; +use libp2p::identity::{Keypair, SigningError}; +use serde::{Deserialize, Serialize}; +use sha256::digest; +use uuid::Uuid; + +use crate::utils::UnixTimestamp; + +pub type TransactionHash = String; +pub type BlockHash = String; + +/// Represents a block. +pub struct Block { + pub header: BlockHeader, + pub transactions: Vec, +} + +/// Represents the header of a block. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct BlockHeader { + pub previous_block_hash: BlockHash, + pub timestamp: UnixTimestamp, + pub nonce: String, + pub transaction_hash: TransactionHash, +} + +/// Represents a transaction. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Transaction { + signature: Option, + pub(crate) timestamp: UnixTimestamp, + pub(crate) transaction: TransactionData, +} + +impl Transaction { + /// Hashes the transaction in sha256 digest + fn _to_hash(&self) -> Result { + serde_json::to_string(&self) + .map_err(TransactionError::UnableToSerializeTransaction) + .map(digest) + } + + /// Encodes the transaction in hex format + pub fn to_hex(&self) -> Result { + serde_json::to_string(&self) + .map_err(TransactionError::UnableToDeserializeTransaction) + .map(hex::encode) + } + + pub fn build( + keypair: Keypair, + payload: T, + ) -> Result { + payload.sign(&keypair)?; + + Ok(Self { + signature: None, + timestamp: Utc::now().timestamp(), + transaction: TransactionData { + transaction_id: Uuid::new_v4().to_string(), + payload: Box::new(payload), + }, + }) + } +} + +/// Transaction data. Ideally contains payload that's `Transactable` +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct TransactionData { + transaction_id: String, + payload: Box, +} + +#[typetag::serde(tag = "type")] +pub trait Transactable: Debug + Send + Sync + DynClone + Display { + // TODO: Implement the _submit method to submit the transaction to the network. + fn _submit(&self) -> Result<(), TransactionError>; + fn sign(&self, keypair: &Keypair) -> Result<(), TransactionError>; +} + +dyn_clone::clone_trait_object!(Transactable); + +#[derive(thiserror::Error, Debug)] +pub enum TransactionError { + #[error("Unable to serialize transaction: {0}")] + UnableToSerializeTransaction(serde_json::Error), + #[error("Unable to deserialize transaction: {0}")] + UnableToDeserializeTransaction(serde_json::Error), + #[error("Unable to sign transaction: {0}")] + SigningError(SigningError), +} diff --git a/src/cli.rs b/src/cli.rs index 17aa4d7..44a94a6 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -10,25 +10,25 @@ pub struct Args { #[arg(short, long, default_value_t = Role::Sender)] pub role: Role, - /// Peer's address + /// Peer's MultiAddress #[arg(short)] - pub peer_address: Option, + pub peer_mutli_address: Option, /// Bootstrap Nodes #[arg(short)] - pub bootstrap: Option, + pub bootstrap_peer_id: Option, } #[derive(Clone, Debug, ValueEnum)] pub enum Role { - Receiver, + BootstapNode, Sender, } impl Display for Role { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match &self { - Role::Receiver => write!(f, "Receiver"), + Role::BootstapNode => write!(f, "Receiver"), Role::Sender => write!(f, "Sender"), } } diff --git a/src/comms/message.rs b/src/comms/message.rs index 6d2e57a..d29043d 100644 --- a/src/comms/message.rs +++ b/src/comms/message.rs @@ -1,27 +1,48 @@ use std::fmt::Display; -use serde::Serialize; +use libp2p::identity::Keypair; +use serde::{Deserialize, Serialize}; -#[derive(Debug, Serialize, PartialEq, Eq)] +use crate::block::{Transactable, TransactionError}; + +#[derive(Clone, Debug, Deserialize, Serialize)] pub enum Message { - RememeberMe, Comms(String), + Transaction(Box), +} + +#[typetag::serde] +impl Transactable for Message { + fn _submit(&self) -> Result<(), TransactionError> { + todo!() + } + + fn sign(&self, keypair: &Keypair) -> Result<(), TransactionError> { + match self { + Message::Comms(chat) => keypair + .sign(chat.as_bytes()) + .map_err(TransactionError::SigningError) + .map(|_| ()), + Message::Transaction(action) => keypair + .sign(action.as_ref().to_string().as_bytes()) + .map_err(TransactionError::SigningError) + .map(|_| ()), + } + } } impl From for Message { fn from(s: String) -> Self { - match s.as_str() { - "RememeberMe" => Message::RememeberMe, - s => Message::Comms(s.to_owned()), - } + let s = s.as_str(); + Message::Comms(s.to_owned()) } } impl Display for Message { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Message::RememeberMe => write!(f, "RememeberMe"), - Message::Comms(s) => write!(f, "{s}"), + Message::Comms(chat) => write!(f, "{chat}"), + Message::Transaction(action) => write!(f, "{action}"), } } } diff --git a/src/comms/mod.rs b/src/comms/mod.rs index c9ae4cf..e216a50 100644 --- a/src/comms/mod.rs +++ b/src/comms/mod.rs @@ -1,8 +1 @@ -use libp2p::{Multiaddr, PeerId}; - pub mod message; - -pub struct Peer { - _id: PeerId, - _addr: Multiaddr, -} diff --git a/src/lib.rs b/src/lib.rs index 1aebcc3..12e1e10 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,9 @@ +pub mod block; pub mod cli; pub mod comms; pub mod network; pub mod storage; +pub mod utils; pub mod tracing { use tracing_subscriber::{ diff --git a/src/main.rs b/src/main.rs index d88d796..42ad393 100644 --- a/src/main.rs +++ b/src/main.rs @@ -62,7 +62,8 @@ async fn main() -> Result<(), Box> { event_runner( swarm, args.role, - args.peer_address, + args.peer_mutli_address, + args.bootstrap_peer_id, Topic(topic.to_string()), ) .await diff --git a/src/network/event.rs b/src/network/event.rs index 92cf4a3..110933b 100644 --- a/src/network/event.rs +++ b/src/network/event.rs @@ -20,10 +20,11 @@ pub async fn event_runner( mut swarm: Swarm, role: Role, peer_address: Option, + _bootstrap: Option, topic: Topic, ) -> Result<(), Box> { match role { - Role::Receiver => loop { + Role::BootstapNode => loop { if let Some(event) = swarm.next().await { match event { SwarmEvent::NewListenAddr { @@ -124,6 +125,11 @@ pub async fn event_runner( "Connection {connection_id} with peer {peer_id} closed, endpoint: {endpoint:?}, num_established: {num_established}, cause: {cause:?}" ); } + SwarmEvent::Behaviour(PeerBehaviorEvent::Kademlia( + kad::Event::InboundRequest { request }, + )) => { + tracing::info!("{request:?}") + } _ => {} } } diff --git a/src/network/mod.rs b/src/network/mod.rs index 527568f..1626400 100644 --- a/src/network/mod.rs +++ b/src/network/mod.rs @@ -1,2 +1,27 @@ pub mod behaviour; pub mod event; + +use libp2p::{Multiaddr, PeerId}; + +use crate::utils::error::Error; + +/// Nodes Peer +pub struct Peer { + pub id: PeerId, + pub addr: Multiaddr, + pub setup: bool, +} + +impl Peer { + pub fn new(id: String, addr: String) -> Result { + Ok(Peer { + id: id + .parse::() + .map_err(|err| Error::InvalidPeerId(err.to_string()))?, + addr: addr + .parse::() + .map_err(|err| Error::InvalidMultiaddr(err.to_string()))?, + setup: false, + }) + } +} diff --git a/src/utils/error.rs b/src/utils/error.rs new file mode 100644 index 0000000..1ffe28a --- /dev/null +++ b/src/utils/error.rs @@ -0,0 +1,7 @@ +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("Invalid peer ID: {0}")] + InvalidPeerId(String), + #[error("Invalid multiaddr: {0}")] + InvalidMultiaddr(String), +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 0000000..806f0f6 --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1,12 @@ +pub mod error; + +use std::time::{Duration, SystemTime}; + +pub type UnixTimestamp = i64; + +pub fn unix_epoch_time() -> UnixTimestamp { + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap_or_else(|_| Duration::from_millis(0)) + .as_millis() as i64 +}