use std::cell::RefCell; use std::collections::HashSet; use chrono::Utc; #[allow(unused_imports)] use log::{debug, error, info, trace, warn}; use sqlite::{Connection, State, Statement}; use crate::{Block, Bytes, Keystore, Transaction, check_domain, get_domain_zone}; use crate::commons::constants::*; use crate::blockchain::enums::{BlockQuality, MineResult}; use crate::blockchain::enums::BlockQuality::*; use crate::blockchain::hash_utils::*; use crate::settings::Settings; use crate::keys::check_public_key_strength; use std::cmp::{min, max}; use crate::blockchain::transaction::{ZoneData, DomainData}; use std::ops::Deref; use crate::dns::protocol::DnsRecord; use crate::blockchain::enums::MineResult::*; const DB_NAME: &str = "blockchain.db"; const SQL_CREATE_TABLES: &str = "CREATE TABLE blocks ( 'id' BIGINT NOT NULL PRIMARY KEY, 'timestamp' BIGINT NOT NULL, 'version' INT, 'difficulty' INTEGER, 'random' INTEGER, 'nonce' INTEGER, 'transaction' TEXT, 'prev_block_hash' BINARY, 'hash' BINARY, 'pub_key' BINARY, 'signature' BINARY); CREATE INDEX block_index ON blocks (id); CREATE TABLE transactions (id INTEGER PRIMARY KEY AUTOINCREMENT, identity BINARY, confirmation BINARY, method TEXT, data TEXT, pub_key BINARY); CREATE INDEX ids ON transactions (identity);"; 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_ADD_TRANSACTION: &str = "INSERT INTO transactions (identity, confirmation, method, data, pub_key) 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 `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_PUBLIC_KEY_BY_ID: &str = "SELECT pub_key FROM transactions WHERE identity = ? ORDER BY id DESC LIMIT 1;"; const SQL_GET_ID_BY_ID: &str = "SELECT identity FROM transactions WHERE identity = ? ORDER BY id DESC LIMIT 1;"; const SQL_GET_TRANSACTION_BY_ID: &str = "SELECT * FROM transactions WHERE identity = ? ORDER BY id DESC LIMIT 1;"; const SQL_GET_TRANSACTIONS_WITH_ZONE: &str = "SELECT data FROM transactions WHERE data LIKE '%difficulty%';"; pub struct Chain { origin: Bytes, pub version: u32, pub blocks: Vec, last_block: Option, last_full_block: Option, max_height: u64, db: Connection, zones: RefCell>, } impl Chain { pub fn new(settings: &Settings) -> Self { let origin = settings.get_origin(); let db = sqlite::open(DB_NAME).expect("Unable to open blockchain DB"); let mut chain = Chain { origin, version: CHAIN_VERSION, blocks: Vec::new(), last_block: None, last_full_block: None, max_height: 0, db, zones: RefCell::new(HashSet::new()), }; chain.init_db(); chain } /// Reads options from DB or initializes and writes them to DB if not found fn init_db(&mut self) { // Trying to get last block from DB to check its version let block: 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(_) => { info!("No blockchain database found. Creating new."); self.db.execute(SQL_CREATE_TABLES).expect("Error creating blocks table"); None } }; // If some block loaded we check its version and determine if we need some migration if let Some(block) = block { self.max_height = block.index; if self.version > block.version { self.migrate_db(block.version, self.version); } else if self.version < block.version { error!("Version downgrade {}->{} is not supported!", block.version, self.version); panic!(); } // 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(None); } } } fn migrate_db(&mut self, from: u32, to: u32) { debug!("Migrating DB from {} to {}", from, to); } pub fn add_block(&mut self, block: Block) { info!("Adding block:\n{:?}", &block); self.blocks.push(block.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(transaction) = transaction { self.add_transaction_to_table(&transaction).expect("Error adding transaction"); } } } /// 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)?; statement.bind(9, &**block.hash)?; statement.bind(10, &**block.pub_key)?; statement.bind(11, &**block.signature)?; statement.next() } /// Adds transaction to transactions table fn add_transaction_to_table(&mut self, t: &Transaction) -> sqlite::Result { let mut statement = self.db.prepare(SQL_ADD_TRANSACTION)?; statement.bind(1, &**t.identity)?; statement.bind(2, &**t.confirmation)?; statement.bind(3, t.method.as_ref() as &str)?; statement.bind(4, t.data.as_ref() as &str)?; statement.bind(5, &**t.pub_key)?; 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"); while 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, pub_key: Option<&[u8]>) -> Option { if let Some(block) = &self.last_full_block { 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 => { self.db.prepare(SQL_GET_LAST_FULL_BLOCK).expect("Unable to prepare") } 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 } }; while 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 } /// Checks if any domain is available to mine for this client (pub_key) pub fn is_domain_available(&self, domain: &str, keystore: &Keystore) -> bool { if domain.is_empty() { return false; } let identity_hash = hash_identity(domain, None); if !self.is_id_available(&identity_hash, &keystore.get_public()) { return false; } let parts: Vec<&str> = domain.rsplitn(2, ".").collect(); if parts.len() > 1 { // We do not support third level domains if parts.last().unwrap().contains(".") { return false; } return self.is_zone_in_blockchain(parts.first().unwrap()); } true } /// Checks if this identity is free or is owned by the same pub_key pub fn is_id_available(&self, identity: &Bytes, public_key: &Bytes) -> bool { let mut statement = self.db.prepare(SQL_GET_PUBLIC_KEY_BY_ID).unwrap(); statement.bind(1, &***identity).expect("Error in bind"); while let State::Row = statement.next().unwrap() { let pub_key = Bytes::from_bytes(&statement.read::>(0).unwrap()); if !pub_key.eq(public_key) { return false; } } true } pub fn get_zones(&self) -> Vec { let mut result = Vec::new(); match self.db.prepare(SQL_GET_TRANSACTIONS_WITH_ZONE) { Ok(mut statement) => { while statement.next().unwrap() == State::Row { let data = statement.read::(0).unwrap(); info!("Got zone data {}", &data); if let Ok(zone_data) = serde_json::from_str(&data) { result.push(zone_data); } } } Err(e) => { warn!("Can't get zones from DB {}", e); } } result } /// Checks if some zone exists in our blockchain pub fn is_zone_in_blockchain(&self, zone: &str) -> bool { if self.zones.borrow().contains(zone) { return true; } // Checking for existing zone in DB let identity_hash = hash_identity(zone, None); if self.is_id_in_blockchain(&identity_hash) { // If there is such a zone self.zones.borrow_mut().insert(zone.to_owned()); return true; } false } /// Checks if some id exists in our blockchain pub fn is_id_in_blockchain(&self, id: &Bytes) -> bool { // Checking for existing zone in DB let mut statement = self.db.prepare(SQL_GET_ID_BY_ID).unwrap(); statement.bind(1, &***id).expect("Error in bind"); while let State::Row = statement.next().unwrap() { // If there is such a zone return true; } false } pub fn can_mine_domain(&self, domain: &str, records: &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_zone_in_blockchain(&zone) { return WrongZone; } if let Some(transaction) = self.get_domain_transaction(&name) { if transaction.pub_key.ne(pub_key) { return NotOwned; } } if serde_json::from_str::>(&records).is_err() { return WrongData; } let identity_hash = hash_identity(&name, None); if let Some(last) = self.get_last_full_block(Some(&pub_key)) { let new_id = !self.is_id_in_blockchain(&identity_hash); let time = last.timestamp + FULL_BLOCKS_INTERVAL - Utc::now().timestamp(); if new_id && time > 0 { return Cooldown { time } } } Fine } /// Gets full Transaction info for any domain. Used by DNS part. pub fn get_domain_transaction(&self, domain: &str) -> Option { if domain.is_empty() { return None; } let identity_hash = hash_identity(domain, None); let mut statement = self.db.prepare(SQL_GET_TRANSACTION_BY_ID).unwrap(); statement.bind(1, &**identity_hash).expect("Error in bind"); while let State::Row = statement.next().unwrap() { let identity = Bytes::from_bytes(&statement.read::>(1).unwrap()); let confirmation = Bytes::from_bytes(&statement.read::>(2).unwrap()); let method = statement.read::(3).unwrap(); let data = statement.read::(4).unwrap(); let pub_key = Bytes::from_bytes(&statement.read::>(5).unwrap()); let transaction = Transaction { identity, confirmation, method, data, pub_key }; debug!("Found transaction for domain {}: {:?}", domain, &transaction); if transaction.check_identity(domain) { return Some(transaction); } } None } pub fn get_domain_info(&self, domain: &str) -> Option { match self.get_domain_transaction(domain) { None => { None } Some(transaction) => { Some(transaction.data) } } } pub fn get_zone_difficulty(&self, zone: &str) -> u32 { match self.get_domain_transaction(zone) { None => { u32::max_value() } Some(transaction) => { match serde_json::from_str::(&transaction.data) { Ok(data) => { data.difficulty } Err(_) => { warn!("Wrong data for zone {}!", zone); u32::max_value() } } } } } pub fn last_block(&self) -> Option { self.last_block.clone() } pub fn height(&self) -> u64 { match self.last_block { None => { 0u64 } Some(ref block) => { block.index } } } pub fn last_hash(&self) -> Bytes { match &self.last_block { None => { Bytes::default() } Some(block) => { block.hash.clone() } } } pub fn next_allowed_block(&self) -> u64 { match self.last_full_block { None => { self.height() + 1 } Some(ref block) => { if block.index < LOCKER_BLOCK_START { self.height() + 1 } else { max(block.index, self.height()) + LOCKER_BLOCK_SIGNS } } } } pub fn max_height(&self) -> u64 { self.max_height } pub fn update_max_height(&mut self, height: u64) { if height > self.max_height { self.max_height = height; } } /// Check if this block can be added to our blockchain pub fn check_new_block(&self, block: &Block) -> BlockQuality { let timestamp = Utc::now().timestamp(); if block.timestamp > timestamp { warn!("Ignoring block from the future:\n{:?}", &block); return Bad; } if !check_public_key_strength(&block.pub_key, KEYSTORE_DIFFICULTY) { warn!("Ignoring block with weak public key:\n{:?}", &block); return Bad; } let difficulty = match block.transaction { None => { LOCKER_DIFFICULTY } Some(_) => { BLOCK_DIFFICULTY } }; if block.difficulty < difficulty { warn!("Block difficulty is lower than needed"); return Bad; } if !hash_is_good(&block.hash, block.difficulty as usize) { warn!("Ignoring block with low difficulty:\n{:?}", &block); return Bad; } if !check_block_hash(block) { warn!("Block {:?} has wrong hash! Ignoring!", &block); return Bad; } if !check_block_signature(&block) { warn!("Block {:?} has wrong signature! Ignoring!", &block); return Bad; } if let Some(transaction) = &block.transaction { if !self.is_id_available(&transaction.identity, &block.pub_key) { 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); if new_id && last.timestamp + FULL_BLOCKS_INTERVAL > block.timestamp { warn!("Block {:?} is mined too early!", &block); return Bad; } } if let Ok(data) = serde_json::from_str::(&transaction.data) { if self.get_zone_difficulty(&data.zone) > block.difficulty { warn!("Block {:?} is mined with too low difficulty!", &block); return Bad; } } } match &self.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 != 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 is from the future, how is this possible?"); 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!("Someone mined full block over full block"); return Bad; } else { if self.check_block_for_lock(&block, full_block) == Bad { return Bad; } } } else if locker_blocks < LOCKER_BLOCK_LOCKERS && block.transaction.is_none() { if self.check_block_for_lock(&block, full_block) == Bad { return Bad; } } } } if block.index <= last_block.index { if last_block.hash == block.hash { warn!("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 != block.hash { warn!("Got forked block {} with hash {:?} instead of {:?}", block.index, block.hash, last_block.hash); Fork } else { warn!("Ignoring block {}, we already have it", block.index); Twin }; } } } } Good } fn check_block_for_lock(&self, block: &Block, full_block: &Block) -> BlockQuality { // If we got a locker/signing block let lockers: HashSet = self.get_block_lockers(full_block).into_iter().collect(); if !lockers.contains(&block.pub_key) { warn!("Ignoring block {}, as wrong locker", block.index); return Bad; } // If this locker's public key has already locked/signed that block we return error for i in (full_block.index + 1)..block.index { let locker = self.get_block(i).expect("Error in DB!"); if locker.pub_key == block.pub_key { warn!("Ignoring block {}, already locked by this key", block.index); return Bad; } } Good } /// Gets a public key of a node that needs to mine "locker" block above this block /// block - last full block pub fn get_block_lockers(&self, block: &Block) -> Vec { let mut result = Vec::new(); if block.index < LOCKER_BLOCK_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 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); 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()); set.insert(b.pub_key); } count += 1; } } trace!("Got lockers for block {}: {:?}", block.index, &result); 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::>(7).unwrap().as_slice()); let hash = Bytes::from_bytes(statement.read::>(8).unwrap().as_slice()); let pub_key = Bytes::from_bytes(statement.read::>(9).unwrap().as_slice()); let signature = Bytes::from_bytes(statement.read::>(10).unwrap().as_slice()); Some(Block::from_all_params(index, timestamp, version, difficulty, random, nonce, prev_block_hash, hash, pub_key, signature, transaction)) } }