diff --git a/Cargo.toml b/Cargo.toml index f720624..fc91025 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "alfis" -version = "0.4.37" +version = "0.5.0" authors = ["Revertron "] edition = "2018" build = "build.rs" diff --git a/README.md b/README.md index 419474b..3515104 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Alternative Free Identity System -This project represents a minimal blockchain without cryptocurrency, capable of sustaining any number of domain name zones and domains. +This project represents a minimal blockchain without cryptocurrency, capable of sustaining any number of domain names in a bunch of original alternative zones. ![Screenshot](img/keys.png) diff --git a/other-tlds.txt b/other-tlds.txt index e86888f..5b93477 100644 --- a/other-tlds.txt +++ b/other-tlds.txt @@ -32,4 +32,5 @@ onion i2p meshname meship +mem test \ No newline at end of file diff --git a/src/blockchain/chain.rs b/src/blockchain/chain.rs index fd0cef0..f4d3488 100644 --- a/src/blockchain/chain.rs +++ b/src/blockchain/chain.rs @@ -1,6 +1,8 @@ use std::cell::RefCell; -use std::collections::{HashSet, HashMap}; +use std::cmp::max; +use std::collections::{HashMap, HashSet}; use std::fs; +use std::ops::Deref; use std::path::Path; use chrono::Utc; @@ -8,38 +10,32 @@ use chrono::Utc; use log::{debug, error, info, trace, warn}; use sqlite::{Connection, State, Statement}; -use crate::{Block, Bytes, Keystore, Transaction, check_domain, get_domain_zone, is_yggdrasil_record}; -use crate::blockchain::transaction::TransactionType; -use crate::commons::constants::*; -use crate::blockchain::types::{BlockQuality, MineResult, Options}; -use crate::blockchain::types::BlockQuality::*; +use crate::{Block, Bytes, check_domain, get_domain_zone, is_yggdrasil_record, Keystore, Transaction}; use crate::blockchain::hash_utils::*; -use crate::settings::Settings; -use crate::keys::check_public_key_strength; -use std::cmp::max; -use crate::blockchain::transaction::{ZoneData, DomainData}; -use std::ops::Deref; +use crate::blockchain::transaction::DomainData; +use crate::blockchain::types::{BlockQuality, MineResult, Options, ZoneData}; +use crate::blockchain::types::BlockQuality::*; use crate::blockchain::types::MineResult::*; +use crate::commons::constants::*; +use crate::keys::check_public_key_strength; +use crate::settings::Settings; -const TEMP_DB_NAME: &str = "temp.db"; -const SQL_CREATE_TABLES: &str = include_str!("sql/create_db.sql"); +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_TRUNCATE_ZONES: &str = "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_ADD_DOMAIN: &str = "INSERT INTO domains (id, timestamp, identity, confirmation, data, owner) 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_PUBLIC_KEY_BY_ID: &str = "SELECT pub_key FROM domains WHERE id < ? AND identity = ? LIMIT 1;"; -const SQL_GET_ZONE_PUBLIC_KEY_BY_ID: &str = "SELECT pub_key FROM zones WHERE id < ? AND identity = ? LIMIT 1;"; +const SQL_GET_DOMAIN_OWNER_BY_ID: &str = "SELECT owner FROM domains WHERE id < ? AND identity = ? LIMIT 1;"; const SQL_GET_DOMAIN_BY_ID: &str = "SELECT * FROM domains WHERE identity = ? ORDER BY id DESC LIMIT 1;"; -const SQL_GET_DOMAINS_BY_KEY: &str = "SELECT * FROM domains WHERE pub_key = ?;"; -const SQL_GET_ZONES: &str = "SELECT data FROM zones;"; +const SQL_GET_DOMAINS_BY_KEY: &str = "SELECT * FROM domains WHERE owner = ?;"; const SQL_GET_OPTIONS: &str = "SELECT * FROM options;"; @@ -52,7 +48,7 @@ pub struct Chain { last_full_block: Option, max_height: u64, db: Connection, - zones: RefCell>, + zones: Vec, signers: RefCell, } @@ -61,7 +57,7 @@ impl Chain { let origin = settings.get_origin(); let db = sqlite::open(db_name).expect("Unable to open blockchain DB"); - let zones = RefCell::new(HashSet::new()); + 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 @@ -164,10 +160,6 @@ impl Chain { let mut statement = self.db.prepare(SQL_TRUNCATE_DOMAINS)?; statement.bind(1, index as i64)?; - statement.next()?; - - let mut statement = self.db.prepare(SQL_TRUNCATE_ZONES)?; - statement.bind(1, index as i64)?; statement.next() } @@ -365,7 +357,6 @@ impl Chain { fn add_transaction_to_table(&mut self, index: u64, timestamp: i64, t: &Transaction) -> sqlite::Result { let sql = match t.class.as_ref() { "domain" => SQL_ADD_DOMAIN, - "zone" => SQL_ADD_ZONE, _ => return Err(sqlite::Error { code: None, message: None }) }; @@ -375,7 +366,7 @@ impl Chain { 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.bind(6, &**t.owner)?; statement.next() } @@ -453,7 +444,7 @@ impl Chain { return false; } let identity_hash = hash_identity(domain, None); - if !self.is_id_available(height, &identity_hash, &keystore.get_public(), false) { + if !self.is_id_available(height, &identity_hash, &keystore.get_public()) { return false; } @@ -463,19 +454,15 @@ impl Chain { if parts.last().unwrap().contains(".") { return false; } - return self.is_zone_in_blockchain(height, parts.first().unwrap()); + return self.is_available_zone(parts.first().unwrap()); } true } /// Checks if this identity is free or is owned by the same pub_key - pub fn is_id_available(&self, height: u64, 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(); + pub fn is_id_available(&self, height: u64, identity: &Bytes, public_key: &Bytes) -> bool { + // TODO check for `owner` field + 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, &***identity).expect("Error in bind"); while let State::Row = statement.next().unwrap() { @@ -487,50 +474,39 @@ impl Chain { true } - pub fn get_zones(&self) -> Vec { - let mut map = HashMap::new(); - match self.db.prepare(SQL_GET_ZONES) { - Ok(mut statement) => { - while statement.next().unwrap() == State::Row { - let data = statement.read::(0).unwrap(); - //debug!("Got zone data {}", &data); - if let Ok(zone_data) = serde_json::from_str::(&data) { - map.insert(zone_data.name.clone(), zone_data); - } - } - } - Err(e) => { - warn!("Can't get zones from DB {}", e); - } + pub fn get_zones(&self) -> &Vec { + &self.zones + } + + fn load_zones() -> Vec { + let mut result: Vec = Vec::new(); + let zones: Vec<_> = ZONES_TXT.split("\n").collect(); + for zone in zones { + let yggdrasil = zone == "ygg" || zone == "anon"; + result.push(ZoneData {name: zone.to_owned(), yggdrasil}) } - let result: Vec = map.drain().map(|(_, value)| value).collect(); 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_zone_in_blockchain(&self, height: u64, zone: &str) -> bool { - if self.zones.borrow().contains(zone) { - return true; + pub fn is_available_zone(&self, zone: &str) -> bool { + for z in &self.zones { + if z.name == zone { + return true; + } } - // Checking for existing zone in DB - let identity_hash = hash_identity(zone, None); - if self.is_id_in_blockchain(height, &identity_hash, true) { - // 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, height: u64, 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).unwrap(); + 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).expect("Error in bind"); while let State::Row = statement.next().unwrap() { @@ -546,17 +522,18 @@ impl Chain { return WrongName; } let zone = get_domain_zone(&name); - if !self.is_zone_in_blockchain(height, &zone) { + if !self.is_available_zone(&zone) { return WrongZone; } if let Some(transaction) = self.get_domain_transaction(&name) { - if transaction.pub_key.ne(pub_key) { + if transaction.owner.ne(pub_key) { return NotOwned; } } let identity_hash = hash_identity(&name, None); + // TODO extract method if let Some(last) = self.get_last_full_block(MAX, Some(&pub_key)) { - let new_id = !self.is_id_in_blockchain(height, &identity_hash, false); + let new_id = !self.is_domain_in_blockchain(height, &identity_hash); let time = last.timestamp + NEW_DOMAINS_INTERVAL - Utc::now().timestamp(); if new_id && time > 0 { return Cooldown { time } @@ -586,7 +563,7 @@ impl Chain { let class = String::from("domain"); let data = statement.read::(4).unwrap(); let pub_key = Bytes::from_bytes(&statement.read::>(5).unwrap()); - let transaction = Transaction { identity, confirmation, class, data, pub_key }; + let transaction = Transaction { identity, confirmation, class, data, owner: pub_key }; debug!("Found transaction for domain {}: {:?}", domain, &transaction); if transaction.check_identity(domain) { return Some(transaction); @@ -619,8 +596,8 @@ impl Chain { let confirmation = Bytes::from_bytes(&statement.read::>(3).unwrap()); let class = String::from("domain"); let data = statement.read::(4).unwrap(); - let pub_key = Bytes::from_bytes(&statement.read::>(5).unwrap()); - let transaction = Transaction { identity: identity.clone(), confirmation: confirmation.clone(), class, data, pub_key }; + let owner = Bytes::from_bytes(&statement.read::>(5).unwrap()); + let transaction = Transaction { identity: identity.clone(), confirmation: confirmation.clone(), class, data, owner }; //debug!("Found transaction for domain {}: {:?}", domain, &transaction); if let Some(data) = transaction.get_domain_data() { let mut domain = keystore.decrypt(data.domain.as_slice(), &confirmation.as_slice()[..12]); @@ -646,16 +623,6 @@ impl Chain { result } - pub fn get_zone_difficulty(&self, zone: &str) -> u32 { - let zones = self.get_zones(); - for z in zones.iter() { - if z.name.eq(zone) { - return z.difficulty; - } - } - u32::MAX - } - pub fn last_block(&self) -> Option { self.last_block.clone() } @@ -732,7 +699,7 @@ impl Chain { let difficulty = match &block.transaction { None => { if block.index == 1 { - ZONE_DIFFICULTY + ORIGIN_DIFFICULTY } else { SIGNER_DIFFICULTY } @@ -767,28 +734,20 @@ impl Chain { } } } - if matches!(Transaction::get_type(&block.transaction), TransactionType::Zone) { - if self.get_zones().len() >= MAXIMUM_ZONES { - warn!("Ignoring excess zone block"); - return Bad; - } - } if let Some(transaction) = &block.transaction { let current_height = match last_block { None => { 0 } Some(block) => { block.index } }; - // TODO check for zone transaction - let is_domain_available = self.is_id_available(current_height, &transaction.identity, &block.pub_key, false); - let is_zone_available = self.is_id_available(current_height, &transaction.identity, &block.pub_key, true); - if !is_domain_available || !is_zone_available { + // If this domain is available to this public key + if !self.is_id_available(current_height, &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(block.index, Some(&block.pub_key)) { if last.index < block.index { - let new_id = !self.is_id_in_blockchain(block.index, &transaction.identity, false); + let new_id = !self.is_domain_in_blockchain(block.index, &transaction.identity); if new_id && last.timestamp + NEW_DOMAINS_INTERVAL > block.timestamp { warn!("Block {:?} is mined too early!", &block); return Bad; @@ -798,7 +757,7 @@ impl Chain { // Check if yggdrasil only property of zone is not violated if let Some(block_data) = transaction.get_domain_data() { let zones = self.get_zones(); - for z in &zones { + for z in zones { if z.name == block_data.zone { if z.yggdrasil { for record in &block_data.records { @@ -911,15 +870,10 @@ impl Chain { fn get_difficulty_for_transaction(&self, transaction: &Transaction) -> u32 { match transaction.class.as_ref() { - "domain" => { + CLASS_DOMAIN => { return match serde_json::from_str::(&transaction.data) { - Ok(data) => { - for zone in self.get_zones().iter() { - if zone.name == data.zone { - return zone.difficulty; - } - } - u32::MAX + Ok(_) => { + DOMAIN_DIFFICULTY } Err(_) => { warn!("Error parsing DomainData from {:?}", transaction); @@ -927,7 +881,7 @@ impl Chain { } } } - "zone" => { ZONE_DIFFICULTY } + CLASS_ORIGIN => { ORIGIN_DIFFICULTY } _ => { u32::MAX } } } @@ -1005,9 +959,10 @@ impl SignersCache { #[cfg(test)] pub mod tests { - use crate::{Chain, Settings}; - use simplelog::{ConfigBuilder, TermLogger, TerminalMode, ColorChoice}; use log::LevelFilter; + use simplelog::{ColorChoice, ConfigBuilder, TerminalMode, TermLogger}; + + use crate::{Chain, Settings}; fn init_logger() { let config = ConfigBuilder::new() diff --git a/src/blockchain/sql/create_db.sql b/src/blockchain/data/create_db.sql similarity index 75% rename from src/blockchain/sql/create_db.sql rename to src/blockchain/data/create_db.sql index cef15b8..16a7f9d 100644 --- a/src/blockchain/sql/create_db.sql +++ b/src/blockchain/data/create_db.sql @@ -20,17 +20,8 @@ CREATE TABLE domains ( 'identity' BINARY, 'confirmation' BINARY, 'data' TEXT, - 'pub_key' BINARY + 'owner' 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); \ No newline at end of file diff --git a/src/blockchain/data/zones.txt b/src/blockchain/data/zones.txt new file mode 100644 index 0000000..2e3d250 --- /dev/null +++ b/src/blockchain/data/zones.txt @@ -0,0 +1,10 @@ +anon +btn +conf +index +merch +mirror +mob +screen +srv +ygg \ No newline at end of file diff --git a/src/blockchain/filter.rs b/src/blockchain/filter.rs index 0977c12..63418fc 100644 --- a/src/blockchain/filter.rs +++ b/src/blockchain/filter.rs @@ -48,7 +48,7 @@ impl DnsFilter for BlockchainFilter { let zone = parts[0].to_owned(); match data { None => { - if self.context.lock().unwrap().chain.is_zone_in_blockchain(i64::MAX as u64, &zone) { + if self.context.lock().unwrap().chain.is_available_zone(&zone) { trace!("Not found data for domain {}", &search); // Create DnsPacket let mut packet = DnsPacket::new(); @@ -194,7 +194,7 @@ impl BlockchainFilter { } fn get_zone_response(&self, zone: &str, serial: u32, mut packet: &mut DnsPacket) -> bool { - let have_zone = self.context.lock().unwrap().chain.is_zone_in_blockchain(i64::MAX as u64, zone); + let have_zone = self.context.lock().unwrap().chain.is_available_zone(zone); if have_zone { BlockchainFilter::add_soa_record(zone.to_owned(), serial, &mut packet); } diff --git a/src/blockchain/transaction.rs b/src/blockchain/transaction.rs index 632350e..270efea 100644 --- a/src/blockchain/transaction.rs +++ b/src/blockchain/transaction.rs @@ -1,4 +1,5 @@ use std::fmt; +use std::fmt::{Display, Formatter}; use serde::{Deserialize, Serialize, Serializer}; use serde::ser::SerializeStruct; @@ -6,29 +7,37 @@ use serde::ser::SerializeStruct; use crate::blockchain::hash_utils::*; use crate::bytes::Bytes; use crate::dns::protocol::DnsRecord; -use std::fmt::{Display, Formatter}; +use crate::{CLASS_ORIGIN, CLASS_DOMAIN}; extern crate serde; extern crate serde_json; #[derive(Clone, Deserialize, PartialEq)] pub struct Transaction { + #[serde(default, skip_serializing_if = "Bytes::is_zero")] pub identity: Bytes, + #[serde(default, skip_serializing_if = "Bytes::is_zero")] pub confirmation: Bytes, pub class: String, pub data: String, - pub pub_key: Bytes, + #[serde(default, skip_serializing_if = "Bytes::is_zero")] + pub owner: Bytes, } impl Transaction { - pub fn from_str(identity: String, method: String, data: String, pub_key: Bytes) -> Self { + pub fn from_str(identity: String, method: String, data: String, owner: Bytes) -> Self { let hash = hash_identity(&identity, None); - let confirmation = hash_identity(&identity, Some(&pub_key)); - return Self::new(hash, confirmation, method, data, pub_key); + let confirmation = hash_identity(&identity, Some(&owner)); + return Self::new(hash, confirmation, method, data, owner); } - pub fn new(identity: Bytes, confirmation: Bytes, method: String, data: String, pub_key: Bytes) -> Self { - Transaction { identity, confirmation, class: method, data, pub_key } + pub fn new(identity: Bytes, confirmation: Bytes, method: String, data: String, owner: Bytes) -> Self { + Transaction { identity, confirmation, class: method, data, owner } + } + + pub fn origin(hash: Bytes, owner: Bytes) -> Self { + let data = serde_json::to_string(&Origin { zones: hash }).unwrap(); + Transaction { identity: Bytes::default(), confirmation: Bytes::default(), class: String::from(CLASS_ORIGIN), data, owner } } pub fn from_json(json: &str) -> Option { @@ -50,13 +59,13 @@ impl Transaction { pub fn check_identity(&self, domain: &str) -> bool { let hash = hash_identity(&domain, None); - let confirmation = hash_identity(&domain, Some(&self.pub_key)); + let confirmation = hash_identity(&domain, Some(&self.owner)); self.identity.eq(&hash) && self.confirmation.eq(&confirmation) } /// Returns [DomainData] from this transaction if it has it pub fn get_domain_data(&self) -> Option { - if self.class == "domain" { + if self.class == CLASS_DOMAIN { if let Ok(data) = serde_json::from_str::(&self.data) { return Some(data) } @@ -69,12 +78,9 @@ impl Transaction { match what { None => { TransactionType::Signing } Some(transaction) => { - if let Some(_) = transaction.get_domain_data() { + if transaction.class == CLASS_DOMAIN { return TransactionType::Domain; } - if let Ok(_) = serde_json::from_str::(&transaction.data) { - return TransactionType::Zone; - } TransactionType::Unknown } } @@ -88,7 +94,7 @@ impl fmt::Debug for Transaction { .field("confirmation", &self.confirmation) .field("class", &self.class) .field("data", &self.data) - .field("pub_key", &&self.pub_key) + .field("pub_key", &&self.owner) .finish() } } @@ -100,7 +106,7 @@ impl Serialize for Transaction { structure.serialize_field("confirmation", &self.confirmation)?; structure.serialize_field("class", &self.class)?; structure.serialize_field("data", &self.data)?; - structure.serialize_field("pub_key", &self.pub_key)?; + structure.serialize_field("pub_key", &self.owner)?; structure.end() } } @@ -109,38 +115,28 @@ pub enum TransactionType { Unknown, Signing, Domain, - Zone, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct DomainData { pub domain: Bytes, pub zone: String, + pub info: String, + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub records: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub contacts: Vec, - #[serde(default)] - pub owners: Vec } impl DomainData { - pub fn new(domain: Bytes, zone: String, records: Vec, contacts: Vec, owners: Vec) -> Self { - Self { domain, zone, records, contacts, owners } + pub fn new(domain: Bytes, zone: String, info: String, records: Vec, contacts: Vec) -> Self { + Self { domain, zone, info, records, contacts } } } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] -pub struct ZoneData { - pub name: String, - pub difficulty: u32, - pub yggdrasil: bool, - #[serde(default)] - pub owners: Vec -} - -impl Display for ZoneData { - fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - f.write_str(&format!("{} ({})", self.name, self.difficulty)) - } +pub struct Origin { + zones: Bytes } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] diff --git a/src/blockchain/types.rs b/src/blockchain/types.rs index de8fc00..c7b516e 100644 --- a/src/blockchain/types.rs +++ b/src/blockchain/types.rs @@ -1,3 +1,6 @@ +use std::fmt::{Display, Formatter}; +use serde::{Deserialize, Serialize}; + /// Represents a result of block check on block's arrival #[derive(PartialEq)] pub enum BlockQuality { @@ -34,4 +37,16 @@ impl Options { pub fn empty() -> Self { Options { origin: String::new(), version: 0 } } -} \ No newline at end of file +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct ZoneData { + pub name: String, + pub yggdrasil: bool, +} + +impl Display for ZoneData { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + f.write_str(&format!("{}, yggdrasil: {}", self.name, self.yggdrasil)) + } +} diff --git a/src/commons/constants.rs b/src/commons/constants.rs index 86aca44..60e4e94 100644 --- a/src/commons/constants.rs +++ b/src/commons/constants.rs @@ -1,10 +1,10 @@ use std::time::Duration; pub const DB_VERSION: u32 = 0; -pub const CHAIN_VERSION: u32 = 0; +pub const CHAIN_VERSION: u32 = 1; -pub const ZONE_DIFFICULTY: u32 = 28; -pub const ZONE_MIN_DIFFICULTY: u32 = 22; +pub const ORIGIN_DIFFICULTY: u32 = 30; +pub const DOMAIN_DIFFICULTY: u32 = 24; pub const SIGNER_DIFFICULTY: u32 = 16; pub const KEYSTORE_DIFFICULTY: u32 = 23; @@ -37,7 +37,7 @@ pub const ZONE_MAX_LENGTH: usize = 10; pub const MAX_RECONNECTS: u32 = 5; pub const DB_NAME: &str = "blockchain.db"; -pub const CLASS_ZONE: &str = "zone"; +pub const CLASS_ORIGIN: &str = "origin"; pub const CLASS_DOMAIN: &str = "domain"; pub const ALFIS_DEBUG: &str = "ALFIS_DEBUG"; @@ -46,7 +46,7 @@ pub const LISTEN_PORT: u16 = 4244; pub const UI_REFRESH_DELAY_MS: u128 = 250; pub const LOG_REFRESH_DELAY_SEC: u64 = 60; -pub const POLL_TIMEOUT: Option = Some(Duration::from_millis(250)); +pub const POLL_TIMEOUT: Option = Some(Duration::from_millis(25000)); pub const MAX_PACKET_SIZE: usize = 1 * 1024 * 1024; // 1 Mb pub const MAX_READ_BLOCK_TIME: u128 = 500; pub const MAX_IDLE_SECONDS: u64 = 180; diff --git a/src/commons/mod.rs b/src/commons/mod.rs index f7f33e2..358a974 100644 --- a/src/commons/mod.rs +++ b/src/commons/mod.rs @@ -32,6 +32,13 @@ pub fn check_domain(name: &str, allow_dots: bool) -> bool { if name.starts_with('.') || name.starts_with('-') || name.ends_with('.') || name.ends_with('-') { return false; } + let parts: Vec<&str> = name.rsplitn(2, ".").collect(); + if parts.len() == 2 { + if parts[1].len() < 3 && is_numeric(parts[1]) { + return false; + } + } + let mut last_dot = false; let mut last_hyphen = false; for char in name.chars() { @@ -60,6 +67,15 @@ pub fn check_domain(name: &str, allow_dots: bool) -> bool { true } +pub fn is_numeric(str: &str) -> bool { + for char in str.chars() { + if !char.is_numeric() { + return false; + } + } + true +} + pub fn get_domain_zone(domain: &str) -> String { let parts: Vec<&str> = domain.rsplitn(2, ".").collect(); if !parts.is_empty() { @@ -160,6 +176,9 @@ mod test { assert!(!check_domain("ab.c-", true)); assert!(!check_domain(".ab.c", true)); assert!(!check_domain("ab.c-", true)); + assert!(check_domain("777.com", true)); + assert!(!check_domain("77.com", true)); + assert!(!check_domain("7.com", true)); } #[test] diff --git a/src/dns/buffer.rs b/src/dns/buffer.rs index 3d99632..75cac67 100644 --- a/src/dns/buffer.rs +++ b/src/dns/buffer.rs @@ -169,14 +169,6 @@ impl VectorPacketBuffer { } impl PacketBuffer for VectorPacketBuffer { - fn find_label(&self, label: &str) -> Option { - self.label_lookup.get(label).cloned() - } - - fn save_label(&mut self, label: &str, pos: usize) { - self.label_lookup.insert(label.to_string(), pos); - } - fn read(&mut self) -> Result { let res = self.buffer[self.pos]; self.pos += 1; @@ -220,42 +212,33 @@ impl PacketBuffer for VectorPacketBuffer { Ok(()) } + + fn find_label(&self, label: &str) -> Option { + self.label_lookup.get(label).cloned() + } + + fn save_label(&mut self, label: &str, pos: usize) { + self.label_lookup.insert(label.to_string(), pos); + } } -pub struct StreamPacketBuffer<'a, T> -where - T: Read, -{ +pub struct StreamPacketBuffer<'a, T> where T: Read { pub stream: &'a mut T, pub buffer: Vec, pub pos: usize, } -impl<'a, T> StreamPacketBuffer<'a, T> -where - T: Read + 'a, -{ +impl<'a, T> StreamPacketBuffer<'a, T> where T: Read + 'a { pub fn new(stream: &'a mut T) -> StreamPacketBuffer<'_, T> { StreamPacketBuffer { - stream: stream, + stream, buffer: Vec::new(), pos: 0, } } } -impl<'a, T> PacketBuffer for StreamPacketBuffer<'a, T> -where - T: Read + 'a, -{ - fn find_label(&self, _: &str) -> Option { - None - } - - fn save_label(&mut self, _: &str, _: usize) { - unimplemented!(); - } - +impl<'a, T> PacketBuffer for StreamPacketBuffer<'a, T> where T: Read + 'a { fn read(&mut self) -> Result { while self.pos >= self.buffer.len() { let mut local_buffer = [0; 1]; @@ -310,6 +293,14 @@ where self.pos += steps; Ok(()) } + + fn find_label(&self, _: &str) -> Option { + None + } + + fn save_label(&mut self, _: &str, _: usize) { + unimplemented!(); + } } pub struct BytePacketBuffer { @@ -333,12 +324,6 @@ impl Default for BytePacketBuffer { } impl PacketBuffer for BytePacketBuffer { - fn find_label(&self, _: &str) -> Option { - None - } - - fn save_label(&mut self, _: &str, _: usize) {} - fn read(&mut self) -> Result { if self.pos >= 512 { return Err(BufferError::EndOfBuffer); @@ -393,6 +378,12 @@ impl PacketBuffer for BytePacketBuffer { Ok(()) } + + fn find_label(&self, _: &str) -> Option { + None + } + + fn save_label(&mut self, _: &str, _: usize) {} } #[cfg(test)] diff --git a/src/event.rs b/src/event.rs index 021c6e5..fd0327c 100644 --- a/src/event.rs +++ b/src/event.rs @@ -8,7 +8,6 @@ pub enum Event { KeyCreated { path: String, public: String, hash: String }, KeyLoaded { path: String, public: String, hash: String }, KeySaved { path: String, public: String, hash: String }, - ZonesChanged, NewBlockReceived, BlockchainChanged { index: u64 }, ActionStopMining, diff --git a/src/main.rs b/src/main.rs index 510a796..8300fdf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,7 +16,7 @@ use simplelog::*; #[cfg(windows)] use winapi::um::wincon::{ATTACH_PARENT_PROCESS, AttachConsole, FreeConsole}; -use alfis::{Block, Bytes, Chain, Miner, Context, Network, Settings, dns_utils, Keystore, ZONE_DIFFICULTY, ALFIS_DEBUG, DB_NAME}; +use alfis::{Block, Bytes, Chain, Miner, Context, Network, Settings, dns_utils, Keystore, ORIGIN_DIFFICULTY, ALFIS_DEBUG, DB_NAME, Transaction}; use std::fs::OpenOptions; use std::process::exit; use std::io::{Seek, SeekFrom}; @@ -208,7 +208,8 @@ fn create_genesis_if_needed(context: &Arc>, miner: &Arc>, mut block: Block, running: Arc= 1000 { block.timestamp = Utc::now().timestamp(); - if elapsed > 5000 { + if elapsed > 10000 { let speed = (nonce - prev_nonce) / (elapsed as u64 / 1000); //debug!("Mining speed {} H/s, max difficulty {}", speed, max_diff); if let Ok(mut context) = context.try_lock() { diff --git a/src/p2p/network.rs b/src/p2p/network.rs index e4be91c..6195b27 100644 --- a/src/p2p/network.rs +++ b/src/p2p/network.rs @@ -17,8 +17,7 @@ use mio::event::Event; use mio::net::{TcpListener, TcpStream}; use rand::random; -use crate::{Block, Context, p2p::Message, p2p::Peer, p2p::Peers, p2p::State, Transaction}; -use crate::blockchain::transaction::TransactionType; +use crate::{Block, Context, p2p::Message, p2p::Peer, p2p::Peers, p2p::State}; use crate::blockchain::types::BlockQuality; use crate::commons::*; @@ -539,13 +538,9 @@ fn handle_block(context: Arc>, peers: &mut Peers, token: &Token, let max_height = context.chain.max_height(); match context.chain.check_new_block(&block) { BlockQuality::Good => { - let zone = matches!(Transaction::get_type(&block.transaction), TransactionType::Zone); context.chain.add_block(block); let my_height = context.chain.get_height(); context.bus.post(crate::event::Event::BlockchainChanged { index: my_height }); - if zone { - context.bus.post(crate::event::Event::ZonesChanged); - } // If it was the last block to sync if my_height == max_height { context.bus.post(crate::event::Event::SyncFinished); @@ -573,13 +568,9 @@ fn handle_block(context: Arc>, peers: &mut Peers, token: &Token, debug!("Got forked block {} with hash {:?}", block.index, block.hash); let last_block = context.chain.last_block().unwrap(); if block.is_better_than(&last_block) { - let zone = matches!(Transaction::get_type(&block.transaction), TransactionType::Zone); context.chain.replace_block(block).expect("Error replacing block with fork"); let index = context.chain.get_height(); context.bus.post(crate::event::Event::BlockchainChanged { index }); - if zone { - context.bus.post(crate::event::Event::ZonesChanged); - } } else { debug!("Fork in not better than our block, dropping."); } diff --git a/src/web_ui.rs b/src/web_ui.rs index e90dbff..3f3be5c 100644 --- a/src/web_ui.rs +++ b/src/web_ui.rs @@ -14,18 +14,18 @@ use log::{debug, error, info, LevelFilter, trace, warn}; use serde::Deserialize; use web_view::Content; -use alfis::{Block, Bytes, Context, get_domain_zone, Keystore, Transaction, ZONE_MIN_DIFFICULTY, is_yggdrasil_record}; -use alfis::{check_domain, keys}; -use alfis::blockchain::transaction::{DomainData, ZoneData}; +use alfis::{Block, Bytes, Context, Keystore, Transaction}; +use alfis::keys; +use alfis::blockchain::hash_utils::hash_identity; +use alfis::blockchain::transaction::DomainData; use alfis::blockchain::types::MineResult; -use alfis::commons::{ZONE_DIFFICULTY, ZONE_MAX_LENGTH, CLASS_DOMAIN, CLASS_ZONE}; +use alfis::commons::*; use alfis::dns::protocol::DnsRecord; use alfis::event::Event; use alfis::miner::Miner; use Cmd::*; use self::web_view::{Handle, WebView}; -use alfis::blockchain::hash_utils::hash_identity; pub fn run_interface(context: Arc>, miner: Arc>) { let file_content = include_str!("webview/index.html"); @@ -57,10 +57,6 @@ pub fn run_interface(context: Arc>, miner: Arc>) { action_create_domain(Arc::clone(&context), Arc::clone(&miner), web_view, name, data); } TransferDomain { .. } => {} - CheckZone { name } => { action_check_zone(&context, web_view, name); } - MineZone { name, data } => { - action_create_zone(Arc::clone(&context), Arc::clone(&miner), web_view, name, data); - } StopMining => { context.lock().unwrap().bus.post(Event::ActionStopMining); } Open { link } => { if open::that(&link).is_err() { @@ -110,19 +106,6 @@ fn run_interface_loop(context: &mut Arc>, interface: &mut WebView } } -fn action_check_zone(context: &Arc>, web_view: &mut WebView<()>, name: String) { - let name = name.to_lowercase(); - if name.len() > ZONE_MAX_LENGTH || !check_domain(&name, false) || context.lock().unwrap().x_zones.has_zone(&name) { - web_view.eval("zoneAvailable(false)").expect("Error evaluating!"); - } else { - let c = context.lock().unwrap(); - if let Some(keystore) = c.get_keystore() { - let available = c.get_chain().is_domain_available(c.get_chain().get_height(), &name, &keystore); - web_view.eval(&format!("zoneAvailable({})", available)).expect("Error evaluating!"); - } - } -} - fn action_check_record(web_view: &mut WebView<()>, data: String) { match serde_json::from_str::(&data) { Ok(_) => { web_view.eval("recordOkay(true)").expect("Error evaluating!"); } @@ -296,15 +279,6 @@ fn action_loaded(context: &Arc>, web_view: &mut WebView<()>) { format!("setLeftStatusBarText('Idle'); setRightStatusBarText('Nodes: {}, Blocks: {}')", nodes, blocks) } } - Event::ZonesChanged => { - info!("New zone arrived"); - if let Ok(zones) = serde_json::to_string(&context.chain.get_zones()) { - let _ = handle.dispatch(move |web_view|{ - web_view.eval(&format!("zonesChanged('{}');", &zones)) - }); - } - String::new() // Nothing - } Event::BlockchainChanged {index} => { debug!("Current blockchain height is {}", index); event_handle_info(&handle, &format!("Blockchain changed, current block count is {} now.", index)); @@ -331,9 +305,9 @@ fn action_loaded(context: &Arc>, web_view: &mut WebView<()>) { let index = c.chain.get_height(); if index > 0 { c.bus.post(Event::BlockchainChanged { index }); - if let Ok(zones) = serde_json::to_string(&c.chain.get_zones()) { - let _ = web_view.eval(&format!("zonesChanged('{}');", &zones)); - } + } + if let Ok(zones) = serde_json::to_string(&c.chain.get_zones()) { + let _ = web_view.eval(&format!("zonesChanged('{}');", &zones)); } event_info(web_view, "Application loaded"); } @@ -384,7 +358,7 @@ fn action_create_domain(context: Arc>, miner: Arc>, }; // Check if yggdrasil only quality of zone is not violated let zones = context.chain.get_zones(); - for z in &zones { + for z in zones { if z.name == data.zone { if z.yggdrasil { for record in &data.records { @@ -399,10 +373,8 @@ fn action_create_domain(context: Arc>, miner: Arc>, } match context.chain.can_mine_domain(context.chain.get_height(), &name, &pub_key) { MineResult::Fine => { - let zone = get_domain_zone(&name); - let difficulty = context.chain.get_zone_difficulty(&zone); std::mem::drop(context); - create_domain(c, miner, CLASS_DOMAIN, &name, data, difficulty, &keystore); + create_domain(c, miner, CLASS_DOMAIN, &name, data, DOMAIN_DIFFICULTY, &keystore); let _ = web_view.eval("domainMiningStarted();"); event_info(web_view, &format!("Mining of domain \\'{}\\' has started", &name)); } @@ -434,72 +406,6 @@ fn action_create_domain(context: Arc>, miner: Arc>, } } -fn action_create_zone(context: Arc>, miner: Arc>, web_view: &mut WebView<()>, name: String, data: String) { - if context.lock().unwrap().chain.is_waiting_signers() { - show_warning(web_view, "Waiting for last full block to be signed. Try again later."); - info!("Waiting for last full block to be signed. Try again later."); - return; - } - - let name = name.to_lowercase(); - if name.len() > ZONE_MAX_LENGTH || !check_domain(&name, false) || context.lock().unwrap().x_zones.has_zone(&name) { - warn!("This zone is unavailable for mining!"); - show_warning(web_view, "This zone is unavailable for mining!"); - return; - } - let data = data.to_lowercase(); - let mut data = match serde_json::from_str::(&data) { - Ok(zone) => { - if zone.difficulty < ZONE_MIN_DIFFICULTY { - warn!("Zone difficulty cannot be lower than {}!", ZONE_MIN_DIFFICULTY); - show_warning(web_view, &format!("Zone difficulty cannot be lower than {}!", ZONE_MIN_DIFFICULTY)); - return; - } - if name != zone.name { - warn!("Something wrong with zone data!"); - show_warning(web_view, "Something wrong with zone data!"); - return; - } - zone - } - Err(_) => { - warn!("Something wrong with zone data!"); - show_warning(web_view, "Something wrong with zone data!"); - return; - } - }; - let (keystore, transaction) = { - let context = context.lock().unwrap(); - (context.get_keystore(), context.chain.get_domain_transaction(&name)) - }; - if let Some(keystore) = keystore { - data.owners = if data.owners.is_empty() { - vec!(keystore.get_public()) - } else { - data.owners - }; - let data = serde_json::to_string(&data).unwrap(); - match transaction { - None => { - create_zone(Arc::clone(&context), miner.clone(), CLASS_ZONE, &name, &data, ZONE_DIFFICULTY, &keystore); - event_info(web_view, &format!("Mining of zone \\'{}\\' has started", &name)); - } - Some(transaction) => { - if transaction.pub_key == keystore.get_public() { - create_zone(Arc::clone(&context), miner.clone(), CLASS_ZONE, &name, &data, ZONE_DIFFICULTY, &keystore); - event_info(web_view, &format!("Mining of zone \\'{}\\' has started", &name)); - } else { - warn!("Tried to mine not owned domain!"); - show_warning(web_view, "You cannot change domain that you don't own!"); - } - } - } - } else { - warn!("Can not mine without keys!"); - show_warning(web_view, "You don't have keys loaded!
Load or mine the keys and try again."); - } -} - fn show_warning(web_view: &mut WebView<()>, text: &str) { let str = text.replace('\'', "\\'"); match web_view.eval(&format!("showWarning('{}');", &str)) { @@ -574,18 +480,6 @@ fn format_event_now(kind: &str, message: &str) -> String { format!("addEvent('{}', '{}', '{}');", kind, time.format("%d.%m.%y %X"), message) } -fn create_zone(context: Arc>, miner: Arc>, class: &str, name: &str, data: &str, difficulty: u32, keystore: &Keystore) { - let name = name.to_owned(); - info!("Generating domain or zone {}", &name); - if context.lock().unwrap().x_zones.has_zone(&name) { - error!("Unable to mine IANA/OpenNIC/etc zone {}!", &name); - return; - } - let transaction = Transaction::from_str(name, class.to_owned(), data.to_owned(), keystore.get_public().clone()); - let block = Block::new(Some(transaction), keystore.get_public(), Bytes::default(), difficulty); - miner.lock().unwrap().add_block(block, keystore.clone()); -} - fn create_domain(_context: Arc>, miner: Arc>, class: &str, name: &str, mut data: DomainData, difficulty: u32, keystore: &Keystore) { let name = name.to_owned(); let confirmation = hash_identity(&name, Some(&keystore.get_public())); @@ -604,8 +498,6 @@ pub enum Cmd { LoadKey, CreateKey, SaveKey, - CheckZone { name: String }, - MineZone { name: String, data: String }, CheckRecord { data: String }, CheckDomain { name: String }, MineDomain { name: String, data: String }, diff --git a/src/webview/index.html b/src/webview/index.html index 8da1c0f..0b16e35 100644 --- a/src/webview/index.html +++ b/src/webview/index.html @@ -29,14 +29,6 @@ Domains -
  • - - - - - Zones - -
  • @@ -71,7 +63,7 @@ -

    To mine domains (or zones) you need to mine a strong pair of keys.

    +

    To mine domains you need to mine a strong pair of keys.

    @@ -82,32 +74,6 @@ - -
    - -

    Enter domain name, add some DNS-records, then hit the "Mine domain" button!

    +

    Enter domain name, choose domain zone, add some DNS-records, then hit the "Mine domain" button! Note: zones with * are restricted to Yggdrasil only.

    diff --git a/src/webview/scripts.js b/src/webview/scripts.js index 6d94161..d806627 100644 --- a/src/webview/scripts.js +++ b/src/webview/scripts.js @@ -246,7 +246,6 @@ function domainMiningStarted() { document.getElementById("domain_records").disabled = true; document.getElementById("add_record_button").disabled = true; document.getElementById("new_domain_button").disabled = true; - document.getElementById("new_zone_button").disabled = true; document.getElementById("new_key_button").disabled = true; } @@ -258,23 +257,9 @@ function domainMiningUnavailable() { document.getElementById("domain_records").disabled = true; document.getElementById("add_record_button").disabled = true; document.getElementById("new_domain_button").disabled = true; - document.getElementById("new_zone_button").disabled = true; document.getElementById("new_key_button").disabled = true; } -function createZone() { - var new_zone = document.getElementById("new_zone").value; - var difficulty = document.getElementById("new_zone_difficulty").value; - var yggdrasil = document.getElementById("yggdrasil_only").checked; - var obj = {}; - obj.name = new_zone; - obj.difficulty = parseInt(difficulty); - obj.yggdrasil = yggdrasil; - obj.owners = []; // TODO make a dialog to fill them - data = JSON.stringify(obj); - external.invoke(JSON.stringify({cmd: 'mineZone', name: new_zone, data: data})); -} - function sendAction(param) { external.invoke(JSON.stringify(param)); } @@ -301,40 +286,6 @@ function domainAvailable(available) { } } -function onZoneChange() { - var button = document.getElementById("new_zone_button"); - var diff = document.getElementById("new_zone_difficulty"); - d = parseInt(diff.value); - // Checking for NaN first - if (d != d || d < 15 || d > 30) { - button.disabled = true; - diff.className = "input is-danger"; - } else { - diff.className = "input"; - var input = document.getElementById("new_zone"); - external.invoke(JSON.stringify({cmd: 'checkZone', name: input.value})); - } -} - -function zoneAvailable(available) { - var input = document.getElementById("new_zone"); - var button = document.getElementById("new_zone_button"); - if (available) { - input.className = "input"; - button.disabled = false; - var diff = document.getElementById("new_zone_difficulty"); - d = parseInt(diff.value); - // Checking for NaN first - if (d != d || d < 15 || d > 30) { - button.disabled = true; - diff.className = "input is-danger"; - } - } else { - input.className = "input is-danger"; - button.disabled = true; - } -} - function showModalDialog(text, callback) { var message = document.getElementById("modal_text"); message.textContent = text; @@ -443,7 +394,6 @@ function showMiningIndicator(visible, blue) { document.getElementById("domain_records").disabled = false; document.getElementById("add_record_button").disabled = false; document.getElementById("new_domain_button").disabled = false; - document.getElementById("new_zone_button").disabled = false; document.getElementById("new_key_button").disabled = false; } } @@ -493,11 +443,6 @@ function keystoreChanged(path, pub_key, hash) { var new_domain = document.getElementById("new_domain"); new_domain.disabled = false; - - var new_zone = document.getElementById("new_zone"); - new_zone.disabled = false; - var new_zone_difficulty = document.getElementById("new_zone_difficulty"); - new_zone_difficulty.disabled = false; } function closeZonesDropdown() { @@ -520,7 +465,11 @@ function refreshZonesList() { }); availableZones.forEach(function(value, index, array) { - var zone = value.name + " (" + value.difficulty + "🔥)"; + var note = ""; + if (value.yggdrasil) { + note = "*"; + } + var zone = value.name + note; var add_class = ""; if (typeof currentZone !== 'undefined' && currentZone.name == value.name) { add_class = "is-active"; @@ -535,7 +484,11 @@ function refreshZonesList() { links.innerHTML = buf; if (typeof currentZone !== 'undefined') { var cur_name = document.getElementById("zones-current-name"); - cur_name.innerHTML = "." + currentZone.name + " (" + currentZone.difficulty + "🔥)"; + var name = "." + currentZone.name; + if (currentZone.yggdrasil) { + name = name + "*"; + } + cur_name.innerHTML = name; } }