diff --git a/src/blockchain/block.rs b/src/blockchain/block.rs index 11ee012..6004c80 100644 --- a/src/blockchain/block.rs +++ b/src/blockchain/block.rs @@ -64,7 +64,7 @@ impl Block { } pub fn is_genesis(&self) -> bool { - self.index == 0 && self.transaction.is_none() && self.prev_block_hash == Bytes::default() + self.index == 1 && self.transaction.is_none() && self.prev_block_hash == Bytes::default() } pub fn as_bytes(&self) -> Vec { diff --git a/src/blockchain/blockchain.rs b/src/blockchain/blockchain.rs index fe63417..ba74ea5 100644 --- a/src/blockchain/blockchain.rs +++ b/src/blockchain/blockchain.rs @@ -9,18 +9,43 @@ 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}; +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 ( + 'id' BIGINT NOT NULL PRIMARY KEY, + 'timestamp' BIGINT NOT NULL, + 'version' INT, + 'difficulty' INTEGER, + 'random' INTEGER, + 'nonce' INTEGER, + 'transaction' TEXT, + 'prev_block_hash' BINARY, + 'hash' BINARY, + 'pub_key' BINARY, + 'signature' BINARY); + CREATE INDEX block_index ON blocks (id); + CREATE TABLE transactions (id INTEGER PRIMARY KEY AUTOINCREMENT, identity BINARY, confirmation BINARY, method TEXT, data TEXT, pub_key BINARY); + CREATE INDEX ids ON transactions (identity);"; +const SQL_ADD_BLOCK: &str = "INSERT INTO blocks (id, timestamp, version, difficulty, random, nonce, 'transaction',\ + prev_block_hash, hash, pub_key, signature) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"; +const SQL_GET_LAST_BLOCK: &str = "SELECT * FROM blocks ORDER BY id DESC LIMIT 1;"; +const SQL_ADD_TRANSACTION: &str = "INSERT INTO transactions (identity, confirmation, method, data, pub_key) VALUES (?, ?, ?, ?, ?)"; +const SQL_GET_BLOCK_BY_ID: &str = "SELECT * FROM blocks WHERE id=? LIMIT 1;"; +const SQL_GET_LAST_FULL_BLOCK: &str = "SELECT * FROM blocks WHERE `transaction`<>'' ORDER BY id DESC LIMIT 1;"; +const SQL_GET_PUBLIC_KEY_BY_ID: &str = "SELECT pub_key FROM transactions WHERE identity = ? ORDER BY id DESC LIMIT 1;"; +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 { origin: Bytes, pub version: u32, pub blocks: Vec, last_block: Option, + last_full_block: Option, max_height: u64, db: Connection, - zones: RefCell> + zones: RefCell>, } impl Blockchain { @@ -28,99 +53,120 @@ impl Blockchain { let origin = settings.get_origin(); let db = sqlite::open(DB_NAME).expect("Unable to open blockchain DB"); - let mut blockchain = Blockchain{ origin, version: CHAIN_VERSION, blocks: Vec::new(), last_block: None, max_height: 0, db, zones: RefCell::new(HashSet::new()) }; + let mut blockchain = Blockchain { + origin, + version: CHAIN_VERSION, + blocks: Vec::new(), + last_block: None, + last_full_block: None, + max_height: 0, + db, + zones: RefCell::new(HashSet::new()), + }; blockchain.init_db(); blockchain } /// Reads options from DB or initializes and writes them to DB if not found fn init_db(&mut self) { - match self.db.prepare("SELECT * FROM blocks ORDER BY id DESC LIMIT 1;") { + // Trying to get last block from DB to check its version + let block: Option = match self.db.prepare(SQL_GET_LAST_BLOCK) { Ok(mut statement) => { + let mut result = None; while statement.next().unwrap() == State::Row { match Self::get_block_from_statement(&mut statement) { - None => { error!("Something wrong with block in DB!"); } + None => { + error!("Something wrong with block in DB!"); + panic!(); + } Some(block) => { - info!("Loaded last block: {:?}", &block); - self.version = block.version; - self.last_block = Some(block); + debug!("Loaded last block: {:?}", &block); + result = Some(block); + break; } } - debug!("Blockchain version from DB = {}", self.version); } + result } Err(_) => { info!("No blockchain database found. Creating new."); - self.db.execute(" - CREATE TABLE blocks ( - 'id' BIGINT, - 'timestamp' BIGINT, - 'version' TEXT, - 'difficulty' INTEGER, - 'random' INTEGER, - 'nonce' INTEGER, - 'transaction' TEXT, - 'prev_block_hash' BINARY, - 'hash' BINARY, - 'pub_key' BINARY, - 'signature' BINARY - ); - CREATE INDEX block_index ON blocks (id); - CREATE TABLE transactions (id INTEGER PRIMARY KEY AUTOINCREMENT, identity BINARY, confirmation BINARY, method TEXT, data TEXT, pub_key BINARY); - CREATE INDEX ids ON transactions (identity);" - ).expect("Error creating blocks table"); + self.db.execute(SQL_CREATE_TABLES).expect("Error creating blocks table"); + None + } + }; + // If some block loaded we check its version and determine if we need some migration + if let Some(block) = block { + self.max_height = block.index; + if self.version > block.version { + self.migrate_db(block.version, self.version); + } else if self.version < block.version { + error!("Version downgrade {}->{} is not supported!", block.version, self.version); + panic!(); + } + // Cache some info + self.last_block = Some(block.clone()); + if block.transaction.is_some() { + self.last_full_block = Some(block); + } else { + self.last_full_block = self.get_last_full_block(); } } } + fn migrate_db(&mut self, from: u32, to: u32) { + debug!("Migrating DB from {} to {}", from, to); + } + pub fn add_block(&mut self, block: Block) { info!("Adding block:\n{:?}", &block); self.blocks.push(block.clone()); self.last_block = Some(block.clone()); + if block.transaction.is_some() { + self.last_full_block = Some(block.clone()); + } let transaction = block.transaction.clone(); - - { - // Adding block to DB - let mut statement = self.db.prepare("INSERT INTO blocks (\ - id, timestamp, version, difficulty, random, nonce, 'transaction',\ - prev_block_hash, hash, pub_key, signature)\ - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);").unwrap(); - statement.bind(1, block.index as i64).expect("Error in bind"); - statement.bind(2, block.timestamp as i64).expect("Error in bind"); - statement.bind(3, block.version as i64).expect("Error in bind"); - statement.bind(4, block.difficulty as i64).expect("Error in bind"); - statement.bind(5, block.random as i64).expect("Error in bind"); - statement.bind(6, block.nonce as i64).expect("Error in bind"); - match &transaction { - None => { statement.bind(7, "").expect("Error in bind"); } - Some(transaction) => { - statement.bind(7, transaction.to_string().as_ref() as &str).expect("Error in bind"); - } + if self.add_block_to_table(block).is_ok() { + if let Some(transaction) = transaction { + self.add_transaction_to_table(&transaction).expect("Error adding transaction"); } - statement.bind(8, block.prev_block_hash.as_bytes()).expect("Error in bind"); - statement.bind(9, block.hash.as_bytes()).expect("Error in bind"); - statement.bind(10, block.pub_key.as_bytes()).expect("Error in bind"); - statement.bind(11, block.signature.as_bytes()).expect("Error in bind"); - statement.next().expect("Error adding block to DB"); } + } - if let Some(transaction) = transaction { - self.add_transaction(&transaction); + /// Adds block to blocks table + fn add_block_to_table(&mut self, block: Block) -> sqlite::Result { + let mut statement = self.db.prepare(SQL_ADD_BLOCK)?; + statement.bind(1, block.index as i64)?; + statement.bind(2, block.timestamp as i64)?; + statement.bind(3, block.version as i64)?; + statement.bind(4, block.difficulty as i64)?; + statement.bind(5, block.random as i64)?; + statement.bind(6, block.nonce as i64)?; + match &block.transaction { + None => { statement.bind(7, "")?; } + Some(transaction) => { + 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.next() } - fn add_transaction(&mut self, t: &Transaction) { - let mut statement = self.db.prepare("INSERT INTO transactions (identity, confirmation, method, data, pub_key) VALUES (?, ?, ?, ?, ?)").unwrap(); - statement.bind(1, t.identity.as_bytes()).expect("Error in bind"); - statement.bind(2, t.confirmation.as_bytes()).expect("Error in bind"); - statement.bind(3, t.method.as_ref() as &str).expect("Error in bind"); - statement.bind(4, t.data.as_ref() as &str).expect("Error in bind"); - statement.bind(5, t.pub_key.as_bytes()).expect("Error in bind"); - statement.next().expect("Error adding transaction to DB"); + /// 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(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.next() } pub fn get_block(&self, index: u64) -> Option { - match self.db.prepare("SELECT * FROM blocks WHERE id=? LIMIT 1;") { + match self.db.prepare(SQL_GET_BLOCK_BY_ID) { Ok(mut statement) => { statement.bind(1, index as i64).expect("Error in bind"); while statement.next().unwrap() == State::Row { @@ -130,10 +176,10 @@ impl Blockchain { None } Some(block) => { - debug!("Loaded block: {:?}", &block); + trace!("Loaded block: {:?}", &block); Some(block) } - } + }; } None } @@ -144,12 +190,38 @@ impl Blockchain { } } + /// Gets last block that has a Transaction within + pub fn get_last_full_block(&self) -> Option { + match self.db.prepare(SQL_GET_LAST_FULL_BLOCK) { + Ok(mut statement) => { + while statement.next().unwrap() == State::Row { + return match Self::get_block_from_statement(&mut statement) { + None => { + error!("Something wrong with block in DB!"); + None + } + Some(block) => { + trace!("Got last full block: {:?}", &block); + Some(block) + } + }; + } + None + } + Err(e) => { + warn!("Can't find any full blocks: {}", e); + None + } + } + } + + /// Checks if any domain is available to mine for this client (pub_key) pub fn is_domain_available(&self, domain: &str, keystore: &Keystore) -> bool { if domain.is_empty() { return false; } let identity_hash = hash_identity(domain, None); - let mut statement = self.db.prepare("SELECT pub_key FROM transactions WHERE identity = ? ORDER BY id DESC LIMIT 1;").unwrap(); + let mut statement = self.db.prepare(SQL_GET_PUBLIC_KEY_BY_ID).unwrap(); statement.bind(1, identity_hash.as_bytes()).expect("Error in bind"); while let State::Row = statement.next().unwrap() { let pub_key = Bytes::from_bytes(statement.read::>(0).unwrap().as_slice()); @@ -170,6 +242,7 @@ impl Blockchain { true } + /// Checks if some zone exists in our blockchain pub fn is_zone_in_blockchain(&self, zone: &str) -> bool { if self.zones.borrow().contains(zone) { return true; @@ -177,7 +250,7 @@ impl Blockchain { // Checking for existing zone in DB let identity_hash = hash_identity(zone, None); - let mut statement = self.db.prepare("SELECT identity FROM transactions WHERE identity = ? ORDER BY id DESC LIMIT 1;").unwrap(); + let mut statement = self.db.prepare(SQL_GET_ID_BY_ID).unwrap(); statement.bind(1, identity_hash.as_bytes()).expect("Error in bind"); while let State::Row = statement.next().unwrap() { // If there is such a zone @@ -187,13 +260,14 @@ impl Blockchain { false } + /// Gets full Transaction info for any domain. Used by DNS part. pub fn get_domain_transaction(&self, domain: &str) -> Option { if domain.is_empty() { return None; } let identity_hash = hash_identity(domain, None); - let mut statement = self.db.prepare("SELECT * FROM transactions WHERE identity = ? ORDER BY id DESC LIMIT 1;").unwrap(); + let mut statement = self.db.prepare(SQL_GET_TRANSACTION_BY_ID).unwrap(); statement.bind(1, identity_hash.as_bytes()).expect("Error in bind"); while let State::Row = statement.next().unwrap() { let identity = Bytes::from_bytes(statement.read::>(1).unwrap().as_slice()); @@ -225,7 +299,7 @@ impl Blockchain { match self.last_block { None => { 0u64 } Some(ref block) => { - block.index + 1 + block.index } } } @@ -247,14 +321,19 @@ impl Blockchain { } } + /// Check if this block can be added to our blockchain pub fn check_new_block(&self, block: &Block) -> BlockQuality { let timestamp = Utc::now().timestamp(); if block.timestamp > timestamp { warn!("Ignoring block from the future:\n{:?}", &block); return Bad; } - if !hash_is_good(block.hash.as_bytes(), BLOCK_DIFFICULTY as usize) { - warn!("Ignoring block with low difficulty:\n{:?}", &block); + let difficulty = match block.transaction { + None => { LOCKER_DIFFICULTY } + Some(_) => { BLOCK_DIFFICULTY } + }; + if block.difficulty < difficulty { + warn!("Block difficulty is lower than needed"); return Bad; } if !hash_is_good(block.hash.as_bytes(), block.difficulty as usize) { @@ -264,6 +343,7 @@ impl Blockchain { match &self.last_block { None => { if !block.is_genesis() { + warn!("Block is from the future, how is this possible?"); return Future; } if !self.origin.is_zero() && block.hash != self.origin { @@ -277,16 +357,31 @@ impl Blockchain { return Bad; } if last_block.index + 1 < block.index { - warn!("Got block from the future"); + warn!("Block is from the future, how is this possible?"); return Future; } - if last_block.index >= block.index && last_block.hash == block.hash { - warn!("Ignoring block {}, we already have it", block.index); - return Twin; + if block.index <= last_block.index { + if last_block.hash == block.hash { + warn!("Ignoring block {}, we already have it", block.index); + return Twin; + } + if let Some(my_block) = self.get_block(block.index) { + return if my_block.hash != block.hash { + warn!("Got forked block {} with hash {:?} instead of {:?}", block.index, block.hash, last_block.hash); + Fork + } else { + warn!("Ignoring block {}, we already have it", block.index); + Twin + }; + } } - if last_block.index == block.index && last_block.hash != block.hash { - warn!("Got forked block {} with hash {:?} instead of {:?}", block.index, block.hash, last_block.hash); - return Fork; + if block.transaction.is_none() { + if let Some(locker) = self.get_block_locker(&last_block, block.timestamp) { + if locker != block.pub_key { + warn!("Ignoring block {}, as wrong locker", block.index); + return Bad; + } + } } } } @@ -302,6 +397,38 @@ impl Blockchain { Good } + /// Gets a public key of a node that needs to mine "locker" block above this block + pub fn get_block_locker(&self, block: &Block, timestamp: i64) -> Option { + if block.hash.is_empty() || block.hash.is_zero() { + return None; + } + if block.index < LOCKER_BLOCK_START { + return None; + } + match self.get_last_full_block() { + Some(b) => { + if b.index + LOCKER_BLOCK_COUNT <= block.index { + trace!("Block {} is locked enough", b.index); + return None; + } + } + None => {} + } + // How many 5 min intervals have passed since this block? + let intervals = ((timestamp - block.timestamp) / LOCKER_BLOCK_INTERVAL) as u64; + let tail = block.hash.get_tail_u64(); + let start_index = 1 + ((tail + tail * intervals) % (block.index - 2)); + for index in start_index..block.index { + if let Some(b) = self.get_block(index) { + if b.pub_key != block.pub_key { + trace!("Locker block for block {} must be mined by owner of block {} block_hash: {:?}", block.index, b.index, block.hash); + return Some(b.pub_key); + } + } + } + None + } + fn get_block_from_statement(statement: &mut Statement) -> Option { let index = statement.read::(0).unwrap() as u64; let timestamp = statement.read::(1).unwrap(); @@ -324,7 +451,7 @@ pub enum BlockQuality { Twin, Future, Bad, - Fork + Fork, } pub fn check_block_hash(block: &Block) -> bool { @@ -337,7 +464,6 @@ pub fn check_block_hash(block: &Block) -> bool { pub fn check_block_signature(block: &Block) -> bool { let mut copy = block.clone(); - copy.signature = Bytes::zero64(); - let data = serde_json::to_string(©).unwrap(); - Keystore::check(data.as_bytes(), copy.pub_key.as_bytes(), block.signature.as_bytes()) + copy.signature = Bytes::default(); + Keystore::check(©.as_bytes(), copy.pub_key.as_bytes(), block.signature.as_bytes()) } diff --git a/src/blockchain/constants.rs b/src/blockchain/constants.rs index 3a6790f..90b571f 100644 --- a/src/blockchain/constants.rs +++ b/src/blockchain/constants.rs @@ -1,2 +1,6 @@ -pub const BLOCK_DIFFICULTY: u32 = 24; +pub const BLOCK_DIFFICULTY: u32 = 20; +pub const LOCKER_DIFFICULTY: u32 = 16; pub const CHAIN_VERSION: u32 = 1; +pub const LOCKER_BLOCK_START: u64 = 5; +pub const LOCKER_BLOCK_COUNT: u64 = 3; +pub const LOCKER_BLOCK_INTERVAL: i64 = 300; diff --git a/src/dns/authority.rs b/src/dns/authority.rs index 960b3d4..5f208ac 100644 --- a/src/dns/authority.rs +++ b/src/dns/authority.rs @@ -6,6 +6,8 @@ use std::io::Write; use std::path::Path; use std::sync::{LockResult, RwLock, RwLockReadGuard, RwLockWriteGuard}; +#[allow(unused_imports)] +use log::{trace, debug, info, warn, error}; use derive_more::{Display, From, Error}; use crate::dns::buffer::{PacketBuffer, StreamPacketBuffer, VectorPacketBuffer}; @@ -71,7 +73,13 @@ impl<'a> Zones { } pub fn load(&mut self) -> Result<()> { - let zones_dir = Path::new("zones").read_dir()?; + let zones_dir = match Path::new("zones").read_dir() { + Ok(result) => { result } + Err(_) => { + debug!("Authority dir (zones) not found, skipping."); + return Ok(()); + } + }; for wrapped_filename in zones_dir { let filename = match wrapped_filename { diff --git a/src/dns/context.rs b/src/dns/context.rs index 023f916..0d4d068 100644 --- a/src/dns/context.rs +++ b/src/dns/context.rs @@ -1,6 +1,5 @@ //! The `ServerContext in this thread holds the common state across the server -use std::fs; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; @@ -70,7 +69,7 @@ impl ServerContext { authority: Authority::new(), cache: SynchronizedCache::new(), filters: Vec::new(), - client: Box::new(DnsNetworkClient::new(34255)), + client: Box::new(DnsNetworkClient::new(10000 + (rand::random::() % 20000))), dns_host: String::from("0.0.0.0"), dns_port: 53, api_port: 5380, @@ -88,9 +87,6 @@ impl ServerContext { } pub fn initialize(&mut self) -> Result<()> { - // Create zones directory if it doesn't exist - fs::create_dir_all(self.zones_dir)?; - // Start UDP client thread self.client.run()?; diff --git a/src/event.rs b/src/event.rs index 216886e..9a1831b 100644 --- a/src/event.rs +++ b/src/event.rs @@ -1,3 +1,5 @@ +use crate::Bytes; + #[derive(Clone, PartialEq, Debug)] pub enum Event { MinerStarted, @@ -10,6 +12,7 @@ pub enum Event { NewBlockReceived, BlockchainChanged, ActionStopMining, + ActionMineLocker { index: u64, hash: Bytes }, NetworkStatus { nodes: usize, blocks: u64 }, Syncing { have: u64, height: u64 }, SyncFinished, diff --git a/src/keys.rs b/src/keys.rs index 6f76d75..fb9cacf 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -18,6 +18,7 @@ use log::{trace, debug, info, warn, error}; use crate::hash_is_good; use std::cmp::Ordering; use num_bigint::BigUint; +use std::convert::TryInto; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Keystore { @@ -137,6 +138,12 @@ impl Bytes { 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() } } @@ -227,7 +234,7 @@ impl<'dd> Deserialize<'dd> for Bytes { #[cfg(test)] mod tests { - use crate::Keystore; + use crate::{Keystore, Bytes}; #[test] pub fn test_signature() { @@ -237,4 +244,10 @@ mod tests { 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]); + assert_eq!(bytes.get_tail_u64(), 16777215); + } } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 8450882..98189f7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,8 @@ #![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}; @@ -32,11 +34,10 @@ use alfis::dns::server::{DnsServer, DnsUdpServer, DnsTcpServer}; use alfis::dns::protocol::DnsRecord; use alfis::blockchain::filter::BlockchainFilter; -extern crate serde; -extern crate serde_json; - const KEYSTORE_DIFFICULTY: usize = 24; const SETTINGS_FILENAME: &str = "alfis.cfg"; +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 @@ -46,8 +47,6 @@ fn main() { AttachConsole(ATTACH_PARENT_PROCESS); } - let version = env!("CARGO_PKG_VERSION"); - println!("Starting ALFIS {}", version); let args: Vec = env::args().collect(); let program = args[0].clone(); @@ -56,6 +55,7 @@ fn main() { opts.optflag("n", "nogui", "Run without graphic user interface"); opts.optflag("v", "verbose", "Show more debug messages"); opts.optflag("d", "debug", "Show trace messages, more than debug"); + opts.optflag("l", "list", "List blocks from DB and exit"); opts.optopt("c", "config", "Path to config file", ""); let opt_matches = match opts.parse(&args[1..]) { @@ -83,19 +83,29 @@ fn main() { Some(path) => { path } }; SimpleLogger::new().with_level(level).init().unwrap(); + info!(target: LOG_TARGET_MAIN, "Starting ALFIS {}", env!("CARGO_PKG_VERSION")); let settings = Settings::load(&config_name).expect("Error loading settings"); let keystore: Keystore = match Keystore::from_file(&settings.key_file, "") { None => { - warn!("Generated temporary keystore. Please, generate full-privileged keys."); + warn!(target: LOG_TARGET_MAIN, "Generated temporary keystore. Please, generate full-privileged keys."); Keystore::new() } Some(keystore) => { keystore } }; let blockchain: Blockchain = Blockchain::new(&settings); - match blockchain.get_block(0) { - None => { info!("No blocks found in DB"); } - Some(block) => { info!("Loaded DB with origin {:?}", &block.hash); } + if opt_matches.opt_present("l") { + for i in 1..(blockchain.height() + 1) { + if let Some(block) = blockchain.get_block(i) { + info!(target: LOG_TARGET_MAIN, "{:?}", &block); + } + } + return; + } + + match blockchain.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))); @@ -131,14 +141,14 @@ fn start_dns_server(context: &Arc>, settings: &Settings) { if server_context.enable_udp { let udp_server = DnsUdpServer::new(server_context.clone(), 20); if let Err(e) = udp_server.run_server() { - error!("Failed to bind UDP listener: {:?}", e); + error!(target: LOG_TARGET_MAIN, "Failed to bind UDP listener: {:?}", e); } } if server_context.enable_tcp { let tcp_server = DnsTcpServer::new(server_context.clone(), 20); if let Err(e) = tcp_server.run_server() { - error!("Failed to bind TCP listener: {:?}", e); + error!(target: LOG_TARGET_MAIN, "Failed to bind TCP listener: {:?}", e); } } } @@ -172,7 +182,7 @@ fn run_interface(context: Arc>, miner: Arc>) { .user_data(()) .invoke_handler(|web_view, arg| { use Cmd::*; - debug!("Command {}", arg); + debug!(target: LOG_TARGET_UI, "Command {}", arg); match serde_json::from_str(arg).unwrap() { Loaded => { web_view.eval("showMiningIndicator(false, false);").expect("Error evaluating!"); @@ -180,7 +190,7 @@ fn run_interface(context: Arc>, miner: Arc>) { let mut status = Status::new(); let mut c = context.lock().unwrap(); c.bus.register(move |_uuid, e| { - debug!("Got event from bus {:?}", &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) } @@ -230,7 +240,7 @@ fn run_interface(context: Arc>, miner: Arc>) { }; if !eval.is_empty() { - debug!("Evaluating {}", &eval); + debug!(target: LOG_TARGET_UI, "Evaluating {}", &eval); handle.dispatch(move |web_view| { web_view.eval(&eval.replace("\\", "\\\\")) }).expect("Error dispatching!"); @@ -238,7 +248,7 @@ fn run_interface(context: Arc>, miner: Arc>) { true }); let eval = format!("keystoreChanged('{}', '{}');", c.keystore.get_path(), &c.keystore.get_public().to_string()); - debug!("Evaluating {}", &eval); + debug!(target: LOG_TARGET_UI, "Evaluating {}", &eval); web_view.eval(&eval.replace("\\", "\\\\")).expect("Error evaluating!"); } LoadKey {} => { @@ -248,10 +258,10 @@ fn run_interface(context: Arc>, miner: Arc>) { Some(file_name) => { match Keystore::from_file(&file_name, "") { None => { - error!("Error loading keystore '{}'!", &file_name); + error!(target: LOG_TARGET_UI, "Error loading keystore '{}'!", &file_name); } Some(keystore) => { - info!("Loaded keystore with key: {:?}", &keystore.get_public()); + 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); @@ -272,7 +282,7 @@ fn run_interface(context: Arc>, miner: Arc>) { let path = new_path.clone(); let public = c.keystore.get_public().to_string(); c.keystore.save(&new_path, ""); - info!("Key file saved to {}", &path); + info!(target: LOG_TARGET_UI, "Key file saved to {}", &path); c.bus.post(Event::KeySaved { path, public }); } } @@ -284,7 +294,7 @@ fn run_interface(context: Arc>, miner: Arc>) { web_view.eval(&format!("domainAvailable({})", available)).expect("Error evaluating!"); } CreateDomain { name, records, .. } => { - debug!("Got records: {}", records); + debug!(target: LOG_TARGET_UI, "Got records: {}", records); let name = name.to_lowercase(); if !check_domain(&name, true) { return Ok(()); @@ -302,13 +312,13 @@ fn run_interface(context: Arc>, miner: Arc>) { if transaction.pub_key == keystore.get_public() { create_domain(miner.clone(), name, records, &keystore); } else { - warn!("Tried to mine not owned domain!"); + 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!("Error in DNS records for domain!"); + 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.")); } } @@ -340,7 +350,7 @@ fn run_interface(context: Arc>, miner: Arc>) { if transaction.pub_key == keystore.get_public() { create_domain(miner.clone(), name, data, &keystore); } else { - warn!("Tried to mine not owned domain!"); + 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!")); } } @@ -364,14 +374,14 @@ fn run_interface(context: Arc>, miner: Arc>) { loop { match interface.step() { None => { - info!("Interface closed, exiting"); + info!(target: LOG_TARGET_UI, "Interface closed, exiting"); break; } Some(result) => { match result { Ok(_) => {} Err(_) => { - error!("Something wrong with webview, exiting"); + error!(target: LOG_TARGET_UI, "Something wrong with webview, exiting"); break; } } @@ -393,9 +403,9 @@ fn create_genesis(miner: Arc>, keystore: &Keystore) { fn create_domain>(miner: Arc>, name: S, data: S, keystore: &Keystore) { let name = name.into(); - info!("Generating domain or zone {}", name); + 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(), "domain".into(), data.into(), keystore.get_public().clone()); + 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); @@ -413,10 +423,10 @@ fn create_key(context: Arc>) { miners_count.fetch_add(1, Ordering::Relaxed); match generate_key(KEYSTORE_DIFFICULTY, mining.clone()) { None => { - debug!("Keystore mining finished"); + debug!(target: LOG_TARGET_UI, "Keystore mining finished"); } Some(keystore) => { - info!("Key mined successfully: {:?}", &keystore.get_public()); + 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() }); @@ -444,7 +454,7 @@ fn generate_key(difficulty: usize, mining: Arc) -> Option rng.fill_bytes(&mut buf); let keystore = Keystore::from_bytes(&buf); if keystore.hash_is_good(difficulty) { - info!("Generated keypair: {:?}", &keystore); + info!(target: LOG_TARGET_UI, "Generated keypair: {:?}", &keystore); return Some(keystore); } if !mining.load(Ordering::Relaxed) { diff --git a/src/miner.rs b/src/miner.rs index b9442a0..2c7c6a3 100644 --- a/src/miner.rs +++ b/src/miner.rs @@ -12,7 +12,7 @@ use num_cpus; use crate::{Block, Bytes, Context, hash_is_good}; use crate::blockchain::blockchain::BlockQuality; -use crate::blockchain::{BLOCK_DIFFICULTY, CHAIN_VERSION}; +use crate::blockchain::{BLOCK_DIFFICULTY, CHAIN_VERSION, LOCKER_DIFFICULTY}; use crate::event::Event; pub struct Miner { @@ -52,11 +52,11 @@ impl Miner { let mining = self.mining.clone(); let cond_var = self.cond_var.clone(); thread::spawn(move || { - running.store(true, Ordering::Relaxed); - while running.load(Ordering::Relaxed) { + running.store(true, Ordering::SeqCst); + while running.load(Ordering::SeqCst) { // If some transaction is being mined now, we yield - if mining.load(Ordering::Relaxed) { - thread::sleep(Duration::from_millis(100)); + if mining.load(Ordering::SeqCst) { + thread::sleep(Duration::from_millis(1000)); continue; } @@ -72,9 +72,25 @@ impl Miner { } }); let mining = self.mining.clone(); + let blocks = self.blocks.clone(); + let cond_var = self.cond_var.clone(); self.context.lock().unwrap().bus.register(move |_uuid, e| { - if e == Event::ActionStopMining { - mining.store(false, Ordering::SeqCst); + match e { + Event::NewBlockReceived => {} + Event::BlockchainChanged => {} + Event::ActionStopMining => { + mining.store(false, Ordering::SeqCst); + } + Event::ActionMineLocker { index, hash } => { + if !mining.load(Ordering::SeqCst) { + let mut block = Block::new(None, Bytes::default(), hash); + block.index = index; + blocks.lock().unwrap().push(block); + cond_var.notify_all(); + info!("Added a locker block to mine"); + } + } + _ => {} } true }); @@ -89,12 +105,32 @@ impl Miner { block.signature = Bytes::default(); block.hash = Bytes::default(); block.version = CHAIN_VERSION; - block.difficulty = BLOCK_DIFFICULTY; - block.index = context.lock().unwrap().blockchain.height(); - block.prev_block_hash = match context.lock().unwrap().blockchain.last_block() { - None => { Bytes::default() } - Some(block) => { block.hash } - }; + // If this block needs to be a locker + if block.index > 0 && !block.prev_block_hash.is_empty() { + info!("Mining locker block"); + block.difficulty = LOCKER_DIFFICULTY; + block.pub_key = context.lock().unwrap().keystore.get_public(); + match context.lock().unwrap().blockchain.last_block() { + None => {} + Some(last_block) => { + info!("Last block found"); + // If we were doing something else and got new block before we could mine this block + if last_block.index > block.index || last_block.hash != block.prev_block_hash { + warn!("We missed block to lock"); + context.lock().unwrap().bus.post(Event::MinerStopped); + mining.store(false, Ordering::SeqCst); + return; + } + } + } + } else { + block.difficulty = BLOCK_DIFFICULTY; + block.index = context.lock().unwrap().blockchain.height() + 1; + block.prev_block_hash = match context.lock().unwrap().blockchain.last_block() { + None => { Bytes::default() } + Some(block) => { block.hash } + }; + } context.lock().unwrap().bus.post(Event::MinerStarted); let live_threads = Arc::new(AtomicU32::new(0u32)); diff --git a/src/p2p/network.rs b/src/p2p/network.rs index a23ecdd..a017158 100644 --- a/src/p2p/network.rs +++ b/src/p2p/network.rs @@ -3,7 +3,7 @@ extern crate serde_json; use std::{io, thread}; use std::io::{Read, Write}; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Mutex, MutexGuard}; use std::time::{Duration, Instant}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; @@ -17,6 +17,7 @@ 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::CHAIN_VERSION; +use chrono::Utc; const SERVER: Token = Token(0); const POLL_TIMEOUT: Option = Some(Duration::from_millis(3000)); @@ -107,6 +108,7 @@ impl Network { let context = context.lock().unwrap(); (context.blockchain.height(), context.blockchain.last_hash()) }; + mine_locker_block(context.clone()); peers.send_pings(poll.registry(), height, hash); peers.connect_new_peers(poll.registry(), &mut unique_token); } @@ -293,7 +295,7 @@ fn handle_message(context: Arc>, message: Message, peers: &mut Pe if peer.is_higher(my_height) { context.blockchain.update_max_height(height); context.bus.post(crate::event::Event::Syncing { have: my_height, height}); - State::message(Message::GetBlock { index: my_height }) + State::message(Message::GetBlock { index: my_height + 1 }) } else { State::message(Message::GetPeers) } @@ -306,8 +308,8 @@ fn handle_message(context: Arc>, message: Message, peers: &mut Pe let peer = peers.get_mut_peer(token).unwrap(); peer.set_height(height); peer.set_active(true); - if peer.is_higher(my_height) || my_hash.ne(&hash) { - State::message(Message::GetBlock { index: my_height }) + if peer.is_higher(my_height) || ( height == my_height && my_hash != hash) { + State::message(Message::GetBlock { index: my_height + 1 }) } else { State::message(Message::pong(my_height, my_hash)) } @@ -316,12 +318,17 @@ fn handle_message(context: Arc>, message: Message, peers: &mut Pe let peer = peers.get_mut_peer(token).unwrap(); peer.set_height(height); peer.set_active(true); - if peer.is_higher(my_height) || my_hash.ne(&hash) { + let is_higher = peer.is_higher(my_height); + + let mut context = context.lock().unwrap(); + let blocks_count = context.blockchain.height(); + context.bus.post(crate::event::Event::NetworkStatus { nodes: peers.get_peers_active_count(), blocks: blocks_count }); + + if is_higher { + State::message(Message::GetBlock { index: my_height + 1 }) + } else if my_hash != hash { State::message(Message::GetBlock { index: my_height }) } else { - let mut context = context.lock().unwrap(); - let blocks_count = context.blockchain.height(); - context.bus.post(crate::event::Event::NetworkStatus { nodes: peers.get_peers_active_count(), blocks: blocks_count }); State::idle() } } @@ -346,7 +353,10 @@ fn handle_message(context: Arc>, message: Message, peers: &mut Pe Ok(block) => block, Err(_) => return State::Error }; + let peer = peers.get_mut_peer(token).unwrap(); + peer.set_received_block(block.index); let context = context.clone(); + let peers_count = peers.get_peers_active_count(); thread::spawn(move || { let mut context = context.lock().unwrap(); let max_height = context.blockchain.max_height(); @@ -361,12 +371,17 @@ fn handle_message(context: Arc>, message: Message, peers: &mut Pe } else { context.bus.post(crate::event::Event::Syncing { have: my_height, height: max_height}); } + context.bus.post(crate::event::Event::NetworkStatus { nodes: peers_count, blocks: my_height }); } BlockQuality::Twin => { debug!("Ignoring duplicate block {}", block.index); } BlockQuality::Future => { debug!("Ignoring future block {}", block.index); } BlockQuality::Bad => { debug!("Ignoring bad block {} with hash {:?}", block.index, block.hash); } // TODO deal with forks - BlockQuality::Fork => { debug!("Ignoring forked block {} with hash {:?}", block.index, block.hash); } + BlockQuality::Fork => { + debug!("Ignoring forked block {} with hash {:?}", block.index, block.hash); + //let peer = peers.get_mut_peer(token).unwrap(); + //deal_with_fork(context, peer, block); + } } }); State::idle() @@ -374,6 +389,71 @@ 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() { + info!("No locker mining while syncing"); + return; + } + match context.blockchain.get_block_locker(&block, Utc::now().timestamp()) { + Some(key) => { + if key == context.keystore.get_public() { + info!("We have an honor to mine locker block!"); + context.bus.post(crate::event::Event::ActionMineLocker { index: block.index + 1, hash: block.hash }); + } else { + info!("Locker block must be mined by another node: {:?}", &key); + } + } + None => {} + } + } +} + +#[allow(dead_code)] +fn deal_with_fork(context: MutexGuard, peer: &mut Peer, block: Block) { + peer.add_fork_block(block); + let mut vector: Vec<&Block> = peer.get_fork().values().collect(); + vector.sort_by(|a, b| a.index.cmp(&b.index)); + if vector[0].index == 0 { + return; + } + if let Some(prev_block) = context.blockchain.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; + } + // Okay, prev_block is the common root for our chain and the fork + let mut check_ok = true; + vector.insert(0, &prev_block); + let mut prev_block = &vector[0]; + for block in &vector { + if block == prev_block { + continue; + } + if !check_block(block, prev_block) { + check_ok = false; + break; + } + prev_block = block; + } + match check_ok { + true => { + // TODO count fork chain "work" and decide which chain is "better" + } + false => { + warn!("Fork chain is wrong!"); + peer.set_state(State::Banned); + } + } + }; +} + +fn check_block(block: &Block, prev: &Block) -> bool { + prev.index == block.index - 1 && prev.hash == block.prev_block_hash +} + /// Connecting to configured (bootstrap) peers fn connect_peers(peers_addrs: Vec, poll: &mut Poll, peers: &mut Peers, unique_token: &mut Token) { for peer in peers_addrs.iter() { diff --git a/src/p2p/peer.rs b/src/p2p/peer.rs index 88a1b12..d3333e8 100644 --- a/src/p2p/peer.rs +++ b/src/p2p/peer.rs @@ -1,6 +1,8 @@ -use crate::p2p::State; use std::net::SocketAddr; +use std::collections::HashMap; use mio::net::TcpStream; +use crate::p2p::State; +use crate::Block; #[derive(Debug)] pub struct Peer { @@ -11,11 +13,13 @@ pub struct Peer { inbound: bool, public: bool, active: bool, + received_block: u64, + fork: HashMap } impl Peer { pub fn new(addr: SocketAddr, stream: TcpStream, state: State, inbound: bool) -> Self { - Peer { addr, stream, state, height: 0, inbound, public: false, active: false } + Peer { addr, stream, state, height: 0, inbound, public: false, active: false, received_block: 0, fork: HashMap::new() } } pub fn get_addr(&self) -> SocketAddr { @@ -46,6 +50,18 @@ impl Peer { self.height > height } + pub fn is_lower(&self, height: u64) -> bool { + self.height < height + } + + pub fn set_received_block(&mut self, index: u64) { + self.received_block = index; + } + + pub fn has_more_blocks(&self, height: u64) -> bool { + self.height > self.received_block && self.height > height && self.get_state().is_idle() + } + pub fn is_public(&self) -> bool { self.public } @@ -70,6 +86,14 @@ impl Peer { self.inbound } + pub fn add_fork_block(&mut self, block: Block) { + self.fork.insert(block.index, block); + } + + pub fn get_fork(&self) -> &HashMap { + &self.fork + } + /// If loopback address then we care about ip and port. /// If regular address then we only care about the ip and ignore the port. pub fn equals(&self, addr: &SocketAddr) -> bool { diff --git a/src/p2p/peers.rs b/src/p2p/peers.rs index 5ccb09b..8b238a0 100644 --- a/src/p2p/peers.rs +++ b/src/p2p/peers.rs @@ -157,17 +157,35 @@ impl Peers { } } + // If someone has more blocks we sync if !ping_sent { let mut rng = rand::thread_rng(); match self.peers .iter_mut() - .filter_map(|(token, peer)| if peer.get_state().is_idle() && peer.is_higher(height) { Some((token, peer)) } else { None }) + .filter_map(|(token, peer)| if peer.has_more_blocks(height) { Some((token, peer)) } else { None }) .choose(&mut rng) { None => {} Some((token, peer)) => { debug!("Found some peer higher than we are, sending block request"); registry.reregister(peer.get_stream(), token.clone(), Interest::WRITABLE).unwrap(); - peer.set_state(State::message(Message::GetBlock { index: height })); + peer.set_state(State::message(Message::GetBlock { index: height + 1 })); + ping_sent = true; + } + } + } + + // If someone has less blocks (we mined a new block) we send a ping with our height + if !ping_sent { + let mut rng = rand::thread_rng(); + match self.peers + .iter_mut() + .filter_map(|(token, peer)| if peer.is_lower(height) && peer.get_state().is_idle() { Some((token, peer)) } else { None }) + .choose(&mut rng) { + None => {} + Some((token, peer)) => { + debug!("Found some peer lower than we are, sending ping"); + registry.reregister(peer.get_stream(), token.clone(), Interest::WRITABLE).unwrap(); + peer.set_state(State::message(Message::Ping { height, hash })); } } }