diff --git a/Cargo.toml b/Cargo.toml index 3c97be8..427dade 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "alfis" -version = "0.2.1" +version = "0.3.0" authors = ["Revertron "] edition = "2018" build = "build.rs" @@ -15,6 +15,7 @@ log = "0.4.14" toml = "0.5.8" simple_logger = "1.11.0" rust-crypto = "^0.2" +blakeout = "0.1.0" num_cpus = "1.13.0" byteorder = "1.3.2" web-view = { version = "0.7.2", features = [] } @@ -24,7 +25,6 @@ serde_json = "1.0.42" num-bigint = "0.2" num-traits = "0.2" bincode = "1.2.0" -groestl = "0.8.0" base64 = "0.11.0" chrono = { version = "0.4.13", features = ["serde"] } rand = "0.7.2" diff --git a/src/blockchain/block.rs b/src/blockchain/block.rs index 6004c80..bf32ab9 100644 --- a/src/blockchain/block.rs +++ b/src/blockchain/block.rs @@ -5,9 +5,7 @@ extern crate num_traits; use std::fmt::Debug; use serde::{Serialize, Deserialize}; -use crypto::sha2::Sha256; -use crypto::digest::Digest; -use crate::keys::Bytes; +use crate::bytes::Bytes; use crate::Transaction; #[derive(Clone, Serialize, Deserialize, PartialEq, Debug)] @@ -70,12 +68,4 @@ impl Block { pub fn as_bytes(&self) -> Vec { Vec::from(serde_json::to_string(&self).unwrap().as_bytes()) } -} - -pub fn hash(data: &[u8]) -> Bytes { - let mut buf: [u8; 32] = [0; 32]; - let mut digest = Sha256::new(); - digest.input(data); - digest.result(&mut buf); - Bytes::new(buf.to_vec()) } \ No newline at end of file diff --git a/src/blockchain/blockchain.rs b/src/blockchain/chain.rs similarity index 91% rename from src/blockchain/blockchain.rs rename to src/blockchain/chain.rs index 46536f4..78ee988 100644 --- a/src/blockchain/blockchain.rs +++ b/src/blockchain/chain.rs @@ -1,15 +1,17 @@ +use std::cell::RefCell; +use std::collections::HashSet; + +use chrono::Utc; +#[allow(unused_imports)] +use log::{debug, error, info, trace, warn}; use sqlite::{Connection, State, Statement}; -use crate::{Block, Bytes, Keystore, Transaction, hash_is_good}; +use crate::{Block, Bytes, Keystore, Transaction}; +use crate::blockchain::constants::{BLOCK_DIFFICULTY, CHAIN_VERSION, LOCKER_BLOCK_COUNT, LOCKER_BLOCK_INTERVAL, LOCKER_BLOCK_START, LOCKER_DIFFICULTY}; +use crate::blockchain::enums::BlockQuality; +use crate::blockchain::enums::BlockQuality::*; +use crate::blockchain::hash_utils::*; use crate::settings::Settings; -#[allow(unused_imports)] -use log::{trace, debug, info, warn, error}; -use std::collections::HashSet; -use std::cell::RefCell; -use chrono::Utc; -use crate::blockchain::transaction::hash_identity; -use crate::blockchain::blockchain::BlockQuality::*; -use crate::blockchain::{BLOCK_DIFFICULTY, CHAIN_VERSION, LOCKER_BLOCK_START, LOCKER_DIFFICULTY, LOCKER_BLOCK_COUNT, LOCKER_BLOCK_INTERVAL}; const DB_NAME: &str = "blockchain.db"; const SQL_CREATE_TABLES: &str = "CREATE TABLE blocks ( @@ -37,7 +39,7 @@ const SQL_GET_PUBLIC_KEY_BY_ID: &str = "SELECT pub_key FROM transactions WHERE i const SQL_GET_ID_BY_ID: &str = "SELECT identity FROM transactions WHERE identity = ? ORDER BY id DESC LIMIT 1;"; const SQL_GET_TRANSACTION_BY_ID: &str = "SELECT * FROM transactions WHERE identity = ? ORDER BY id DESC LIMIT 1;"; -pub struct Blockchain { +pub struct Chain { origin: Bytes, pub version: u32, pub blocks: Vec, @@ -48,12 +50,12 @@ pub struct Blockchain { zones: RefCell>, } -impl Blockchain { +impl Chain { pub fn new(settings: &Settings) -> Self { let origin = settings.get_origin(); let db = sqlite::open(DB_NAME).expect("Unable to open blockchain DB"); - let mut blockchain = Blockchain { + let mut chain = Chain { origin, version: CHAIN_VERSION, blocks: Vec::new(), @@ -63,8 +65,8 @@ impl Blockchain { db, zones: RefCell::new(HashSet::new()), }; - blockchain.init_db(); - blockchain + chain.init_db(); + chain } /// Reads options from DB or initializes and writes them to DB if not found @@ -147,21 +149,21 @@ impl Blockchain { statement.bind(7, transaction.to_string().as_str())?; } } - statement.bind(8, block.prev_block_hash.as_bytes())?; - statement.bind(9, block.hash.as_bytes())?; - statement.bind(10, block.pub_key.as_bytes())?; - statement.bind(11, block.signature.as_bytes())?; + statement.bind(8, block.prev_block_hash.as_slice())?; + statement.bind(9, block.hash.as_slice())?; + statement.bind(10, block.pub_key.as_slice())?; + statement.bind(11, block.signature.as_slice())?; statement.next() } /// Adds transaction to transactions table fn add_transaction_to_table(&mut self, t: &Transaction) -> sqlite::Result { let mut statement = self.db.prepare(SQL_ADD_TRANSACTION)?; - statement.bind(1, t.identity.as_bytes())?; - statement.bind(2, t.confirmation.as_bytes())?; + statement.bind(1, t.identity.as_slice())?; + statement.bind(2, t.confirmation.as_slice())?; statement.bind(3, t.method.as_ref() as &str)?; statement.bind(4, t.data.as_ref() as &str)?; - statement.bind(5, t.pub_key.as_bytes())?; + statement.bind(5, t.pub_key.as_slice())?; statement.next() } @@ -239,7 +241,7 @@ impl Blockchain { /// Checks if this identity is free or is owned by the same pub_key pub fn is_id_available(&self, identity: &Bytes, public_key: &Bytes) -> bool { let mut statement = self.db.prepare(SQL_GET_PUBLIC_KEY_BY_ID).unwrap(); - statement.bind(1, identity.as_bytes()).expect("Error in bind"); + statement.bind(1, identity.as_slice()).expect("Error in bind"); while let State::Row = statement.next().unwrap() { let pub_key = Bytes::from_bytes(statement.read::>(0).unwrap().as_slice()); if !pub_key.eq(public_key) { @@ -258,7 +260,7 @@ impl Blockchain { // Checking for existing zone in DB let identity_hash = hash_identity(zone, None); let mut statement = self.db.prepare(SQL_GET_ID_BY_ID).unwrap(); - statement.bind(1, identity_hash.as_bytes()).expect("Error in bind"); + statement.bind(1, identity_hash.as_slice()).expect("Error in bind"); while let State::Row = statement.next().unwrap() { // If there is such a zone self.zones.borrow_mut().insert(zone.to_owned()); @@ -275,7 +277,7 @@ impl Blockchain { let identity_hash = hash_identity(domain, None); let mut statement = self.db.prepare(SQL_GET_TRANSACTION_BY_ID).unwrap(); - statement.bind(1, identity_hash.as_bytes()).expect("Error in bind"); + statement.bind(1, identity_hash.as_slice()).expect("Error in bind"); while let State::Row = statement.next().unwrap() { let identity = Bytes::from_bytes(statement.read::>(1).unwrap().as_slice()); let confirmation = Bytes::from_bytes(statement.read::>(2).unwrap().as_slice()); @@ -343,7 +345,7 @@ impl Blockchain { warn!("Block difficulty is lower than needed"); return Bad; } - if !hash_is_good(block.hash.as_bytes(), block.difficulty as usize) { + if !hash_is_good(block.hash.as_slice(), block.difficulty as usize) { warn!("Ignoring block with low difficulty:\n{:?}", &block); return Bad; } @@ -456,27 +458,4 @@ impl Blockchain { let signature = Bytes::from_bytes(statement.read::>(10).unwrap().as_slice()); Some(Block::from_all_params(index, timestamp, version, difficulty, random, nonce, prev_block_hash, hash, pub_key, signature, transaction)) } -} - -#[derive(PartialEq)] -pub enum BlockQuality { - Good, - Twin, - Future, - Bad, - Fork, -} - -pub fn check_block_hash(block: &Block) -> bool { - let mut copy: Block = block.clone(); - copy.hash = Bytes::default(); - copy.signature = Bytes::default(); - let data = serde_json::to_string(©).unwrap(); - crate::blockchain::block::hash(data.as_bytes()) == block.hash -} - -pub fn check_block_signature(block: &Block) -> bool { - let mut copy = block.clone(); - copy.signature = Bytes::default(); - Keystore::check(©.as_bytes(), copy.pub_key.as_bytes(), block.signature.as_bytes()) -} +} \ No newline at end of file diff --git a/src/blockchain/constants.rs b/src/blockchain/constants.rs index 40326b3..a737b1a 100644 --- a/src/blockchain/constants.rs +++ b/src/blockchain/constants.rs @@ -1,6 +1,9 @@ +pub const CHAIN_VERSION: u32 = 1; + pub const BLOCK_DIFFICULTY: u32 = 24; pub const LOCKER_DIFFICULTY: u32 = 18; -pub const CHAIN_VERSION: u32 = 1; +pub const KEYSTORE_DIFFICULTY: usize = 25; + pub const LOCKER_BLOCK_START: u64 = 10; pub const LOCKER_BLOCK_COUNT: u64 = 3; pub const LOCKER_BLOCK_INTERVAL: i64 = 300; diff --git a/src/blockchain/enums.rs b/src/blockchain/enums.rs new file mode 100644 index 0000000..9d5e23d --- /dev/null +++ b/src/blockchain/enums.rs @@ -0,0 +1,9 @@ +/// Represents a result of block check on block's arrival +#[derive(PartialEq)] +pub enum BlockQuality { + Good, + Twin, + Future, + Bad, + Fork, +} diff --git a/src/blockchain/filter.rs b/src/blockchain/filter.rs index 097853f..b8ea362 100644 --- a/src/blockchain/filter.rs +++ b/src/blockchain/filter.rs @@ -33,11 +33,11 @@ impl DnsFilter for BlockchainFilter { } debug!("Searching domain {} and record {}", &search, &subdomain); - let data = self.context.lock().unwrap().blockchain.get_domain_info(&search); + let data = self.context.lock().unwrap().chain.get_domain_info(&search); match data { None => { debug!("Not found data for domain {}", &search); - if self.context.lock().unwrap().blockchain.is_zone_in_blockchain(parts[0]) { + if self.context.lock().unwrap().chain.is_zone_in_blockchain(parts[0]) { // Create DnsPacket let mut packet = DnsPacket::new(); packet.questions.push(DnsQuestion::new(String::from(qname), qtype)); diff --git a/src/blockchain/hash_utils.rs b/src/blockchain/hash_utils.rs new file mode 100644 index 0000000..2c3e92a --- /dev/null +++ b/src/blockchain/hash_utils.rs @@ -0,0 +1,81 @@ +use blakeout::Blakeout; +use crypto::digest::Digest; +use crypto::sha2::Sha256; +use num_bigint::BigUint; +use num_traits::One; + +use crate::{Block, Bytes, Keystore}; + +/// Creates needed hasher by current blockchain version +fn get_hasher_for_version(version: u32) -> Box { + match version { + 2 => Box::new(Blakeout::default()), + _ => Box::new(Sha256::new()) + } +} + +/// Checks block's hash and returns true on valid hash or false otherwise +pub fn check_block_hash(block: &Block) -> bool { + let mut copy: Block = block.clone(); + copy.hash = Bytes::default(); + copy.signature = Bytes::default(); + let data = serde_json::to_string(©).unwrap(); + let mut hasher = get_hasher_for_version(block.version); + hash_data(&mut *hasher, data.as_bytes()) == block.hash +} + +/// Hashes data by given hasher +pub fn hash_data(digest: &mut dyn Digest, data: &[u8]) -> Bytes { + let mut buf = match digest.output_bytes() { + 32 => Bytes::zero32(), + 64 => Bytes::zero64(), + _ => panic!("Supplied wrong digest!") + }; + + digest.input(data); + digest.result(buf.as_mut_slice()); + buf +} + +/// Checks block's signature, returns true if the signature is valid, false otherwise +pub fn check_block_signature(block: &Block) -> bool { + let mut copy = block.clone(); + copy.signature = Bytes::default(); + Keystore::check(©.as_bytes(), copy.pub_key.as_slice(), block.signature.as_slice()) +} + +/// Hashes some identity (domain in case of DNS). If you give it a public key, it will hash with it as well. +/// Giving public key is needed to create a confirmation field in [Transaction] +pub fn hash_identity(identity: &str, key: Option<&Bytes>) -> Bytes { + let mut buf: [u8; 32] = [0; 32]; + let mut digest = Sha256::new(); + digest.input_str(identity); + if let Some(key) = key { + digest.input(key.as_slice()); + } + digest.result(&mut buf); + Bytes::from_bytes(&buf) +} + +/// There is no default PartialEq implementation for arrays > 32 in size +pub fn same_hash(left: &[u8], right: &[u8]) -> bool { + if left.len() != right.len() { + return false; + } + // We iterate whole slices to eliminate timing attacks + let mut result = true; + for (x, y) in left.iter().zip(right) { + if x != y { + result = false; + } + } + result +} + +/// Checks if this hash contains enough zeroes +pub fn hash_is_good(hash: &[u8], difficulty: usize) -> bool { + let target = BigUint::one() << ((hash.len() << 3) - difficulty); + let hash_int = BigUint::from_bytes_be(&hash); + + return hash_int < target; +} diff --git a/src/blockchain/mod.rs b/src/blockchain/mod.rs index 3ab5909..0d4431e 100644 --- a/src/blockchain/mod.rs +++ b/src/blockchain/mod.rs @@ -1,10 +1,12 @@ pub mod transaction; pub mod block; -pub mod blockchain; +pub mod chain; pub mod filter; pub mod constants; +pub mod hash_utils; +pub mod enums; pub use transaction::Transaction; pub use block::Block; -pub use blockchain::Blockchain; +pub use chain::Chain; pub use constants::*; \ No newline at end of file diff --git a/src/blockchain/transaction.rs b/src/blockchain/transaction.rs index ae65953..b01ddf4 100644 --- a/src/blockchain/transaction.rs +++ b/src/blockchain/transaction.rs @@ -1,14 +1,14 @@ -use crate::keys::*; +use std::fmt; + +use serde::{Deserialize, Serialize, Serializer}; +use serde::ser::SerializeStruct; + +use crate::blockchain::hash_utils::*; +use crate::bytes::Bytes; extern crate serde; extern crate serde_json; -use serde::{Serialize, Deserialize, Serializer}; -use serde::ser::SerializeStruct; -use std::fmt; -use crypto::sha2::Sha256; -use crypto::digest::Digest; - #[derive(Clone, Deserialize, PartialEq)] pub struct Transaction { pub identity: Bytes, @@ -75,15 +75,4 @@ impl Serialize for Transaction { structure.serialize_field("pub_key", &self.pub_key)?; structure.end() } -} - -pub fn hash_identity(identity: &str, key: Option<&Bytes>) -> Bytes { - let mut buf: [u8; 32] = [0; 32]; - let mut digest = Sha256::new(); - digest.input_str(identity); - if let Some(key) = key { - digest.input(key.as_bytes()); - } - digest.result(&mut buf); - Bytes::from_bytes(&buf) } \ No newline at end of file diff --git a/src/bytes.rs b/src/bytes.rs new file mode 100644 index 0000000..586fdaf --- /dev/null +++ b/src/bytes.rs @@ -0,0 +1,170 @@ +extern crate serde; +extern crate serde_json; + +use std::cmp::Ordering; +use std::convert::TryInto; +use std::fmt; +use std::fmt::{Formatter, Error}; + +use num_bigint::BigUint; + +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +// For deserialization +use serde::de::{Error as DeError, Visitor}; + +#[derive(Clone)] +pub struct Bytes { + data: Vec +} + +impl Bytes { + pub fn new(data: Vec) -> Self { + Bytes { data } + } + + pub fn from_bytes(data: &[u8]) -> Self { + Bytes { data: Vec::from(data) } + } + + pub fn length(&self) -> usize { + self.data.len() + } + + pub fn is_empty(&self) -> bool { + self.data.is_empty() + } + + pub fn is_zero(&self) -> bool { + if self.data.is_empty() { + return true; + } + for x in self.data.iter() { + if *x != 0 { + return false; + } + } + return true; + } + + /// Returns a byte slice of the hash contents. + pub fn as_slice(&self) -> &[u8] { + self.data.as_slice() + } + + /// Returns a mutable byte slice (to fill by hasher) + pub fn as_mut_slice(&mut self) -> &mut [u8] { + self.data.as_mut_slice() + } + + pub fn as_vec(&self) -> &Vec { + &self.data + } + + pub fn to_string(&self) -> String { + crate::utils::to_hex(&self.data) + } + + pub fn get_tail_u64(&self) -> u64 { + let index = self.data.len() - 8; + let bytes: [u8; 8] = self.data[index..].try_into().unwrap(); + u64::from_be_bytes(bytes) + } + + pub fn zero32() -> Self { + Bytes { data: [0u8; 32].to_vec() } + } + + pub fn zero64() -> Self { + Bytes { data: [0u8; 64].to_vec() } + } +} + +impl Default for Bytes { + fn default() -> Bytes { + Bytes { data: Vec::new() } + } +} + +impl PartialEq for Bytes { + fn eq(&self, other: &Self) -> bool { + crate::blockchain::hash_utils::same_hash(&self.data, &other.data) + } + + fn ne(&self, other: &Self) -> bool { + !crate::blockchain::hash_utils::same_hash(&self.data, &other.data) + } +} + +impl Eq for Bytes {} + +impl PartialOrd for Bytes { + fn partial_cmp(&self, other: &Self) -> Option { + let self_hash_int = BigUint::from_bytes_be(&self.data); + let other_hash_int = BigUint::from_bytes_be(&other.data); + Some(self_hash_int.cmp(&other_hash_int)) + } +} + +impl Ord for Bytes { + fn cmp(&self, other: &Self) -> Ordering { + let self_hash_int = BigUint::from_bytes_be(&self.data); + let other_hash_int = BigUint::from_bytes_be(&other.data); + self_hash_int.cmp(&other_hash_int) + } +} + +impl fmt::Debug for Bytes { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.write_str(&crate::utils::to_hex(&self.data)) + } +} + +impl Serialize for Bytes { + fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> where + S: Serializer { + serializer.serialize_str(&crate::utils::to_hex(&self.data)) + } +} + +struct BytesVisitor; + +impl<'de> Visitor<'de> for BytesVisitor { + type Value = Bytes; + + fn expecting(&self, formatter: &mut Formatter) -> Result<(), Error> { + formatter.write_str("32 or 64 bytes") + } + + fn visit_str(self, value: &str) -> Result where E: DeError, { + if value.len() == 64 || value.len() == 128 { + Ok(Bytes::new(crate::from_hex(value).unwrap())) + } else { + Err(E::custom("Key must be 32 or 64 bytes!")) + } + } + + fn visit_bytes(self, value: &[u8]) -> Result where E: DeError, { + if value.len() == 32 || value.len() == 64 { + Ok(Bytes::from_bytes(value)) + } else { + Err(E::custom("Key must be 32 or 64 bytes!")) + } + } +} + +impl<'dd> Deserialize<'dd> for Bytes { + fn deserialize(deserializer: D) -> Result>::Error> where D: Deserializer<'dd> { + deserializer.deserialize_str(BytesVisitor) + } +} + +#[cfg(test)] +mod tests { + use crate::bytes::Bytes; + + #[test] + pub fn test_tail_bytes() { + let bytes = Bytes::new(vec![0, 255, 255, 255, 0, 255, 255, 255]); + assert_eq!(bytes.get_tail_u64(), 72057589759737855u64); + } +} diff --git a/src/context.rs b/src/context.rs index d644333..33743ca 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,20 +1,19 @@ -use crate::{Blockchain, Bus, Keystore}; +use crate::{Chain, Bus, Keystore, Settings}; use crate::event::Event; -use crate::settings::Settings; #[allow(unused_imports)] use log::{trace, debug, info, warn, error}; pub struct Context { pub settings: Settings, pub keystore: Keystore, - pub blockchain: Blockchain, + pub chain: Chain, pub bus: Bus, } impl Context { /// Creating an essential context to work with - pub fn new(settings: Settings, keystore: Keystore, blockchain: Blockchain) -> Context { - Context { settings, keystore, blockchain, bus: Bus::new() } + pub fn new(settings: Settings, keystore: Keystore, chain: Chain) -> Context { + Context { settings, keystore, chain, bus: Bus::new() } } /// Load keystore and return Context @@ -39,7 +38,7 @@ impl Context { self.keystore = keystore; } - pub fn get_blockchain(&self) -> &Blockchain { - &self.blockchain + pub fn get_chain(&self) -> &Chain { + &self.chain } } \ No newline at end of file diff --git a/src/dns/mod.rs b/src/dns/mod.rs index e4eb8f3..b5e3157 100644 --- a/src/dns/mod.rs +++ b/src/dns/mod.rs @@ -24,4 +24,4 @@ pub mod resolve; pub mod server; pub mod filter; -mod netutil; +mod netutil; \ No newline at end of file diff --git a/src/dns_utils.rs b/src/dns_utils.rs new file mode 100644 index 0000000..51fcc82 --- /dev/null +++ b/src/dns_utils.rs @@ -0,0 +1,45 @@ +use std::sync::{Arc, Mutex}; + +use crate::{Context, Settings}; +use crate::blockchain::filter::BlockchainFilter; +use crate::dns::server::{DnsServer, DnsUdpServer, DnsTcpServer}; +use crate::dns::context::{ServerContext, ResolveStrategy}; +#[allow(unused_imports)] +use log::{debug, error, info, LevelFilter, trace, warn}; + +/// Starts UDP and TCP DNS-servers +pub fn start_dns_server(context: &Arc>, settings: &Settings) { + let server_context = create_server_context(context.clone(), &settings); + + if server_context.enable_udp { + let udp_server = DnsUdpServer::new(server_context.clone(), settings.dns.threads); + if let Err(e) = udp_server.run_server() { + error!("Failed to bind UDP listener: {:?}", e); + } + } + + if server_context.enable_tcp { + let tcp_server = DnsTcpServer::new(server_context.clone(), settings.dns.threads); + if let Err(e) = tcp_server.run_server() { + error!("Failed to bind TCP listener: {:?}", e); + } + } +} + +/// Creates DNS-context with all needed settings +fn create_server_context(context: Arc>, settings: &Settings) -> Arc { + let mut server_context = ServerContext::new(); + server_context.allow_recursive = true; + server_context.dns_listen = settings.dns.listen.clone(); + server_context.resolve_strategy = match settings.dns.forwarders.is_empty() { + true => { ResolveStrategy::Recursive } + false => { ResolveStrategy::Forward { upstreams: settings.dns.forwarders.clone() } } + }; + server_context.filters.push(Box::new(BlockchainFilter::new(context))); + match server_context.initialize() { + Ok(_) => {} + Err(e) => { panic!("DNS server failed to initialize: {:?}", e); } + } + + Arc::new(server_context) +} diff --git a/src/event.rs b/src/event.rs index 9a1831b..596dba4 100644 --- a/src/event.rs +++ b/src/event.rs @@ -6,9 +6,9 @@ pub enum Event { MinerStopped, KeyGeneratorStarted, KeyGeneratorStopped, - KeyCreated { path: String, public: String }, - KeyLoaded { path: String, public: String }, - KeySaved { path: String, public: String }, + KeyCreated { path: String, public: String, hash: String }, + KeyLoaded { path: String, public: String, hash: String }, + KeySaved { path: String, public: String, hash: String }, NewBlockReceived, BlockchainChanged, ActionStopMining, diff --git a/src/keys.rs b/src/keys.rs index a099d71..06a0a0a 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -2,29 +2,39 @@ extern crate crypto; extern crate serde; extern crate serde_json; -use crypto::ed25519::{keypair, signature, verify}; -use rand::{thread_rng, Rng}; -use std::fmt; -use std::fmt::{Error, Formatter}; +use std::thread; use std::fs; use std::fs::File; use std::io::Write; use std::path::Path; -use serde::{Serialize, Deserialize, Serializer, Deserializer}; -// For deserialization -use serde::de::{Error as DeError, Visitor}; +use std::sync::{Arc, atomic, Mutex}; +use std::sync::atomic::{AtomicBool, AtomicUsize}; + +use crypto::ed25519::{keypair, signature, verify}; #[allow(unused_imports)] -use log::{trace, debug, info, warn, error}; -use crate::hash_is_good; -use std::cmp::Ordering; -use num_bigint::BigUint; -use std::convert::TryInto; +use log::{debug, error, info, trace, warn}; +use rand::{Rng, RngCore, thread_rng}; +use serde::{Deserialize, Serialize}; +#[cfg(not(target_os = "macos"))] +use thread_priority::{set_current_thread_priority, ThreadPriority}; + +use crate::blockchain::hash_utils::*; +use crate::Context; +use crate::event::Event; +use crate::blockchain::KEYSTORE_DIFFICULTY; +use crate::bytes::Bytes; +use blakeout::Blakeout; +use self::crypto::digest::Digest; +use std::time::Instant; +use std::cell::RefCell; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Keystore { private_key: Bytes, public_key: Bytes, #[serde(skip)] + hash: RefCell, + #[serde(skip)] path: String, #[serde(skip)] seed: Vec, @@ -36,12 +46,12 @@ impl Keystore { let mut rng = thread_rng(); rng.fill(&mut buf); let (private, public) = keypair(&buf); - Keystore { private_key: Bytes::from_bytes(&private), public_key: Bytes::from_bytes(&public), path: String::new(), seed: Vec::from(&buf[..]) } + Keystore { private_key: Bytes::from_bytes(&private), public_key: Bytes::from_bytes(&public), hash: RefCell::new(Bytes::default()), path: String::new(), seed: Vec::from(&buf[..]) } } pub fn from_bytes(seed: &[u8]) -> Self { let (private, public) = keypair(&seed); - Keystore { private_key: Bytes::from_bytes(&private), public_key: Bytes::from_bytes(&public), path: String::new(), seed: Vec::from(seed) } + Keystore { private_key: Bytes::from_bytes(&private), public_key: Bytes::from_bytes(&public), hash: RefCell::new(Bytes::default()), path: String::new(), seed: Vec::from(seed) } } pub fn from_file(filename: &str, _password: &str) -> Option { @@ -82,159 +92,96 @@ impl Keystore { &self.path } + pub fn get_hash(&self) -> Bytes { + if self.hash.borrow().is_empty() { + self.hash.replace(hash_data(&mut Blakeout::default(), &self.public_key.as_slice())); + } + self.hash.borrow().clone() + } + pub fn sign(&self, message: &[u8]) -> [u8; 64] { - signature(message, self.private_key.data.as_slice()) + signature(message, self.private_key.as_slice()) } pub fn check(message: &[u8], public_key: &[u8], signature: &[u8]) -> bool { verify(message, public_key, signature) } - - pub fn hash_is_good(&self, difficulty: usize) -> bool { - hash_is_good(self.public_key.as_bytes(), difficulty) - } } -#[derive(Clone)] -pub struct Bytes { - data: Vec -} - -impl Bytes { - pub fn new(data: Vec) -> Self { - Bytes { data } - } - - pub fn from_bytes(data: &[u8]) -> Self { - Bytes { data: Vec::from(data) } - } - - pub fn length(&self) -> usize { - self.data.len() - } - - pub fn is_empty(&self) -> bool { - self.data.is_empty() - } - - pub fn is_zero(&self) -> bool { - if self.data.is_empty() { - return true; - } - for x in self.data.iter() { - if *x != 0 { - return false; +pub fn create_key(context: Arc>) { + let mining = Arc::new(AtomicBool::new(true)); + let miners_count = Arc::new(AtomicUsize::new(0)); + { context.lock().unwrap().bus.post(Event::KeyGeneratorStarted); } + for _ in 0..num_cpus::get() { + let context = context.clone(); + let mining = mining.clone(); + let miners_count = miners_count.clone(); + thread::spawn(move || { + #[cfg(not(target_os = "macos"))] + let _ = set_current_thread_priority(ThreadPriority::Min); + miners_count.fetch_add(1, atomic::Ordering::SeqCst); + match generate_key(KEYSTORE_DIFFICULTY, mining.clone()) { + None => { + debug!("Keystore mining finished"); + } + Some(keystore) => { + mining.store(false, atomic::Ordering::SeqCst); + let mut context = context.lock().unwrap(); + let hash = keystore.get_hash().to_string(); + info!("Key mined successfully: {:?}, hash: {}", &keystore.get_public(), &hash); + context.bus.post(Event::KeyCreated { path: keystore.get_path().to_owned(), public: keystore.get_public().to_string(), hash }); + context.set_keystore(keystore); + } } - } - return true; + let miners = miners_count.fetch_sub(1, atomic::Ordering::SeqCst) - 1; + if miners == 0 { + context.lock().unwrap().bus.post(Event::KeyGeneratorStopped); + } + }); } - - /// Returns a byte slice of the hash contents. - pub fn as_bytes(&self) -> &[u8] { - self.data.as_slice() - } - - pub fn to_string(&self) -> String { - crate::utils::to_hex(&self.data) - } - - pub fn get_tail_u64(&self) -> u64 { - let index = self.data.len() - 8; - let bytes: [u8; 8] = self.data[index..].try_into().unwrap(); - u64::from_be_bytes(bytes) - } - - pub fn zero32() -> Self { - Bytes { data: [0u8; 32].to_vec() } - } - - pub fn zero64() -> Self { - Bytes { data: [0u8; 64].to_vec() } - } -} - -impl Default for Bytes { - fn default() -> Bytes { - Bytes { data: Vec::new() } - } -} - -impl PartialEq for Bytes { - fn eq(&self, other: &Self) -> bool { - crate::utils::same_hash(&self.data, &other.data) - } - - fn ne(&self, other: &Self) -> bool { - !crate::utils::same_hash(&self.data, &other.data) - } -} - -impl Eq for Bytes {} - -impl PartialOrd for Bytes { - fn partial_cmp(&self, other: &Self) -> Option { - let self_hash_int = BigUint::from_bytes_be(&self.data); - let other_hash_int = BigUint::from_bytes_be(&other.data); - Some(self_hash_int.cmp(&other_hash_int)) - } -} - -impl Ord for Bytes { - fn cmp(&self, other: &Self) -> Ordering { - let self_hash_int = BigUint::from_bytes_be(&self.data); - let other_hash_int = BigUint::from_bytes_be(&other.data); - self_hash_int.cmp(&other_hash_int) - } -} - -impl fmt::Debug for Bytes { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - fmt.write_str(&crate::utils::to_hex(&self.data)) - } -} - -impl Serialize for Bytes { - fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> where - S: Serializer { - serializer.serialize_str(&crate::utils::to_hex(&self.data)) - } -} - -struct BytesVisitor; - -impl<'de> Visitor<'de> for BytesVisitor { - type Value = Bytes; - - fn expecting(&self, formatter: &mut Formatter) -> Result<(), Error> { - formatter.write_str("32 or 64 bytes") - } - - fn visit_str(self, value: &str) -> Result where E: DeError, { - if value.len() == 64 || value.len() == 128 { - Ok(Bytes::new(crate::from_hex(value).unwrap())) + context.lock().unwrap().bus.register(move |_uuid, e| { + if e == Event::ActionStopMining { + info!("Stopping keystore miner"); + mining.store(false, atomic::Ordering::SeqCst); + false } else { - Err(E::custom("Key must be 32 or 64 bytes!")) + true } - } - - fn visit_bytes(self, value: &[u8]) -> Result where E: DeError, { - if value.len() == 32 || value.len() == 64 { - Ok(Bytes::from_bytes(value)) - } else { - Err(E::custom("Key must be 32 or 64 bytes!")) - } - } + }); } -impl<'dd> Deserialize<'dd> for Bytes { - fn deserialize(deserializer: D) -> Result>::Error> where D: Deserializer<'dd> { - deserializer.deserialize_str(BytesVisitor) +fn generate_key(difficulty: usize, mining: Arc) -> Option { + let mut rng = rand::thread_rng(); + let mut buf = [0u8; 32]; + let mut digest = Blakeout::default(); + let mut time = Instant::now(); + let mut count = 0u128; + loop { + rng.fill_bytes(&mut buf); + let keystore = Keystore::from_bytes(&buf); + digest.reset(); + digest.input(keystore.public_key.as_slice()); + digest.result(&mut buf); + if hash_is_good(&buf, difficulty) { + info!("Generated keypair: {:?}", &keystore); + return Some(keystore); + } + if !mining.load(atomic::Ordering::SeqCst) { + return None; + } + let elapsed = time.elapsed().as_millis(); + if elapsed >= 60000 { + debug!("Mining speed {} H/s", count / 60); + time = Instant::now(); + count = 0; + } + count += 1; } } #[cfg(test)] mod tests { - use crate::{Keystore, Bytes}; + use crate::{Bytes, Keystore}; #[test] pub fn test_signature() { @@ -242,12 +189,6 @@ mod tests { let data = b"{ identity: 178135D209C697625E3EC71DA5C760382E54936F824EE5083908DA66B14ECE18,\ confirmation: A4A0AFECD1A511825226F0D3437C6C6BDAE83554040AA7AEB49DEFEAB0AE9EA4 }"; let signature = keystore.sign(data); - assert!(Keystore::check(data, keystore.get_public().as_bytes(), &signature), "Wrong signature!") - } - - #[test] - pub fn test_tail_bytes() { - let bytes = Bytes::new(vec![0, 255, 255, 255, 0, 255, 255, 255]); - assert_eq!(bytes.get_tail_u64(), 72057589759737855u64); + assert!(Keystore::check(data, keystore.get_public().as_slice(), &signature), "Wrong signature!") } } \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 22d0a56..9032aa3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,12 @@ pub use blockchain::block::Block; pub use blockchain::transaction::Transaction; -pub use crate::blockchain::Blockchain; +pub use crate::blockchain::Chain; pub use crate::context::Context; -pub use settings::Settings; -pub use crate::keys::Bytes; +pub use crate::miner::Miner; +pub use crate::p2p::Network; +pub use crate::settings::Settings; +pub use crate::bytes::Bytes; pub use crate::keys::Keystore; pub use crate::simplebus::*; pub use crate::utils::*; @@ -18,5 +20,7 @@ pub mod context; pub mod event; pub mod p2p; pub mod dns; +pub mod dns_utils; pub mod settings; +pub mod bytes; diff --git a/src/main.rs b/src/main.rs index 5b037be..2303ee3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,44 +2,25 @@ // This is silently ignored on non-windows systems. // See https://msdn.microsoft.com/en-us/library/4cc7ya5b.aspx for more details. #![windows_subsystem = "windows"] -extern crate web_view; -extern crate tinyfiledialogs as tfd; -extern crate serde; -extern crate serde_json; use std::env; use std::sync::{Arc, Mutex}; -use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::thread; -use std::time::{Duration, Instant}; +use std::time::Duration; -#[cfg(windows)] -use winapi::um::wincon::{AttachConsole, FreeConsole, ATTACH_PARENT_PROCESS}; - -use rand::RngCore; -#[cfg(not(target_os = "macos"))] -use thread_priority::*; -use serde::Deserialize; -use web_view::*; use getopts::Options; -use simple_logger::{SimpleLogger}; #[allow(unused_imports)] -use log::{trace, debug, info, warn, error, LevelFilter}; +use log::{debug, error, info, LevelFilter, trace, warn}; +use simple_logger::SimpleLogger; +#[cfg(windows)] +use winapi::um::wincon::{ATTACH_PARENT_PROCESS, AttachConsole, FreeConsole}; -use alfis::{Blockchain, Bytes, Context, Keystore, Transaction, check_domain, Block}; -use alfis::event::Event; -use alfis::miner::Miner; -use alfis::p2p::Network; -use alfis::settings::Settings; -use alfis::dns::context::{ServerContext, ResolveStrategy}; -use alfis::dns::server::{DnsServer, DnsUdpServer, DnsTcpServer}; -use alfis::dns::protocol::DnsRecord; -use alfis::blockchain::filter::BlockchainFilter; +use alfis::{Block, Bytes, Chain, Miner, Context, Network, Settings, dns_utils, Keystore}; + +mod web_ui; -const KEYSTORE_DIFFICULTY: usize = 24; const SETTINGS_FILENAME: &str = "alfis.toml"; const LOG_TARGET_MAIN: &str = "alfis::Main"; -const LOG_TARGET_UI: &str = "alfis::UI"; fn main() { // When linked with the windows subsystem windows won't automatically attach @@ -95,23 +76,23 @@ fn main() { } Some(keystore) => { keystore } }; - let blockchain: Blockchain = Blockchain::new(&settings); + let chain: Chain = Chain::new(&settings); if opt_matches.opt_present("l") { - for i in 1..(blockchain.height() + 1) { - if let Some(block) = blockchain.get_block(i) { + for i in 1..(chain.height() + 1) { + if let Some(block) = chain.get_block(i) { info!(target: LOG_TARGET_MAIN, "{:?}", &block); } } return; } - match blockchain.get_block(1) { + match chain.get_block(1) { None => { info!(target: LOG_TARGET_MAIN, "No blocks found in DB"); } Some(block) => { trace!(target: LOG_TARGET_MAIN, "Loaded DB with origin {:?}", &block.hash); } } let settings_copy = settings.clone(); - let context: Arc> = Arc::new(Mutex::new(Context::new(settings, keystore, blockchain))); - start_dns_server(&context, &settings_copy); + let context: Arc> = Arc::new(Mutex::new(Context::new(settings, keystore, chain))); + dns_utils::start_dns_server(&context, &settings_copy); let mut miner_obj = Miner::new(context.clone()); miner_obj.start_mining_thread(); @@ -127,7 +108,7 @@ fn main() { thread::sleep(sleep); } } else { - run_interface(context.clone(), miner.clone()); + web_ui::run_interface(context.clone(), miner.clone()); } // Without explicitly detaching the console cmd won't redraw it's prompt. @@ -137,393 +118,18 @@ fn main() { } } -fn start_dns_server(context: &Arc>, settings: &Settings) { - let server_context = create_server_context(context.clone(), &settings); - - if server_context.enable_udp { - let udp_server = DnsUdpServer::new(server_context.clone(), settings.dns.threads); - if let Err(e) = udp_server.run_server() { - error!(target: LOG_TARGET_MAIN, "Failed to bind UDP listener: {:?}", e); - } - } - - if server_context.enable_tcp { - let tcp_server = DnsTcpServer::new(server_context.clone(), settings.dns.threads); - if let Err(e) = tcp_server.run_server() { - error!(target: LOG_TARGET_MAIN, "Failed to bind TCP listener: {:?}", e); - } - } -} - fn create_genesis_if_needed(context: &Arc>, miner: &Arc>) { // If there is no origin in settings and no blockchain in DB, generate genesis block let context = context.lock().unwrap(); - let last_block = context.get_blockchain().last_block(); + let last_block = context.get_chain().last_block(); let origin = context.settings.origin.clone(); if origin.eq("") && last_block.is_none() { // If blockchain is empty, we are going to mine a Genesis block - create_genesis(miner.clone(), &context.get_keystore()); + let block = Block::new(None, context.get_keystore().get_public(), Bytes::default()); + miner.lock().unwrap().add_block(block); } } -fn run_interface(context: Arc>, miner: Arc>) { - let file_content = include_str!("webview/index.html"); - let mut styles = inline_style(include_str!("webview/bulma.css")); - styles.push_str(&inline_style(include_str!("webview/busy_indicator.css"))); - let scripts = inline_script(include_str!("webview/scripts.js")); - - let html = Content::Html(file_content.to_owned().replace("{styles}", &styles).replace("{scripts}", &scripts)); - let title = format!("ALFIS {}", env!("CARGO_PKG_VERSION")); - let mut interface = web_view::builder() - .title(&title) - .content(html) - .size(1023, 720) - .min_size(895, 350) - .resizable(true) - .debug(false) - .user_data(()) - .invoke_handler(|web_view, arg| { - use Cmd::*; - debug!(target: LOG_TARGET_UI, "Command {}", arg); - match serde_json::from_str(arg).unwrap() { - Loaded => { - web_view.eval("showMiningIndicator(false, false);").expect("Error evaluating!"); - let handle = web_view.handle(); - let mut status = Status::new(); - let mut c = context.lock().unwrap(); - c.bus.register(move |_uuid, e| { - debug!(target: LOG_TARGET_UI, "Got event from bus {:?}", &e); - let eval = match e { - Event::KeyCreated { path, public } => { format!("keystoreChanged('{}', '{}');", &path, &public) } - Event::KeyLoaded { path, public } => { format!("keystoreChanged('{}', '{}');", &path, &public) } - Event::KeySaved { path, public } => { format!("keystoreChanged('{}', '{}');", &path, &public) } - Event::MinerStarted => { - status.mining = true; - String::from("setLeftStatusBarText('Mining...'); showMiningIndicator(true, false);") - } - Event::KeyGeneratorStarted => { - status.mining = true; - String::from("setLeftStatusBarText('Mining...'); showMiningIndicator(true, false);") - } - Event::MinerStopped | Event::KeyGeneratorStopped => { - status.mining = false; - if status.syncing { - String::from("setLeftStatusBarText('Syncing...'); showMiningIndicator(true, true);") - } else { - String::from("setLeftStatusBarText('Idle'); showMiningIndicator(false, false);") - } - } - Event::Syncing { have, height } => { - status.syncing = true; - status.synced_blocks = have; - status.sync_height = height; - if status.mining { - String::from("setLeftStatusBarText('Mining...'); showMiningIndicator(true, false);") - } else { - format!("setLeftStatusBarText('Synchronizing {}/{}'); showMiningIndicator(true, true);", have, height) - } - } - Event::SyncFinished => { - status.syncing = false; - if status.mining { - String::from("setLeftStatusBarText('Mining...'); showMiningIndicator(true, false);") - } else { - format!("setLeftStatusBarText('Idle'); showMiningIndicator(false, false);") - } - } - Event::NetworkStatus { nodes, blocks } => { - if status.mining || status.syncing || nodes < 3 { - format!("setRightStatusBarText('Nodes: {}, Blocks: {}')", nodes, blocks) - } else { - format!("setLeftStatusBarText('Idle'); setRightStatusBarText('Nodes: {}, Blocks: {}')", nodes, blocks) - } - } - _ => { String::new() } - }; - - if !eval.is_empty() { - debug!(target: LOG_TARGET_UI, "Evaluating {}", &eval); - handle.dispatch(move |web_view| { - web_view.eval(&eval.replace("\\", "\\\\")) - }).expect("Error dispatching!"); - } - true - }); - let eval = format!("keystoreChanged('{}', '{}');", c.keystore.get_path(), &c.keystore.get_public().to_string()); - debug!(target: LOG_TARGET_UI, "Evaluating {}", &eval); - web_view.eval(&eval.replace("\\", "\\\\")).expect("Error evaluating!"); - } - LoadKey {} => { - let result = tfd::open_file_dialog("Open keys file", "", Some((&["*.key"], "*.key"))); - match result { - None => {} - Some(file_name) => { - match Keystore::from_file(&file_name, "") { - None => { - error!(target: LOG_TARGET_UI, "Error loading keystore '{}'!", &file_name); - } - Some(keystore) => { - info!(target: LOG_TARGET_UI, "Loaded keystore with key: {:?}", &keystore.get_public()); - let mut c = context.lock().unwrap(); - c.bus.post(Event::KeyLoaded { path: keystore.get_path().to_owned(), public: keystore.get_public().to_string() }); - c.set_keystore(keystore); - } - } - } - } - } - CreateKey {} => { - create_key(context.clone()); - } - SaveKey {} => { - let result = tfd::save_file_dialog_with_filter("Save keys file", "", &["*.key"], "Key files (*.key)"); - match result { - None => {} - Some(new_path) => { - let mut c = context.lock().unwrap(); - let path = new_path.clone(); - let public = c.keystore.get_public().to_string(); - c.keystore.save(&new_path, ""); - info!(target: LOG_TARGET_UI, "Key file saved to {}", &path); - c.bus.post(Event::KeySaved { path, public }); - } - } - } - CheckDomain { name } => { - let name = name.to_lowercase(); - let c = context.lock().unwrap(); - let available = c.get_blockchain().is_domain_available(&name, &c.get_keystore()); - web_view.eval(&format!("domainAvailable({})", available)).expect("Error evaluating!"); - } - CreateDomain { name, records, .. } => { - debug!(target: LOG_TARGET_UI, "Got records: {}", records); - let name = name.to_lowercase(); - if !check_domain(&name, true) { - return Ok(()); - } - if serde_json::from_str::>(&records).is_ok() { - let (keystore, transaction) = { - let context = context.lock().unwrap(); - (context.get_keystore(), context.blockchain.get_domain_transaction(&name)) - }; - match transaction { - None => { - create_domain(miner.clone(), name, records, &keystore); - } - Some(transaction) => { - if transaction.pub_key == keystore.get_public() { - create_domain(miner.clone(), name, records, &keystore); - } else { - warn!(target: LOG_TARGET_UI, "Tried to mine not owned domain!"); - let _ = web_view.eval(&format!("showWarning('{}');", "You cannot change domain that you don't own!")); - } - } - } - } else { - warn!(target: LOG_TARGET_UI, "Error in DNS records for domain!"); - let _ = web_view.eval(&format!("showWarning('{}');", "Something wrong with your records! Please, correct the error and try again.")); - } - } - ChangeDomain { .. } => {} - RenewDomain { .. } => {} - TransferDomain { .. } => {} - CheckZone { name } => { - let name = name.to_lowercase(); - if !check_domain(&name, false) { - web_view.eval("zoneAvailable(false)").expect("Error evaluating!"); - } else { - let c = context.lock().unwrap(); - let available = c.get_blockchain().is_domain_available(&name, &c.get_keystore()); - web_view.eval(&format!("zoneAvailable({})", available)).expect("Error evaluating!"); - } - } - CreateZone { name, data } => { - let name = name.to_lowercase(); - let data = data.to_lowercase(); - let (keystore, transaction) = { - let context = context.lock().unwrap(); - (context.get_keystore(), context.blockchain.get_domain_transaction(&name)) - }; - match transaction { - None => { - create_domain(miner.clone(), name, data, &keystore); - } - Some(transaction) => { - if transaction.pub_key == keystore.get_public() { - create_domain(miner.clone(), name, data, &keystore); - } else { - warn!(target: LOG_TARGET_UI, "Tried to mine not owned domain!"); - let _ = web_view.eval(&format!("showWarning('{}');", "You cannot change domain that you don't own!")); - } - } - } - } - StopMining => { - context.lock().unwrap().bus.post(Event::ActionStopMining); - } - } - //dbg!(&signature); - Ok(()) - }) - .build() - .expect("Error building GUI"); - - // We use this ugly loop to lower CPU usage a lot. - // If we use .run() or only .step() in a loop without sleeps it will try - // to support 60FPS and uses more CPU than it should. - let pause = Duration::from_millis(25); - let mut start = Instant::now(); - loop { - match interface.step() { - None => { - info!(target: LOG_TARGET_UI, "Interface closed, exiting"); - break; - } - Some(result) => { - match result { - Ok(_) => {} - Err(_) => { - error!(target: LOG_TARGET_UI, "Something wrong with webview, exiting"); - break; - } - } - } - } - if start.elapsed().as_millis() > 1 { - thread::sleep(pause); - start = Instant::now(); - } - } - interface.exit(); -} - -fn create_genesis(miner: Arc>, keystore: &Keystore) { - let block = Block::new(None, keystore.get_public(), Bytes::default()); - let mut miner_guard = miner.lock().unwrap(); - miner_guard.add_block(block); -} - -fn create_domain>(miner: Arc>, name: S, data: S, keystore: &Keystore) { - let name = name.into(); - info!(target: LOG_TARGET_UI, "Generating domain or zone {}", name); - //let tags_vector: Vec = tags.into().trim().split(",").map(|s| s.trim()).map(String::from).collect(); - let transaction = Transaction::from_str(name.into(), "dns".into(), data.into(), keystore.get_public().clone()); - let block = Block::new(Some(transaction), keystore.get_public(), Bytes::default()); - let mut miner_guard = miner.lock().unwrap(); - miner_guard.add_block(block); -} - -fn create_key(context: Arc>) { - let mining = Arc::new(AtomicBool::new(true)); - let miners_count = Arc::new(AtomicUsize::new(0)); - { context.lock().unwrap().bus.post(Event::KeyGeneratorStarted); } - for _ in 0..num_cpus::get() { - let context = context.clone(); - let mining = mining.clone(); - let miners_count = miners_count.clone(); - thread::spawn(move || { - #[cfg(not(target_os = "macos"))] - let _ = set_current_thread_priority(ThreadPriority::Min); - miners_count.fetch_add(1, Ordering::Relaxed); - match generate_key(KEYSTORE_DIFFICULTY, mining.clone()) { - None => { - debug!(target: LOG_TARGET_UI, "Keystore mining finished"); - } - Some(keystore) => { - info!(target: LOG_TARGET_UI, "Key mined successfully: {:?}", &keystore.get_public()); - let mut c = context.lock().unwrap(); - mining.store(false, Ordering::Relaxed); - c.bus.post(Event::KeyCreated { path: keystore.get_path().to_owned(), public: keystore.get_public().to_string() }); - c.set_keystore(keystore); - } - } - let miners = miners_count.fetch_sub(1, Ordering::Relaxed) - 1; - if miners == 0 { - context.lock().unwrap().bus.post(Event::KeyGeneratorStopped); - } - }); - } - context.lock().unwrap().bus.register(move |_uuid, e| { - if e == Event::ActionStopMining { - mining.store(false, Ordering::Relaxed); - } - false - }); -} - -fn generate_key(difficulty: usize, mining: Arc) -> Option { - let mut rng = rand::thread_rng(); - let mut buf = [0u8; 32]; - loop { - rng.fill_bytes(&mut buf); - let keystore = Keystore::from_bytes(&buf); - if keystore.hash_is_good(difficulty) { - info!(target: LOG_TARGET_UI, "Generated keypair: {:?}", &keystore); - return Some(keystore); - } - if !mining.load(Ordering::Relaxed) { - return None; - } - } -} - -fn create_server_context(context: Arc>, settings: &Settings) -> Arc { - let mut server_context = ServerContext::new(); - server_context.allow_recursive = true; - server_context.dns_listen = settings.dns.listen.clone(); - server_context.resolve_strategy = match settings.dns.forwarders.is_empty() { - true => { ResolveStrategy::Recursive } - false => { ResolveStrategy::Forward { upstreams: settings.dns.forwarders.clone() } } - }; - server_context.filters.push(Box::new(BlockchainFilter::new(context))); - match server_context.initialize() { - Ok(_) => {} - Err(e) => { panic!("DNS server failed to initialize: {:?}", e); } - } - - Arc::new(server_context) -} - -#[derive(Deserialize)] -#[serde(tag = "cmd", rename_all = "camelCase")] -pub enum Cmd { - Loaded, - LoadKey {}, - CreateKey {}, - SaveKey {}, - CheckZone { name: String }, - CreateZone { name: String, data: String }, - CheckDomain { name: String }, - CreateDomain { name: String, records: String, tags: String }, - ChangeDomain { name: String, records: String, tags: String }, - RenewDomain { name: String, days: u16 }, - TransferDomain { name: String, owner: String }, - StopMining, -} - -struct Status { - pub mining: bool, - pub syncing: bool, - pub synced_blocks: u64, - pub sync_height: u64, - pub nodes_connected: usize, - pub chain_height: u64 -} - -impl Status { - fn new() -> Self { - Status { mining: false, syncing: false, synced_blocks: 0, sync_height: 0, nodes_connected: 0, chain_height: 0 } - } -} - -fn inline_style(s: &str) -> String { - format!(r#""#, s) -} - -fn inline_script(s: &str) -> String { - format!(r#""#, s) -} - #[cfg(test)] mod tests { use alfis::dns::protocol::{DnsRecord, TransientTtl}; diff --git a/src/miner.rs b/src/miner.rs index dec2cd7..9d633d5 100644 --- a/src/miner.rs +++ b/src/miner.rs @@ -12,9 +12,10 @@ use num_cpus; #[cfg(not(target_os = "macos"))] use thread_priority::*; -use crate::{Block, Bytes, Context, hash_is_good}; -use crate::blockchain::blockchain::BlockQuality; +use crate::{Block, Bytes, Context}; use crate::blockchain::{BLOCK_DIFFICULTY, CHAIN_VERSION, LOCKER_DIFFICULTY}; +use crate::blockchain::enums::BlockQuality; +use crate::blockchain::hash_utils::*; use crate::event::Event; pub struct Miner { @@ -112,7 +113,7 @@ impl Miner { info!("Mining locker block"); block.difficulty = LOCKER_DIFFICULTY; block.pub_key = context.lock().unwrap().keystore.get_public(); - match context.lock().unwrap().blockchain.last_block() { + match context.lock().unwrap().chain.last_block() { None => {} Some(last_block) => { info!("Last block found"); @@ -127,8 +128,8 @@ impl Miner { } } else { block.difficulty = BLOCK_DIFFICULTY; - block.index = context.lock().unwrap().blockchain.height() + 1; - block.prev_block_hash = match context.lock().unwrap().blockchain.last_block() { + block.index = context.lock().unwrap().chain.height() + 1; + block.prev_block_hash = match context.lock().unwrap().chain.last_block() { None => { Bytes::default() } Some(block) => { block.hash } }; @@ -161,13 +162,13 @@ impl Miner { let index = block.index; let mut context = context.lock().unwrap(); block.signature = Bytes::from_bytes(&context.keystore.sign(&block.as_bytes())); - if context.blockchain.check_new_block(&block) != BlockQuality::Good { + if context.chain.check_new_block(&block) != BlockQuality::Good { warn!("Error adding mined block!"); if index == 0 { error!("To mine genesis block you need to make 'origin' an empty string in config."); } } else { - context.blockchain.add_block(block); + context.chain.add_block(block); } context.bus.post(Event::MinerStopped); mining.store(false, Ordering::SeqCst); diff --git a/src/p2p/network.rs b/src/p2p/network.rs index a017158..bd7722e 100644 --- a/src/p2p/network.rs +++ b/src/p2p/network.rs @@ -15,7 +15,7 @@ use log::{trace, debug, info, warn, error}; use crate::{Context, Block, p2p::Message, p2p::State, p2p::Peer, p2p::Peers}; use std::net::{SocketAddr, IpAddr, SocketAddrV4, ToSocketAddrs}; -use crate::blockchain::blockchain::BlockQuality; +use crate::blockchain::enums::BlockQuality; use crate::blockchain::CHAIN_VERSION; use chrono::Utc; @@ -95,7 +95,7 @@ impl Network { if !handle_connection_event(context.clone(), &mut peers, &poll.registry(), &event) { let _ = peers.close_peer(poll.registry(), &token); let mut context = context.lock().unwrap(); - let blocks_count = context.blockchain.height(); + let blocks_count = context.chain.height(); context.bus.post(crate::event::Event::NetworkStatus { nodes: peers.get_peers_active_count(), blocks: blocks_count }); } } @@ -106,7 +106,7 @@ impl Network { // Send pings to idle peers let (height, hash) = { let context = context.lock().unwrap(); - (context.blockchain.height(), context.blockchain.last_hash()) + (context.chain.height(), context.chain.last_hash()) }; mine_locker_block(context.clone()); peers.send_pings(poll.registry(), height, hash); @@ -193,7 +193,7 @@ fn handle_connection_event(context: Arc>, peers: &mut Peers, regi if from.elapsed().as_secs() >= 30 { let data: String = { let c = context.lock().unwrap(); - let message = Message::ping(c.blockchain.height(), c.blockchain.last_hash()); + let message = Message::ping(c.chain.height(), c.chain.last_hash()); serde_json::to_string(&message).unwrap() }; send_message(peer.get_stream(), &data.into_bytes()); @@ -267,7 +267,7 @@ fn handle_message(context: Arc>, message: Message, peers: &mut Pe let (my_height, my_hash, my_origin, my_version) = { let context = context.lock().unwrap(); // TODO cache it somewhere - (context.blockchain.height(), context.blockchain.last_hash(), &context.settings.origin.clone(), CHAIN_VERSION) + (context.chain.height(), context.chain.last_hash(), &context.settings.origin.clone(), CHAIN_VERSION) }; match message { Message::Hand { origin, version, public } => { @@ -290,10 +290,10 @@ fn handle_message(context: Arc>, message: Message, peers: &mut Pe peer.set_height(height); peer.set_active(true); let mut context = context.lock().unwrap(); - let blocks_count = context.blockchain.height(); + let blocks_count = context.chain.height(); context.bus.post(crate::event::Event::NetworkStatus { nodes: active_count + 1, blocks: blocks_count }); if peer.is_higher(my_height) { - context.blockchain.update_max_height(height); + context.chain.update_max_height(height); context.bus.post(crate::event::Event::Syncing { have: my_height, height}); State::message(Message::GetBlock { index: my_height + 1 }) } else { @@ -321,7 +321,7 @@ fn handle_message(context: Arc>, message: Message, peers: &mut Pe let is_higher = peer.is_higher(my_height); let mut context = context.lock().unwrap(); - let blocks_count = context.blockchain.height(); + let blocks_count = context.chain.height(); context.bus.post(crate::event::Event::NetworkStatus { nodes: peers.get_peers_active_count(), blocks: blocks_count }); if is_higher { @@ -342,7 +342,7 @@ fn handle_message(context: Arc>, message: Message, peers: &mut Pe } Message::GetBlock { index } => { let context = context.lock().unwrap(); - match context.blockchain.get_block(index) { + match context.chain.get_block(index) { Some(block) => State::message(Message::block(block.index, serde_json::to_string(&block).unwrap())), None => State::Error } @@ -359,11 +359,11 @@ fn handle_message(context: Arc>, message: Message, peers: &mut Pe let peers_count = peers.get_peers_active_count(); thread::spawn(move || { let mut context = context.lock().unwrap(); - let max_height = context.blockchain.max_height(); - match context.blockchain.check_new_block(&block) { + let max_height = context.chain.max_height(); + match context.chain.check_new_block(&block) { BlockQuality::Good => { - context.blockchain.add_block(block); - let my_height = context.blockchain.height(); + context.chain.add_block(block); + let my_height = context.chain.height(); context.bus.post(crate::event::Event::BlockchainChanged); // If it was the last block to sync if my_height == max_height { @@ -392,12 +392,12 @@ fn handle_message(context: Arc>, message: Message, peers: &mut Pe /// Sends an Event to miner to start mining locker block if "locker" is our public key fn mine_locker_block(context: Arc>) { let mut context = context.lock().unwrap(); - if let Some(block) = context.blockchain.last_block() { - if block.index < context.blockchain.max_height() { + if let Some(block) = context.chain.last_block() { + if block.index < context.chain.max_height() { info!("No locker mining while syncing"); return; } - match context.blockchain.get_block_locker(&block, Utc::now().timestamp()) { + match context.chain.get_block_locker(&block, Utc::now().timestamp()) { Some(key) => { if key == context.keystore.get_public() { info!("We have an honor to mine locker block!"); @@ -419,7 +419,7 @@ fn deal_with_fork(context: MutexGuard, peer: &mut Peer, block: Block) { if vector[0].index == 0 { return; } - if let Some(prev_block) = context.blockchain.get_block(vector[0].index - 1) { + if let Some(prev_block) = context.chain.get_block(vector[0].index - 1) { // If this block is not root of the fork (we need to go ~deeper~ more backwards) if vector[0].prev_block_hash != prev_block.hash { return; diff --git a/src/utils.rs b/src/utils.rs index 6de4033..f33d6f5 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,6 +1,4 @@ use std::num; -use num_bigint::BigUint; -use num_traits::One; /// Convert bytes array to HEX format pub fn to_hex(buf: &[u8]) -> String { @@ -56,23 +54,6 @@ fn split_n(s: &str, n: usize) -> Vec<&str> { .collect() } -/// There is no default PartialEq implementation for arrays > 32 in size -pub fn same_hash(left: &[u8], right: &[u8]) -> bool { - for (x, y) in left.iter().zip(right) { - if x != y { - return false; - } - } - true -} - -pub fn hash_is_good(hash: &[u8], difficulty: usize) -> bool { - let target = BigUint::one() << ((hash.len() << 3) - difficulty); - let hash_int = BigUint::from_bytes_be(&hash); - - return hash_int < target; -} - #[cfg(test)] mod test { use crate::check_domain; diff --git a/src/web_ui.rs b/src/web_ui.rs new file mode 100644 index 0000000..da53c6d --- /dev/null +++ b/src/web_ui.rs @@ -0,0 +1,304 @@ +extern crate web_view; +extern crate tinyfiledialogs as tfd; +extern crate serde; +extern crate serde_json; + +use std::sync::{Arc, Mutex}; +use std::thread; +use std::time::{Duration, Instant}; + +use web_view::Content; +#[allow(unused_imports)] +use log::{debug, error, info, LevelFilter, trace, warn}; +use serde::Deserialize; + +use alfis::{Block, Bytes, Context, Keystore, Transaction}; +use alfis::miner::Miner; +use alfis::{keys, check_domain}; +use alfis::event::Event; +use alfis::dns::protocol::DnsRecord; +use Cmd::*; + +pub fn run_interface(context: Arc>, miner: Arc>) { + let file_content = include_str!("webview/index.html"); + let mut styles = inline_style(include_str!("webview/bulma.css")); + styles.push_str(&inline_style(include_str!("webview/busy_indicator.css"))); + let scripts = inline_script(include_str!("webview/scripts.js")); + + let html = Content::Html(file_content.to_owned().replace("{styles}", &styles).replace("{scripts}", &scripts)); + let title = format!("ALFIS {}", env!("CARGO_PKG_VERSION")); + let mut interface = web_view::builder() + .title(&title) + .content(html) + .size(1023, 720) + .min_size(895, 350) + .resizable(true) + .debug(false) + .user_data(()) + .invoke_handler(|web_view, arg| { + debug!("Command {}", arg); + match serde_json::from_str(arg).unwrap() { + Loaded => { + web_view.eval("showMiningIndicator(false, false);").expect("Error evaluating!"); + let handle = web_view.handle(); + let mut status = Status::new(); + let mut c = context.lock().unwrap(); + c.bus.register(move |_uuid, e| { + debug!("Got event from bus {:?}", &e); + let eval = match e { + Event::KeyCreated { path, public, hash } => { format!("keystoreChanged('{}', '{}', '{}');", &path, &public, &hash) } + Event::KeyLoaded { path, public, hash } => { format!("keystoreChanged('{}', '{}', '{}');", &path, &public, &hash) } + Event::KeySaved { path, public, hash } => { format!("keystoreChanged('{}', '{}', '{}');", &path, &public, &hash) } + Event::MinerStarted => { + status.mining = true; + String::from("setLeftStatusBarText('Mining...'); showMiningIndicator(true, false);") + } + Event::KeyGeneratorStarted => { + status.mining = true; + String::from("setLeftStatusBarText('Mining...'); showMiningIndicator(true, false);") + } + Event::MinerStopped | Event::KeyGeneratorStopped => { + status.mining = false; + if status.syncing { + String::from("setLeftStatusBarText('Syncing...'); showMiningIndicator(true, true);") + } else { + String::from("setLeftStatusBarText('Idle'); showMiningIndicator(false, false);") + } + } + Event::Syncing { have, height } => { + status.syncing = true; + status.synced_blocks = have; + status.sync_height = height; + if status.mining { + String::from("setLeftStatusBarText('Mining...'); showMiningIndicator(true, false);") + } else { + format!("setLeftStatusBarText('Synchronizing {}/{}'); showMiningIndicator(true, true);", have, height) + } + } + Event::SyncFinished => { + status.syncing = false; + if status.mining { + String::from("setLeftStatusBarText('Mining...'); showMiningIndicator(true, false);") + } else { + format!("setLeftStatusBarText('Idle'); showMiningIndicator(false, false);") + } + } + Event::NetworkStatus { nodes, blocks } => { + if status.mining || status.syncing || nodes < 3 { + format!("setRightStatusBarText('Nodes: {}, Blocks: {}')", nodes, blocks) + } else { + format!("setLeftStatusBarText('Idle'); setRightStatusBarText('Nodes: {}, Blocks: {}')", nodes, blocks) + } + } + _ => { String::new() } + }; + + if !eval.is_empty() { + debug!("Evaluating {}", &eval); + handle.dispatch(move |web_view| { + web_view.eval(&eval.replace("\\", "\\\\")) + }).expect("Error dispatching!"); + } + true + }); + let eval = format!("keystoreChanged('{}', '{}', '{}');", c.keystore.get_path(), &c.keystore.get_public().to_string(), &c.keystore.get_hash().to_string()); + debug!("Evaluating {}", &eval); + web_view.eval(&eval.replace("\\", "\\\\")).expect("Error evaluating!"); + } + LoadKey {} => { + let result = tfd::open_file_dialog("Open keys file", "", Some((&["*.key"], "*.key"))); + match result { + None => {} + Some(file_name) => { + match Keystore::from_file(&file_name, "") { + None => { + error!("Error loading keystore '{}'!", &file_name); + } + Some(keystore) => { + info!("Loaded keystore with key: {:?}", &keystore.get_public()); + let mut c = context.lock().unwrap(); + let path = keystore.get_path().to_owned(); + let public = keystore.get_public().to_string(); + let hash = keystore.get_hash().to_string(); + c.bus.post(Event::KeyLoaded { path, public, hash }); + c.set_keystore(keystore); + } + } + } + } + } + CreateKey {} => { + keys::create_key(context.clone()); + } + SaveKey {} => { + let result = tfd::save_file_dialog_with_filter("Save keys file", "", &["*.key"], "Key files (*.key)"); + match result { + None => {} + Some(new_path) => { + let mut c = context.lock().unwrap(); + let path = new_path.clone(); + let public = c.keystore.get_public().to_string(); + let hash = c.keystore.get_hash().to_string(); + c.keystore.save(&new_path, ""); + info!("Key file saved to {}", &path); + c.bus.post(Event::KeySaved { path, public, hash }); + } + } + } + CheckDomain { name } => { + let name = name.to_lowercase(); + let c = context.lock().unwrap(); + let available = c.get_chain().is_domain_available(&name, &c.get_keystore()); + web_view.eval(&format!("domainAvailable({})", available)).expect("Error evaluating!"); + } + CreateDomain { name, records, .. } => { + debug!("Got records: {}", records); + let name = name.to_lowercase(); + if !check_domain(&name, true) { + return Ok(()); + } + if serde_json::from_str::>(&records).is_ok() { + let (keystore, transaction) = { + let context = context.lock().unwrap(); + (context.get_keystore(), context.chain.get_domain_transaction(&name)) + }; + match transaction { + None => { + create_domain(miner.clone(), name, records, &keystore); + } + Some(transaction) => { + if transaction.pub_key == keystore.get_public() { + create_domain(miner.clone(), name, records, &keystore); + } else { + warn!("Tried to mine not owned domain!"); + let _ = web_view.eval(&format!("showWarning('{}');", "You cannot change domain that you don't own!")); + } + } + } + } else { + warn!("Error in DNS records for domain!"); + let _ = web_view.eval(&format!("showWarning('{}');", "Something wrong with your records! Please, correct the error and try again.")); + } + } + ChangeDomain { .. } => {} + RenewDomain { .. } => {} + TransferDomain { .. } => {} + CheckZone { name } => { + let name = name.to_lowercase(); + if !check_domain(&name, false) { + web_view.eval("zoneAvailable(false)").expect("Error evaluating!"); + } else { + let c = context.lock().unwrap(); + let available = c.get_chain().is_domain_available(&name, &c.get_keystore()); + web_view.eval(&format!("zoneAvailable({})", available)).expect("Error evaluating!"); + } + } + CreateZone { name, data } => { + let name = name.to_lowercase(); + let data = data.to_lowercase(); + let (keystore, transaction) = { + let context = context.lock().unwrap(); + (context.get_keystore(), context.chain.get_domain_transaction(&name)) + }; + match transaction { + None => { + create_domain(miner.clone(), name, data, &keystore); + } + Some(transaction) => { + if transaction.pub_key == keystore.get_public() { + create_domain(miner.clone(), name, data, &keystore); + } else { + warn!("Tried to mine not owned domain!"); + let _ = web_view.eval(&format!("showWarning('{}');", "You cannot change domain that you don't own!")); + } + } + } + } + StopMining => { + context.lock().unwrap().bus.post(Event::ActionStopMining); + } + } + //dbg!(&signature); + Ok(()) + }) + .build() + .expect("Error building GUI"); + + // We use this ugly loop to lower CPU usage a lot. + // If we use .run() or only .step() in a loop without sleeps it will try + // to support 60FPS and uses more CPU than it should. + let pause = Duration::from_millis(25); + let mut start = Instant::now(); + loop { + match interface.step() { + None => { + info!("Interface closed, exiting"); + break; + } + Some(result) => { + match result { + Ok(_) => {} + Err(_) => { + error!("Something wrong with webview, exiting"); + break; + } + } + } + } + if start.elapsed().as_millis() > 1 { + thread::sleep(pause); + start = Instant::now(); + } + } + interface.exit(); +} + +fn create_domain>(miner: Arc>, name: S, data: S, keystore: &Keystore) { + let name = name.into(); + info!("Generating domain or zone {}", name); + //let tags_vector: Vec = tags.into().trim().split(",").map(|s| s.trim()).map(String::from).collect(); + let transaction = Transaction::from_str(name.into(), "dns".into(), data.into(), keystore.get_public().clone()); + let block = Block::new(Some(transaction), keystore.get_public(), Bytes::default()); + let mut miner_guard = miner.lock().unwrap(); + miner_guard.add_block(block); +} + +#[derive(Deserialize)] +#[serde(tag = "cmd", rename_all = "camelCase")] +pub enum Cmd { + Loaded, + LoadKey {}, + CreateKey {}, + SaveKey {}, + CheckZone { name: String }, + CreateZone { name: String, data: String }, + CheckDomain { name: String }, + CreateDomain { name: String, records: String, tags: String }, + ChangeDomain { name: String, records: String, tags: String }, + RenewDomain { name: String, days: u16 }, + TransferDomain { name: String, owner: String }, + StopMining, +} + +struct Status { + pub mining: bool, + pub syncing: bool, + pub synced_blocks: u64, + pub sync_height: u64, + pub nodes_connected: usize, + pub chain_height: u64 +} + +impl Status { + fn new() -> Self { + Status { mining: false, syncing: false, synced_blocks: 0, sync_height: 0, nodes_connected: 0, chain_height: 0 } + } +} + +fn inline_style(s: &str) -> String { + format!(r#""#, s) +} + +fn inline_script(s: &str) -> String { + format!(r#""#, s) +} diff --git a/src/webview/index.html b/src/webview/index.html index 5e52255..62a7188 100644 --- a/src/webview/index.html +++ b/src/webview/index.html @@ -150,6 +150,11 @@

Not loaded

+
+ +

Not loaded

+
+
diff --git a/src/webview/scripts.js b/src/webview/scripts.js index c4bb3a7..798054f 100644 --- a/src/webview/scripts.js +++ b/src/webview/scripts.js @@ -281,12 +281,14 @@ function setRightStatusBarText(text) { bar.innerHTML = text; } -function keystoreChanged(path, pub_key) { +function keystoreChanged(path, pub_key, hash) { if (path == '') { path = "In memory"; } - key_file_name = document.getElementById("key_file_name"); + var key_file_name = document.getElementById("key_file_name"); key_file_name.innerHTML = path; - key_file_key = document.getElementById("key_public_key"); - key_file_key.innerHTML = pub_key; + var key_public_key = document.getElementById("key_public_key"); + key_public_key.innerHTML = pub_key; + var key_public_hash = document.getElementById("key_public_hash"); + key_public_hash.innerHTML = hash; } \ No newline at end of file