diff --git a/Cargo.toml b/Cargo.toml index a84d8d1..368f7d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ edition = "2018" [dependencies] rust-crypto = "^0.2" -num_cpus = "1.10.1" +num_cpus = "1.13.0" byteorder = "1.3.2" web-view = { version = "0.5.4", features = [] } serde = { version = "1.0.102", features = ["derive"] } diff --git a/src/block.rs b/src/block.rs index d3dbbc9..0acf3df 100644 --- a/src/block.rs +++ b/src/block.rs @@ -24,8 +24,9 @@ pub struct Block { pub nonce: u64, #[serde(skip_serializing_if = "Option::is_none")] pub transaction: Option, + #[serde(default, skip_serializing_if = "Key::is_zero")] pub prev_block_hash: Key, - #[serde(default, skip_serializing_if = "Key::is_empty")] + #[serde(default, skip_serializing_if = "Key::is_zero")] pub hash: Key, } @@ -36,7 +37,8 @@ impl Block { timestamp, chain_id, version, - difficulty: 18, + // TODO make difficulty parameter + difficulty: 20, random: 0, nonce: 0, transaction, diff --git a/src/blockchain.rs b/src/blockchain.rs index b270644..9d9dd5e 100644 --- a/src/blockchain.rs +++ b/src/blockchain.rs @@ -19,16 +19,6 @@ impl Blockchain { block } - pub fn genesis(chain_id: u32, version: u32) -> Block { - Block::new(0, Utc::now().timestamp(), chain_id, version, Key::zero32(), None) - } - - pub fn make_genesis(&mut self) { - let mut genesis = Self::genesis(self.chain_id, self.version); - genesis.mine(); - self.add_block(genesis); - } - pub fn add_block(&mut self, block: Block) { if self.check_block(&block, None) { println!("Adding block:\n{:?}", &block); diff --git a/src/context.rs b/src/context.rs index 601ca00..613caab 100644 --- a/src/context.rs +++ b/src/context.rs @@ -34,6 +34,14 @@ impl Context { pub fn set_keystore(&mut self, keystore: Keystore) { self.keystore = keystore; } + + pub fn get_blockchain(&self) -> &Blockchain { + &self.blockchain + } + + pub fn add_salt(&mut self, name: String, salt: String) { + &self.settings.salts.insert(name, salt); + } } pub struct Settings { @@ -44,7 +52,7 @@ pub struct Settings { impl Settings { /// TODO parse settings - pub fn new>(chain_id: u32, version: u32, settings: S) -> Settings { - Settings { chain_id, version, salts: HashMap::new() } + pub fn new>(settings: S) -> Settings { + Settings { chain_id: 42, version: 0, salts: HashMap::new() } } } \ No newline at end of file diff --git a/src/keys.rs b/src/keys.rs index b32a4e3..3e4fef3 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -83,6 +83,18 @@ impl Key { 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_bytes(&self) -> &[u8] { &self.data diff --git a/src/main.rs b/src/main.rs index 44c9c5d..6f5501a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,38 +1,56 @@ #![windows_subsystem = "windows"] extern crate web_view; use alfis::{Blockchain, Block, Action, Transaction, Keystore, Key, Settings, Context}; +use alfis::miner::Miner; +use alfis::utils::random_string; use web_view::*; use std::thread; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Mutex}; extern crate serde; use serde::{Serialize, Deserialize}; -use alfis::miner::Miner; use std::collections::HashMap; extern crate serde_json; +const ONE_YEAR: u16 = 365; +const GENESIS_ZONE: &str = "ygg"; +const GENESIS_ZONE_DIFFICULTY: u16 = 20; +const SALT_LENGTH: usize = 20; + fn main() { println!("ALFIS 0.1.0"); - let settings = Settings::new(42, 0,""); + let settings = Settings::new(""); let keystore: Keystore = Keystore::from_file("default.key", "").unwrap(); let blockchain: Blockchain = Blockchain::new(settings.chain_id, settings.version); let context: Arc> = Arc::new(Mutex::new(Context::new(settings, keystore, blockchain))); - let miner: Arc> = Arc::new(Mutex::new(Miner::new(context.clone()))); + let mut miner_obj = Miner::new(context.clone()); + miner_obj.start_mining_thread(); + let miner: Arc> = Arc::new(Mutex::new(miner_obj)); - // TODO check settings and if there is no mention of bootstrap nodes, generate genesis block - /*let chain_for_genesis = blockchain.clone(); - thread::spawn(move || { - let mut block = Blockchain::genesis(42, 0); - // TODO remake genesis to use Miner - block.mine(); - chain_for_genesis.lock().unwrap().add_block(block); - println!("Blockchain with genesis block has been created"); - });*/ + create_genesis_if_needed(&context, &miner); run_interface(context.clone(), miner.clone()); } +fn create_genesis_if_needed(context: &Arc>, miner: &Arc>) { + // TODO check settings and if there is no mention of bootstrap nodes, generate genesis block + let keystore = { + // This code block makes it possible to contain quick lock here, and let the miner below work + let context_guard = context.lock().unwrap(); + if context_guard.get_blockchain().blocks.is_empty() { + // If blockchain is empty, we are going to mine a Genesis block + Some(context_guard.get_keystore()) + } else { + None + } + }; + + if keystore.is_some() { + create_genesis(miner.clone(), GENESIS_ZONE, &keystore.unwrap(), GENESIS_ZONE_DIFFICULTY); + } +} + fn run_interface(context: Arc>, miner: Arc>) { let file_content = include_str!("index.html"); let styles = inline_style(include_str!("bulma.css")); @@ -65,11 +83,17 @@ fn run_interface(context: Arc>, miner: Arc>) { c.set_keystore(Keystore::new()); } CreateDomain { name, records, tags } => { - let mut c = context.lock().unwrap(); - create_domain(miner.clone(), name, records, tags, c.get_keystore()); + let salt = random_string(SALT_LENGTH); + let keystore = { + let mut guard = context.lock().unwrap(); + guard.add_salt(name.clone(), salt.clone()); + guard.get_keystore() + }; + create_domain(miner.clone(), name, salt, &keystore); } ChangeDomain { name, records, tags } => { - + let keystore = { context.lock().unwrap().get_keystore() }; + // TODO } RenewDomain { name, days } => {} TransferDomain { name, owner } => {} @@ -81,18 +105,38 @@ fn run_interface(context: Arc>, miner: Arc>) { .unwrap(); } -fn create_domain(miner: Arc>, name: String, records: String, tags: String, keystore: Keystore) { - let rec_vector: Vec = records.trim().split("\n").map(|s| s.trim()).map(String::from).collect(); - let tags_vector: Vec = tags.trim().split(",").map(|s| s.trim()).map(String::from).collect(); - let mut transaction = { transaction_new_domain(keystore, name, rec_vector, tags_vector, 365) }; +fn create_genesis>(miner: Arc>, name: S, keystore: &Keystore, difficulty: u16) { + // Creating transaction + // TODO Do we need to add here an owners key? + let action = Action::genesis(name.into(), Key::zero32(), difficulty); + let mut transaction = Transaction::new(action, keystore.get_public().clone()); + // Signing it with private key from Signature + let sign_hash = keystore.sign(&transaction.get_bytes()); + transaction.set_signature(Key::from_bytes(&sign_hash)); let mut miner_guard = miner.lock().unwrap(); miner_guard.add_transaction(transaction); - miner_guard.mine(); } -fn transaction_claim_name>(keystore: Keystore, name: S, salt: S) -> Transaction { +fn create_domain>(miner: Arc>, name: S, salt: S, keystore: &Keystore) { + //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 mut transaction = { transaction_new_domain(keystore, name.into(), salt.into()) }; + let mut miner_guard = miner.lock().unwrap(); + miner_guard.add_transaction(transaction); +} + +fn create_zone>(miner: Arc>, name: S, salt: S, keystore: &Keystore) { + //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 mut transaction = { transaction_new_domain(keystore, name.into(), salt.into()) }; + let mut miner_guard = miner.lock().unwrap(); + miner_guard.add_transaction(transaction); +} + +fn transaction_claim_name>(keystore: &Keystore, name: S, salt: S) -> Transaction { // Creating transaction - let action = Action::claim_name(name.into(), salt.into(), &keystore); + // TODO Do not use owner for now, make a field in UI and use it if filled + let action = Action::new_domain(name.into(), salt.into(), Key::zero32()); let mut transaction = Transaction::new(action, keystore.get_public().clone()); // Signing it with private key from Signature let sign_hash = keystore.sign(&transaction.get_bytes()); @@ -100,9 +144,13 @@ fn transaction_claim_name>(keystore: Keystore, name: S, salt: S) transaction } -fn transaction_new_domain>(keystore: Keystore, name: S, records: Vec, tags: Vec, days: u16) -> Transaction { +fn transaction_new_domain>(keystore: &Keystore, name: S, salt: S) -> Transaction { + let name_string = name.into(); + let salt_string = salt.into(); + println!("Generating domain {} with salt: {}", name_string, salt_string); // Creating transaction - let action = Action::new_domain(name.into(), &keystore, records, tags, days); + // TODO Do not use owner for now, make a field in UI and use it if filled + let action = Action::new_domain(name_string, salt_string, Key::zero32()); let mut transaction = Transaction::new(action, keystore.get_public().clone()); // Signing it with private key from Signature let sign_hash = keystore.sign(&transaction.get_bytes()); @@ -110,6 +158,26 @@ fn transaction_new_domain>(keystore: Keystore, name: S, records: transaction } +/*fn transaction_claim_zone>(keystore: &Keystore, hash: S, difficulty: u16) -> Transaction { + // Creating transaction + let action = Action::new_zone(hash.into(), salt.into(), &keystore, difficulty); + let mut transaction = Transaction::new(action, keystore.get_public().clone()); + // Signing it with private key from Signature + let sign_hash = keystore.sign(&transaction.get_bytes()); + transaction.set_signature(Key::from_bytes(&sign_hash)); + transaction +}*/ + +/*fn transaction_new_zone>(keystore: &Keystore, name: S, salt: S, records: Vec, tags: Vec, days: u16) -> Transaction { + // Creating transaction + let action = Action::fill_domain(name.into(), salt.into(), &keystore, records, tags, days); + let mut transaction = Transaction::new(action, keystore.get_public().clone()); + // Signing it with private key from Signature + let sign_hash = keystore.sign(&transaction.get_bytes()); + transaction.set_signature(Key::from_bytes(&sign_hash)); + transaction +}*/ + #[derive(Deserialize)] #[serde(tag = "cmd", rename_all = "camelCase")] pub enum Cmd { diff --git a/src/miner.rs b/src/miner.rs index 8e1ec0e..eff0876 100644 --- a/src/miner.rs +++ b/src/miner.rs @@ -1,12 +1,14 @@ use crate::{Transaction, Block, Keystore, Key, Context}; -use std::sync::{Mutex, Arc}; +use std::sync::{Mutex, Arc, Condvar}; use crypto::digest::Digest; -use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::atomic::{AtomicBool, Ordering, AtomicU32}; use chrono::Utc; use num_bigint::BigUint; use num_traits::One; use crypto::sha2::Sha256; use std::thread; +use std::time::Duration; +use num_cpus; pub struct Miner { context: Arc>, @@ -16,6 +18,8 @@ pub struct Miner { transactions: Arc>>, last_block: Option, running: Arc, + mining: Arc, + cond_var: Arc } impl Miner { @@ -29,83 +33,145 @@ impl Miner { transactions: Arc::new(Mutex::new(Vec::new())), last_block: c.blockchain.blocks.last().cloned(), 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); + self.cond_var.notify_one(); } pub fn stop(&mut self) { + self.mining.store(false, Ordering::Relaxed); self.running.store(false, Ordering::Relaxed); + self.cond_var.notify_all(); } - pub fn mine(&mut self) { - let transaction = { self.transactions.lock().unwrap().first().cloned() }; - match transaction { - Some(transaction) => { - self.mine_internal(transaction); - }, - None => { - println!("Nothing to mine"); - }, - } + pub fn start_mining_thread(&mut self) { + let context = self.context.clone(); + let transactions = self.transactions.clone(); + let running = self.running.clone(); + 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) { + // If some transaction is being mined now, we yield + if mining.load(Ordering::Relaxed) { + thread::sleep(Duration::from_millis(100)); + continue; + } + + let mut lock = transactions.lock().unwrap(); + if lock.len() > 0 { + println!("Starting to mine some transaction"); + let transaction = lock.remove(0); + mining.store(true, Ordering::Relaxed); + Miner::mine_internal(context.clone(), transactions.clone(), transaction, mining.clone(), cond_var.clone()); + } else { + println!("Waiting for transactions"); + cond_var.wait(lock); + println!("Got notified on new transaction"); + } + } + }); } pub fn is_mining(&self) -> bool { self.running.load(Ordering::Relaxed) } - fn mine_internal(&mut self, mut transaction: Transaction) { + fn mine_internal(context: Arc>, transactions: Arc>>, mut transaction: Transaction, mining: Arc, cond_var: Arc) { let mut last_block_time = 0i64; + let mut chain_id = 0u32; + let mut version = 0u32; + { + let c = context.lock().unwrap(); + chain_id = c.settings.chain_id; + version = c.settings.version; + } let block = { - // Signing it with private key from Keystore - let sign_hash = self.keystore.sign(&transaction.get_bytes()); - transaction.set_signature(Key::from_bytes(&sign_hash)); + 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(Key::from_bytes(&sign_hash)); + } - match &self.last_block { + // Get last block for mining + let last_block = { context.lock().unwrap().blockchain.blocks.last().cloned() }; + match last_block { None => { + println!("Mining genesis block"); // Creating a block with that signed transaction - Block::new(0,Utc::now().timestamp(), self.chain_id, self.version, Key::zero32(), Some(transaction)) + Block::new(0, Utc::now().timestamp(), chain_id, version, Key::zero32(), Some(transaction.clone())) }, Some(block) => { last_block_time = block.timestamp; // Creating a block with that signed transaction - Block::new(block.index + 1,Utc::now().timestamp(), self.chain_id, self.version, block.hash.clone(), Some(transaction)) + Block::new(block.index + 1, Utc::now().timestamp(), chain_id, version, block.hash.clone(), Some(transaction.clone())) }, } }; - //let blockchain = self.blockchain.clone(); - let transactions = self.transactions.clone(); - let running = self.running.clone(); - running.store(true, Ordering::Relaxed); - let context = self.context.clone(); - thread::spawn(move || { - match find_hash(&mut Sha256::new(), block, last_block_time, running.clone()) { - None => { - println!("Mining stopped"); - }, - Some(block) => { - //blockchain.lock().unwrap().add_block(block); - transactions.lock().unwrap().remove(0); - running.store(false, Ordering::Relaxed); - context.lock().unwrap().blockchain.add_block(block); - }, - } - }); + + let live_threads = Arc::new(AtomicU32::new(0u32)); + let cpus = num_cpus::get(); + println!("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::Relaxed); + let mut count = 0u32; + match find_hash(&mut Sha256::new(), block, last_block_time, mining.clone()) { + None => { + println!("Mining did not find suitable hash or was stopped"); + count = live_threads.fetch_sub(1, Ordering::Relaxed); + // If this is the last thread, but mining was not stopped by another thread + if count == 0 && mining.load(Ordering::Relaxed) { + // If all threads came empty with mining we return transaction to the queue + transactions.lock().unwrap().push(transaction); + mining.store(false, Ordering::Relaxed); + cond_var.notify_one(); + } + }, + Some(block) => { + count = live_threads.fetch_sub(1, Ordering::Relaxed); + context.lock().unwrap().blockchain.add_block(block); + mining.store(false, Ordering::Relaxed); + }, + } + }); + } + } + + fn get_last_block(&self) -> Option { + let context = self.context.lock().unwrap(); + context.blockchain.blocks.last().cloned() } } fn find_hash(digest: &mut dyn Digest, mut block: Block, prev_block_time: i64, running: Arc) -> Option { let mut buf: [u8; 32] = [0; 32]; block.random = rand::random(); - let start_difficulty = block.difficulty; + println!("Mining block {}", serde_json::to_string(&block).unwrap()); + //let start_difficulty = block.difficulty; for nonce in 0..std::u64::MAX { if !running.load(Ordering::Relaxed) { return None; } block.timestamp = Utc::now().timestamp(); block.nonce = nonce; + // if nonce % 1000 == 0 { + // println!("Nonce {}", nonce); + // } // TODO uncomment for real run //block.difficulty = start_difficulty + get_time_difficulty(prev_block_time, block.timestamp); diff --git a/src/transaction.rs b/src/transaction.rs index 118d8bb..08f2484 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -1,51 +1,59 @@ use crate::keys::*; + extern crate serde; extern crate serde_json; use serde::{Serialize, Deserialize, Serializer}; use serde::ser::SerializeStruct; use std::fmt; +use crate::transaction::Action::Genesis; +use crypto::sha2::Sha256; +use crypto::digest::Digest; #[derive(Clone, Serialize, Deserialize, PartialEq)] #[serde(tag = "type")] pub enum Action { - ClaimName { hash: String, owner: Key }, - NewDomain { name: String, owner: Key, #[serde(default, skip_serializing_if = "Vec::is_empty")] records: Vec, #[serde(default, skip_serializing_if = "Vec::is_empty")] tags: Vec, days: u16 }, - ChangeDomain { name: String, records: Vec, tags: Vec }, + NewDomain { hash: String, #[serde(default, skip_serializing_if = "Key::is_zero")] owner: Key }, + FillDomain { name: String, salt: String, owner: Key, #[serde(default, skip_serializing_if = "Vec::is_empty")] records: Vec, #[serde(default, skip_serializing_if = "Vec::is_empty")] tags: Vec, days: u16 }, + ChangeDomain { name: String, records: Vec, tags: Vec, #[serde(default, skip_serializing_if = "Key::is_zero")] owner: Key }, RenewDomain { name: String, days: u16 }, - MoveDomain { name: String, new_owner: Key }, - NewZone { name: String, difficulty: u16}, - ChangeZone { name: String, difficulty: u16}, + NewZone { hash: String, #[serde(default, skip_serializing_if = "Key::is_zero")] owner: Key, difficulty: u16 }, + ChangeZone { name: String, salt: String, #[serde(default, skip_serializing_if = "Key::is_zero")] owner: Key, difficulty: u16 }, + Genesis { name: String, #[serde(default, skip_serializing_if = "Key::is_zero")] owner: Key, difficulty: u16 }, } impl Action { - pub fn claim_name(name: String, salt: String, signature: &Keystore) -> Self { - let hash = format!("{} {}", salt, name); - Action::ClaimName {hash, owner: signature.get_public()} + pub fn new_domain>(name: S, salt: S, owner: Key) -> Self { + let hash = format!("{} {}", salt.into(), name.into()); + // TODO Do not use owner for now, make a field in UI and use it if filled + Action::NewDomain { hash: Action::get_hash(&hash), owner } } - pub fn new_domain(name: String, signature: &Keystore, records: Vec, tags: Vec, days: u16) -> Self { - Action::NewDomain {name, owner: signature.get_public(), records, tags, days} + pub fn fill_domain>(name: S, salt: S, owner: Key, records: Vec, tags: Vec, days: u16) -> Self { + Action::FillDomain { name: name.into(), salt: salt.into(), owner, records, tags, days } } - pub fn change_domain(name: String, records: Vec, tags: Vec) -> Self { - Action::ChangeDomain {name, records, tags} + // TODO change new_owner to Key + pub fn change_domain>(name: S, records: Vec, tags: Vec, new_owner: [u8; 32]) -> Self { + Action::ChangeDomain { name: name.into(), records, tags, owner: Key::from_bytes(&new_owner) } } - pub fn renew_domain(name: String, days: u16) -> Self { - Action::RenewDomain {name, days} + pub fn renew_domain>(name: S, days: u16) -> Self { + Action::RenewDomain { name: name.into(), days } } - pub fn move_domain(name: String, new_owner: [u8; 32]) -> Self { - Action::MoveDomain {name, new_owner: Key::from_bytes(&new_owner)} + pub fn new_zone>(name: S, salt: S, owner: Key, difficulty: u16) -> Self { + let hash = format!("{} {}", salt.into(), name.into()); + Action::NewZone { hash, owner, difficulty } } - pub fn new_zone(name: String, difficulty: u16) -> Self { - Action::NewZone {name, difficulty} + // TODO change new_owner to Key + pub fn change_zone>(name: S, salt: S, new_owner: [u8; 32], difficulty: u16) -> Self { + Action::ChangeZone { name: name.into(), salt: salt.into(), owner: Key::from_bytes(&new_owner), difficulty } } - pub fn change_zone(name: String, difficulty: u16) -> Self { - Action::ChangeZone {name, difficulty} + pub fn genesis>(name: S, owner: Key, difficulty: u16) -> Self { + Genesis { name: name.into(), owner, difficulty } } pub fn get_bytes(&self) -> Vec { @@ -57,54 +65,66 @@ impl Action { // Let it panic (for now) if something is not okay serde_json::from_slice(bytes.as_slice()).unwrap() } + + fn get_hash(data: &str) -> String { + let mut digest = Sha256::new(); + digest.input(data.as_bytes()); + digest.result_str() + } } impl fmt::Debug for Action { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { match self { - Action::ClaimName { hash, owner } => { - fmt.debug_struct("ClaimName") + Action::NewDomain { hash, owner } => { + fmt.debug_struct("NewDomain") .field("hash", hash) .field("owner", owner) .finish() - }, - Action::NewDomain { name, owner, records, tags, days } => { - fmt.debug_struct("NewDomain") + } + Action::FillDomain { name, salt, owner, records, tags, days } => { + fmt.debug_struct("FillDomain") .field("name", name) + .field("salt", salt) .field("owner", &owner) .field("records", records) .field("tags", tags) .field("days", days) .finish() - }, - Action::ChangeDomain { name, records, tags } => { + } + Action::ChangeDomain { name, records, tags, owner } => { fmt.debug_struct("ChangeDomain") .field("name", name) .field("records", records) .field("tags", tags) + .field("owner", &owner) .finish() - }, + } Action::RenewDomain { name, days } => { fmt.debug_struct("RenewDomain") .field("name", name) .field("days", days) .finish() - }, - Action::MoveDomain { name, new_owner } => { - fmt.debug_struct("MoveDomain") - .field("name", name) - .field("new_owner", new_owner) - .finish() - }, - Action::NewZone { name, difficulty } => { + } + Action::NewZone { hash, owner, difficulty } => { fmt.debug_struct("NewZone") - .field("name", name) + .field("hash", hash) + .field("owner", &owner) .field("difficulty", difficulty) .finish() - }, - Action::ChangeZone { name, difficulty } => { + } + Action::ChangeZone { name, salt, owner, difficulty } => { fmt.debug_struct("ChangeZone") .field("name", name) + .field("salt", salt) + .field("owner", &owner) + .field("difficulty", difficulty) + .finish() + } + Action::Genesis { name, owner, difficulty } => { + fmt.debug_struct("Genesis") + .field("name", name) + .field("owner", &owner) .field("difficulty", difficulty) .finish() } @@ -121,7 +141,7 @@ pub struct Transaction { impl Transaction { pub fn new(action: Action, pub_key: Key) -> Self { - Transaction {action, pub_key, signature: Key::zero64()} + Transaction { action, pub_key, signature: Key::zero64() } } pub fn set_signature(&mut self, hash: Key) { diff --git a/src/utils.rs b/src/utils.rs index 928ca20..20bb8ca 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,4 +1,5 @@ use std::num; +use rand::Rng; /// Convert bytes array to HEX format pub fn to_hex(buf: &[u8]) -> String { @@ -30,4 +31,17 @@ pub fn same_hash(left: &[u8], right: &[u8]) -> bool { } } true +} + +/// 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 x in 0..length { + let position: usize = rng.gen::() % chars.len(); + let c: char = *chars.get(position).unwrap(); + result.push(c); + } + result } \ No newline at end of file