Changed identity format in transactions, now it will be double Sha256.
Added new checks for forked blocks. Added options table. Added posibility to replace blocks in DB by more appropriate forks. Divided transactions table to domains and zones tables. Added a timestamp to domains and zones table, it will give us possibility to purge old domains. Changed difficulty check to check head and tail (sum of them) of the hash. Added encrypted (by Chacha20) domain name to DomainData, added contacts and owners vectors for it too. Added yggdrasil flag to ZoneData - it will restrict all IPs for domains in particular zone to Yggdrasil only. Changed difficulties of various block types. Added a temporary (for a run) unique ID to all handshakes. Start of signing blocks mining will be after 60 seconds after full block. Added mining status to statusbar.
This commit is contained in:
+13
-2
@@ -1,12 +1,11 @@
|
||||
extern crate serde;
|
||||
extern crate serde_json;
|
||||
extern crate num_bigint;
|
||||
extern crate num_traits;
|
||||
|
||||
use std::fmt::Debug;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use crate::bytes::Bytes;
|
||||
use crate::Transaction;
|
||||
use crate::blockchain::hash_utils::hash_difficulty;
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
|
||||
pub struct Block {
|
||||
@@ -68,4 +67,16 @@ impl Block {
|
||||
pub fn as_bytes(&self) -> Vec<u8> {
|
||||
Vec::from(serde_json::to_string(&self).unwrap().as_bytes())
|
||||
}
|
||||
|
||||
pub fn is_better_than(&self, other: &Block) -> bool {
|
||||
if self.transaction.is_none() && other.transaction.is_some() {
|
||||
return false;
|
||||
}
|
||||
if hash_difficulty(self.hash.as_slice()) < hash_difficulty(other.hash.as_slice()) {
|
||||
return false;
|
||||
}
|
||||
// TODO add more checks
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
+218
-116
@@ -1,5 +1,7 @@
|
||||
use std::cell::RefCell;
|
||||
use std::collections::{HashSet, HashMap};
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
use chrono::Utc;
|
||||
#[allow(unused_imports)]
|
||||
@@ -8,49 +10,40 @@ 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::types::{BlockQuality, MineResult, Options};
|
||||
use crate::blockchain::types::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::*;
|
||||
use crate::blockchain::types::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 TEMP_DB_NAME: &str = "temp.db";
|
||||
const SQL_CREATE_TABLES: &str = include_str!("sql/create_db.sql");
|
||||
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_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_ADD_TRANSACTION: &str = "INSERT INTO transactions (identity, confirmation, method, data, pub_key) VALUES (?, ?, ?, ?, ?)";
|
||||
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_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%';";
|
||||
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;";
|
||||
const SQL_GET_ZONES: &str = "SELECT data FROM zones;";
|
||||
|
||||
const SQL_GET_OPTIONS: &str = "SELECT * FROM options;";
|
||||
|
||||
pub struct Chain {
|
||||
origin: Bytes,
|
||||
pub version: u32,
|
||||
pub blocks: Vec<Block>,
|
||||
last_block: Option<Block>,
|
||||
last_full_block: Option<Block>,
|
||||
max_height: u64,
|
||||
@@ -63,22 +56,22 @@ impl Chain {
|
||||
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()),
|
||||
};
|
||||
let zones = RefCell::new(HashSet::new());
|
||||
let mut chain = Chain { origin, last_block: None, last_full_block: None, max_height: 0, db, zones };
|
||||
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() && !self.origin.is_zero() && self.origin.to_string() != options.origin {
|
||||
self.clear_db();
|
||||
}
|
||||
if options.version < DB_VERSION {
|
||||
self.migrate_db(options.version, DB_VERSION);
|
||||
}
|
||||
|
||||
// Trying to get last block from DB to check its version
|
||||
let block: Option<Block> = match self.db.prepare(SQL_GET_LAST_BLOCK) {
|
||||
Ok(mut statement) => {
|
||||
@@ -98,21 +91,14 @@ impl Chain {
|
||||
}
|
||||
result
|
||||
}
|
||||
Err(_) => {
|
||||
info!("No blockchain database found. Creating new.");
|
||||
self.db.execute(SQL_CREATE_TABLES).expect("Error creating blocks table");
|
||||
Err(e) => {
|
||||
info!("No blockchain database found. Creating new. {}", e);
|
||||
self.db.execute(SQL_CREATE_TABLES).expect("Error creating DB tables");
|
||||
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() {
|
||||
@@ -127,9 +113,41 @@ impl Chain {
|
||||
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<db> :)
|
||||
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 = statement.read::<String>(0).unwrap();
|
||||
let value = statement.read::<String>(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);
|
||||
self.blocks.push(block.clone());
|
||||
let index = block.index;
|
||||
let timestamp = block.timestamp;
|
||||
self.last_block = Some(block.clone());
|
||||
if block.transaction.is_some() {
|
||||
self.last_full_block = Some(block.clone());
|
||||
@@ -137,11 +155,39 @@ impl Chain {
|
||||
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");
|
||||
self.add_transaction_to_table(index, timestamp, &transaction).expect("Error adding transaction");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn replace_block(&mut self, index: u64, block: Block) -> sqlite::Result<()> {
|
||||
debug!("Replacing block {} with:\n{:?}", index, &block);
|
||||
let old_block = self.get_block(index).unwrap();
|
||||
if old_block.transaction.is_some() {
|
||||
let mut statement = self.db.prepare(SQL_DELETE_DOMAIN)?;
|
||||
statement.bind(1, index as i64)?;
|
||||
statement.next()?;
|
||||
|
||||
let mut statement = self.db.prepare(SQL_DELETE_ZONE)?;
|
||||
statement.bind(1, index as i64)?;
|
||||
statement.next()?;
|
||||
}
|
||||
|
||||
let index = block.index;
|
||||
let timestamp = block.timestamp;
|
||||
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.replace_block_in_table(block).is_ok() {
|
||||
if let Some(transaction) = transaction {
|
||||
self.add_transaction_to_table(index, timestamp, &transaction).expect("Error adding transaction");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Adds block to blocks table
|
||||
fn add_block_to_table(&mut self, block: Block) -> sqlite::Result<State> {
|
||||
let mut statement = self.db.prepare(SQL_ADD_BLOCK)?;
|
||||
@@ -164,14 +210,43 @@ impl Chain {
|
||||
statement.next()
|
||||
}
|
||||
|
||||
/// Replaces block in blocks table on arrival of better block from some fork
|
||||
fn replace_block_in_table(&mut self, block: Block) -> sqlite::Result<State> {
|
||||
let mut statement = self.db.prepare(SQL_REPLACE_BLOCK)?;
|
||||
statement.bind(1, block.timestamp as i64)?;
|
||||
statement.bind(2, block.version as i64)?;
|
||||
statement.bind(3, block.difficulty as i64)?;
|
||||
statement.bind(4, block.random as i64)?;
|
||||
statement.bind(5, block.nonce as i64)?;
|
||||
match &block.transaction {
|
||||
None => { statement.bind(6, "")?; }
|
||||
Some(transaction) => {
|
||||
statement.bind(6, transaction.to_string().as_str())?;
|
||||
}
|
||||
}
|
||||
statement.bind(7, &**block.prev_block_hash)?;
|
||||
statement.bind(8, &**block.hash)?;
|
||||
statement.bind(9, &**block.pub_key)?;
|
||||
statement.bind(10, &**block.signature)?;
|
||||
statement.bind(11, block.index as i64)?;
|
||||
statement.next()
|
||||
}
|
||||
|
||||
/// Adds transaction to transactions table
|
||||
fn add_transaction_to_table(&mut self, t: &Transaction) -> sqlite::Result<State> {
|
||||
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)?;
|
||||
fn add_transaction_to_table(&mut self, index: u64, timestamp: i64, t: &Transaction) -> sqlite::Result<State> {
|
||||
let sql = match t.class.as_ref() {
|
||||
"domain" => SQL_ADD_DOMAIN,
|
||||
"zone" => SQL_ADD_ZONE,
|
||||
_ => 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)?;
|
||||
statement.bind(4, &**t.confirmation)?;
|
||||
statement.bind(5, t.data.as_ref() as &str)?;
|
||||
statement.bind(6, &**t.pub_key)?;
|
||||
statement.next()
|
||||
}
|
||||
|
||||
@@ -244,7 +319,7 @@ impl Chain {
|
||||
return false;
|
||||
}
|
||||
let identity_hash = hash_identity(domain, None);
|
||||
if !self.is_id_available(&identity_hash, &keystore.get_public()) {
|
||||
if !self.is_id_available(&identity_hash, &keystore.get_public(), false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -260,8 +335,13 @@ impl Chain {
|
||||
}
|
||||
|
||||
/// 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();
|
||||
pub fn is_id_available(&self, identity: &Bytes, public_key: &Bytes, zone: bool) -> bool {
|
||||
let sql = match zone {
|
||||
true => { SQL_GET_ZONE_PUBLIC_KEY_BY_ID }
|
||||
false => { SQL_GET_DOMAIN_PUBLIC_KEY_BY_ID }
|
||||
};
|
||||
|
||||
let mut statement = self.db.prepare(sql).unwrap();
|
||||
statement.bind(1, &***identity).expect("Error in bind");
|
||||
while let State::Row = statement.next().unwrap() {
|
||||
let pub_key = Bytes::from_bytes(&statement.read::<Vec<u8>>(0).unwrap());
|
||||
@@ -274,7 +354,7 @@ impl Chain {
|
||||
|
||||
pub fn get_zones(&self) -> Vec<ZoneData> {
|
||||
let mut map = HashMap::new();
|
||||
match self.db.prepare(SQL_GET_TRANSACTIONS_WITH_ZONE) {
|
||||
match self.db.prepare(SQL_GET_ZONES) {
|
||||
Ok(mut statement) => {
|
||||
while statement.next().unwrap() == State::Row {
|
||||
let data = statement.read::<String>(0).unwrap();
|
||||
@@ -300,7 +380,7 @@ impl Chain {
|
||||
|
||||
// Checking for existing zone in DB
|
||||
let identity_hash = hash_identity(zone, None);
|
||||
if self.is_id_in_blockchain(&identity_hash) {
|
||||
if self.is_id_in_blockchain(&identity_hash, true) {
|
||||
// If there is such a zone
|
||||
self.zones.borrow_mut().insert(zone.to_owned());
|
||||
return true;
|
||||
@@ -309,9 +389,13 @@ impl Chain {
|
||||
}
|
||||
|
||||
/// Checks if some id exists in our blockchain
|
||||
pub fn is_id_in_blockchain(&self, id: &Bytes) -> bool {
|
||||
pub fn is_id_in_blockchain(&self, id: &Bytes, zone: bool) -> bool {
|
||||
let sql = match zone {
|
||||
true => { SQL_GET_ZONE_PUBLIC_KEY_BY_ID }
|
||||
false => { SQL_GET_DOMAIN_PUBLIC_KEY_BY_ID }
|
||||
};
|
||||
// Checking for existing zone in DB
|
||||
let mut statement = self.db.prepare(SQL_GET_ID_BY_ID).unwrap();
|
||||
let mut statement = self.db.prepare(sql).unwrap();
|
||||
statement.bind(1, &***id).expect("Error in bind");
|
||||
while let State::Row = statement.next().unwrap() {
|
||||
// If there is such a zone
|
||||
@@ -320,7 +404,7 @@ impl Chain {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn can_mine_domain(&self, domain: &str, records: &str, pub_key: &Bytes) -> MineResult {
|
||||
pub fn can_mine_domain(&self, domain: &str, pub_key: &Bytes) -> MineResult {
|
||||
let name = domain.to_lowercase();
|
||||
if !check_domain(&name, true) {
|
||||
return WrongName;
|
||||
@@ -334,13 +418,10 @@ impl Chain {
|
||||
return NotOwned;
|
||||
}
|
||||
}
|
||||
if serde_json::from_str::<Vec<DnsRecord>>(&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();
|
||||
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 {
|
||||
return Cooldown { time }
|
||||
}
|
||||
@@ -356,15 +437,20 @@ impl Chain {
|
||||
}
|
||||
let identity_hash = hash_identity(domain, None);
|
||||
|
||||
let mut statement = self.db.prepare(SQL_GET_TRANSACTION_BY_ID).unwrap();
|
||||
let mut statement = self.db.prepare(SQL_GET_DOMAIN_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::<Vec<u8>>(1).unwrap());
|
||||
let confirmation = Bytes::from_bytes(&statement.read::<Vec<u8>>(2).unwrap());
|
||||
let method = statement.read::<String>(3).unwrap();
|
||||
let data = statement.read::<String>(4).unwrap();
|
||||
let pub_key = Bytes::from_bytes(&statement.read::<Vec<u8>>(5).unwrap());
|
||||
let transaction = Transaction { identity, confirmation, method, data, pub_key };
|
||||
let timestamp = statement.read::<i64>(1).unwrap();
|
||||
if timestamp < Utc::now().timestamp() - DOMAIN_LIFETIME {
|
||||
// This domain is too old
|
||||
return None;
|
||||
}
|
||||
let identity = Bytes::from_bytes(&statement.read::<Vec<u8>>(2).unwrap());
|
||||
let confirmation = Bytes::from_bytes(&statement.read::<Vec<u8>>(3).unwrap());
|
||||
let method = statement.read::<String>(4).unwrap();
|
||||
let data = statement.read::<String>(5).unwrap();
|
||||
let pub_key = Bytes::from_bytes(&statement.read::<Vec<u8>>(6).unwrap());
|
||||
let transaction = Transaction { identity, confirmation, class: method, data, pub_key };
|
||||
debug!("Found transaction for domain {}: {:?}", domain, &transaction);
|
||||
if transaction.check_identity(domain) {
|
||||
return Some(transaction);
|
||||
@@ -381,18 +467,13 @@ impl Chain {
|
||||
}
|
||||
|
||||
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::<ZoneData>(&transaction.data) {
|
||||
Ok(data) => { data.difficulty }
|
||||
Err(_) => {
|
||||
warn!("Wrong data for zone {}!", zone);
|
||||
u32::max_value()
|
||||
}
|
||||
}
|
||||
let zones = self.get_zones();
|
||||
for z in zones.iter() {
|
||||
if z.name.eq(zone) {
|
||||
return z.difficulty;
|
||||
}
|
||||
}
|
||||
u32::max_value()
|
||||
}
|
||||
|
||||
pub fn last_block(&self) -> Option<Block> {
|
||||
@@ -441,7 +522,7 @@ impl Chain {
|
||||
/// 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 {
|
||||
if block.timestamp > timestamp + 60 {
|
||||
warn!("Ignoring block from the future:\n{:?}", &block);
|
||||
return Bad;
|
||||
}
|
||||
@@ -449,15 +530,21 @@ impl Chain {
|
||||
warn!("Ignoring block with weak public key:\n{:?}", &block);
|
||||
return Bad;
|
||||
}
|
||||
let difficulty = match block.transaction {
|
||||
None => { LOCKER_DIFFICULTY }
|
||||
Some(_) => { BLOCK_DIFFICULTY }
|
||||
let difficulty = match &block.transaction {
|
||||
None => {
|
||||
if block.index == 1 {
|
||||
ZONE_DIFFICULTY
|
||||
} else {
|
||||
LOCKER_DIFFICULTY
|
||||
}
|
||||
}
|
||||
Some(t) => { self.get_difficulty_for_transaction(&t) }
|
||||
};
|
||||
if block.difficulty < difficulty {
|
||||
warn!("Block difficulty is lower than needed");
|
||||
return Bad;
|
||||
}
|
||||
if !hash_is_good(&block.hash, block.difficulty as usize) {
|
||||
if hash_difficulty(&block.hash) < block.difficulty {
|
||||
warn!("Ignoring block with low difficulty:\n{:?}", &block);
|
||||
return Bad;
|
||||
}
|
||||
@@ -470,23 +557,18 @@ impl Chain {
|
||||
return Bad;
|
||||
}
|
||||
if let Some(transaction) = &block.transaction {
|
||||
if !self.is_id_available(&transaction.identity, &block.pub_key) {
|
||||
// TODO check for zone transaction
|
||||
if !self.is_id_available(&transaction.identity, &block.pub_key, false) {
|
||||
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 {
|
||||
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 Ok(data) = serde_json::from_str::<DomainData>(&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 => {
|
||||
@@ -505,25 +587,25 @@ impl Chain {
|
||||
return Bad;
|
||||
}
|
||||
if last_block.index + 1 < block.index {
|
||||
warn!("Block is from the future, how is this possible?");
|
||||
warn!("Block {} arrived too early.", block.index);
|
||||
return Future;
|
||||
}
|
||||
if block.index > LOCKER_BLOCK_START {
|
||||
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");
|
||||
warn!("Not enough signing blocks over full {} block!", full_block.index);
|
||||
return Bad;
|
||||
} else {
|
||||
if self.check_block_for_lock(&block, full_block) == Bad {
|
||||
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_lock(&block, full_block) == Bad {
|
||||
if self.check_block_for_signing(&block, full_block) == Bad {
|
||||
return Bad;
|
||||
}
|
||||
}
|
||||
@@ -531,7 +613,7 @@ impl Chain {
|
||||
}
|
||||
|
||||
if block.index <= last_block.index {
|
||||
if last_block.hash == block.hash {
|
||||
if block.index == last_block.index && last_block.hash == block.hash {
|
||||
debug!("Ignoring block {}, we already have it", block.index);
|
||||
return Twin;
|
||||
}
|
||||
@@ -552,27 +634,47 @@ impl Chain {
|
||||
Good
|
||||
}
|
||||
|
||||
fn check_block_for_lock(&self, block: &Block, full_block: &Block) -> BlockQuality {
|
||||
fn get_difficulty_for_transaction(&self, transaction: &Transaction) -> u32 {
|
||||
match transaction.class.as_ref() {
|
||||
"domain" => {
|
||||
return match serde_json::from_str::<DomainData>(&transaction.data) {
|
||||
Ok(data) => {
|
||||
for zone in self.get_zones().iter() {
|
||||
if zone.name == data.zone {
|
||||
return zone.difficulty;
|
||||
}
|
||||
}
|
||||
u32::max_value()
|
||||
}
|
||||
Err(_) => { u32::max_value() }
|
||||
}
|
||||
}
|
||||
"zone" => { ZONE_DIFFICULTY }
|
||||
_ => { u32::max_value() }
|
||||
}
|
||||
}
|
||||
|
||||
fn check_block_for_signing(&self, block: &Block, full_block: &Block) -> BlockQuality {
|
||||
// If we got a locker/signing block
|
||||
let lockers: HashSet<Bytes> = self.get_block_lockers(full_block).into_iter().collect();
|
||||
if !lockers.contains(&block.pub_key) {
|
||||
warn!("Ignoring block {}, as wrong locker", block.index);
|
||||
let signers: HashSet<Bytes> = 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 locker's public key has already locked/signed that block we return error
|
||||
// If this signers' 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);
|
||||
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 a public key of a node that needs to mine "locker" block above this block
|
||||
/// Gets public keys of a node that needs to mine "signature" block above this block
|
||||
/// block - last full block
|
||||
pub fn get_block_lockers(&self, block: &Block) -> Vec<Bytes> {
|
||||
pub fn get_block_signers(&self, block: &Block) -> Vec<Bytes> {
|
||||
let mut result = Vec::new();
|
||||
if block.index < LOCKER_BLOCK_START {
|
||||
return result;
|
||||
@@ -592,7 +694,7 @@ impl Chain {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
trace!("Got lockers for block {}: {:?}", block.index, &result);
|
||||
trace!("Got signers for block {}: {:?}", block.index, &result);
|
||||
result
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
/// Represents a result of block check on block's arrival
|
||||
#[derive(PartialEq)]
|
||||
pub enum BlockQuality {
|
||||
Good,
|
||||
Twin,
|
||||
Future,
|
||||
Bad,
|
||||
Fork,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum MineResult {
|
||||
Fine,
|
||||
WrongName,
|
||||
WrongData,
|
||||
WrongKey,
|
||||
WrongZone,
|
||||
NotOwned,
|
||||
Cooldown { time: i64 }
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
use blakeout::Blakeout;
|
||||
use num_bigint::BigUint;
|
||||
use num_traits::One;
|
||||
|
||||
use crate::{Block, Bytes, Keystore};
|
||||
use sha2::{Sha256, Digest};
|
||||
use std::convert::TryInto;
|
||||
|
||||
/// Checks block's hash and returns true on valid hash or false otherwise
|
||||
pub fn check_block_hash(block: &Block) -> bool {
|
||||
@@ -31,12 +30,17 @@ pub fn check_block_signature(block: &Block) -> bool {
|
||||
/// Hashes some identity (domain in case of DNS). If you give it a public key, it will hash with it as well.
|
||||
/// Giving public key is needed to create a confirmation field in [Transaction]
|
||||
pub fn hash_identity(identity: &str, key: Option<&Bytes>) -> Bytes {
|
||||
let mut digest = Sha256::default();
|
||||
digest.update(identity.as_bytes());
|
||||
if let Some(key) = key {
|
||||
digest.update(key.as_slice());
|
||||
let base = hash_sha256(identity.as_bytes());
|
||||
let identity = hash_sha256(&base);
|
||||
match key {
|
||||
None => { Bytes::from_bytes(&identity) }
|
||||
Some(key) => {
|
||||
let mut buf = Vec::new();
|
||||
buf.append(&mut identity.clone());
|
||||
buf.append(&mut key.to_vec());
|
||||
Bytes::from_bytes(&hash_sha256(&buf))
|
||||
}
|
||||
}
|
||||
Bytes::from_bytes(&digest.finalize()[..])
|
||||
}
|
||||
|
||||
/// There is no default PartialEq implementation for arrays > 32 in size
|
||||
@@ -54,10 +58,56 @@ pub fn same_hash(left: &[u8], right: &[u8]) -> bool {
|
||||
result
|
||||
}
|
||||
|
||||
/// Checks if this hash contains enough zeroes
|
||||
pub fn hash_is_good(hash: &[u8], difficulty: usize) -> bool {
|
||||
let target = BigUint::one() << ((hash.len() << 3) - difficulty);
|
||||
let hash_int = BigUint::from_bytes_be(&hash);
|
||||
/// Returns hash difficulty
|
||||
pub fn hash_difficulty(hash: &[u8]) -> u32 {
|
||||
let bytes: [u8; 8] = hash[..8].try_into().unwrap();
|
||||
let int_start = u64::from_be_bytes(bytes);
|
||||
let bytes: [u8; 8] = hash[hash.len() - 8..].try_into().unwrap();
|
||||
let int_end = u64::from_be_bytes(bytes);
|
||||
int_start.leading_zeros() + int_end.trailing_zeros()
|
||||
}
|
||||
|
||||
return hash_int < target;
|
||||
pub fn hash_difficulty_key(hash: &[u8]) -> u32 {
|
||||
let bytes: [u8; 8] = hash[..8].try_into().unwrap();
|
||||
let int = u64::from_be_bytes(bytes);
|
||||
int.leading_zeros()
|
||||
}
|
||||
|
||||
pub fn hash_sha256(data: &[u8]) -> Vec<u8> {
|
||||
let mut digest = Sha256::default();
|
||||
digest.update(data.as_ref());
|
||||
Vec::from(&digest.finalize()[..])
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::blockchain::hash_utils::hash_sha256;
|
||||
use std::convert::TryInto;
|
||||
|
||||
#[test]
|
||||
pub fn test_hash() {
|
||||
let id = b"example.com";
|
||||
let key = b"some_key";
|
||||
|
||||
let base = hash_sha256(id);
|
||||
|
||||
let identity = hash_sha256(&base);
|
||||
|
||||
let mut buf = Vec::new();
|
||||
buf.append(&mut identity.clone());
|
||||
buf.append(&mut key.to_vec());
|
||||
let confirmation = hash_sha256(&buf);
|
||||
|
||||
println!("result1 = {:?}", &base);
|
||||
println!("result2 = {:?}", &identity);
|
||||
println!("result3 = {:?}", &confirmation);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hash_is_good() {
|
||||
let hash = vec!(0u8,0u8,0u8,255,255,255,255,255);
|
||||
let bytes: [u8; 8] = hash[..8].try_into().unwrap();
|
||||
let int = u64::from_be_bytes(bytes);
|
||||
println!("int = {}", int);
|
||||
}
|
||||
}
|
||||
@@ -7,5 +7,5 @@ pub mod block;
|
||||
pub mod chain;
|
||||
pub mod filter;
|
||||
pub mod hash_utils;
|
||||
pub mod enums;
|
||||
pub mod types;
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
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 INDEX keys ON blocks (pub_key);
|
||||
|
||||
CREATE TABLE domains (
|
||||
'id' BIGINT NOT NULL PRIMARY KEY,
|
||||
'timestamp' BIGINT NOT NULL,
|
||||
'identity' BINARY,
|
||||
'confirmation' BINARY,
|
||||
'data' TEXT,
|
||||
'pub_key' BINARY
|
||||
);
|
||||
CREATE INDEX ids ON domains ('identity');
|
||||
|
||||
CREATE TABLE zones (
|
||||
'id' BIGINT NOT NULL PRIMARY KEY,
|
||||
'timestamp' BIGINT NOT NULL,
|
||||
'identity' BINARY,
|
||||
'confirmation' BINARY,
|
||||
'data' TEXT,
|
||||
'pub_key' BINARY
|
||||
);
|
||||
|
||||
CREATE TABLE options ('name' TEXT NOT NULL, 'value' TEXT NOT NULL);
|
||||
@@ -15,7 +15,7 @@ extern crate serde_json;
|
||||
pub struct Transaction {
|
||||
pub identity: Bytes,
|
||||
pub confirmation: Bytes,
|
||||
pub method: String,
|
||||
pub class: String,
|
||||
pub data: String,
|
||||
pub pub_key: Bytes,
|
||||
}
|
||||
@@ -28,7 +28,7 @@ impl Transaction {
|
||||
}
|
||||
|
||||
pub fn new(identity: Bytes, confirmation: Bytes, method: String, data: String, pub_key: Bytes) -> Self {
|
||||
Transaction { identity, confirmation, method, data, pub_key }
|
||||
Transaction { identity, confirmation, class: method, data, pub_key }
|
||||
}
|
||||
|
||||
pub fn from_json(json: &str) -> Option<Self> {
|
||||
@@ -60,9 +60,9 @@ impl fmt::Debug for Transaction {
|
||||
fmt.debug_struct("Transaction")
|
||||
.field("identity", &self.identity)
|
||||
.field("confirmation", &self.confirmation)
|
||||
.field("method", &self.method)
|
||||
.field("class", &self.class)
|
||||
.field("data", &self.data)
|
||||
.field("pub", &&self.pub_key)
|
||||
.field("pub_key", &&self.pub_key)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@@ -72,7 +72,7 @@ impl Serialize for Transaction {
|
||||
let mut structure = serializer.serialize_struct("Transaction", 5).unwrap();
|
||||
structure.serialize_field("identity", &self.identity)?;
|
||||
structure.serialize_field("confirmation", &self.confirmation)?;
|
||||
structure.serialize_field("method", &self.method)?;
|
||||
structure.serialize_field("class", &self.class)?;
|
||||
structure.serialize_field("data", &self.data)?;
|
||||
structure.serialize_field("pub_key", &self.pub_key)?;
|
||||
structure.end()
|
||||
@@ -81,24 +81,43 @@ impl Serialize for Transaction {
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct DomainData {
|
||||
pub domain: Bytes,
|
||||
pub zone: String,
|
||||
pub records: Vec<DnsRecord>
|
||||
pub records: Vec<DnsRecord>,
|
||||
pub contacts: Vec<ContactsData>,
|
||||
#[serde(default)]
|
||||
pub owners: Vec<Bytes>
|
||||
}
|
||||
|
||||
impl DomainData {
|
||||
pub fn new(zone: String, records: Vec<DnsRecord>) -> Self {
|
||||
Self { zone, records }
|
||||
pub fn new(domain: Bytes, zone: String, records: Vec<DnsRecord>, contacts: Vec<ContactsData>, owners: Vec<Bytes>) -> Self {
|
||||
Self { domain, zone, records, contacts, owners }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ZoneData {
|
||||
pub name: String,
|
||||
pub difficulty: u32
|
||||
pub difficulty: u32,
|
||||
pub yggdrasil: bool,
|
||||
#[serde(default)]
|
||||
pub owners: Vec<Bytes>
|
||||
}
|
||||
|
||||
impl Display for ZoneData {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
|
||||
f.write_str(&format!("{} ({})", self.name, self.difficulty))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ContactsData {
|
||||
pub name: String,
|
||||
pub value: String
|
||||
}
|
||||
|
||||
impl Display for ContactsData {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
|
||||
f.write_str(&format!("{}: {}", self.name, self.value))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/// Represents a result of block check on block's arrival
|
||||
#[derive(PartialEq)]
|
||||
pub enum BlockQuality {
|
||||
Good,
|
||||
Twin,
|
||||
Future,
|
||||
Bad,
|
||||
Fork,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum MineResult {
|
||||
Fine,
|
||||
WrongName,
|
||||
WrongData,
|
||||
WrongKey,
|
||||
WrongZone,
|
||||
NotOwned,
|
||||
Cooldown { time: i64 },
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Options {
|
||||
pub origin: String,
|
||||
pub version: u32,
|
||||
}
|
||||
|
||||
impl Options {
|
||||
pub fn new(origin: String, version: u32) -> Self {
|
||||
Options { origin, version }
|
||||
}
|
||||
|
||||
pub fn empty() -> Self {
|
||||
Options { origin: String::new(), version: 0 }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user