From 92ebefce916c92ee170ed5e31b2dc1e95e3eb082 Mon Sep 17 00:00:00 2001 From: Revertron Date: Tue, 2 Mar 2021 18:11:17 +0100 Subject: [PATCH] Changed and refactored block and transaction structures. Moved signature from transaction scope to block. --- Cargo.toml | 2 +- src/blockchain/block.rs | 43 +++++---- src/blockchain/blockchain.rs | 161 ++++++++++++++++++---------------- src/blockchain/constants.rs | 2 + src/blockchain/mod.rs | 4 +- src/blockchain/transaction.rs | 49 ++++------- src/keys.rs | 20 +++++ src/main.rs | 34 ++----- src/miner.rs | 80 +++++++---------- src/p2p/network.rs | 13 ++- src/utils.rs | 13 --- 11 files changed, 206 insertions(+), 215 deletions(-) create mode 100644 src/blockchain/constants.rs diff --git a/Cargo.toml b/Cargo.toml index c17fe45..a22ea67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "alfis" -version = "0.1.6" +version = "0.2.0" authors = ["Revertron "] edition = "2018" build = "build.rs" diff --git a/src/blockchain/block.rs b/src/blockchain/block.rs index be33495..11ee012 100644 --- a/src/blockchain/block.rs +++ b/src/blockchain/block.rs @@ -15,7 +15,7 @@ pub struct Block { pub index: u64, pub timestamp: i64, pub version: u32, - pub difficulty: usize, + pub difficulty: u32, pub random: u32, pub nonce: u64, #[serde(skip_serializing_if = "Option::is_none")] @@ -24,25 +24,30 @@ pub struct Block { pub prev_block_hash: Bytes, #[serde(default, skip_serializing_if = "Bytes::is_zero")] pub hash: Bytes, + #[serde(default, skip_serializing_if = "Bytes::is_zero")] + pub pub_key: Bytes, + #[serde(default, skip_serializing_if = "Bytes::is_zero")] + pub signature: Bytes, } impl Block { - pub fn new(index: u64, timestamp: i64, version: u32, prev_block_hash: Bytes, transaction: Option) -> Self { + pub fn new(transaction: Option, pub_key: Bytes, prev_block_hash: Bytes) -> Self { Block { - index, - timestamp, - version, - // TODO make difficulty parameter - difficulty: 20, + index: 0, + timestamp: 0, + version: 0, + difficulty: 0, random: 0, nonce: 0, transaction, prev_block_hash, hash: Bytes::default(), + pub_key, + signature: Bytes::default() } } - pub fn from_all_params(index: u64, timestamp: i64, version: u32, difficulty: usize, random: u32, nonce: u64, prev_block_hash: Bytes, hash: Bytes, transaction: Option) -> Self { + pub fn from_all_params(index: u64, timestamp: i64, version: u32, difficulty: u32, random: u32, nonce: u64, prev_block_hash: Bytes, hash: Bytes, pub_key: Bytes, signature: Bytes, transaction: Option) -> Self { Block { index, timestamp, @@ -53,18 +58,24 @@ impl Block { transaction, prev_block_hash, hash, + pub_key, + signature } } - 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()) - } - pub fn is_genesis(&self) -> bool { self.index == 0 && self.transaction.is_none() && self.prev_block_hash == Bytes::default() } + + 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/blockchain.rs index 8d83a82..6d3b0de 100644 --- a/src/blockchain/blockchain.rs +++ b/src/blockchain/blockchain.rs @@ -1,11 +1,15 @@ use sqlite::{Connection, State, Statement}; -use crate::{Block, Bytes, Keystore, Transaction}; +use crate::{Block, Bytes, Keystore, Transaction, hash_is_good}; 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; const DB_NAME: &str = "blockchain.db"; @@ -58,30 +62,19 @@ impl Blockchain { 'nonce' INTEGER, 'transaction' TEXT, 'prev_block_hash' BINARY, - '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, signature BINARY); + 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"); } } } - pub fn add_block(&mut self, block: Block) -> Result<(), &str> { - match &self.last_block { - None => {} - Some(last_block) => { - if last_block.index >= block.index && last_block.hash == block.hash { - info!("Ignoring block {}, we already have it", block.index); - return Err("Already have that block"); - } - } - } - if !self.check_block(&block, &self.last_block) { - warn!("Bad block found, ignoring:\n{:?}", &block); - return Err("Bad block found, ignoring"); - } + pub fn add_block(&mut self, block: Block) { info!("Adding block:\n{:?}", &block); self.blocks.push(block.clone()); self.last_block = Some(block.clone()); @@ -90,9 +83,9 @@ impl Blockchain { { // Adding block to DB let mut statement = self.db.prepare("INSERT INTO blocks (\ - id, timestamp, version, difficulty, random,\ - nonce, 'transaction', prev_block_hash, hash)\ - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);").unwrap(); + 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"); @@ -107,28 +100,23 @@ impl Blockchain { } 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"); } - match &transaction { - None => { - Err("Error adding transaction!") - } - Some(transaction) => { - self.add_transaction(transaction); - Ok(()) - } + if let Some(transaction) = transaction { + self.add_transaction(&transaction); } } fn add_transaction(&mut self, t: &Transaction) { - let mut statement = self.db.prepare("INSERT INTO transactions (identity, confirmation, method, data, pub_key, signature) VALUES (?, ?, ?, ?, ?, ?)").unwrap(); + 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.bind(6, t.signature.as_bytes()).expect("Error in bind"); statement.next().expect("Error adding transaction to DB"); } @@ -161,7 +149,7 @@ impl Blockchain { if domain.is_empty() { return false; } - let identity_hash = Transaction::hash_identity(domain); + 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(); statement.bind(1, identity_hash.as_bytes()).expect("Error in bind"); while let State::Row = statement.next().unwrap() { @@ -189,7 +177,7 @@ impl Blockchain { } // Checking for existing zone in DB - let identity_hash = Transaction::hash_identity(zone); + 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(); statement.bind(1, identity_hash.as_bytes()).expect("Error in bind"); while let State::Row = statement.next().unwrap() { @@ -204,7 +192,7 @@ impl Blockchain { if domain.is_empty() { return None; } - let identity_hash = Transaction::hash_identity(domain); + 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(); statement.bind(1, identity_hash.as_bytes()).expect("Error in bind"); @@ -214,10 +202,9 @@ impl Blockchain { let method = statement.read::(3).unwrap(); let data = statement.read::(4).unwrap(); let pub_key = Bytes::from_bytes(statement.read::>(5).unwrap().as_slice()); - let signature = Bytes::from_bytes(statement.read::>(6).unwrap().as_slice()); - let transaction = Transaction { identity, confirmation, method, data, pub_key, signature }; + let transaction = Transaction { identity, confirmation, method, data, pub_key }; debug!("Found transaction for domain {}: {:?}", domain, &transaction); - if transaction.check_for(domain) { + if transaction.check_identity(domain) { return Some(transaction); } } @@ -254,75 +241,97 @@ impl Blockchain { } } - /*pub fn check(&self) -> bool { - let mut prev_block = None; - for block in self.blocks.iter() { - if !self.check_block(block, &prev_block) { - println!("Block {:?} is bad", block); - return false; - } - prev_block = Some(block); + 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; } - true - }*/ - - fn check_block(&self, block: &Block, prev_block: &Option) -> bool { - if !check_block_hash(block) { - warn!("{:?} has wrong hash! Ignoring!", &block); - return false; + if !hash_is_good(block.hash.as_bytes(), BLOCK_DIFFICULTY as usize) { + warn!("Ignoring block with low difficulty:\n{:?}", &block); + return Bad; } - // TODO make transaction not Optional - let transaction = block.transaction.as_ref().unwrap(); - if !check_transaction_signature(&transaction) { - warn!("{:?} has wrong signature! Ignoring block!", &transaction); - return false; + if !hash_is_good(block.hash.as_bytes(), block.difficulty as usize) { + warn!("Ignoring block with low difficulty:\n{:?}", &block); + return Bad; } - match prev_block { + match &self.last_block { None => { - if block.index != 0 { - return false; + if !block.is_genesis() { + return Future; } - - if self.origin.is_zero() && block.index > 0 { - panic!("Error adding block {} without origin! Please, fill in origin in config!", block.index); + if !self.origin.is_zero() && block.hash != self.origin { + warn!("Mining gave us a bad block:\n{:?}", &block); + return Bad; } - - self.origin.is_zero() || block.hash.eq(&self.origin) } - Some(prev) => { - if block.index != prev.index + 1 { - info!("Discarding block with index {} as not needed now", block.index); - return false; + Some(last_block) => { + if block.timestamp < last_block.timestamp && block.index > last_block.index { + warn!("Ignoring block with timestamp/index collision:\n{:?}", &block); + return Bad; + } + if last_block.index + 1 < block.index { + warn!("Got block from the future"); + 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 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; } - block.prev_block_hash.eq(&prev.hash) } } + if !check_block_hash(block) { + warn!("Block {:?} has wrong hash! Ignoring!", &block); + return Bad; + } + if !check_block_signature(&block) { + warn!("Block {:?} has wrong signature! Ignoring!", &block); + return Bad; + } + + Good } fn get_block_from_statement(statement: &mut Statement) -> Option { let index = statement.read::(0).unwrap() as u64; let timestamp = statement.read::(1).unwrap(); let version = statement.read::(2).unwrap() as u32; - let difficulty = statement.read::(3).unwrap() as usize; + let difficulty = statement.read::(3).unwrap() as u32; let random = statement.read::(4).unwrap() as u32; let nonce = statement.read::(5).unwrap() as u64; let transaction = Transaction::from_json(&statement.read::(6).unwrap()); let prev_block_hash = Bytes::from_bytes(statement.read::>(7).unwrap().as_slice()); let hash = Bytes::from_bytes(statement.read::>(8).unwrap().as_slice()); - Some(Block::from_all_params(index, timestamp, version, difficulty, random, nonce, prev_block_hash, hash, transaction)) + let pub_key = Bytes::from_bytes(statement.read::>(9).unwrap().as_slice()); + 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(); - Block::hash(data.as_bytes()) == block.hash + crate::blockchain::block::hash(data.as_bytes()) == block.hash } -pub fn check_transaction_signature(transaction: &Transaction) -> bool { - let mut copy = transaction.clone(); +pub fn check_block_signature(block: &Block) -> bool { + let mut copy = block.clone(); copy.signature = Bytes::zero64(); - let data = copy.get_bytes(); - Keystore::check(data.as_slice(), copy.pub_key.as_bytes(), transaction.signature.as_bytes()) + let data = serde_json::to_string(©).unwrap(); + Keystore::check(data.as_bytes(), copy.pub_key.as_bytes(), block.signature.as_bytes()) } diff --git a/src/blockchain/constants.rs b/src/blockchain/constants.rs new file mode 100644 index 0000000..3a6790f --- /dev/null +++ b/src/blockchain/constants.rs @@ -0,0 +1,2 @@ +pub const BLOCK_DIFFICULTY: u32 = 24; +pub const CHAIN_VERSION: u32 = 1; diff --git a/src/blockchain/mod.rs b/src/blockchain/mod.rs index f330957..3ab5909 100644 --- a/src/blockchain/mod.rs +++ b/src/blockchain/mod.rs @@ -2,7 +2,9 @@ pub mod transaction; pub mod block; pub mod blockchain; pub mod filter; +pub mod constants; pub use transaction::Transaction; pub use block::Block; -pub use blockchain::Blockchain; \ No newline at end of file +pub use blockchain::Blockchain; +pub use constants::*; \ No newline at end of file diff --git a/src/blockchain/transaction.rs b/src/blockchain/transaction.rs index e588e96..ae65953 100644 --- a/src/blockchain/transaction.rs +++ b/src/blockchain/transaction.rs @@ -16,18 +16,17 @@ pub struct Transaction { pub method: String, pub data: String, pub pub_key: Bytes, - pub signature: Bytes, } impl Transaction { pub fn from_str(identity: String, method: String, data: String, pub_key: Bytes) -> Self { - let hash = Self::hash_identity(&identity); - let confirmation = Self::hash_with_key(&identity, &pub_key); + let hash = hash_identity(&identity, None); + let confirmation = hash_identity(&identity, Some(&pub_key)); return Self::new(hash, confirmation, method, data, pub_key); } pub fn new(identity: Bytes, confirmation: Bytes, method: String, data: String, pub_key: Bytes) -> Self { - Transaction { identity, confirmation, method, data, pub_key, signature: Bytes::zero64() } + Transaction { identity, confirmation, method, data, pub_key } } pub fn from_json(json: &str) -> Option { @@ -37,10 +36,6 @@ impl Transaction { } } - pub fn set_signature(&mut self, hash: Bytes) { - self.signature = hash; - } - pub fn get_bytes(&self) -> Vec { // Let it panic if something is not okay serde_json::to_vec(&self).unwrap() @@ -51,26 +46,9 @@ impl Transaction { serde_json::to_string(&self).unwrap() } - pub fn hash_identity(identity: &str) -> Bytes { - let mut buf: [u8; 32] = [0; 32]; - let mut digest = Sha256::new(); - digest.input_str(identity); - digest.result(&mut buf); - Bytes::from_bytes(&buf) - } - - pub fn hash_with_key(identity: &str, key: &Bytes) -> Bytes { - let mut buf: [u8; 32] = [0; 32]; - let mut digest = Sha256::new(); - digest.input_str(identity); - digest.input(key.as_bytes()); - digest.result(&mut buf); - Bytes::from_bytes(&buf) - } - - pub fn check_for(&self, domain: &str) -> bool { - let hash = Self::hash_identity(&domain); - let confirmation = Self::hash_with_key(&domain, &self.pub_key); + pub fn check_identity(&self, domain: &str) -> bool { + let hash = hash_identity(&domain, None); + let confirmation = hash_identity(&domain, Some(&self.pub_key)); self.identity.eq(&hash) && self.confirmation.eq(&confirmation) } } @@ -83,20 +61,29 @@ impl fmt::Debug for Transaction { .field("method", &self.method) .field("data", &self.data) .field("pub", &&self.pub_key) - .field("sign", &&self.signature) .finish() } } impl Serialize for Transaction { fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> where S: Serializer { - let mut structure = serializer.serialize_struct("Transaction", 6).unwrap(); + let mut structure = serializer.serialize_struct("Transaction", 4).unwrap(); structure.serialize_field("identity", &self.identity)?; structure.serialize_field("confirmation", &self.confirmation)?; structure.serialize_field("method", &self.method)?; structure.serialize_field("data", &self.data)?; structure.serialize_field("pub_key", &self.pub_key)?; - structure.serialize_field("signature", &self.signature)?; 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/keys.rs b/src/keys.rs index 2707f67..6f76d75 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -16,6 +16,8 @@ use serde::de::{Error as DeError, Visitor}; #[allow(unused_imports)] use log::{trace, debug, info, warn, error}; use crate::hash_is_good; +use std::cmp::Ordering; +use num_bigint::BigUint; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Keystore { @@ -160,6 +162,24 @@ impl PartialEq for Bytes { } } +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)) diff --git a/src/main.rs b/src/main.rs index 65645d9..8450882 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,7 +22,7 @@ use simple_logger::{SimpleLogger}; #[allow(unused_imports)] use log::{trace, debug, info, warn, error, LevelFilter}; -use alfis::{Blockchain, Bytes, Context, Keystore, Transaction, check_domain}; +use alfis::{Blockchain, Bytes, Context, Keystore, Transaction, check_domain, Block}; use alfis::event::Event; use alfis::miner::Miner; use alfis::p2p::Network; @@ -35,10 +35,6 @@ use alfis::blockchain::filter::BlockchainFilter; extern crate serde; extern crate serde_json; -#[allow(dead_code)] -const ONE_YEAR: u16 = 365; -const GENESIS_ZONE: &str = "ygg"; -const GENESIS_ZONE_DIFFICULTY: u16 = 20; const KEYSTORE_DIFFICULTY: usize = 24; const SETTINGS_FILENAME: &str = "alfis.cfg"; @@ -150,12 +146,11 @@ fn start_dns_server(context: &Arc>, settings: &Settings) { 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(); - // TODO compare first block's hash to origin let last_block = context.get_blockchain().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(), GENESIS_ZONE, &context.get_keystore(), GENESIS_ZONE_DIFFICULTY); + create_genesis(miner.clone(), &context.get_keystore()); } } @@ -390,33 +385,20 @@ fn run_interface(context: Arc>, miner: Arc>) { interface.exit(); } -fn create_genesis>(miner: Arc>, name: S, keystore: &Keystore, difficulty: u16) { - let mut transaction = Transaction::from_str(name.into(), "zone".to_owned(), difficulty.to_string(), keystore.get_public().clone()); - // Signing it with private key from Signature - let sign_hash = keystore.sign(&transaction.get_bytes()); - transaction.set_signature(Bytes::from_bytes(&sign_hash)); +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_transaction(transaction); + miner_guard.add_block(block); } fn create_domain>(miner: Arc>, name: S, data: S, keystore: &Keystore) { let name = name.into(); info!("Generating domain or zone {}", name); - //let rec_vector: Vec = records.into().trim().split("\n").map(|s| s.trim()).map(String::from).collect(); //let tags_vector: Vec = tags.into().trim().split(",").map(|s| s.trim()).map(String::from).collect(); - let transaction = create_transaction(keystore, name, "domain".into(), data.into()); + let transaction = Transaction::from_str(name.into(), "domain".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_transaction(transaction); -} - -fn create_transaction>(keystore: &Keystore, name: S, method: S, data: S) -> Transaction { - // Creating transaction - // TODO Do not use owner for now, make a field in UI and use it if filled - let mut transaction = Transaction::from_str(name.into(), method.into(), data.into(), keystore.get_public().clone()); - // Signing it with private key from Signature - let sign_hash = keystore.sign(&transaction.get_bytes()); - transaction.set_signature(Bytes::from_bytes(&sign_hash)); - transaction + miner_guard.add_block(block); } fn create_key(context: Arc>) { diff --git a/src/miner.rs b/src/miner.rs index fd20123..b9442a0 100644 --- a/src/miner.rs +++ b/src/miner.rs @@ -6,16 +6,18 @@ use std::time::Duration; use chrono::Utc; use crypto::digest::Digest; use crypto::sha2::Sha256; -use num_cpus; #[allow(unused_imports)] -use log::{trace, debug, info, warn, error}; +use log::{debug, error, info, trace, warn}; +use num_cpus; -use crate::{Block, Bytes, Context, hash_is_good, Transaction}; +use crate::{Block, Bytes, Context, hash_is_good}; +use crate::blockchain::blockchain::BlockQuality; +use crate::blockchain::{BLOCK_DIFFICULTY, CHAIN_VERSION}; use crate::event::Event; pub struct Miner { context: Arc>, - transactions: Arc>>, + blocks: Arc>>, running: Arc, mining: Arc, cond_var: Arc @@ -25,15 +27,15 @@ impl Miner { pub fn new(context: Arc>) -> Self { Miner { context: context.clone(), - transactions: Arc::new(Mutex::new(Vec::new())), + blocks: Arc::new(Mutex::new(Vec::new())), running: Arc::new(AtomicBool::new(false)), mining: Arc::new(AtomicBool::new(false)), cond_var: Arc::new(Condvar::new()) } } - pub fn add_transaction(&mut self, transaction: Transaction) { - self.transactions.lock().unwrap().push(transaction); + pub fn add_block(&mut self, block: Block) { + self.blocks.lock().unwrap().push(block); self.cond_var.notify_one(); } @@ -45,7 +47,7 @@ impl Miner { pub fn start_mining_thread(&mut self) { let context = self.context.clone(); - let transactions = self.transactions.clone(); + let blocks = self.blocks.clone(); let running = self.running.clone(); let mining = self.mining.clone(); let cond_var = self.cond_var.clone(); @@ -58,12 +60,12 @@ impl Miner { continue; } - let mut lock = transactions.lock().unwrap(); + let mut lock = blocks.lock().unwrap(); if lock.len() > 0 { - info!("Got new transaction to mine"); - let transaction = lock.remove(0); + info!("Got new block to mine"); + let block = lock.remove(0); mining.store(true, Ordering::SeqCst); - Miner::mine_internal(context.clone(), transactions.clone(), transaction, mining.clone(), cond_var.clone()); + Miner::mine_internal(context.clone(), block, mining.clone()); } else { let _ = cond_var.wait(lock).expect("Error in wait lock!"); } @@ -82,46 +84,27 @@ impl Miner { self.running.load(Ordering::Relaxed) } - fn mine_internal(context: Arc>, transactions: Arc>>, mut transaction: Transaction, mining: Arc, cond_var: Arc) { - let version= { - let mut c = context.lock().unwrap(); - c.bus.post(Event::MinerStarted); - c.settings.version - }; - let block = { - if transaction.signature.is_zero() { - // Signing it with private key from Keystore - let c = context.lock().unwrap(); - let sign_hash = c.keystore.sign(&transaction.get_bytes()); - transaction.set_signature(Bytes::from_bytes(&sign_hash)); - } - - // Get last block for mining - let last_block = { context.lock().unwrap().blockchain.last_block() }; - match last_block { - None => { - warn!("Mining genesis block"); - // Creating a block with that signed transaction - Block::new(0, Utc::now().timestamp(), version, Bytes::zero32(), Some(transaction.clone())) - }, - Some(block) => { - // Creating a block with that signed transaction - Block::new(block.index + 1, Utc::now().timestamp(), version, block.hash.clone(), Some(transaction.clone())) - }, - } + fn mine_internal(context: Arc>, mut block: Block, mining: Arc) { + // Clear signature and hash just in case + 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 } }; + context.lock().unwrap().bus.post(Event::MinerStarted); let live_threads = Arc::new(AtomicU32::new(0u32)); let cpus = num_cpus::get(); debug!("Starting {} threads for mining", cpus); for _ in 0..cpus { - let transactions = transactions.clone(); let context = context.clone(); - let transaction = transaction.clone(); let block = block.clone(); let mining = mining.clone(); let live_threads = live_threads.clone(); - let cond_var = cond_var.clone(); thread::spawn(move || { live_threads.fetch_add(1, Ordering::SeqCst); match find_hash(&mut Sha256::new(), block, mining.clone()) { @@ -134,14 +117,17 @@ impl Miner { context.bus.post(Event::MinerStopped); } }, - Some(block) => { + Some(mut block) => { let index = block.index; let mut context = context.lock().unwrap(); - if context.blockchain.add_block(block).is_err() { + block.signature = Bytes::from_bytes(&context.keystore.sign(&block.as_bytes())); + if context.blockchain.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.bus.post(Event::MinerStopped); mining.store(false, Ordering::SeqCst); @@ -154,6 +140,7 @@ impl Miner { fn find_hash(digest: &mut dyn Digest, mut block: Block, running: Arc) -> Option { let mut buf: [u8; 32] = [0; 32]; + let difficulty = block.difficulty as usize; loop { block.random = rand::random(); debug!("Mining block {}", serde_json::to_string(&block).unwrap()); @@ -165,13 +152,12 @@ fn find_hash(digest: &mut dyn Digest, mut block: Block, running: Arc block.nonce = nonce; digest.reset(); - digest.input(serde_json::to_string(&block).unwrap().as_bytes()); + digest.input(&block.as_bytes()); digest.result(&mut buf); - if hash_is_good(&buf, block.difficulty) { + if hash_is_good(&buf, difficulty) { block.hash = Bytes::from_bytes(&buf); return Some(block); } } } - None } \ No newline at end of file diff --git a/src/p2p/network.rs b/src/p2p/network.rs index db867a4..4873094 100644 --- a/src/p2p/network.rs +++ b/src/p2p/network.rs @@ -15,6 +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; const SERVER: Token = Token(0); const POLL_TIMEOUT: Option = Some(Duration::from_millis(3000)); @@ -340,13 +341,13 @@ fn handle_message(context: Arc>, message: Message, peers: &mut Pe Ok(block) => block, Err(_) => return State::Error }; - // TODO check here if the block is good before trying to add let context = context.clone(); thread::spawn(move || { let mut context = context.lock().unwrap(); let max_height = context.blockchain.max_height(); - match context.blockchain.add_block(block) { - Ok(_) => { + match context.blockchain.check_new_block(&block) { + BlockQuality::Good => { + context.blockchain.add_block(block); let my_height = context.blockchain.height(); context.bus.post(crate::event::Event::BlockchainChanged); // If it was the last block to sync @@ -356,7 +357,11 @@ fn handle_message(context: Arc>, message: Message, peers: &mut Pe context.bus.post(crate::event::Event::Syncing { have: my_height, height: max_height}); } } - Err(_) => { warn!("Discarded received block"); } + 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); } } }); State::idle() diff --git a/src/utils.rs b/src/utils.rs index 2956f12..77cf2bc 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -74,19 +74,6 @@ pub fn hash_is_good(hash: &[u8], difficulty: usize) -> bool { return hash_int < target; } -/// Generates random string of given length -pub fn random_string(length: usize) -> String { - let chars: Vec = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!?".chars().collect(); - let mut rng = rand::thread_rng(); - let mut result = String::with_capacity(length); - for _ in 0..length { - let position: usize = rng.gen::() % chars.len(); - let c: char = *chars.get(position).unwrap(); - result.push(c); - } - result -} - #[cfg(test)] mod test { use crate::check_domain;