use std::cell::RefCell; use std::cmp::{max, min}; use std::collections::{HashMap, HashSet}; use std::fs; use std::ops::Deref; use std::path::Path; use chrono::Utc; #[allow(unused_imports)] use log::{debug, error, info, trace, warn}; use sqlite::{Connection, State, Statement}; use lazy_static::lazy_static; use crate::blockchain::hash_utils::*; use crate::blockchain::transaction::{DomainData, DomainState}; use crate::blockchain::types::BlockQuality::*; use crate::blockchain::types::MineResult::*; use crate::blockchain::types::{BlockQuality, MineResult, Options, ZoneData}; use crate::commons::constants::*; use crate::keystore::check_public_key_strength; use crate::settings::Settings; use crate::{check_domain, get_domain_zone, is_yggdrasil_record, Block, Bytes, Keystore, Transaction, from_hex}; use rand::prelude::IteratorRandom; const TEMP_DB_NAME: &str = ":memory:"; const SQL_CREATE_TABLES: &str = include_str!("data/create_db.sql"); const ZONES_TXT: &str = include_str!("data/zones.txt"); 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_TRUNCATE_BLOCKS: &str = "DELETE FROM blocks WHERE id >= ?;"; const SQL_TRUNCATE_DOMAINS: &str = "DELETE FROM domains WHERE id >= ?;"; const SQL_ADD_DOMAIN: &str = "INSERT INTO domains (id, timestamp, identity, confirmation, data, signing, encryption) 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 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_OWNER_BY_ID: &str = "SELECT signing, timestamp FROM domains WHERE id < ? AND identity = ? ORDER BY id DESC LIMIT 1;"; const SQL_GET_DOMAIN_BY_ID: &str = "SELECT * FROM domains WHERE identity = ? AND id < ? ORDER BY id DESC LIMIT 1;"; const SQL_GET_DOMAINS_BY_KEY: &str = "SELECT timestamp, identity, data, signing FROM domains WHERE signing = ? ORDER BY id;"; const SQL_GET_DOMAINS_COUNT: &str = "SELECT count(DISTINCT identity) FROM domains;"; const SQL_GET_USERS_COUNT: &str = "SELECT count(DISTINCT pub_key) FROM blocks;"; const SQL_GET_USER_BLOCK_COUNT: &str = "SELECT count(pub_key) FROM blocks WHERE pub_key = ? AND id < ?"; const SQL_GET_DOMAIN_UPDATE_TIME: &str = "SELECT domains.timestamp FROM blocks JOIN domains ON blocks.id = domains.id WHERE difficulty >= 23 AND identity = ? ORDER BY domains.id DESC LIMIT 1;"; const SQL_GET_OPTIONS: &str = "SELECT * FROM options;"; lazy_static! { static ref WRONG_HASHES: Vec = vec![ Bytes::from_bytes(&from_hex("5B2D63CD8BD854B23A34A49AD850BF520BDD8D4514F9B20A3DF01430A59F0000").unwrap()), Bytes::from_bytes(&from_hex("4448E0582878FCB982C0DDAFEB441A03A30FB62FA6ECD1EA4D51C29A30980000").unwrap()) ]; } /// Max possible block index const MAX: u64 = i64::MAX as u64; pub struct Chain { origin: Bytes, last_block: Option, last_full_block: Option, max_height: u64, db: Connection, zones: Vec, signers: RefCell } impl Chain { pub fn new(settings: &Settings, db_name: &str) -> Self { let origin = settings.get_origin(); let db = sqlite::open(db_name).expect("Unable to open blockchain DB"); let zones = Self::load_zones(); let mut chain = Chain { origin, last_block: None, last_full_block: None, max_height: 0, db, zones, signers: SignersCache::new() }; chain.init_db(); chain } /// Reads options from DB or initializes and writes them to DB if not found fn init_db(&mut self) { let options = self.get_options(); if !self.origin.is_zero() && !options.origin.is_empty() && self.origin.to_string() != options.origin { self.clear_db(); } #[allow(clippy::absurd_extreme_comparisons)] if options.version < DB_VERSION { self.migrate_db(options.version, DB_VERSION); } // Trying to get last block from DB to check its version // If some block loaded we check its version and determine if we need some migration if let Some(block) = self.load_last_block() { // 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(MAX, None); } } } pub fn check_chain(&mut self, count: u64) { let height = self.get_height(); let start = if height > count { info!("Checking last {} blocks...", count); height - count + 1 } else { info!("Local blockchain height is {}, starting full blockchain check...", height); 1 }; let mut last_block: Option = None; let mut last_full_block: Option = None; if start > 1 { last_block = self.get_block(start - 1); if let Some(last) = &last_block { last_full_block = match &last.transaction { None => self.get_last_full_block(last.index, None), Some(_) => Some(last.clone()) }; } } for id in start..=height { debug!("Checking block {}", id); let block = self.get_block(id); match block { 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); } debug!("Block {} with hash {:?} is good!", block.index, &block.hash); last_block = Some(block); continue; } if WRONG_HASHES.contains(&block.hash) { error!("Block {} has bad hash:\n{:?}", block.index, &block); info!("Truncating database from block {}...", block.index); if let Err(e) = self.truncate_db_from_block(block.index) { error!("{}", e); panic!("Error truncating database! Please, delete 'blockchain.db' and restart."); } break; } //let last = self.last_block.clone().unwrap(); if self.check_block(&block, &last_block, &last_full_block) != Good { error!("Block {} is bad:\n{:?}", block.index, &block); info!("Truncating database from block {}...", block.index); match self.truncate_db_from_block(block.index) { Ok(_) => {} Err(e) => { error!("{}", e); panic!("Error truncating database! Please, delete 'blockchain.db' and restart."); } } break; } debug!("Block {} with hash {:?} is good!", block.index, &block.hash); if block.transaction.is_some() { self.last_full_block = Some(block.clone()); } if block.transaction.is_some() { last_full_block = Some(block.clone()); } last_block = Some(block); } } } self.last_block = self.load_last_block(); self.last_full_block = self.get_last_full_block(MAX, None); debug!("Last block after chain check: {:?}", &self.last_block); } fn truncate_db_from_block(&mut self, index: u64) -> sqlite::Result { let mut statement = self.db.prepare(SQL_TRUNCATE_BLOCKS)?; statement.bind((1, index as i64))?; statement.next()?; let mut statement = self.db.prepare(SQL_TRUNCATE_DOMAINS)?; statement.bind((1, index as i64))?; statement.next() } fn load_last_block(&mut self) -> 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!"); panic!(); } Some(block) => { debug!("Loaded last block: {:?}", &block); result = Some(block); break; } } } result } Err(e) => { info!("No blockchain database found. Creating new. {}", e); self.db.execute(SQL_CREATE_TABLES).expect("Error creating DB tables"); None } } } fn migrate_db(&mut self, from: u32, to: u32) { debug!("Migrating DB from {} to {}", from, to); } fn clear_db(&mut self) { warn!("Clearing DB"); // We cannot close DB connection and recreate file, // therefore we switch our db to temporary file, delete main DB and switch back. // I know that this is a crutch, but this way I don't need to use Option :) self.db = sqlite::open(TEMP_DB_NAME).expect("Unable to open temporary blockchain DB"); let file = Path::new(DB_NAME); if fs::remove_file(&file).is_err() { panic!("Unable to remove database!"); } self.db = sqlite::open(DB_NAME).expect("Unable to open blockchain DB"); let file = Path::new(TEMP_DB_NAME); let _ = fs::remove_file(&file).is_err(); } fn get_options(&self) -> Options { let mut options = Options::empty(); if let Ok(mut statement) = self.db.prepare(SQL_GET_OPTIONS) { while let State::Row = statement.next().unwrap() { let name: String = statement.read(0).unwrap(); let value: String = statement.read(1).unwrap(); match name.as_ref() { "origin" => options.origin = value, "version" => options.version = value.parse().unwrap(), _ => {} } } } options } pub fn add_block(&mut self, block: Block) { debug!("Adding block:\n{:?}", &block); let index = block.index; let timestamp = block.timestamp; let owner = block.pub_key.clone(); self.last_block = Some(block.clone()); if block.transaction.is_some() { self.last_full_block = Some(block.clone()); } let transaction = block.transaction.clone(); if self.add_block_to_table(block).is_ok() { if let Some(mut transaction) = transaction { if transaction.signing.is_empty() { transaction.signing = owner; } self.add_transaction_to_table(index, timestamp, &transaction).expect("Error adding transaction"); } } } pub fn replace_block(&mut self, block: Block) -> sqlite::Result<()> { info!("Replacing block {} with:\n{:?}", block.index, &block); self.signers.borrow_mut().clear(); self.truncate_db_from_block(block.index)?; self.add_block(block); Ok(()) } pub fn get_sign_block(&self, keys: &[Keystore]) -> Option<(Block, Keystore)> { if self.get_height() < BLOCK_SIGNERS_START { trace!("Too early to start block signings"); return None; } if keys.is_empty() { trace!("We can't sign blocks without keys"); return None; } if self.get_height() < self.get_max_height() { trace!("No signing while syncing"); return None; } let block = match self.last_full_block { None => { return None; } Some(ref block) => block.clone() }; // TODO maybe make some config option to mine signing blocks above? let sign_count = self.get_height() - block.index; if sign_count >= BLOCK_SIGNERS_MIN { trace!("Block {} has enough signing blocks", block.index); return None; } if let Some(block) = &self.last_block { if block.timestamp + 60 > Utc::now().timestamp() { info!("Waiting for other blocks before signing."); return None; } } let (last_hash, last_index) = match &self.last_block { Some(block) => (block.hash.clone(), block.index), None => { return None; } }; let signers: HashSet = self.get_block_signers(&block).into_iter().collect(); let mut rng = rand::thread_rng(); let keystore = keys .iter() .filter(|keystore| signers.contains(&keystore.get_public())) .filter(|keystore| { for index in block.index..=self.get_height() { let b = self.get_block(index).unwrap(); if b.pub_key == keystore.get_public() { debug!("We already mined signing block for block {} by {:?}", block.index, &b.pub_key); return false; } } true }) .choose(&mut rng); if let Some(keystore) = keystore { info!("We have an honor to mine signing block!"); let mut block = Block::new(None, Bytes::default(), last_hash, SIGNER_DIFFICULTY); block.index = last_index + 1; return Some((block, keystore.clone())); } 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.get_height() - full_block.index; if sign_count >= BLOCK_SIGNERS_MIN { return None; } if let Some(last) = &self.last_block { block.index = last.index + 1; block.prev_block_hash = last.hash.clone(); return Some(block); } } None } pub fn is_waiting_signers(&self) -> bool { if self.get_height() < BLOCK_SIGNERS_START { return false; } if let Some(full_block) = &self.last_full_block { let sign_count = self.get_height() - full_block.index; if sign_count < BLOCK_SIGNERS_MIN { return true; } } false } /// 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_slice()))?; statement.bind((9, block.hash.as_slice()))?; statement.bind((10, block.pub_key.as_slice()))?; statement.bind((11, block.signature.as_slice()))?; statement.next() } /// Adds transaction to transactions table fn add_transaction_to_table(&mut self, index: u64, timestamp: i64, t: &Transaction) -> sqlite::Result { let sql = match t.class.as_ref() { CLASS_DOMAIN => SQL_ADD_DOMAIN, CLASS_ORIGIN => return Ok(State::Done), _ => return Err(sqlite::Error { code: None, message: None }) }; let mut statement = self.db.prepare(sql)?; statement.bind((1, index as i64))?; statement.bind((2, timestamp))?; statement.bind((3, t.identity.as_slice()))?; statement.bind((4, t.confirmation.as_slice()))?; statement.bind((5, t.data.as_ref() as &str))?; statement.bind((6, t.signing.as_slice()))?; statement.bind((7, t.encryption.as_slice()))?; statement.next() } pub fn get_block(&self, index: u64) -> Option { match self.db.prepare(SQL_GET_BLOCK_BY_ID) { Ok(mut statement) => { statement.bind((1, index as i64)).expect("Error in bind"); if 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!("Loaded block: {:?}", &block); Some(block) } }; } None } Err(_) => { warn!("Can't find requested block {}", index); None } } } /// Gets last block that has a Transaction within pub fn get_last_full_block(&self, before: u64, pub_key: Option<&[u8]>) -> Option { if let Some(block) = &self.last_full_block { if block.index < before { match pub_key { None => { return Some(block.clone()); } Some(key) => { if block.pub_key.deref().eq(key) { return Some(block.clone()); } } } } } let mut statement = match pub_key { None => { let mut statement = self.db.prepare(SQL_GET_LAST_FULL_BLOCK).expect("Unable to prepare"); statement.bind((1, before 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, before as i64)).expect("Unable to bind"); statement.bind((2, pub_key)).expect("Unable to bind"); statement } }; if 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 } pub fn can_mine_domain(&self, height: u64, domain: &str, pub_key: &Bytes) -> MineResult { let name = domain.to_lowercase(); if !check_domain(&name, true) { return WrongName; } let zone = get_domain_zone(&name); if !self.is_available_zone(&zone) { return WrongZone; } let (transaction, state) = self.get_domain_transaction_and_state(&name); if let Some(transaction) = transaction { let owner = transaction.signing.eq(pub_key); match state { DomainState::NotFound => {} DomainState::Alive { .. } => if !owner { return NotOwned; }, DomainState::Expired { .. } => if !owner { return NotOwned; }, DomainState::Free { .. } => {} } } let identity_hash = hash_identity(&name, None); self.can_mine_identity(&identity_hash, height, Utc::now().timestamp(), pub_key) } fn can_mine_identity(&self, identity_hash: &Bytes, height: u64, time: i64, pub_key: &Bytes) -> MineResult { if let Some(last) = self.get_last_full_block(height, Some(pub_key)) { // If this domain/identity is new let want_new_domain = !self.is_domain_in_blockchain(height, &identity_hash); // And the user hasn't mined a domain in previous 24h, then we allow her to mine let time = last.timestamp + NEW_DOMAINS_INTERVAL - time; if want_new_domain && time > 0 { return Cooldown { time }; } } Fine } /// Checks if this identity is free or is owned by the same pub_key pub fn is_id_available(&self, height: u64, time: i64, identity: &Bytes, public_key: &Bytes) -> bool { let (transaction, state) = self.get_identity_transaction_and_state(identity, height, time); if transaction.is_none() { return true; } if transaction.unwrap().signing.eq(public_key) { return true; } match state { DomainState::NotFound => true, DomainState::Alive { .. } => false, DomainState::Expired { .. } => false, DomainState::Free { .. } => true } } pub fn get_zones(&self) -> &Vec { &self.zones } fn load_zones() -> Vec { let mut result: Vec = Vec::new(); let zones_text = ZONES_TXT.replace("\r", ""); let zones: Vec<_> = zones_text.split('\n').collect(); for zone in zones { let yggdrasil = zone == "ygg" || zone == "anon"; result.push(ZoneData { name: zone.to_owned(), yggdrasil }) } result } pub fn get_zones_hash() -> Bytes { Bytes::from_bytes(hash_sha256(ZONES_TXT.as_bytes()).as_slice()) } /// Checks if some zone exists in our blockchain pub fn is_available_zone(&self, zone: &str) -> bool { for z in &self.zones { if z.name == zone { return true; } } false } /// Checks if some id exists in our blockchain pub fn is_domain_in_blockchain(&self, height: u64, id: &Bytes) -> bool { // Checking for existing domain in DB let mut statement = self.db.prepare(SQL_GET_DOMAIN_OWNER_BY_ID).unwrap(); statement.bind((1, height as i64)).expect("Error in bind"); statement.bind((2, id.as_slice())).expect("Error in bind"); if let State::Row = statement.next().unwrap() { // If there is such an ID return true; } false } pub fn get_domain_renewal_time(&self, time: i64, identity_hash: &Bytes) -> Option { let mut statement = self.db.prepare(SQL_GET_DOMAIN_UPDATE_TIME).unwrap(); statement.bind((1, identity_hash.as_slice())).expect("Error in bind"); if let State::Row = statement.next().unwrap() { let timestamp: i64 = statement.read(0).unwrap(); if timestamp < time - DOMAIN_LIFETIME { // This domain is too old return None; } return Some(timestamp); } None } pub fn get_domain_update_time(&self, identity_hash: &Bytes, height: u64, time: i64) -> Option { let mut statement = self.db.prepare(SQL_GET_DOMAIN_BY_ID).unwrap(); statement.bind((1, identity_hash.as_slice())).expect("Error in bind"); statement.bind((2, height as i64)).expect("Error in bind"); if let State::Row = statement.next().unwrap() { let timestamp: i64 = statement.read(1).unwrap(); if timestamp < time - DOMAIN_LIFETIME { // This domain is too old return None; } return Some(timestamp); } None } pub fn get_identity_transaction_and_state(&self, identity_hash: &Bytes, height: u64, time: i64) -> (Option, DomainState) { let mut statement = self.db.prepare(SQL_GET_DOMAIN_BY_ID).unwrap(); statement.bind((1, identity_hash.as_slice())).expect("Error in bind"); statement.bind((2, height as i64)).expect("Error in bind"); if let State::Row = statement.next().unwrap() { let timestamp: i64 = statement.read(1).unwrap(); // Determine current state of the domain let state = if timestamp + DOMAIN_LIFETIME >= time { DomainState::Alive { renewed_time: timestamp, until: timestamp + DOMAIN_LIFETIME } } else if timestamp + DOMAIN_LIFETIME + DOMAIN_RENEW_TIME >= time { DomainState::Expired { renewed_time: timestamp, until: timestamp + DOMAIN_LIFETIME + DOMAIN_RENEW_TIME } } else { DomainState::Free { renewed_time: timestamp } }; let identity = Bytes::from_bytes(&statement.read::, usize>(2).unwrap()); let confirmation = Bytes::from_bytes(&statement.read::, usize>(3).unwrap()); let class = String::from(CLASS_DOMAIN); let data: String = statement.read(4).unwrap(); let signing = Bytes::from_bytes(&statement.read::, usize>(5).unwrap()); let encryption = Bytes::from_bytes(&statement.read::, usize>(6).unwrap()); let transaction = Transaction { identity, confirmation, class, data, signing, encryption }; return (Some(transaction), state); } (None, DomainState::NotFound) } /// Gets full Transaction info for any domain. Used by DNS part. pub fn get_domain_transaction_and_state(&self, domain: &str) -> (Option, DomainState) { if domain.is_empty() { return (None, DomainState::NotFound); } let identity_hash = hash_identity(domain, None); let (transaction, state) = self.get_identity_transaction_and_state(&identity_hash, self.get_height(), Utc::now().timestamp()); if let Some(transaction) = transaction { debug!("Found transaction for domain {}: {:?}", domain, &transaction); if transaction.check_identity(domain) { return (Some(transaction), state); } } (None, DomainState::NotFound) } pub fn get_domain_info(&self, domain: &str) -> Option { match self.get_domain_transaction_and_state(domain) { (None, _) => None, (Some(transaction), state) => { if matches!(state, DomainState::Alive {..}) { Some(transaction.data) } else { None } } } } pub fn get_domains_count(&self) -> i64 { let mut statement = self.db.prepare(SQL_GET_DOMAINS_COUNT).unwrap(); if let State::Row = statement.next().unwrap() { return statement.read::(0).unwrap(); } 0 } pub fn get_users_count(&self) -> i64 { let mut statement = self.db.prepare(SQL_GET_USERS_COUNT).unwrap(); if let State::Row = statement.next().unwrap() { return statement.read::(0).unwrap(); } 0 } pub fn get_user_block_count(&self, pub_key: &Bytes, max_height: u64) -> i64 { let mut statement = self.db.prepare(SQL_GET_USER_BLOCK_COUNT).unwrap(); statement.bind((1, pub_key.as_slice())).expect("Error in bind"); statement.bind((2, max_height as i64)).expect("Error in bind"); if let State::Row = statement.next().unwrap() { return statement.read::(0).unwrap(); } 0i64 } pub fn get_my_domains(&self, keystore: Option<&Keystore>) -> HashMap { if keystore.is_none() { return HashMap::new(); } let mut result = HashMap::new(); let keystore = keystore.unwrap(); let pub_key = keystore.get_public(); let mut statement = self.db.prepare(SQL_GET_DOMAINS_BY_KEY).unwrap(); statement.bind((1, pub_key.as_slice())).expect("Error in bind"); let height = self.get_height(); while let State::Row = statement.next().unwrap() { let timestamp = statement.read::(0).unwrap(); let identity = Bytes::from_bytes(&statement.read::, usize>(1).unwrap()); let data = statement.read::(2).unwrap(); let signing = Bytes::from_bytes(&statement.read::, usize>(3).unwrap()); // Get the last transaction for this id and check if it is still ours // TODO use state to show it in UI let (transaction, _state) = self.get_identity_transaction_and_state(&identity, height, Utc::now().timestamp()); if let Some(transaction) = transaction { if transaction.signing != signing { trace!("Identity {:?} is not ours anymore, skipping", &identity); continue; } } //trace!("Found transaction for domain {:?}", &transaction); if let Ok(data) = serde_json::from_str::(&data) { let decrypted = keystore.decrypt(data.encrypted.as_slice()); let mut domain = String::from_utf8(decrypted.to_vec()).unwrap(); if domain.is_empty() { domain = String::from("unknown"); } // TODO optimize match self.get_domain_renewal_time(timestamp, &identity) { None => result.insert(identity, (domain, timestamp, data)), Some(t) => result.insert(identity, (domain, t, data)) }; } } result } pub fn last_block(&self) -> Option { self.last_block.clone() } pub fn get_height(&self) -> u64 { match self.last_block { None => 0u64, Some(ref block) => block.index } } pub fn get_last_hash(&self) -> Bytes { match &self.last_block { None => Bytes::default(), Some(block) => block.hash.clone() } } pub fn get_soa_serial(&self) -> u32 { match &self.last_full_block { None => 0, Some(block) => block.timestamp as u32 } } pub fn next_allowed_full_block(&self) -> u64 { match self.last_full_block { None => self.get_height() + 1, Some(ref block) => { if block.index < BLOCK_SIGNERS_START { self.get_height() + 1 } else { max(block.index + BLOCK_SIGNERS_MIN, self.get_height() + 1) } } } } pub fn get_max_height(&self) -> u64 { self.max_height } pub fn update_max_height(&mut self, height: u64) { self.max_height = height; } pub fn check_new_block(&self, block: &Block) -> BlockQuality { self.check_block(block, &self.last_block, &self.last_full_block) } /// Check if this block can be added to our blockchain pub fn check_block(&self, block: &Block, last_block: &Option, last_full_block: &Option) -> BlockQuality { if block.version > CHAIN_VERSION { warn!("Ignoring block from unsupported version:\n{:?}", &block); return Bad; } if WRONG_HASHES.contains(&block.hash) { warn!("Got block with hash from wrong hashes."); return Bad; } let timestamp = Utc::now().timestamp(); if block.timestamp > timestamp + 60 { warn!("Ignoring block from the future:\n{:?}", &block); return Bad; } if let Some(last) = last_block { if block.index > last.index + 1 { debug!("Got future block {}", block.index); return Future; } } let difficulty = match &block.transaction { None => { if block.index == 1 { ORIGIN_DIFFICULTY } else { SIGNER_DIFFICULTY } } Some(t) => self.get_difficulty_for_transaction(t, block.index, block.timestamp) }; if block.difficulty < difficulty { warn!("Block difficulty is lower than needed: {} < {}", block.difficulty, difficulty); return Bad; } if hash_difficulty(&block.hash) < block.difficulty { warn!("Ignoring block with low difficulty:\n{:?}", &block); return Bad; } if !check_block_hash(block) { warn!("Ignoring block with wrong hash:\n{:?}", &block); return Bad; } if !check_block_signature(block) { warn!("Ignoring block with wrong signature:\n{:?}", &block); return Bad; } if let Some(prev_block) = self.get_block(block.index - 1) { // https://en.bitcoinwiki.org/wiki/Limited_Confidence_Proof-of-Activity if block.prev_block_hash.ne(&prev_block.hash) { if block.index < self.get_height() - LIMITED_CONFIDENCE_DEPTH { warn!("Ignoring block from shorter chain:\n{:?}", &block); return Bad; } else { warn!("Rewinding chain of block with wrong previous hash:\n{:?}", &block); return Rewind; } } } if let Some(transaction) = &block.transaction { if !check_public_key_strength(&block.pub_key, KEYSTORE_DIFFICULTY) { warn!("Ignoring block with weak public key:\n{:?}", &block); return Bad; } // If this domain is not available to this public key if !self.is_id_available(block.index - 1, timestamp, &transaction.identity, &block.pub_key) { warn!("Block {:?} is trying to spoof an identity!", &block); return Bad; } if self.can_mine_identity(&transaction.identity, block.index, block.timestamp, &block.pub_key) != Fine { warn!("Block {:?} is mined too early!", &block); return Bad; } // Check if yggdrasil only property of zone is not violated if let Some(block_data) = transaction.get_domain_data() { if block_data.records.len() > MAX_RECORDS { warn!("Someone mined too many records!"); return Bad; } let zones = self.get_zones(); for z in zones { if z.name == block_data.zone && z.yggdrasil { for record in &block_data.records { if !is_yggdrasil_record(record) { warn!("Someone mined domain with clearnet records for Yggdrasil only zone!"); return Bad; } if let Some(data) = record.get_data() { if data.len() > MAX_DATA_LEN { warn!("Someone mined too long record!"); return Bad; } } } } } } } match 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.ne(&self.origin) { warn!("Mining gave us a bad block:\n{:?}", &block); return Bad; } } 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!("Block {} arrived too early.", block.index); return Future; } if block.index > BLOCK_SIGNERS_START { // If this block is main, signed part of blockchain if !self.is_good_sign_block(block, last_full_block) { return Bad; } } if block.index <= last_block.index { if block.index == last_block.index && last_block.hash == block.hash { debug!("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.ne(&block.hash) { warn!("Got forked block {} with hash {:?} instead of {:?}", block.index, block.hash, last_block.hash); Fork } else { debug!("Ignoring block {}, we already have it", block.index); Twin }; } } else if block.prev_block_hash.ne(&last_block.hash) { warn!("Ignoring block with wrong previous hash:\n{:?}", &block); return Bad; } } } Good } /// Checks if this block is a good signature block fn is_good_sign_block(&self, block: &Block, last_full_block: &Option) -> bool { // If this is not a signing block if block.transaction.is_some() { return true; } if let Some(full_block) = &last_full_block { let sign_count = self.get_height() - full_block.index; if sign_count < BLOCK_SIGNERS_MIN { // Last full block is not locked enough if block.index > full_block.index && 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) { return false; } } else if sign_count < BLOCK_SIGNERS_ALL && block.transaction.is_none() && !self.is_good_signer_for_block(block, full_block) { 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) -> bool { // If we got a 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 } fn get_difficulty_for_transaction(&self, transaction: &Transaction, height: u64, time: i64) -> u32 { match transaction.class.as_ref() { CLASS_DOMAIN => { // If this domain is already in blockchain we approve slightly smaller difficulty let discount = self.get_identity_discount(&transaction.identity, false, height, time); // TODO move this check somewhere appropriate return match serde_json::from_str::(&transaction.data) { Ok(_) => DOMAIN_DIFFICULTY - discount, Err(_) => { warn!("Error parsing DomainData from {:?}", transaction); u32::MAX } } } CLASS_ORIGIN => ORIGIN_DIFFICULTY, _ => u32::MAX } } pub fn get_identity_discount(&self, identity: &Bytes, renewal: bool, height: u64, time: i64) -> u32 { match self.get_domain_update_time(identity, height, time) { None => 0u32, Some(timestamp) => { if renewal || self.get_height() < BLOCKS_WITHOUT_DISCOUNT { return 1; } // Weeks since this domain was changed + 1, but not more than 7 weeks. // So max discount will be 8 bits of difficulty. (min((time - timestamp) / ONE_WEEK, 7i64) + 1) as u32 } } } /// 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 minimum_block_count = if block.index < 855 { 1i64 } else { block.index as i64 / 100 }; let mut result = Vec::new(); if block.index < BLOCK_SIGNERS_START || self.get_height() < block.index { return result; } assert!(block.transaction.is_some()); if self.signers.borrow().has_signers_for(block.index) { return self.signers.borrow().signers.clone(); } let mut set = HashSet::new(); let mut tail = block.signature.get_tail_u64(); let mut count = 1; let mut mitigated = false; let window = block.index - 1; // Without the last block trace!("Calculating signers, tail: {}, window: {}", tail, window); while set.len() < BLOCK_SIGNERS_ALL as usize { let index = (tail.wrapping_mul(count) % window) + 1; // We want it to start from 1 if let Some(b) = self.get_block(index) { let block_count = self.get_user_block_count(&b.pub_key, block.index); if block_count < minimum_block_count { //debug!("Skipping public key {:?} from block {}, it has too little {} blocks", &b.pub_key, index, block_count); count += 1; continue; } if b.pub_key != block.pub_key && !set.contains(&b.pub_key) { result.push(b.pub_key.clone()); set.insert(b.pub_key); } } count += 1; if !mitigated && count > 5000 { tail = tail / 13; mitigated = true; } } debug!("Got signers for block {}: {:?}, loop count {}", block.index, &result, count); let mut signers = self.signers.borrow_mut(); signers.index = block.index; signers.signers = result.clone(); result } 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 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::, usize>(7).unwrap().as_slice()); let hash = Bytes::from_bytes(statement.read::, usize>(8).unwrap().as_slice()); let pub_key = Bytes::from_bytes(statement.read::, usize>(9).unwrap().as_slice()); let signature = Bytes::from_bytes(statement.read::, usize>(10).unwrap().as_slice()); Some(Block::from_all_params(index, timestamp, version, difficulty, random, nonce, prev_block_hash, hash, pub_key, signature, transaction)) } } struct SignersCache { index: u64, signers: Vec } impl SignersCache { pub fn new() -> RefCell { let cache = SignersCache { index: 0, signers: Vec::new() }; RefCell::new(cache) } pub fn has_signers_for(&self, index: u64) -> bool { self.index == index && !self.signers.is_empty() } pub fn clear(&mut self) { self.index = 0; self.signers.clear(); } } #[cfg(test)] pub mod tests { use log::LevelFilter; #[allow(unused_imports)] use log::{debug, error, info, trace, warn}; use simplelog::{ColorChoice, ConfigBuilder, TermLogger, TerminalMode, LevelPadding, format_description}; use crate::{Block, Chain, Settings}; fn init_logger() { let config = ConfigBuilder::new() .add_filter_ignore_str("mio::poll") .set_thread_level(LevelFilter::Off) .set_location_level(LevelFilter::Off) .set_target_level(LevelFilter::Error) .set_level_padding(LevelPadding::Right) .set_time_level(LevelFilter::Error) .set_time_format_custom(format_description!("[hour]:[minute]:[second].[subsecond digits:3]")) .build(); if let Err(e) = TermLogger::init(LevelFilter::Trace, config, TerminalMode::Stdout, ColorChoice::Auto) { println!("Unable to initialize logger!\n{}", e); } } #[test] pub fn load_and_check() { init_logger(); let settings = Settings::default(); let mut chain = Chain::new(&settings, "./tests/blockchain.db"); chain.check_chain(u64::MAX); assert_eq!(chain.get_height(), 149); } #[test] pub fn check_serde() { let settings = Settings::default(); let chain = Chain::new(&settings, "./tests/blockchain.db"); // Check the first block, its transaction doesn't have identity let block = chain.get_block(1).unwrap(); let buf = serde_cbor::to_vec(&block).unwrap(); let block2: Block = serde_cbor::from_slice(&buf[..]).unwrap(); assert_eq!(block, block2); // Check second block, it is common "full" block with domain let block = chain.get_block(2).unwrap(); let buf = serde_cbor::to_vec(&block).unwrap(); let block2: Block = serde_cbor::from_slice(&buf[..]).unwrap(); assert_eq!(block, block2); // Check block 36, it is an "empty" block, used to sign full blocks let block = chain.get_block(36).unwrap(); let buf = serde_cbor::to_vec(&block).unwrap(); let block2: Block = serde_cbor::from_slice(&buf[..]).unwrap(); assert_eq!(block, block2); } }