From 161e6002902ccc2b45f0f38325c94c1f8655997d Mon Sep 17 00:00:00 2001 From: Revertron Date: Tue, 20 Apr 2021 18:46:06 +0200 Subject: [PATCH] Added blockchain full check on start. Fixed cond_var blocking. --- src/blockchain/chain.rs | 112 +++++++++++++++++++++++++++++++++------- src/miner.rs | 8 +-- src/p2p/network.rs | 56 ++++---------------- src/web_ui.rs | 9 ---- 4 files changed, 106 insertions(+), 79 deletions(-) diff --git a/src/blockchain/chain.rs b/src/blockchain/chain.rs index 1ce4538..b1a2841 100644 --- a/src/blockchain/chain.rs +++ b/src/blockchain/chain.rs @@ -30,13 +30,16 @@ const SQL_REPLACE_BLOCK: &str = "UPDATE blocks SET timestamp = ?, version = ?, d 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_DELETE_BLOCK_AND_TRANSACTIONS: &str = "DELETE FROM blocks WHERE id = ?; DELETE FROM domains WHERE id = ?; DELETE FROM zones WHERE id = ?;"; +const SQL_TRUNCATE_DB_FROM_BLOCK: &str = "DELETE FROM blocks WHERE id >= ?; DELETE FROM domains WHERE id >= ?; DELETE FROM zones WHERE id >= ?;"; + 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 = ?"; const SQL_DELETE_ZONE: &str = "DELETE FROM zones WHERE id = ?"; 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_LAST_FULL_BLOCK_FOR_KEY: &str = "SELECT * FROM blocks WHERE `transaction`<>'' AND pub_key = ? ORDER BY id DESC LIMIT 1;"; +const SQL_GET_LAST_FULL_BLOCK: &str = "SELECT * FROM blocks WHERE id < ? AND `transaction`<>'' ORDER BY id DESC LIMIT 1;"; +const SQL_GET_LAST_FULL_BLOCK_FOR_KEY: &str = "SELECT * FROM blocks WHERE id < ? AND `transaction`<>'' AND pub_key = ? ORDER BY id DESC LIMIT 1;"; const SQL_GET_DOMAIN_PUBLIC_KEY_BY_ID: &str = "SELECT pub_key FROM domains WHERE identity = ? ORDER BY id DESC LIMIT 1;"; const SQL_GET_ZONE_PUBLIC_KEY_BY_ID: &str = "SELECT pub_key FROM zones WHERE identity = ? ORDER BY id DESC LIMIT 1;"; const SQL_GET_DOMAIN_BY_ID: &str = "SELECT * FROM domains WHERE identity = ? ORDER BY id DESC LIMIT 1;"; @@ -83,9 +86,68 @@ impl Chain { if block.transaction.is_some() { self.last_full_block = Some(block); } else { - self.last_full_block = self.get_last_full_block(None); + self.last_full_block = self.get_last_full_block(u64::MAX, None); } } + self.check_chain(); + } + + fn check_chain(&mut self) { + let height = self.height(); + info!("Local blockchain height is {}, starting full blockchain check...", height); + for id in 1..=height { + info!("Checking block {}", id); + match self.get_block(id) { + None => { + panic!("Blockchain is corrupted! Please, delete 'blockchain.db' and restart."); + } + Some(block) => { + if block.index == 1 { + if block.hash != self.origin { + panic!("Loaded DB is not of origin {:?}! Please, delete 'blockchain.db' and restart.", &self.origin); + } + self.last_block = Some(block); + continue; + } + + //let last = self.last_block.clone().unwrap(); + if self.check_new_block(&block) != BlockQuality::Good { + error!("Block {} is bad:\n{:?}", block.index, &block); + info!("Truncating database from block {}...", block.index); + if self.truncate_db_from_block(block.index).is_err() { + panic!("Error truncating database! Please, delete 'blockchain.db' and restart."); + } + break; + } + info!("Block {} is good!", block.index); + if block.transaction.is_some() { + self.last_full_block = Some(block.clone()); + } + self.last_block = Some(block); + } + } + } + self.last_block = self.load_last_block(); + self.last_full_block = self.get_last_full_block(u64::MAX, None); + info!("Last block after chain check: {:?}", &self.last_block); + } + + #[allow(dead_code)] + fn delete_block(&mut self, index: u64) -> sqlite::Result { + let mut statement = self.db.prepare(SQL_DELETE_BLOCK_AND_TRANSACTIONS)?; + statement.bind(1, index as i64)?; + statement.bind(2, index as i64)?; + statement.bind(3, index as i64)?; + statement.next() + } + + #[allow(dead_code)] + fn truncate_db_from_block(&mut self, index: u64) -> sqlite::Result { + let mut statement = self.db.prepare(SQL_TRUNCATE_DB_FROM_BLOCK)?; + statement.bind(1, index as i64)?; + statement.bind(2, index as i64)?; + statement.bind(3, index as i64)?; + statement.next() } fn load_last_block(&mut self) -> Option { @@ -241,14 +303,21 @@ impl Chain { } pub fn update_sign_block_for_mining(&self, mut block: Block) -> Option { + info!("11"); if let Some(full_block) = &self.last_full_block { + info!("22"); let sign_count = self.height() - full_block.index; if sign_count >= BLOCK_SIGNERS_MIN { + info!("23"); return None; } - block.index = self.height() + 1; - block.prev_block_hash = self.last_block.clone().unwrap().hash; - return Some(block); + info!("33"); + if let Some(last) = &self.last_block { + block.index = last.index + 1; + block.prev_block_hash = last.hash.clone(); + info!("44"); + return Some(block); + } } None } @@ -363,7 +432,7 @@ impl Chain { } /// Gets last block that has a Transaction within - pub fn get_last_full_block(&self, pub_key: Option<&[u8]>) -> Option { + pub fn get_last_full_block(&self, index: u64, pub_key: Option<&[u8]>) -> Option { if let Some(block) = &self.last_full_block { match pub_key { None => { return Some(block.clone()); } @@ -377,11 +446,14 @@ impl Chain { let mut statement = match pub_key { None => { - self.db.prepare(SQL_GET_LAST_FULL_BLOCK).expect("Unable to prepare") + let mut statement = self.db.prepare(SQL_GET_LAST_FULL_BLOCK).expect("Unable to prepare"); + statement.bind(1, index as i64).expect("Unable to bind"); + statement } Some(pub_key) => { let mut statement = self.db.prepare(SQL_GET_LAST_FULL_BLOCK_FOR_KEY).expect("Unable to prepare"); - statement.bind(1, pub_key).expect("Unable to bind"); + statement.bind(1, index as i64).expect("Unable to bind"); + statement.bind(2, pub_key).expect("Unable to bind"); statement } }; @@ -506,7 +578,7 @@ impl Chain { } } let identity_hash = hash_identity(&name, None); - if let Some(last) = self.get_last_full_block(Some(&pub_key)) { + if let Some(last) = self.get_last_full_block(u64::MAX, Some(&pub_key)) { let new_id = !self.is_id_in_blockchain(&identity_hash, false); let time = last.timestamp + NEW_DOMAINS_INTERVAL - Utc::now().timestamp(); if new_id && time > 0 { @@ -684,7 +756,7 @@ impl Chain { return Bad; } if let Some(prev_block) = self.get_block(block.index - 1) { - if block.prev_block_hash != prev_block.hash { + if block.prev_block_hash.ne(&prev_block.hash) { warn!("Ignoring block with wrong previous hash:\n{:?}", &block); return Bad; } @@ -710,11 +782,13 @@ impl Chain { warn!("Block {:?} is trying to spoof an identity!", &block); return Bad; } - if let Some(last) = self.get_last_full_block(Some(&block.pub_key)) { - let new_id = !self.is_id_in_blockchain(&transaction.identity, false); - if new_id && last.timestamp + NEW_DOMAINS_INTERVAL > block.timestamp { - warn!("Block {:?} is mined too early!", &block); - return Bad; + if let Some(last) = self.get_last_full_block(block.index, Some(&block.pub_key)) { + if last.index < block.index { + let new_id = !self.is_id_in_blockchain(&transaction.identity, false); + if new_id && last.timestamp + NEW_DOMAINS_INTERVAL > block.timestamp { + warn!("Block {:?} is mined too early!", &block); + return Bad; + } } } // Check if yggdrasil only quality of zone is not violated @@ -740,7 +814,7 @@ impl Chain { warn!("Block is from the future, how is this possible?"); return Future; } - if !self.origin.is_zero() && block.hash != self.origin { + if !self.origin.is_zero() && block.hash.ne(&self.origin) { warn!("Mining gave us a bad block:\n{:?}", &block); return Bad; } @@ -767,7 +841,7 @@ impl Chain { return Twin; } if let Some(my_block) = self.get_block(block.index) { - return if my_block.hash != block.hash { + return if my_block.hash.ne(&block.hash) { warn!("Got forked block {} with hash {:?} instead of {:?}", block.index, block.hash, last_block.hash); Fork } else { @@ -775,7 +849,7 @@ impl Chain { Twin }; } - } else if block.prev_block_hash != last_block.hash { + } else if block.prev_block_hash.ne(&last_block.hash) { warn!("Ignoring block with wrong previous hash:\n{:?}", &block); return Bad; } diff --git a/src/miner.rs b/src/miner.rs index e7c5814..7f0b18f 100644 --- a/src/miner.rs +++ b/src/miner.rs @@ -78,8 +78,8 @@ impl Miner { Miner::mine_internal(Arc::clone(&context), job, mining.clone()); } else { debug!("This job will wait for now"); - thread::sleep(delay); - jobs.push(job); + jobs.insert(0, job); + let _ = cond_var.wait_timeout(jobs, delay).expect("Error in wait lock!"); } } else { let _ = cond_var.wait(jobs).expect("Error in wait lock!"); @@ -87,7 +87,7 @@ impl Miner { } }); let mining = self.mining.clone(); - let blocks = self.jobs.clone(); + let jobs = self.jobs.clone(); let cond_var = self.cond_var.clone(); self.context.lock().unwrap().bus.register(move |_uuid, e| { match e { @@ -100,7 +100,7 @@ impl Miner { if !mining.load(Ordering::SeqCst) { let mut block = Block::new(None, Bytes::default(), hash, SIGNER_DIFFICULTY); block.index = index; - blocks.lock().unwrap().push(MineJob { start, block, keystore: keystore.deref().clone() }); + jobs.lock().unwrap().push(MineJob { start, block, keystore: keystore.deref().clone() }); cond_var.notify_all(); info!("Added a signing block to mine"); } diff --git a/src/p2p/network.rs b/src/p2p/network.rs index 5c8cc39..e077d43 100644 --- a/src/p2p/network.rs +++ b/src/p2p/network.rs @@ -4,7 +4,7 @@ extern crate serde_json; use std::{io, thread}; use std::io::{Read, Write}; use std::net::{IpAddr, Shutdown, SocketAddr, SocketAddrV4}; -use std::sync::{Arc, Mutex, MutexGuard}; +use std::sync::{Arc, Mutex}; use std::sync::atomic::{AtomicBool, Ordering}; use std::time::{Duration, Instant}; @@ -167,6 +167,7 @@ impl Network { let keystore = context.keystore.clone(); if let Some(event) = context.chain.update(&keystore) { context.bus.post(event); + trace!("Posted an event to mine signing block"); } } (height, context.chain.last_hash()) @@ -429,7 +430,9 @@ fn handle_message(context: Arc>, message: Message, peers: &mut Pe let mut context = context.lock().unwrap(); context.chain.update_max_height(height); } - if hash != my_hash { + if hash.ne(&my_hash) { + debug!("1Hashes are different, requesting block {} from {}", my_height, peer.get_addr().ip()); + debug!("My hash: {:?}, their hash: {:?}", &my_hash, &hash); State::message(Message::GetBlock { index: my_height }) } else { State::message(Message::pong(my_height, my_hash)) @@ -443,7 +446,9 @@ fn handle_message(context: Arc>, message: Message, peers: &mut Pe let mut context = context.lock().unwrap(); context.chain.update_max_height(height); } - if hash != my_hash { + if hash.ne(&my_hash) { + debug!("2Hashes are different, requesting block {} from {}", my_height, peer.get_addr().ip()); + debug!("My hash: {:?}, their hash: {:?}", &my_hash, &hash); State::message(Message::GetBlock { index: my_height }) } else { if random::() < 10 { @@ -511,7 +516,7 @@ fn process_new_block(context: Arc>, peers: &mut Peers, token: &To BlockQuality::Future => { debug!("Ignoring future block {}", block.index); } BlockQuality::Bad => { // TODO save bad public keys to banned table - debug!("Ignoring bad block {} with hash {:?} from {}", block.index, block.hash, peer.get_addr()); + debug!("Ignoring bad block from {}:\n{:?}", peer.get_addr(), &block); let height = context.chain.height(); context.chain.update_max_height(height); context.bus.post(crate::event::Event::SyncFinished); @@ -533,49 +538,6 @@ fn process_new_block(context: Arc>, peers: &mut Peers, token: &To State::idle() } -#[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.chain.get_block(vector[0].index - 1) { - // If this block is not root of the fork (we need to go ~deeper~ more backwards) - if vector[0].prev_block_hash != prev_block.hash { - return; - } - // 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 -} - fn would_block(err: &io::Error) -> bool { err.kind() == io::ErrorKind::WouldBlock } diff --git a/src/web_ui.rs b/src/web_ui.rs index 10286ad..9cebc7f 100644 --- a/src/web_ui.rs +++ b/src/web_ui.rs @@ -196,11 +196,6 @@ fn action_loaded(context: &Arc>, web_view: &mut WebView<()>) { let context_copy = Arc::clone(&context); let mut c = context.lock().unwrap(); - let keystore = c.keystore.clone(); - if let Some(event) = c.chain.update(&keystore) { - c.bus.post(event); - } - c.bus.register(move |_uuid, e| { //debug!("Got event from bus {:?}", &e); let status = Arc::clone(&status); @@ -220,10 +215,6 @@ fn action_loaded(context: &Arc>, web_view: &mut WebView<()>) { Event::KeyLoaded { path, public, hash } | Event::KeySaved { path, public, hash } => { load_domains(&mut context, &handle); - let keystore = context.keystore.clone(); - if let Some(event) = context.chain.update(&keystore) { - context.bus.post(event); - } format!("keystoreChanged('{}', '{}', '{}');", &path, &public, &hash) } Event::MinerStarted | Event::KeyGeneratorStarted => {