From 536515519aa66a55a3f1fc9c92b78106f1190be8 Mon Sep 17 00:00:00 2001 From: Revertron Date: Tue, 13 Apr 2021 18:49:26 +0200 Subject: [PATCH] Refactored the work with signing blocks. --- docs/about_ru.md | 5 +- src/blockchain/chain.rs | 175 +++++++++++++++++++++++++++++---------- src/commons/constants.rs | 25 ++++-- src/event.rs | 2 +- src/miner.rs | 52 +++++++----- src/p2p/network.rs | 35 ++------ 6 files changed, 190 insertions(+), 104 deletions(-) diff --git a/docs/about_ru.md b/docs/about_ru.md index a478de5..961f537 100644 --- a/docs/about_ru.md +++ b/docs/about_ru.md @@ -71,11 +71,8 @@ ALFIS это ALternative Free Identity System. Альтернативная бе 3. Начиная с 35-го блока включается режим подписей блоков. Каждый блок, содержащий транзакцию, то есть создающий или меняющий какой-нибудь домен, должен быть подписан группой ~~лиц по предварительному сговору~~ узлов, обладающих блоками перед текущим блоком. Выбираются до 50 последних блоков перед текущим (подписываемым) блоком, среди них вычисляются 7 публичных ключей, владельцы которых должны подписать блок. -Вычисление происходит исходя из последних 4 байт хэша подписываемого блока. +Вычисление происходит исходя из последних 8 байт подписи подписываемого блока. Блок должен быть подписан минимум четырьмя валидаторами. -Этот алгоритм в скором времени будет расширен таким образом, что если блок не подписан нужным числом валидаторов, то через некоторое время количество валидаторов и требуемое количество подписей будет расти. -То есть, например, если через полчаса после блока нет четырёх нужных подписей, то каждый узел вычисляет валидаторов заново (по тому же алгоритму), но чуть больше, и требуется уже больше подписей. -Например, 5 из 9, потом 6 из 11. Таким образом любой злоумышленник, долго и упорно добивавшийся своего доминирования в сети, не сможет захватить 4 нужных подписи, и отказаться от подписывания, тем самым остановив систему. ## Дополнительные возможности ALFIS содержит несколько особенностей, которых нет в обычном DNS. diff --git a/src/blockchain/chain.rs b/src/blockchain/chain.rs index 0e25d62..4c207ad 100644 --- a/src/blockchain/chain.rs +++ b/src/blockchain/chain.rs @@ -19,6 +19,7 @@ use std::cmp::{min, max}; use crate::blockchain::transaction::{ZoneData, DomainData}; use std::ops::Deref; use crate::blockchain::types::MineResult::*; +use crate::event::Event; const DB_NAME: &str = "blockchain.db"; const TEMP_DB_NAME: &str = "temp.db"; @@ -28,6 +29,7 @@ const SQL_ADD_BLOCK: &str = "INSERT INTO blocks (id, timestamp, version, difficu const SQL_REPLACE_BLOCK: &str = "UPDATE blocks SET timestamp = ?, version = ?, difficulty = ?, random = ?, nonce = ?, 'transaction' = ?,\ prev_block_hash = ?, hash = ?, pub_key = ?, signature = ? WHERE id = ?;"; const SQL_GET_LAST_BLOCK: &str = "SELECT * FROM blocks ORDER BY id DESC LIMIT 1;"; +const SQL_GET_FIRST_BLOCK_FOR_KEY: &str = "SELECT id FROM blocks WHERE pub_key = ? LIMIT 1;"; const SQL_ADD_DOMAIN: &str = "INSERT INTO domains (id, timestamp, identity, confirmation, data, pub_key) VALUES (?, ?, ?, ?, ?, ?)"; const SQL_ADD_ZONE: &str = "INSERT INTO zones (id, timestamp, identity, confirmation, data, pub_key) VALUES (?, ?, ?, ?, ?, ?)"; const SQL_DELETE_DOMAIN: &str = "DELETE FROM domains WHERE id = ?"; @@ -185,6 +187,51 @@ impl Chain { Ok(()) } + pub fn update(&mut self, keystore: &Option) -> Option { + if self.height() < BLOCK_SIGNERS_START { + trace!("Too early to start block signings"); + return None; + } + if keystore.is_none() { + trace!("We can't sign blocks without keys"); + return None; + } + if self.height() < self.max_height() { + trace!("No signing while syncing"); + return None; + } + + let block = self.last_block().unwrap(); + if block.transaction.is_none() { + trace!("No need to sign signing block"); + return None; + } + let keystore = keystore.clone().unwrap().clone(); + let signers: HashSet = self.get_block_signers(&block).into_iter().collect(); + if signers.contains(&keystore.get_public()) { + info!("We have an honor to mine signing block!"); + let keystore = Box::new(keystore); + // We start mining sign block after some time, not everyone in the same time + let start = Utc::now().timestamp() + (rand::random::() % BLOCK_SIGNERS_START_RANDOM); + return Some(Event::ActionMineLocker { start, index: block.index + 1, hash: block.hash, keystore }); + } else if !signers.is_empty() { + info!("Signing block must be mined by other nodes"); + } + None + } + + pub fn update_sign_block_for_mining(&self, mut block: Block) -> Option { + if let Some(full_block) = &self.last_full_block { + let sign_count = self.height() - full_block.index; + if sign_count >= BLOCK_SIGNERS_MIN { + return None; + } + block.index = self.height() + 1; + block.prev_block_hash = self.last_block.clone().unwrap().hash; + } + None + } + fn delete_transaction(&mut self, index: u64) -> sqlite::Result<()> { let mut statement = self.db.prepare(SQL_DELETE_DOMAIN)?; statement.bind(1, index as i64)?; @@ -508,10 +555,10 @@ impl Chain { match self.last_full_block { None => { self.height() + 1 } Some(ref block) => { - if block.index < LOCKER_BLOCK_START { + if block.index < BLOCK_SIGNERS_START { self.height() + 1 } else { - max(block.index, self.height()) + LOCKER_BLOCK_SIGNS + max(block.index, self.height()) + BLOCK_SIGNERS_MIN } } } @@ -622,25 +669,10 @@ impl Chain { warn!("Block {} arrived too early.", block.index); return Future; } - if block.index >= LOCKER_BLOCK_START { - // If this block is locked part of blockchain - if let Some(full_block) = &self.last_full_block { - let locker_blocks = self.height() - full_block.index; - if locker_blocks < LOCKER_BLOCK_SIGNS { - // Last full block is not locked enough - if block.transaction.is_some() { - warn!("Not enough signing blocks over full {} block!", full_block.index); - return Bad; - } else { - if self.check_block_for_signing(&block, full_block) == Bad { - return Bad; - } - } - } else if locker_blocks < LOCKER_BLOCK_LOCKERS && block.transaction.is_none() { - if self.check_block_for_signing(&block, full_block) == Bad { - return Bad; - } - } + if block.index >= BLOCK_SIGNERS_START { + // If this block is main, signed part of blockchain + if !self.is_good_sign_block(&block) { + return Bad; } } @@ -666,6 +698,79 @@ impl Chain { Good } + /// Checks if this block is a good signature block + fn is_good_sign_block(&self, block: &Block) -> bool { + if let Some(full_block) = &self.last_full_block { + let sign_count = self.height() - full_block.index; + if sign_count < BLOCK_SIGNERS_MIN { + // Last full block is not locked enough + if block.transaction.is_some() { + warn!("Not enough signing blocks over full {} block!", full_block.index); + return false; + } else { + if !self.is_good_signer_for_block(&block, full_block, sign_count) { + return false; + } + } + } else if sign_count < BLOCK_SIGNERS_ALL && block.transaction.is_none() { + if !self.is_good_signer_for_block(&block, full_block, sign_count) { + return false; + } + } + } + true + } + + /// Check if this block's owner is a good candidate to sign last full block + fn is_good_signer_for_block(&self, block: &Block, full_block: &Block, sign_count: u64) -> bool { + // If the time for chosen signers is up + if self.can_sign_by_pos(sign_count, full_block.timestamp, block.timestamp, &block.pub_key) { + return true; + } + // If we got a locker/signing block + let signers: HashSet = self.get_block_signers(full_block).into_iter().collect(); + if !signers.contains(&block.pub_key) { + warn!("Ignoring block {} from '{:?}', as wrong signer!", block.index, &block.pub_key); + return false; + } + // If this signers' public key has already locked/signed that block we return error + for i in (full_block.index + 1)..block.index { + let signer = self.get_block(i).expect("Error in DB!"); + if signer.pub_key == block.pub_key { + warn!("Ignoring block {} from '{:?}', already signed by this key", block.index, &block.pub_key); + return false; + } + } + true + } + + /// Gets an id of first block of this public key + fn get_first_block_id_for_key(&self, key: &Bytes) -> u64 { + match self.db.prepare(SQL_GET_FIRST_BLOCK_FOR_KEY) { + Ok(mut statement) => { + statement.bind(1, &***key).expect("Error in bind"); + while statement.next().unwrap() == State::Row { + return statement.read::(0).unwrap() as u64; + } + 0 + } + Err(_) => { + 0 + } + } + } + + /// Check if an owner of this public key can sign full block by PoS scheme (be in first 1000 users) + fn can_sign_by_pos(&self, sign_count: u64, block_time: i64, now: i64, pub_key: &Bytes) -> bool { + if sign_count < BLOCK_SIGNERS_MIN && block_time - now > BLOCK_SIGNERS_TIME { + let index = self.get_first_block_id_for_key(&pub_key); + if index > 0 && index <= BLOCK_POS_SIGNERS { + return true; + } + } + false + } + fn get_difficulty_for_transaction(&self, transaction: &Transaction) -> u32 { match transaction.class.as_ref() { "domain" => { @@ -689,38 +794,20 @@ impl Chain { } } - fn check_block_for_signing(&self, block: &Block, full_block: &Block) -> BlockQuality { - // If we got a locker/signing block - let signers: HashSet = self.get_block_signers(full_block).into_iter().collect(); - if !signers.contains(&block.pub_key) { - warn!("Ignoring block {} from '{:?}', as wrong signer!", block.index, &block.pub_key); - return Bad; - } - // If this signers' public key has already locked/signed that block we return error - for i in (full_block.index + 1)..block.index { - let signer = self.get_block(i).expect("Error in DB!"); - if signer.pub_key == block.pub_key { - warn!("Ignoring block {} from '{:?}', already signed by this key", block.index, &block.pub_key); - return Bad; - } - } - Good - } - /// Gets public keys of a node that needs to mine "signature" block above this block /// block - last full block pub fn get_block_signers(&self, block: &Block) -> Vec { let mut result = Vec::new(); - if block.index < LOCKER_BLOCK_START { + if block.index < BLOCK_SIGNERS_START { return result; } let mut set = HashSet::new(); - let tail = block.hash.get_tail_u64(); - let interval = min(block.index, LOCKER_BLOCK_INTERVAL) - 1; + let tail = block.signature.get_tail_u64(); + let interval = min(block.index, BLOCK_SIGNERS_WINDOW) - 1; let start_index = block.index - interval; let mut count = 1; - while set.len() < LOCKER_BLOCK_LOCKERS as usize { - let index = start_index + ((tail * count) % LOCKER_BLOCK_INTERVAL); + while set.len() < BLOCK_SIGNERS_ALL as usize { + let index = start_index + ((tail * count) % BLOCK_SIGNERS_WINDOW); if let Some(b) = self.get_block(index) { if b.pub_key != block.pub_key && !set.contains(&b.pub_key) { result.push(b.pub_key.clone()); diff --git a/src/commons/constants.rs b/src/commons/constants.rs index 0b823ed..9086995 100644 --- a/src/commons/constants.rs +++ b/src/commons/constants.rs @@ -6,11 +6,26 @@ pub const ZONE_MIN_DIFFICULTY: u32 = 22; pub const LOCKER_DIFFICULTY: u32 = 16; pub const KEYSTORE_DIFFICULTY: u32 = 23; -pub const LOCKER_BLOCK_START: u64 = 35; -pub const LOCKER_BLOCK_LOCKERS: u64 = 7; -pub const LOCKER_BLOCK_SIGNS: u64 = 4; -pub const LOCKER_BLOCK_TIME: i64 = 300; -pub const LOCKER_BLOCK_INTERVAL: u64 = 50; +/// Blocks start to be signed starting from this index +pub const BLOCK_SIGNERS_START: u64 = 35; + +/// How many signers are chosen for signing +pub const BLOCK_SIGNERS_ALL: u64 = 7; + +/// Minimal signatures needed +pub const BLOCK_SIGNERS_MIN: u64 = 4; + +/// Last number of blocks from which we select signers +pub const BLOCK_SIGNERS_WINDOW: u64 = 50; + +/// Signers have 30 minutes to sign, after that time any owner of first 1000 block can add needed signature +pub const BLOCK_SIGNERS_TIME: i64 = 1800; + +/// PoS signers, that sign blocks when chosen signers didn't sign +pub const BLOCK_POS_SIGNERS: u64 = 1000; + +/// We start mining signing blocks after random delay, this is the max delay +pub const BLOCK_SIGNERS_START_RANDOM: i64 = 180; pub const NEW_DOMAINS_INTERVAL: i64 = 86400; // One day in seconds pub const DOMAIN_LIFETIME: i64 = 86400 * 365; // One year diff --git a/src/event.rs b/src/event.rs index 42d2588..a9919fd 100644 --- a/src/event.rs +++ b/src/event.rs @@ -13,7 +13,7 @@ pub enum Event { NewBlockReceived, BlockchainChanged { index: u64 }, ActionStopMining, - ActionMineLocker { index: u64, hash: Bytes, keystore: Box }, + ActionMineLocker { start: i64, index: u64, hash: Bytes, keystore: Box }, ActionQuit, NetworkStatus { nodes: usize, blocks: u64 }, Syncing { have: u64, height: u64 }, diff --git a/src/miner.rs b/src/miner.rs index 0b4832b..8ab10df 100644 --- a/src/miner.rs +++ b/src/miner.rs @@ -37,7 +37,7 @@ impl Miner { } pub fn add_block(&mut self, block: Block, keystore: Keystore) { - self.jobs.lock().unwrap().push(MineJob { block, keystore }); + self.jobs.lock().unwrap().push(MineJob { start: 0, block, keystore }); self.cond_var.notify_one(); } @@ -55,21 +55,28 @@ impl Miner { let cond_var = self.cond_var.clone(); thread::spawn(move || { running.store(true, Ordering::SeqCst); + let delay = Duration::from_millis(1000); while running.load(Ordering::SeqCst) { // If some transaction is being mined now, we yield if mining.load(Ordering::SeqCst) { - thread::sleep(Duration::from_millis(1000)); + thread::sleep(delay); continue; } - let mut lock = blocks.lock().unwrap(); - if lock.len() > 0 { - info!("Got new block to mine"); - let block = lock.remove(0); - mining.store(true, Ordering::SeqCst); - Miner::mine_internal(Arc::clone(&context), block, mining.clone()); + let mut jobs = blocks.lock().unwrap(); + if jobs.len() > 0 { + debug!("Got new job to mine"); + let job = jobs.remove(0); + if job.start == 0 || job.start < Utc::now().timestamp() { + mining.store(true, Ordering::SeqCst); + Miner::mine_internal(Arc::clone(&context), job, mining.clone()); + } else { + debug!("This job will wait for now"); + thread::sleep(delay); + jobs.push(job); + } } else { - let _ = cond_var.wait(lock).expect("Error in wait lock!"); + let _ = cond_var.wait(jobs).expect("Error in wait lock!"); } } }); @@ -83,11 +90,11 @@ impl Miner { Event::ActionStopMining => { mining.store(false, Ordering::SeqCst); } - Event::ActionMineLocker { index, hash, keystore } => { + Event::ActionMineLocker { start, index, hash, keystore } => { if !mining.load(Ordering::SeqCst) { let mut block = Block::new(None, Bytes::default(), hash, LOCKER_DIFFICULTY); block.index = index; - blocks.lock().unwrap().push(MineJob { block, keystore: keystore.deref().clone() }); + blocks.lock().unwrap().push(MineJob { start, block, keystore: keystore.deref().clone() }); cond_var.notify_all(); info!("Added a locker block to mine"); } @@ -117,17 +124,15 @@ impl Miner { mining.store(false, Ordering::SeqCst); return; } - match context.lock().unwrap().chain.last_block() { - None => {} - Some(last_block) => { - debug!("Last block found"); - // If we were doing something else and got new block before we could mine this block - if last_block.index > job.block.index || last_block.hash != job.block.prev_block_hash { - warn!("We missed block to lock"); - context.lock().unwrap().bus.post(Event::MinerStopped { success: false, full: false }); - mining.store(false, Ordering::SeqCst); - return; - } + match context.lock().unwrap().chain.update_sign_block_for_mining(job.block) { + None => { + warn!("We missed block to lock"); + context.lock().unwrap().bus.post(Event::MinerStopped { success: false, full: false }); + mining.store(false, Ordering::SeqCst); + return; + } + Some(block) => { + job.block = block; } } } else { @@ -185,6 +190,8 @@ impl Miner { context.settings.origin = block.hash.to_string(); } context.chain.add_block(block); + let option = Some(job.keystore); + context.chain.update(&option); success = true; } context.bus.post(Event::MinerStopped { success, full }); @@ -199,6 +206,7 @@ impl Miner { #[derive(Clone)] pub struct MineJob { + start: i64, block: Block, keystore: Keystore } diff --git a/src/p2p/network.rs b/src/p2p/network.rs index b18d3e0..8a08738 100644 --- a/src/p2p/network.rs +++ b/src/p2p/network.rs @@ -14,12 +14,10 @@ use mio::net::{TcpListener, TcpStream}; use log::{trace, debug, info, warn, error}; use std::net::{SocketAddr, IpAddr, SocketAddrV4, Shutdown}; -use std::collections::HashSet; -use crate::{Context, Block, p2p::Message, p2p::State, p2p::Peer, p2p::Peers, Bytes, is_yggdrasil}; +use crate::{Context, Block, p2p::Message, p2p::State, p2p::Peer, p2p::Peers, is_yggdrasil}; use crate::blockchain::types::BlockQuality; use crate::commons::CHAIN_VERSION; use std::sync::atomic::{AtomicBool, Ordering}; -use chrono::Utc; const SERVER: Token = Token(0); const POLL_TIMEOUT: Option = Some(Duration::from_millis(3000)); @@ -144,7 +142,6 @@ impl Network { } (height, context.chain.last_hash()) }; - mine_signing_block(Arc::clone(&context)); peers.send_pings(poll.registry(), height, hash); peers.connect_new_peers(poll.registry(), &mut unique_token, yggdrasil_only); peers_timer = Instant::now(); @@ -461,6 +458,8 @@ fn handle_message(context: Arc>, message: Message, peers: &mut Pe match context.chain.check_new_block(&block) { BlockQuality::Good => { context.chain.add_block(block); + let keystore = context.keystore.clone(); + context.chain.update(&keystore); let my_height = context.chain.height(); context.bus.post(crate::event::Event::BlockchainChanged { index: my_height }); // If it was the last block to sync @@ -482,6 +481,10 @@ fn handle_message(context: Arc>, message: Message, peers: &mut Pe let last_block = context.chain.last_block().unwrap(); if block.is_better_than(&last_block) { context.chain.replace_block(block.index, block).expect("Error replacing block with fork"); + let keystore = context.keystore.clone(); + context.chain.update(&keystore); + let index = context.chain.height(); + context.bus.post(crate::event::Event::BlockchainChanged { index }); } //let peer = peers.get_mut_peer(token).unwrap(); //deal_with_fork(context, peer, block); @@ -494,30 +497,6 @@ fn handle_message(context: Arc>, message: Message, peers: &mut Pe answer } -/// Sends an Event to miner to start mining locker block if "locker" is our public key -fn mine_signing_block(context: Arc>) { - let mut context = context.lock().unwrap(); - if let Some(block) = context.chain.get_last_full_block(None) { - if block.timestamp + 60 > Utc::now().timestamp() { - return; - } - if let Some(keystore) = &context.keystore { - if block.index < context.chain.max_height() { - trace!("No signing while syncing"); - return; - } - let signers: HashSet = context.chain.get_block_signers(&block).into_iter().collect(); - if signers.contains(&keystore.get_public()) { - info!("We have an honor to mine signing block!"); - let keystore = Box::new(keystore.clone()); - context.bus.post(crate::event::Event::ActionMineLocker { index: block.index + 1, hash: block.hash, keystore }); - } else if !signers.is_empty() { - info!("Signing block must be mined by other nodes"); - } - } - } -} - #[allow(dead_code)] fn deal_with_fork(context: MutexGuard, peer: &mut Peer, block: Block) { peer.add_fork_block(block);